Notes
思いつくままに書いたメモを、雑に整理してある。
開発がある程度進んだら、改めて書き直す。
雑多なメモ
- もし、カスタムコンポーネントが使える動画編集アプリだとしたら、どんなアプリになる?
- アニメーションの保存が普通に JSON だとしたら
- 単に機能拡張に優れた制作環境だとしたら
- 非プログラマーにとっての使い勝手はそんな感じになる
- その場合の使用感を下回るようだと良くない
- ビューポート位置などを node_modules 内に保存する?
- vscode のような多重のユーザー設定?
- ビルド可能にする
- エディタの html をエントリーポイントに追加する
- https://github.com/vitejs/vite-plugin-react-pages みたいに
- waitRender() にはタイムアウトをつけられるといい : tween のときとか、いろんな実装で役立つしそうすべきなので。毎回 Promise.race() するのは面倒だし避けたくなってしまうので。
- ThreadPromise を Thread 以外で await したら、エラーにする
- Promise.all とかで内部的に await されている場合にもエラーにする
- 描画内容が書き換わるのは、アニメーションによるものと、ユーザーの操作による一時的なものがある
- 例えばコンポーネント位置を変えるとき、ドラッグ中は一時的にシーンを上書きしていて、ドラッグが終わったらアニメーション定義が書き換わって更新が永続化される
- 例えば途中でキャンセルされたら、更新がまるごと undo されてもとに戻る
- ユーザーの編集作業中にもアニメーションを前後に動かすことができると、なお良い
- ユーザーが手書きの筆跡を加えようとするのを考えるといい
- あるいは、モーションカーブを書き換えるときにも
ModificationOverlayみたいな感じの概念?
- 表示用の Timeline を作るためのトレースモードかどうかは、一応確認できるようにする
- タイムライン表示のためには必要ない処理とかはスキップしたくなるだろうから、特にライブラリ作成時には
- トレース時と実行時に差異があったらどうする?
- 現在時刻までにわかってる差異はすべて表示に適用して、その上で warning を Timeline 上で確認できるように表示する
- Timeline Action は、調節用の UI やオーバーレイを表示できるようにする
- 例えば、モーションパスとか
- ヒエラルキーのあるシーンを作るとき、一つずつコンポーネントを追加する方法と、まとめて JSX で追加する方法がある
- GUI 上では両方をサポートしたいけど、普通にやると JSX ツリーを作るほうの実現が難しいかも?
- 描画フレームの時刻 + eps までにスケジュールされた処理を呼び出してから描画する
- key を改名する機能?
- 音声のエフェクトとかレンダリングもできるようにしたい
- ダブルバッファリングとかあった方がいい?
- 型アサーションを使って、
getによるコンポーネントの取得に型をつける - 一般的な用途でのアニメーションソフトの使いやすさ、もっと実践的な部分にある気がする
- キーフレーム操作のしやすさとバルク操作でできることとか
- どんなエフェクトが適用可能なのかとか
- どれだけなめらかにプロパティや位置の編集操作をフィードバックできるかとか
- 一度作った表現を使い回せるようにするのがどれだけ楽かとか
- そもそも、用意されている機能にどれだけ気づきやすいかとか
- 応用を思いつきやすいかとか
開発について
- 動作テストをしながらつくる
- 主要な部分から機能を実装していきたい
- 可能な限り標準化された機能を使う : 例えば EventTarget とか
先行するもの
- Manim
- Glisp
- Dacein
- Utopia
- Motion Canvas
- Remotion
- Cavalry
- Tldraw
- Design ∩ Code Systems
- FromJS 2
機能の列挙
雑な列挙であって、網羅的じゃない
- Editor UI Skeleton
- Commands / Hotkeys
- Undo/Redo
- Snapping
- Tools : マウス操作とかを受け取れる
- Actions : タイムラインに表示される
- Scene, Component
- Thread & async-hooks
- Trait : tools, actions と components を疎結合につなぐ
- Timeline (ActionView)
- Timeline: Group/Fence
- Timeline: ハイライトされたのが画面外にあるときのクリック可能な矢印
コンポーネントの参照
- シーンには追加されていないけどインスタンスが存在してるコンポーネントにも key/fullKey でアクセスできるようにすべき?
- シーンに追加されないインスタンスが存在してる場面って、何?
- … Manim だと結構よくある気もするけど React だと全くない気がする
- … 例えば Cavalry の Duplicator とか? ➔ 編集時には一時的にエディタ上で表示できるようにしたらいいかもしれない
Document.getElementByIdと同様に、アニメーション作成時に使う関数からは、シーンに追加されているものだけ取得できる- それとも godot の NodePath みたいに、ツリー構造でのパスを指定して取得できるようにする?
- ツールとかを開発するときには、シーンに追加されていないものにもアクセスできるようにする必要がある
- そうしないと、 Duplicator とかが実装できなくなるので
- つまり Scene だけじゃなくて SceneRoot みたいな別のやつを作ることになる?
- あれ? scene ってのは、単にコンテナコンポーネント?
- そうとも言うけど、 div というより document に近い?
- FunctionalComponent では、 shadow dom みたいに新しい scene が割り当てられるようにしたい
時間の巻き戻しと参照の永続性
- 数式を編集中にアニメーション時間を巻き戻そうとしたらどうなる?
- その数式が表示されていないところまで巻き戻されたら?
- 存在しているけど位置が移動していたら?
- アニメーションの巻き戻しは、時刻0からのリプレイを行うので、コンポーネントの状態や参照が維持されないじゃない!
- 一応、前回のクラスインスタンスを使い回すこともできなくはないかも
- 例えば prototype, properties をすべて消したからのオブジェクトを保持し続けておくとか?
- いいえ、 private properties の扱いがうまくいかないと思う
- Object pooling の一種なので、プロパティを消さないで再利用するようにしたらいいと思う
- コンポーネントへのアクセスは、全て Proxy を介して行うようにするとか
- ComponentProxy は、コンポーネントの id を保持していて、その id を使ってコンポーネントに透過的にアクセス可能にする
- きれいな解決策だと思う
- コンポーネントが消えたときに例外投げられるし
- コンポーネントへの操作があったかどうかを正確にトラッキングするのもできるし
- パフォーマンスの懸念は少しだけある
- 雑なベンチマークだと、アクセス時間が 6 Mops/s から 3 Mops/s くらいになる
- 多分大したことない(大量のパーティクルを表示するような用途じゃなければ : その場合は直に pixi.js を呼び出すコンポーネントを作ったりすべき)
- ComponentProxy は、コンポーネントの id を保持していて、その id を使ってコンポーネントに透過的にアクセス可能にする
- 編集用 UI は、コンポーネントが除去されたあとでも存在できる…ってこと!?
- 必要な Ref たちを持った UI を、アニメーションから独立したオーバーレイで表示する…?
- これ、コンポーネントの UI というより Timeline Action の UI な気がする
- 必要な Ref たちを持った UI を、アニメーションから独立したオーバーレイで表示する…?
まとめ直すと…
- アニメーションの巻き戻しの前後では、参照の同一性を保たないことにする
- 初期化のための関数をすべてのコンポーネントで実装するのは煩雑で面倒なので
- その代わり
fullKeyを使って対応するコンポーネントを取得できるようにする- 簡単に扱うための機能として
ComponentProxyを作る- 例えば、ユーザーが書くアニメーションでは
ComponentProxyは登場しないと思う - アニメーションの巻き戻しを超えたいときに使うだろうから
- 例えば、ユーザーが書くアニメーションでは
- 前回の描画で作った値を使いまわしたいときにも
fullKeyを使うと良い
- 簡単に扱うための機能として
- 数式編集ポップアップとかは、編集に必要な
CodeRefとかをまとめた上でポップアップに予め渡しておく- ポップアップはアニメーションの巻き戻しを超えて存在し続けることができる
画面の描画と内部処理は分離する
- 内部状態は目まぐるしく変化するので UI に反映する前に一度 debounce を挟む
- requestAnimationFrame のタイミングで一気に更新すると思う
- アニメーションの停止中には処理をしないようにする
コード書き換えの仕組み
- プロパティの編集者を特定する・その編集の書き換えを要求する仕組み
- コード書き換え位置の逆伝搬の起点を特定する
- コールスタックを使う
- thread 定義内での関数呼び出しをすべてフックして、呼び出し箇所と関数の対応関係を作っていく。
- 値の更新を逆伝搬させるときにそれを参照して、関数定義に逆関数が紐付けられてたらそれを使う
- あるいは、関数呼び出しのたびにコンテキストを作るようにして、そこに逆伝搬用の追加データとかも置けるようにする?
- Codemod するときには、コードやメタデータのの書き換え箇所の特定子を導出しておいて、まとめて書き換えを適用する
- クライアントとサーバーの両方でコードの解析が必要
- 十分にレスポンシブなら、サーバー側だけでもいい
- そうじゃない場合、クライアント側で解析を回して、書き換え位置の特定子を導出するところまではやっておく
- ユーザーがコードを書き換えることも、スケジュールされた別の書き換えと一緒に適用されることもあるので、他のコード書き換えと一緒に適用できる特定子を導出しておくようにする
- 例えば、複数のコンポーネントの位置を一緒に書き換えるツールとか
- 実行中に具体的な値がアサインされる
key()とか
- コードの書き換えトランザクションは、
assert,modifyの列だと思うassertは、書き換え前の状態が他のコード書き換えやユーザーのファイル編集で変わっていないかを確認するmodifyは、書き換えを行う
- うまく解析して、単に変数をそのまま渡しているだけの関数ならコード書き換えの逆伝搬を行えるようにしたい
- 単純な足し算とかの処理をしてる場合も、できれば
- ユーザー定義の方法でプロパティの書き換えに必要なコード書き換えを指定できるようにしたい
- 例えば、 … 例が思いつかないや。だから実装はあとでもいいかもね。
- クライアントとサーバーの両方でコードの解析が必要
Undo/Redo 1
- Undo/Redo は、永続化されたものと一時的なものの両方に対して作成する
- エディター外でユーザーが行った操作とかも undo できるようにする
- UI 上では、小さな横向きの Git Graph みたいに表示したらわかりやすいかなって
- undo group みたいな composite action を表現可能にしたいね
usingの出番じゃないですか!?いいえ、アクションがエラーしたときは勝手に revert してほしいので、微妙に違うと思う
- ファイルへの変更は、 Diff を保持しておく
- 一部のアクションだけを undo するとかもできるようにしたいね!できる限りで!
- ファイルのハッシュと書き換えに関与する範囲(例: schedule 関数呼び出し)の前後のコードを持っておいて、AST上で一つしか見つからなければその部分を置き換える。
- 別に AST 使わないでもいいかもしれない : reformat したときには詰むかもしれないけど、どうせ prettier の使用を codemod が半ば強制するので。
- schedule 関数呼び出しのところに引数で id を振っておけばいいと思う
- 2つ以上見つかった場合は、ファイルのハッシュが完全に一致した場合に限って undo/redo できるようにする
- 一時的な変更に対しては、どうしようね。
- 使用例: ベジェ曲線で一つずつ制御点を追加していて、間違えて追加した制御点を消したいとき
- 完全な revert は、アニメーションをイチから再実行すればそれでもいい
- いいえ、アニメーションを表示しながらモーションカーブを書き換えるときとか、ダメになる
- 中間アンドゥ点を作りたいときは?
- tldraw では、 HistoryManager が Store を監視していて、すべての更新を自動的にトラッキングしてる
- でも Anisketch では状態を集約的に扱っていなくて各々の Component のメソッド呼び出しやプロパティ書き換えを自由に行えるので、どうしようかな
Undo/Redo 2
コード書き換えについて考えていたときの内容
ここで書いているのは永続化された編集の undo/redo についての話
- 編集エントリー :
apply(dryRun?)+revert(dryRun?) - 編集エントリーの一種として、コード書き換えを保存する
assert,modifyをまとめて編集エントリーとする- 順番を入れ替えた undo/redo ができるようにしたいので
- 順番を入れ替えてもに変な動作をしないためには commutative であってほしい
- でも保証するのは大変そう…
- 必ず古いものから順に apply (undo) されていくようにすればいいだけ?
- 代わりに救済を用意しておく
- 順番を入れ替えることができない(ツリー状)タイムライン
- undo/redo の適用も含めて巻き戻せるようにする
- でも保証するのは大変そう…
- コード以外のファイルの編集は、 glob 指定で undo 可能になるようにするかも
- 画像や動画は大量にコピーされても困るだけだろうから
- ハッシュくらいは保存しておく?
- エディタ操作に起因するファイルの編集は、エディタ操作側が undo/redo を提供すべきかもしれない
- 例えば、画像の簡単な破壊的なダウンサンプリングとか?
- 例えば、設定やリソースが書かれた json, msgpack の編集とか!
- 手書きストロークの軌跡を保存する場合とか、コードに埋め込みたくない
- コード・ファイルの書き換えリクエストを undo/redo の単位にするなら、書き換えAPI側で自動的に undo/redo エントリーを生成することができる
ThreadContextと紐づければ、グループ化もできる
- 対して、永続化前の操作の単位で undo/redo 可能にするときは自動的にはできない
- 使用例: ベジェ曲線を描いていて、間違えて追加した制御点を消したいとき
- 使用例: モーションカーブの座標位置を調整しているとき
- その場合は、特製の編集エントリーを定義すればいいや
- ベジェ曲線作成中に最後までundoすると、ベジェ曲線作成モードが解除されてほしい
- これも、特製の編集エントリーを定義すると実現できる…?
- 入れ子の編集エントリーを作るべき?
- 少なくともUI上ではそんなメンタルモデルを反映したい気持ちがある
- 本当にファイルシステムに永続化するのを遅延したい場合は、仮想ファイルシステム上で Vite を動かすことで対応すればいいかなって
- それなら Vite の HMR とかを活用できて嬉しい
- 色々実験している途中の編集内容、明示的に保存するまでは永続化されてほしくない気持ちとかあるので
- あるいは、単に網羅的で使いやすい undo, editing history とかを提供できればそれでもいいかも?
エディタ自体を操作する API
- アプリ自体を操作するための API
- blender の
bpyみたいな感じ - 例えばタイムラインアイテムを追加したり編集したりできる
- blender の
- カスタムツールを作るときとかに使える
Action と Component の関係の可視化
- コンポーネントを選択したら、対応する Timeline Action がハイライトされるようにする
- Timeline Action のそれぞれがどのコンポーネントにアクセスしたかを記録しておく
Component Lifetime 視点での可視化
- よくあるアニメーションソフトだと、縦にコンポーネントが並んでいて、それぞれのライフタイムがタイムラインに表示される
- Anisketch のタイムラインは根本的にアクションの羅列で、タイムライン上のアイテムのそれぞれがどんな動作・コンポーネントと紐づいているのかがわかりにくい(多分ホバーしないとわからない)
- 同じコンポーネントへのアクションが同じy座標の位置に表示されるようにすれば、ひと目でコンポーネントとアクションの関係がわかるようになる
- 生存期間のアクションを細い横線で繋いだりすれば、なおのこと良いと思う。
- 重なっているアクションがある場合には、縦位置を変えて重ならないようにしたらいいと思う
- コンポーネントの回転と位置の両方を同時に tween する場合とか
- 複数コンポーネントにまたぐアクションは、アクションの色分けとホバー時のハイライトで頑張ってみる?
- タイムラインの表示外のアクションも矢印が表示されてほしい : 存在することを示すために
- 正直、自由に縦位置を並べ替えられることが活きる使い方が存在するのかわからん
- うまく表示できるようならこの表示モードをメインにしてもいい気がする
タイムライン表示と tween
- tween は、読み出すたびに値が変わる LiveProperty として実装すると思う
- この場合、タイムライン上にはどうやって表示すべき?
- LiveProperty がプロパティに指定され続けている間、ずっとスレッドが続いているように表示すべき?
- フォークしたスレッドとして表示すべき?
- タイムラインには LiveProperty を設定したタイミングだけが表示されるべき?
- その場合、加えて LiveProperty が残り続けている期間も細い線とかで表示すべき?
- 直感と合うのは、 tween が値を変え続けている間生き続けるスレッドとして表示されてほしい
- でもキーフレームを打つときは、値を変えるのが終わったあとのタイミングでも tween の LiveProperty がキーフレーム追加の処理を受け付けてほしい
- では、
circle.x = ref(circle.y)とか書いた場合は?- 直感的には、… よくわからん。
- タイムラインの表示オプションにしたほうがいいかも?
Component key, name, tags
- とりあえず、暗黙のうちにソースファイルのパスのハッシュが付加されるようにする
- アニメーション定義ファイル内でスコープされた key
- JSX で指定するか、
key()関数を使ってKeyを得る - 指定しなかった場合、自動的にコード書き換えで key が追加される
- コード書き換えできなかった場合、とりあえずエラーさせておく
- ユーザーが名付ける name (displayName?) とは別にすると思う
- name はエディタ上での表示名、重複OK、変更可能、任意の文字列
- 手書きするコードからは、 key も name も使わないで単純にインスタンスへの参照を変数に拘束すると思う
- もしくは tags を使って取得するかも
- name による検索メソッドは用意しないでいいと思う
- key による検索も用意しないままにしたいけど、コード生成で使うので用意しておく
- tags は Unity, name は Windows.Forms.Control で見覚えがあるかも
- ちなみに godot だと
bool is_unique_name_in_owner()と%Nameがある- GUI上で name を変えてもコード内の
%Nameが書き換わったりしないみたいで、ノードの改名は簡単じゃないみたい - tscn ファイルを見ても特に name 以外のノード識別子は見当たらないので、 name を使って参照するのが一般的らしい
- 確かめてみたら Windows.Forms.Control の場合も name で取得するらしい
- GUI上で name を変えてもコード内の
計画: 柔軟なマウスジェスチャー
- utopiaみたいに、マウス操作でのジェスチャーでいろんな編集操作(移動、グループ移動、…)を呼び出せるようになってると直感的だと思う。
- ユーザー定義のジェスチャーを追加できるようにするとよい
計画: 重なったアクションの編集
- コンポーネント生成、整列、とかの、同じタイミングで実行すべきアクションが縦に長くなってタイムラインが見にくく、操作もしにくくなる懸念がある
- 特に生成と整列とかは、アクションの実行順が大きな意味を持ってる
- もしも懸念が現実になったら、タイムラインの表示方法をいい感じに拡張しよう
- 今のフレームにあるアクション一覧をリストで表示するとか?
- 同時に実行されるアクションを横並びで配置することができるフェンスを作るとか?
- でも、多分整列は
FlexLayoutコンポーネントとかで作るのを標準にするけどね
計画: 複数コンポーネントをまとめて操る
- グループ化してまとめてコンポーネントを動かす仕組み : タグだけでもいいかもしれない? (just like html & css)
- Cavalry にあるような、配列に入ったコンポーネントをまとめていい感じに操る機能、 GUI 上で実現してやりたいね?
計画: 高度な拡張性
- Component に追加のトレイトを実装する関数を作る
- つまり prototype を書き換える系のやつ、多分
- すでに定義されてたら warning 出すとかする
- Component のトレイトを確認する type guard も作ろうね
- 例えば、
isProvidingSvgPathとか
- 例えば、
- 例えば SVG Path を出力するトレイトを追加できるといいかも
- Manim みたいなアニメーションを実現するため
- あるいは Cavalry とか
計画: アニメーション巻き戻しの高速化
- 不要な再計算をスキップしたい
- コンポーネントをシリアライズ可能にもできるようにする
- 実行中のスレッドがなくなったタイミングのスナップショットから再開する
- スレッドも、中間状態を報告して、再実行時にはその部分から再開可能にもできるようにする
- シーンの読み書きをしないで時刻だけから値が決定するような live property でプロパティを変化させてるときとか
- これ、絶対うまくいかないことある。純粋な関数だったら安心だけども。
- いいえ! live property はそのプロパティが必要になったときに呼ばれるって仕様なので、時間をスキップしたときに処理が呼ばれないのは問題ないです!
- そういう使い方をするのが悪いです!単純に他のコンポーネントのプロパティと同期するとか、そういう使い方をするのがいいです!
- ちなみに Cavalry では、状態の巻き戻しは JS を書く人の責任なので、専用の処理を書かないと時間を巻き戻してもアニメーションが巻き戻らない
- でも Anisketch はコード書きがメインのエクスペリエンスなので、開発体験をよくしないとね
- シーンの読み書きをしないで時刻だけから値が決定するような live property でプロパティを変化させてるときとか
- あるいは tween とかは、 wait() の代わりに waitTick() を使えばいいと思う
- ユーザーが単に tween だけをしていれば tween 中の処理が消えるし、 tween 中のプロパティを使ってたりしたら tween はちゃんと実行されるので
計画: 強力なヘルプ活用
- In-App Powerful Community-powered Help
- コマンドパレットよりも柔軟に、やりたいことの実現方法を探すための方法
- GitHub 上のレポジトリを読み込むようにする
- 公式のヘルプレポジトリに加えて、ユーザーが作成したヘルプレポジトリも読み込めるようにする
- 将来的に LLM と連結する、たぶんね
計画: 多言語対応
- エディタ画面・ドキュメントの翻訳もしたい
- エラーメッセージの多言語化もすると良い
- フォントを変える必要もあることに注意: CJK とか
計画: 複雑なテキスト装飾
- svg filters でテキストの装飾を結構複雑にやることができる。それを pixi.js に輸入したい
- 一種のシェーダーグラフみたいなのを作れるようにする
- https://www.smashingmagazine.com/2015/05/why-the-svg-filter-is-awesome/
- https://github.com/poletaevvlad/svg-filter-editor
- https://github.com/2youyou2/shader-graph/
計画: Three.js + Blender 連携
- 多分 three.js を a-frame ot react-three-fiber 経由で使うと思う
- ベースとなる 3D モデルを blender で作って、 three.js でアニメーションとかして、 blender でレンダリングして、その結果をインポートする
- 普通に three.js を使うよりも正確なレンダリングをしたくて。
- glTF の拡張機能で、光源とかも含めたファイルを three.js からエクスポートできるらしい
- 1つ目のパスでアニメーション付き glTF をエクスポートして、blenderを呼び出して、2つ目のパスで three.js の代わりにそれを使って本描画をする感じになると思う
- 多分 blender でアセットを作るってだけのほうがシンプルでレンダリング時間も短くて楽な気がする
- それで十分じゃないケース、ある? Primer みたいな動画でも three.js で十分だろうし。
- どうせ blender 側で見た目の微調節が必要になるはず、多分。きれいに見せたいし。
計画: バージョンの比較を簡単にする
- 複数の少しずつ違うバージョンを作れると楽しい
- 例えば、 git と連携するとか、単に patch を適用するとか
- davinci か何かにそんな機能があった気がする
- 要調査
計画: 制作中にメモを残せるようにする
- 最終的な動画には反映されないアニメーションやメモ書きを残せるようにする
- 共同作業とか、未来の自分とかのための機能として、ワークフローを支える仕組みとして
計画: マルチプレイヤーに対応する
- 多分、実現はそれほど難しくない
- どれだけ嬉しいことかはわからないけど
計画: 安全なサンドボックス環境
- Webcontainer, Sandpack に似たものを用意する
- Deno を使うと node.js の polyfill が簡単に用意できるかもしれない
計画: Docs の高速化など
- ドキュメントの読み込みとパースを早くから実行する
- スクロールや入力中フォームの状態などを保持できるようにする
swup,morphdomのようなことをする- ページ遷移の前後で保持したい要素をセレクタで指定する
- 指定された要素は
morphdomで新しい内容に書き換えられる - それ以外の要素は、まるごと置き換えられる
- スクリプト類はすべて再実行される
- 指定された要素は
計画: radix-ui の高速化など
- ダイアログとか特に遅い
- あるいは単に高速な実装で置き換えるのでもいい
- ひとまずメモ化して radix-ui の rerender を減らしとく
調査: Microtask は遅いけど、許容できる
簡易的なベンチマーク
- 10 calls/frame * 60 fps * 10 min = 360000 updates
- 多分、時間を遡るたびに一からやり直すのでも大丈夫な気がする
// 220 ops/sconst a = []for (let i = 0; i < 360000; i++) a.push(i)
// 102 ops/sconst a = []for (const i of foo()) a.push(i)function* foo() { for (let i = 0; i < 360000; i++) yield 0}
// 28 ops/sconst a = []for (let i = 0; i < 360000; i++) a.push(await 0)