Skip to content

Conversation

@sigprogramming
Copy link
Collaborator

内容

ScoreSequencer.vueのプレビュー処理のところをステートマシンに置き換えます。
コメントの整理も行います。

関連 Issue

その他

@sigprogramming sigprogramming changed the title feat: [project-sequencer-statemachine] 置き換え feat: [project-sequencer-statemachine] プレビュー処理のところをステートマシンに置き換える Feb 18, 2025
@sigprogramming sigprogramming marked this pull request as ready for review February 18, 2025 10:49
@sigprogramming sigprogramming requested a review from a team as a code owner February 18, 2025 10:49
@sigprogramming sigprogramming requested review from Hiroshiba and removed request for a team February 18, 2025 10:49
@sigprogramming sigprogramming changed the title feat: [project-sequencer-statemachine] プレビュー処理のところをステートマシンに置き換える feat: [project-sequencer-statemachine] ステートマシンに置き換える Feb 18, 2025
Comment on lines 137 to -142
<div
ref="rectSelectHitbox"
v-if="previewRectForRectSelect != undefined"
class="rect-select-preview"
:style="{
display: isRectSelecting ? 'block' : 'none',
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

表示/非表示の切り替えをdisplayからv-ifに変更しました。

Copy link
Member

@Hiroshiba Hiroshiba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ほぼLGTMです!!

これをマージしたら完成でしょうか!!
であればマージ後、issue側で色んな人による動作確認を募集する流れが良さそうだなーと!!

