Second Life in Japan
登録
Advertisement

Scripters_cafeログ


08/10/11[]

「LSLのタイマーって正確じゃないよね」という話がたまに出てきますが、どの程度正確じゃないのか、どういった実装になっているのかを検証・考えてみました。


まず、wikiには以下のような記述があります。

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回分)だけ発生する

次のようなスクリプトにてこの挙動を検証しました。

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

Advertisement