-
Notifications
You must be signed in to change notification settings - Fork 313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Undo/Redoを実装したい #116
Comments
UNDO/RODOの実装方針についてです。 Mutationにおいてpatchの生成を正しく行うための実装はいくつか思いつきますがどれが好ましいか意見を聞きたいです。 |
CommandをActionで管理すべきかMutationにすべきかを考えてもいいかなと思いました。 仰るとおり、コマンドを実装するには、CommandをQueueで管理する必要があると思います。 提案されているmutationで行う方法の場合、MutationからMutationを叩けないこと(裏技的に叩いた場合はImmerと馴染ませられるかどうか)が課題かなと感じています。 個人的にはAction使うルートのほうが、MutationからMutationを叩く裏技を使わなくていいのできれいかなと感じていますが、Queueを持つ必要があってそれはそれで面倒かなとも感じています。 |
MutationからMutationを叩けないというのはcommitできないという意味だと思いますが const wrapper = <Arg extends StoreOptions<State>>(arg: Arg): Arg => arg;
export const audioStore = wrapper({
mutations: {
[SET_ENGINE_READY](state, { isEngineReady }: { isEngineReady: boolean }) {
state.isEngineReady = isEngineReady;
},
...
}
} as const); 追記(2021年8月23日) voicevox上の環境で上記のようなコードを書くとtsconfig.jsonの設定で通る筈が、何故かstateがanyと解釈されるうえにその部分だけnoImplicitAnyが適応されトランスパイルが通りません。 type ArgType = { a: number };
type SFunc<S> = (s: S) => void;
const wrapper = <Arg extends Record<string, SFunc<ArgType>>>(arg: Arg) => arg;
const x = wrapper({
func: (s) => {
console.log(s.a);
},
} as const); 追記(2021年8月26日) |
そうです、commitできないという意味でした。 |
現在用いているStoreは全てglobalなStoreにスプレッド構文で展開しており浅いコピーが入っているので問題ありませんが、Moduleの様に直線参照を渡す必要のあるオブジェクトだと書き換えられないように外部に書いて一度コピーを挟む必要がありそうですね |
VuexのModuleはたぶん使わないので、仮にこの方針で行くとしてもモジュールは一旦無いものとしちゃいましょう。 |
バグは多いですがひとまずサンプルとして動くものが出来上がったので、この方針でいいか意見を聞きたいです。(当該コミット) //commands
export const COMMAND_REGISTER_AUDIO_ITEM = "COMMAND_REGISTER_AUDIO_ITEM";
...
export const audioCommandStore = typeAsStoreOptions({
actions: {
[COMMAND_REGISTER_AUDIO_ITEM]: async (
{ state, commit, dispatch },
{
audioItem,
prevAudioKey,
}: { audioItem: AudioItem; prevAudioKey: string | undefined }
) => {
const audioKey = await dispatch(ISSUE_AUDIO_KEY);
const index =
prevAudioKey !== undefined
? state.audioKeys.indexOf(prevAudioKey) + 1
: state.audioKeys.length;
commit(COMMAND_REGISTER_AUDIO_ITEM, { audioItem, audioKey, index });
return audioKey;
},
...
},
mutations: commandMutationsCreator({
[COMMAND_REGISTER_AUDIO_ITEM]: (
draft,
payload: { audioItem: AudioItem; audioKey: string; index: number }
) => {
audioStore.mutations[INSERT_AUDIO_ITEM](draft, payload);
},
...
} as const),
} as const); PS |
actionで非同期処理をしておき、コマンド用mutationで各mutationを叩く設計、なるほどです。 自分が気になったのはQueueの部分です。 となると https://github.com/Hiroshiba/voicevox/issues/116#issuecomment-903135384 で言っていたqueue周りを解決する2つの手法のうち片方はそもそも破綻していたことになりそうです。申し訳ないです。 ユーザーの行動から最終的なstateが一意に定まらないという課題はありますが、Queueを管理する手間が省かれていて実装面で利点があることもあり、いったん提案されている方針で良いかなと思いました。 |
私はこの問題はQueueの問題というよりは非同期な処理を必要とするコマンドを扱う時全般で起こり得る問題だと考えています。 |
コマンド実行中はロックして、他のコマンドにはロック解除まで待ってもらう(無限ループで待機させる)感じでしょうか? |
待機ではなく破棄です。UILockのようにコマンドの実行中は他のコマンドを実行出来なくする感じですね。(とは言ってもこの実装はバグった時に致命的になりそうなのと、そもそもそんな頻繁にコマンドが叩かれる状況が好ましくないと思いますが) |
なるほどです。仰ることはよくわかるのですが、実装コストと別開発者のコストがかなり上がりそうだなと思いました。 |
私も同じ意見です。実装自体は現在のシンプルな方針のまま、一時バッファを用いたコマンドの遅延評価などを用いてコマンドの発行頻度を下げる事を考えた方が合理的でしょう。 |
ImmerとVue3はどちらも変更の検知にProxyを使っているため相性が悪く、ImmerのpoduceWithPatchesでは上手くPatchを生成することができないようです。 |
なるほどです。 |
なるほど・・・ 実際にためてみたら、たしかに2つ目以降の操作が記録されていませんでした。 |
|
置き換えてみたところ外見上は問題なく動作しているように感じました。 |
!!! なるほどです、期待です! |
テキストを入力した後、AudioQueryなどをfetchしたあとにsetTextが走ることでテキスト欄が一瞬フラッシュする現象が気になっています。 IMEが入力を開始したことを検知し、確定するまでをpreview状態とできれば、setTextでstateに更新があったときにstateを無視することができるかなと思いました。 |
現在、候補として挙がっている方式を整理します。
フラッシュはdebounceがstateに変更を検知した際に、蓄積がないと判定されている(IMEの入力は@updateに通知されない)ため更新が起こってしまい、一瞬再描画が起こっているのだと思います。 |
なるほど、すごく良いと思います!!! |
今はUndo/Redo機能がないので、行を間違ってdeleteすると、調整結果が消えてしまい、かなりしんどいです。
Undo/Redo機能はユーザーからも期待の声が上がっています。
https://twitter.com/idiotica/status/1427615544300032000
実はVOICEVOXはすでにcommand patternを実装しているので、Undo/Redoがあります。
https://github.com/Hiroshiba/voicevox/blob/630569e2f46da1956eef35278cc40ee8c3b24495/src/store/command.ts#L101-L107
vuexのstateの変更を一部記録しておき、自動的にundo操作を作り出しています。
ただ、stateの変更と、ユーザーが思っているundoがずれていることがよくあります。
たとえば「テキストの変更をundoしたい」場合、「テキストの変更」と「変更されたテキストから得られたイントネーションの変更」の2つのstate変更をundoしたいはずで、このずれを解消するような実装が必要です。
The text was updated successfully, but these errors were encountered: