Skip to content

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 1
a: wait 3
b: 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.1
a: wait 0.3
b: wait 0.3

一見して当然の挙動ですが、実はほんの少しだけ複雑な処理が行われています。

アニメーション時刻の離散化*

JavaScript において 0.10.2 といった値は浮動小数点数として表現されます。そして、浮動小数点数の計算は常に正確な結果を返すわけではありません。

例えば、0.1 + 0.20.30000000000000004 となり、 0.3 よりもわずかに大きな値となります。

console.log(0.1 + 0.2 > 0.3) // true

このような挙動は、アニメーションの時間経過を正確に表現する上で問題となります。アニメーションは 30 fps や 60 fps で描画されることが一般的ですが、そのフレームを描画する前に処理が実行されるのか、描画した後に処理が実行されるのかによって、描画されるフレーム画像は異なるものとなります。これは意図しない挙動となるかもしれません。

そのような問題を避けるため、 Anisketch ではアニメーション時刻を離散化しています。アニメーション時刻は 1e-6 の倍数として表現され、現在時刻から 1e-6 未満の時間にスケジュールされている処理は同時にスケジュールされてたものとみなして、スケジュールされた順番で処理が呼び出されます。

このようにして、浮動小数点演算による意図しない挙動を回避しています。

さいごに

しかし、アニメーション時刻の離散化が本当にうまく機能しているのか、確信はありません。