Comment on lines 464 to 484
const idleStateId = computed((): IdleStateId => {
if (state.sequencerEditTarget === "NOTE") {
if (state.sequencerNoteTool === "SELECT_FIRST") {
return "selectNotesToolIdle";
} else if (state.sequencerNoteTool === "EDIT_FIRST") {
return "editNotesToolIdle";
} else {
throw new ExhaustiveError(state.sequencerNoteTool);
}
} else if (state.sequencerEditTarget === "PITCH") {
if (state.sequencerPitchTool === "DRAW") {
return "drawPitchToolIdle";
} else if (state.sequencerPitchTool === "ERASE") {
return "erasePitchToolIdle";
} else {
throw new ExhaustiveError(state.sequencerPitchTool);
}
} else {
throw new ExhaustiveError(state.sequencerEditTarget);
}
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここ結構危ない気がしますね!

まず目的が2つになってそうです。
useSequencerStateMachineに渡してステートマシンの初期状態を決める計算に使われてるのと、watchしてステートマシンを置き換えてるのと。

「フラグ3種からIdleStateIdを計算する」関数を切り出して2箇所から関数を叩く形がより良いかもです!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

「フラグ3種からIdleStateIdを計算する」関数を切り出して2箇所から関数を叩く形がより良いかもです!

以下のような形でしょうか?
(関数にするとwatchする変数を別で指定しないといけなくなるので、computed(今の形)の方が良い気がしています)

const computeIdleStateId = (): IdleStateId => {
  if (state.sequencerEditTarget === "NOTE") {
    if (state.sequencerNoteTool === "SELECT_FIRST") {
      return "selectNotesToolIdle";
    } else if (state.sequencerNoteTool === "EDIT_FIRST") {
      return "editNotesToolIdle";
    } else {
      throw new ExhaustiveError(state.sequencerNoteTool);
    }
  } else if (state.sequencerEditTarget === "PITCH") {
    if (state.sequencerPitchTool === "DRAW") {
      return "drawPitchToolIdle";
    } else if (state.sequencerPitchTool === "ERASE") {
      return "erasePitchToolIdle";
    } else {
      throw new ExhaustiveError(state.sequencerPitchTool);
    }
  } else {
    throw new ExhaustiveError(state.sequencerEditTarget);
  }
};

const {
  stateMachine,
  previewMode,
  previewNotes,
  previewRectForRectSelect,
  previewPitchEdit,
  cursorState,
  guideLineTicks,
} = useSequencerStateMachine(store, computeIdleStateId());

watch(() => [
  state.sequencerEditTarget,
  state.sequencerNoteTool,
  state.sequencerPitchTool,
], () => {
  const IdleStateId = computeIdleStateId();
  if (stateMachine.currentStateId !== IdleStateId) {
    stateMachine.transitionTo(IdleStateId, undefined);
  }
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

watchのとこはこういう感じを想像してました!

const idleStateId = computed(() => computeIdleStateId());
watch(idleStateId, (value) => {
  if (stateMachine.currentStateId !== value) {
    stateMachine.transitionTo(value, undefined);
  }
});

けどまあ、ほとんど変わらない気もします。
(watchが不要になるのが一番良さそう感?)

Copy link
Collaborator Author

@sigprogramming sigprogramming Feb 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほどです、ひとまずこのPRではcomputed(現在の形)で実装しようと思います…!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

とりあえず了解です。

ちなみに一つのものに複数の意味を持たせるのは、SOLID原則のS(単一責任の原則)に違反してたりします。
ちょくちょく本読んでるのでまたわかったらお伝えします!

Comment on lines 502 to 506
watch(idleStateId, (value) => {
if (stateMachine.currentStateId !== value) {
stateMachine.transitionTo(value, undefined);
}
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここ、やっぱりステートマシンの外からステートマシンの操作をしてるので、なかなか危なそうだなと感じます!
変な状態遷移が発生し得るので。例えば範囲選択中にツールが切り替えられちゃうとか。

おそらく「ツールを切り替える」というイベントをStateMachineのprocessに流し、ツールの状態変数もステートマシンのContextで管理するのが良いんだろうなーと。

ただ結構大変だと思うのと、より良い設計も思いつけるかもなので、いったんTODOコメントを書き足すというのはどうでしょうか?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

例えば範囲選択中にツールが切り替えられちゃうとか。

これは想定しています。(範囲選択中にツール切り替えのショートカットを押したときなど)
操作中にツールの切り替えが発生した場合は操作をキャンセルする、という仕様でひとまず実装しています。

おそらく「ツールを切り替える」というイベントをStateMachineのprocessに流し、ツールの状態変数もステートマシンのContextで管理するのが良いんだろうなーと。

複合状態(ステートの中にステートがある)を扱える設計にすれば可能だと思います。

また、イベントは、なるべくステートによって扱いが変わるもの(マウスイベントとキーボードイベント)のみ扱うようにするのが良いかなと思っています。
ツールの切り替えはステートマシンで扱わず、ツールごとにステートマシンを用意して、ツールが切り替わったらステートマシンも切り替える、という設計も良いかもと思っています。

このPRではひとまずTODOコメントの追加のみ行おうと思います。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

操作中にツールの切り替えが発生した場合は操作をキャンセルする、という仕様でひとまず実装しています。

なるほどです!
この辺りどこかのドキュメントに書いてまとめて行った方がいいかもですね。
StateMachineディレクトリとかにREADME置くとか、ファイルの先頭にメモを書くとか。

あと操作がキャンセルできないものも将来的にはありえるかも・・・?
適当ですが、自動生成中とか。
でもなんかキャンセルするしかないかもですね!

複合状態(ステートの中にステートがある)を扱える設計にすれば可能だと思います。

「ToolButtonが押された」というイベントを流すだけなので、複合状態が扱えなくても可能かも・・・?

このPRではひとまずTODOコメントの追加のみ行おうと思います。

了解です!

@voicevox-preview-pages
Copy link

voicevox-preview-pages bot commented Feb 19, 2025

🚀 プレビュー用ページを作成しました 🚀

更新時点でのコミットハッシュ:6aa863f

this.context = context;

this.currentState = stateFactories[initialStateId](undefined);
this.currentState.onEnter(context);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onEnterを呼んでいなかったので修正しました。

@Hiroshiba Hiroshiba merged commit bf4cf38 into VOICEVOX:project-sequencer-statemachine Feb 19, 2025
10 checks passed
Copy link
Member

@Hiroshiba Hiroshiba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!!

ステートマシンは完成でしょうか!!
であればみんなで試してみるフェーズかなと!!

@sigprogramming sigprogramming deleted the replace branch February 19, 2025 15:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants