Animation Time
Anisketch ではアニメーションを表現するために wait() 関数を使って時間の経過を待つことができます。一見して単純な処理のようですが、本当にそうでしょうか?
アニメーションの時間経過
2つのスレッドを同時に実行したときの挙動を考えてみましょう。次のコードを実行すると、どのような出力が得られるでしょうか?
thread(async () => { await all( thread(async () => { await wait(3) console.log('a: wait 3') }), thread(async () => { await wait(1) console.log('b: wait 1') await wait(2) console.log('b: wait 3') }), )})このコードを実行すると、以下のような出力が得られます。
b: wait 1a: wait 3b: wait 3予想通りの挙動だったかもしれません。同じ時刻でスケジュールされた処理は、スケジュールされた順番に実行されます。
次の例を見てみましょう。
thread(async () => { await all( thread(async () => { await wait(0.3) console.log('a: wait 0.3') }), thread(async () => { await wait(0.1) console.log('b: wait 0.1') await wait(0.2) console.log('b: wait 0.3') }), )})この場合にはどのような出力が得られるでしょうか?
先に示した例との類推から、次に示す出力が得られることが期待されるでしょう。(実際にそのような出力が得られます。)
b: wait 0.1a: wait 0.3b: wait 0.3一見して当然の挙動ですが、実はほんの少しだけ複雑な処理が行われています。
アニメーション時刻の離散化*
JavaScript において 0.1 や 0.2 といった値は浮動小数点数として表現されます。そして、浮動小数点数の計算は常に正確な結果を返すわけではありません。
例えば、0.1 + 0.2 は 0.30000000000000004 となり、 0.3 よりもわずかに大きな値となります。
console.log(0.1 + 0.2 > 0.3) // trueこのような挙動は、アニメーションの時間経過を正確に表現する上で問題となります。アニメーションは 30 fps や 60 fps で描画されることが一般的ですが、そのフレームを描画する前に処理が実行されるのか、描画した後に処理が実行されるのかによって、描画されるフレーム画像は異なるものとなります。これは意図しない挙動となるかもしれません。
そのような問題を避けるため、 Anisketch ではアニメーション時刻を離散化しています。アニメーション時刻は 1e-6 の倍数として表現され、現在時刻から 1e-6 未満の時間にスケジュールされている処理は同時にスケジュールされてたものとみなして、スケジュールされた順番で処理が呼び出されます。
このようにして、浮動小数点演算による意図しない挙動を回避しています。
さいごに
しかし、アニメーション時刻の離散化が本当にうまく機能しているのか、確信はありません。