« 予防接種 | Home | Movable Typeでコメントの投稿時間にタイムゾーンが適応されない »

Jun 052007

正確なタイマーを作る(できるだけ)

icon_flash8.jpgAS3では"Timer"クラスが追加されていますが、まぁこちらはAS2での話し。
時間を取得するのはDateオブジェクト、もしくはgetTimer。
時間処理を行うには、setInterval、もしくはonEnterFrame。
これらを用いて
「特定の時間から1秒刻みで時を刻み、どれ位誤差が少なく60秒後を算出することができるか?」
ということをフガフガとやってみます。

onEnterFrameの方で実装。31fps / 6000ms計測。

t0 = getTimer();
t00 = t0;
d0 = new Date();
d00 = d0;
mode = false;
if (mode) {
//getTimerの値で計測
this.onEnterFrame = function() {
var t1 = getTimer();
t0 = t1;
if (t1-t00>=6000) {
trace("over");
trace(t1-t00);
trace(new Date()-d00);
delete this.onEnterFrame;
}
};
} else {
//Date Objectの値で計測
this.onEnterFrame = function() {
var d1 = new Date();
d0 = d1;
if (d1-d00>=6000) {
trace("over");
trace(getTimer()-t00);
trace(d1-d00);
delete this.onEnterFrame;
}
};
}

まぁシンプルに6秒後のDateとgetTimerでの差を求めています。
結果は
getTimerでの計測

over
6092
6079

Dateでの計測

over
6092
6094

全体として1.5%程度の時間のずれが発生しています。またgetTimerに比べてDateオブジェクトの方が結果にズレが発生しています。(といっても15/1000秒ですが)のですが、これはDateオブジェクト生成のタイムラグかもしれません。(と言ってもDateで計測しているのになんでやねん?という気もしますが・・・)何回か繰り返した結果もgetTimerの方がかなり安定した結果を出します。ということでまぁ補正するならgetTimerの値を元にするかな?と・・・。

で、実際問題としてonEnterFrameではちょっと使いづらいのでsetIntervalを用いてみます。
onEnterFrameでもそうですが、setIntervalも

インターバル関数は interval にできるだけ近い間隔で呼び出されます。呼び出す間隔にメモリに負荷のかかる長いスクリプトを実行すると、呼び出しの遅延が発生します。呼び出された関数によりビジュアルエレメントに変更が加えられる場合は、updateAfterEvent() 関数を使用して、十分な画面更新回数を確保する必要があります。interval が SWF ファイルのフレームレートを超える場合は、画面の更新に伴う影響を最小限に抑えるために、インターバル関数は interval が経過し "かつ" 再生ヘッドが次のフレームに入った後にのみ呼び出されます。

とフレーム依存ですので、元々誤差が発生するということで、それをgetTimerを使って補正してやろうかと思います。

ということでスクリプトはこんな感じ。(fps=31)

t0 = getTimer();
t00 = t0;
errT = 0;
d0 = new Date();
d00 = d0;
errD = 0;
count = 0;
intervalID = setInterval(this, "tick", 1000);
function tick() {
var t1 = getTimer();
var d1 = new Date();
trace(t1-t0);
errT += t1-t0-1000;
t0 = t1;
if (++count == 10) {
trace("timer over"+count);
trace(t1-t00);
trace(d1-d00);
clearInterval(intervalID);
}
}

またFLVファイルをステージ上に配置して、わざと再生負荷をかけてみました。
で、結果。

1008
1005
1029
1029
1029
1008
1029
1029
1023
1032
timer over10
10221
10219

まぁ1秒ごとに30/1000秒ぐらいの誤差が発生しています。この誤差が何に依存しているのか?というのを検証するために低いFPSでも確認FPS=20でも検証。

FPS=20
1005
1000
1000
1005
1040
1005
1000
1000
1000
1050
timer over10
10105
10094

誤差は小さくなっています。FPSの大きさによるのか、1秒に対しての丸め誤差なのかの検証のためにFPS=19,15でもテスト。

FPS=19
1040
1009
1000
1055
1045
1000
1050
1006
1000
1054
timer over10
10259
10266
FPS=15
timer over10
10382
10390

FPSを小さくしたものの、誤差は増大しています。ということでsetIntervalの誤差はFPSの丸め誤差に依存しているのかしら?と言う結論に勝手に達してみます。

で、ではこれをどのように吸収するか?
考え方としてはgetTimerが正確であるという前提に立っていますが、(これが正確じゃなかったらアウト)以下のような感じ。
getTimerでスタート時間をレコード。
1秒おきにsetIntervalで関数tickをセット。
tickが発動するたびに++countして、スタート時間+1000*countで理想時間を算出。
同時にgetTimerでFlash上の実際の経過時間を取得。
その誤差を1000から引いて補正したduration timeでsetIntervalを設定。
という感じです。
で、以下ソース

t0 = getTimer();
t00 = t0;
errT = 0;
d0 = new Date();
d00 = d0;
errD = 0;
count = 0;
intervalID = setInterval(this, "tick", 1000);
function tick() {
clearInterval(intervalID);
var t1 = getTimer();
var d1 = new Date();
var _et = t1-(t0+count*1000)-1000;
if (++count%10 == 0) {
trace("timer over"+count);
trace(t1-t00);
trace(d1-d00);
}
var nd = 1000-(_et);
intervalID = setInterval(this, "tick", nd);
}

で、実際の計測結果。60秒間計測で10秒おきの時間を表示。

timer over10
10002
10000
timer over20
20031
20031
timer over30
30009
30000
timer over40
40011
40016
timer over50
50010
50016
timer over60
60021
60016

微小な誤差はどうしても1秒間の間に発生してしまうのですが、誤差の積み重ねは発生しておらず、60秒経過後の誤差は21/1000秒(何故かこの場合Dateオブジェクトの方が誤差が少ない・・・なして?)
まぁこれは1/50秒、つまり0.02秒の誤差ということで、ほぼ問題はないのではないでしょうかと・・。
なんか致命的なミスとかあったら御指摘ください。m(_ _)m

ということで毎回こんなことをやっているのもうっとおしいのでクラスにまとめて、AsBroadcasterと組み合わせれば非常に便利ですな。(Classはちょっと業務絡み気味なので公開しませんが、まぁ欲しい人がいて、時間があったら簡単なのでも作ります。)


Leave a comment

Search and Archives