標準的な方法があるかどうかはわかりませんが、これは私が使用した方法です。
簡単に言うと、フレームレートの間隔を決定し、この間隔に従って仮想クロックを進めます。各フレームで、「作業」を完了するのにかかった時間を決定します。フレーム間隔から作業時間を差し引くと、次の間隔に到達するまでに必要な睡眠時間がわかります。
これだけで、「スキューなしで 1 秒あたり N 回のティック」が提供されます。自動修正なので、たまに遅れても、追いつくまでワークロードが軽くなるとスピードアップします。
ワークロードに合わせてフレーム レートを調整する場合は、アイドル時間を調べて、それに応じて間隔を調整します。
このコードは、これを示す小さなプログラムです。Linux で動作しますが、OS X については知りません。1/2 秒間隔を選択したのは、動作を見てタイミングがスムーズに見えるかどうかを確認できるためです。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
/* frame interval is in microseconds */
#define INTERVAL 500000
/* use a variable so it is adjustable */
int interval = INTERVAL;
int ideal = 0;
struct timeval start; /* start time */
void init_time()
{
gettimeofday(&start, 0);
wait((1000000 - start.tv_usec));
gettimeofday(&start, 0);
ideal = start.tv_usec; /* initialize ideal time */
}
int get_time()
{
struct timeval tv;
gettimeofday(&tv, 0);
tv.tv_sec -= start.tv_sec; /* normalize to start time */
int usec = (tv.tv_sec * 1000000) + (tv.tv_usec);
return usec;
}
int wait(int usec)
{
struct timespec ts = { 0, usec * 1000 };
if (nanosleep(&ts, 0) != 0) {
printf("ERROR: nanosleep interrupted\n");
}
}
void dowork()
{
wait((rand() % 5) * 100000); /* simulated workload */
}
void frame()
{
dowork(); /* do your per-frame work here */
int actual = get_time();
int work_time = actual - ideal; /* elapsed time in dowork() */
int idle_time = interval - work_time; /* idle delay to next frame */
#ifdef ENABLE_VARIABLE
if (idle_time < 0) {
/* OPTIONAL: slow frame rate 10% if falling behind */
interval -= idle_time;
} else if (interval > INTERVAL) {
/* OPTIONAL: if we slowed down, but now we have idle time, increase
* rate 10% until we get to our original target rate */
interval -= (interval - INTERVAL)/10;
}
#endif
if (idle_time > 0) {
/* sleep for the idle period */
wait(idle_time);
}
printf("FRAME: time %10d (work %10d, idle %10d)\n",
ideal, work_time, idle_time);
ideal = ideal + interval;
}
void main()
{
int i;
init_time();
/* simulate 50 frames */
for (i=0; i<50; i++)
frame();
}