08/10/11[]
「LSLのタイマーって正確じゃないよね」という話がたまに出てきますが、どの程度正確じゃないのか、どういった実装になっているのかを検証・考えてみました。
まず、wikiには以下のような記述があります。
- Second Life Wiki 「llSetTimerEvent」
The time between timer events can be longer, this is caused by: *Time dilation - See llGetRegionTimeDilation for more information. *Default event delay - Only so many events can be triggered per second. *Event Execution - If the execution of an event takes too long. (意訳 タイマーイベント間の時間は次の理由でタイマーで指定した時間よりも長くなる場合があります。 *Time Dilation - llGetRegionTimeDilation を見てください *デフォルトのイベントディレイ - 1秒あたりにトリガーされるイベント数の制限 *イベントの実行時間 - 1つのイベントの実行時間が長くなると、他のイベントのトリガーもその分遅れます
ここに書かれたような理由で、タイマーイベントのトリガー自体が遅れる可能性はあります。
しかし、例えば長い時間のタイマーを設定し、タイマーがトリガーされる直前などに長いイベント処理を行っていない場合でも、タイマーがトリガーされるタイミングがかなり遅れる場合があるようです。
また、タイマーを起動したプリムを一旦takeしてインベントリにしまい、それを再度rezした場合、take前の経過時間が維持された状態でrezされ、残り時間が経過するとタイマーイベントがトリガーされます。
この挙動から、タイマーの「経過時間」のような値がスクリプトに従属するデータとして保存されていると考えられます。
「経過時間」をカウントしているということは、「前回イベントをトリガーした時刻」と現在時刻を比較してイベントをトリガーしているのではなくて、何らかのタイミングで経過時間を加算していき、その経過時間値が一定の値を超えたらイベントをトリガーしている、ということになります。
この加算処理は、SIM側の1フレームの処理ごとに、論理フレームタイムを加算するといった方法で行われていると推測され、フレームレートのブレがある場合、加算する時間に誤差が出てきます。タイマーの時間が長くなると、この誤差が目に見えて大きくなってしまう、と考えることができます。
これは推測の域を出ませんが、1フレームごとに加算してカウントしていく手法はゲームプログラミングでも用いられているので、このような手法である可能性は高いと考えられます。
09/06/27[]
タイマーのカウントについて、「1つのタイマーイベントの処理終了後から次のタイマーまでの間隔が計算されるのか?」という疑問があり、その場で調査しました。
調査した結果、以下のような挙動になっています。
- 1つのタイマーイベントが発生した直後から、次のタイマーイベントまでの間隔が計算される(カウントされる)
- イベントキューには、タイマーイベントは常に1つしか入らない
- 1回のタイマーイベントの処理がタイマー間隔より長くなった場合、そのイベントの処理終了後、即座に次のタイマーイベントが発生する。
- 処理時間がタイマー時間の2倍より長い状況でも、次のタイマーイベント分(1回分)だけ発生する
- 1回のタイマーイベントの処理がタイマー間隔より長くなった場合、そのイベントの処理終了後、即座に次のタイマーイベントが発生する。
次のようなスクリプトにてこの挙動を検証しました。
integer COUNT;
float WAIT = 4.0;
float TIMER = 3.0;
default {
state_entry() {
COUNT = 0;
llSay(0, "タイマー間隔:" + (string)TIMER + "秒 " +
"ウェイト:" + (string)WAIT + "秒");
llResetTime();
llSetTimerEvent(TIMER);
}
touch_start(integer num) {
llResetScript();
}
timer() {
llSay(0, (string)COUNT + " " + (string)llGetTime());
if ( COUNT++ == 0 ) {
llSleep(WAIT);
}
}
}
実行結果
[15:35] Object: タイマー間隔:3.000000秒 ウェイト:4.000000秒 [15:35] Object: 0 3.027057 [15:35] Object: 1 7.149849 [15:35] Object: 2 9.154529 [15:35] Object: 3 12.160910 [15:35] Object: 4 15.171420
この例では1回目のタイマーイベントで4秒のウェイトをかけているので、2回目のイベントが1回目のイベントの4秒後(llSleepが解けた直後)に発生しています。
タイマー間隔の不思議な挙動[]
上記スクリプトを、ウェイト間隔やタイマー間隔を変えながら何度か実行すると、不思議な現象に遭遇します。ウェイトからの復帰後の2回目のタイマーイベントの発生タイミングがずれます。(上記実行結果のカウンタ1と2の間隔)
例えば、タイマー間隔3秒、ウェイトを5秒にすると、以下のような結果になりました。
[15:48] Object: タイマー間隔:3.000000秒 ウェイト:5.000000秒 [15:48] Object: 0 3.028843 [15:48] Object: 1 8.144355 [15:48] Object: 2 10.147720 [15:48] Object: 3 13.148570 [15:48] Object: 4 16.156180
ウェイトが4秒の時は、カウンタ0,2,3,4がほぼ3秒の倍数の間隔であったのに対し、ウェイトを5秒にするとカウンタ2以降がずれています。(カウンタ1からカウンタ2までの間が2秒というのが共通しています)
また、タイマー間隔1秒、ウェイトを2.5秒にすると、以下のようにカウンタ1とカウンタ2がほぼ連続して発生します。
[15:56] Object: タイマー間隔:1.000000秒 ウェイト:2.500000秒 [15:56] Object: 0 1.005862 [15:56] Object: 1 3.581643 [15:56] Object: 2 3.626860 [15:56] Object: 3 4.628458
この結果だけを見ると、「タイマー処理している間のtimerイベントがキューに積まれている」ようにも見えてしまいます。(タイマー間隔が1秒より短いと、このようにカウンタ1とカウンタ2だけが連続して発生します。3つ以上は連続しないようです)
この件は、もう少し調査が必要そうです。
このページのTinyURL:http://tinyurl.com/SC-timer-detail