generated at
逆操作コマンド
Undo Redoを実装する手段の一つ
操作のヒストリーをコマンドという単位でまとめ、時系列で保存しておく
UndoもしくはUndoのUndo(=Redo)をするために、各コマンドは「そのコマンドで行った変更を打ち消す変更」を実装する
例)
オブジェクトを(100, 100)から(200, 200)の位置に移動した <-> (100, 100)の位置に移動させる
設定の値をfalseにした <-> 設定の値をtrueにする
など

Cons
各操作について常に逆操作を実装しないといけないのでスナップショットに比べると面倒
100操作を戻すのに100回逆操作を適用しないといけない
操作前のアプリケーションの状態を得るために、操作後の(現在の)アプリケーションの状態を必要とする
スナップショットに比べて、状態の不整合が起きやすい(これはスナップショット側が純粋に状態を上書きするため)
Pros
スナップショットに比べて、メモリ、容量をあまり食わない
共同編集の実装でも転送するものの容量が少ない
スナップショットに比べて操作のマージが容易(本当に?)
hr
使用するメモリが少なく、形式によってはファイルに永続化させることで無制限のUndoを実装することも出来る
DBのPoint-In Recoveryもこのような仕組みになっていた気がするkeroxp
GitSVNなどのVCSはこれをピュアに実装した例とも言える
Gitは各commit時点におけるファイルのスナップショットを直接保存していたような気がします igrep
そうですね。全部かはわからないけどmergeとかの時にdiffにしたりして変更適用してるshokai
全部diffで保存してると、1万コミットしたら1万回変更適用しないと最新状態が得られない(容量少ないかわりに計算コストが高い)
スナップショットだと容量がでかくなる問題については、ハッシュ値のテーブル持つことで節約等をしてる
git commit object (とtree object) 概念上はファイルシステムのスナップショットです jokester
でも一回のcommitで数個のファイルだけ変わると、多くのtree objectが再利用できるため容量を無駄に食わない。いわば一種のcopy on writeである
「概念上」とつけたのは、ディスク上はさらに圧縮・差分化を利用した git pack ( https://git-scm.com/book/en/v2/Git-Internals-Packfiles ) という保存形でファイるサイズを節約してるためだった。すべてのスナップショットを素直に保存しなくても、随時復元できるのであればスナップショットが共存してると差異なしといえる。
複雑になると逆操作コマンドの実装が大変になる。
新しいオブジェクトの生成とかやると辛い
コンストラクタと同じパラメータ持ったCreateObjectCommand再実装するか、生成したオブジェクトのインスタンス直接持たせるか、みたいになる
オブジェクトのインスタンス持たせると後で書き換えるとつらい
CreateObjectCommandでだけインスタンスを持たせてそれ以降の書き換えはmodと逆modにしました
後からUndo機能をつけようとすると大変辛いmiyanokomiya
既存実装がコマンド単位になってない(1操作で複数回データを編集してる、副作用がめちゃくちゃあるetc)のを直すところから始まるのでしんどかったりする…butaosuinu
一方で、操作が複雑な結果を生むようなアプリではスナップショットのほうが効果を発揮する
コマンド単体ではアプリケーションの状態を復元できない
大抵のアプリはこれとスナップショットを組み合わせてるんじゃないかな?
「そのコマンドで行った変更を打ち消す変更」としてスナップショットを使う手抜き実装をやりがちmiyanokomiya
複雑なオブジェクト相手にはオブジェクト単位でスナップショット使ったりしますね
snapshotの差分を取って逆操作コマンドにしてもよさそうseanchas_t
virtual DOMみたいにsnapshotをオブジェクトにロードするのを作ったseanchas_t
逆操作コマンドの自動生成の話 obuchiyuki
コマンドによって生じたdiffを逆操作コマンドとする
ObjectとPropertyにIDを振っておいて「自分の変更」のみをHashTableでまとめる。
スナップショットと逆操作コマンドの中間みたいな実装
(永続化もしやすいかも)
別に作ったコマンドを合成とかやり出して難しくなってくるmiyanokomiya
新規オブジェクトを作成 + そのオブジェクトを選択という2つのコマンドを合成するなど
要素の選択を履歴にもつ
キーストロークのundo合成seanchas_t
Cocoaだと連続的なキーストロークかどうかをNSEvent.isARepeatで取れる obuchiyuki
groupByEvent(Cocoaが標準でドラッグのアンドゥをまとめてくれる)obuchiyuki
↑ただこれは中間状態を常に記録してて、1回のUndoで100回のUndoとか発生するので使ってない
AxStudioはDiffなのでドラッグはマウスを離したときのみに記録している
Undoデータにkey的なものを入れておいて、今から入れるコマンドが最後のkeyと同じならマージ的なmiyanokomiya
オブジェクトAに対する移動と、Undoスタックの一番上もオブジェクトAに対する移動だったら
KeyPathみたいな
操作のグルーピングという概念で表されるべきところ、Undoスタックとコマンドに対する処理として表現してる気がする
ドラッグ操作とか連続的な操作をどうコマンドに落とし込むかよく悩むbutaosuinu
例えば1秒で色相環ぐるっと回したら60エントリ積まれたり
AxStudioではPhase<T>クラス(start・continue(T)・end・pulse(T))とか作ってUIがコマンドに送ってるobuchiyuki
ColorPickerで色相環のドラッグならstart→continue(T)→end
色ライブラリの選択ならpulse
これでUndoのタイミングを調整している
STUDIO では "staging状態" を作りました
scrapboxでは連続して編集しまくってる間の操作は貯めておいて、編集が1秒ぐらい止まったらサーバーに送信(保存)してます。その時にundoも記録しているshokai
↑の使い分けに結構悩むんですよね
アンドゥスタックの一番上の命令をその場で書き換えれる場合には書き換えちゃってますね。

Scrapboxのundoはこれですねshokai
他人の見ているエディタにも変更を同期させたいので
操作した人のブラウザ側に、逆操作を溜めておいて
逆操作コマンドを操作コマンドと同様に送信する