Skip to content
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

feat: [project-sequencer-statemachine] 初期ステートの設定周りを変更 #2518

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions src/composables/useSequencerStateMachine.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { computed, ref } from "vue";
import {
ComputedRefs,
IdleStateId,
PartialStore,
Refs,
} from "@/sing/sequencerStateMachine/common";
import { getNoteDuration } from "@/sing/domain";
import { createSequencerStateMachine } from "@/sing/sequencerStateMachine";

export const useSequencerStateMachine = (store: PartialStore) => {
export const useSequencerStateMachine = (
store: PartialStore,
initialStateId: IdleStateId,
) => {
const computedRefs: ComputedRefs = {
snapTicks: computed(() =>
getNoteDuration(store.state.sequencerSnapType, store.state.tpqn),
Expand All @@ -25,11 +29,14 @@ export const useSequencerStateMachine = (store: PartialStore) => {
previewPitchEdit: ref(undefined),
guideLineTicks: ref(0),
};
const stateMachine = createSequencerStateMachine({
...computedRefs,
...refs,
store,
});
const stateMachine = createSequencerStateMachine(
{
...computedRefs,
...refs,
store,
},
initialStateId,
);
return {
stateMachine,
nowPreviewing: computed(() => refs.nowPreviewing.value),
Expand Down
8 changes: 6 additions & 2 deletions src/sing/sequencerStateMachine/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Context,
IdleStateId,
Input,
SequencerStateDefinitions,
} from "@/sing/sequencerStateMachine/common";
Expand All @@ -17,7 +18,10 @@ import { SelectNotesWithRectState } from "@/sing/sequencerStateMachine/states/se
import { DrawPitchState } from "@/sing/sequencerStateMachine/states/drawPitchState";
import { ErasePitchState } from "@/sing/sequencerStateMachine/states/erasePitchState";

export const createSequencerStateMachine = (context: Context) => {
export const createSequencerStateMachine = (
context: Context,
initialStateId: IdleStateId,
) => {
return new StateMachine<SequencerStateDefinitions, Input, Context>(
{
selectNotesToolIdle: () => new SelectNotesToolIdleState(),
Expand All @@ -32,7 +36,7 @@ export const createSequencerStateMachine = (context: Context) => {
drawPitch: (args) => new DrawPitchState(args),
erasePitch: (args) => new ErasePitchState(args),
},
new SelectNotesToolIdleState(),
context,
initialStateId,
);
};
54 changes: 44 additions & 10 deletions src/sing/stateMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export type SetNextState<T extends StateDefinition[]> = <U extends StateId<T>>(

/**
* ステートマシンのステートを表すインターフェース。
*
* @template State このインターフェースを実装するステートの型。
* @template Input ステートが処理する入力の型。
* @template Context ステート間で共有されるコンテキストの型。
Expand All @@ -58,7 +57,6 @@ export interface State<

/**
* 入力を処理し、必要に応じて次のステートを設定する。
*
* @param payload `input`、`context`、`setNextState`関数を含むペイロード。
*/
process(payload: {
Expand All @@ -69,14 +67,12 @@ export interface State<

/**
* ステートに入ったときに呼び出される。
*
* @param context ステート間で共有されるコンテキスト。
*/
onEnter(context: Context): void;

/**
* ステートから出るときに呼び出される。
*
* @param context ステート間で共有されるコンテキスト。
*/
onExit(context: Context): void;
Expand All @@ -91,9 +87,17 @@ type StateFactories<T extends StateDefinition[], Input, Context> = {
) => State<T, Input, Context> & { readonly id: U };
};

/**
* 初期ステートとして設定可能なステートのIDを表す型。
Copy link
Member

@Hiroshiba Hiroshiba Feb 3, 2025

Choose a reason for hiding this comment

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

「設定可能なステートのID」と書いていて1つの ID ではないことがわかるので、「表す型」は自明かも。

Suggested change
* 初期ステートとして設定可能なステートのIDを表す型
* 初期ステートとして設定可能なステートのID

だけど他の定義にはそう書いてあるので、ここは書いた方が良さそう!
変えるなら全部変えた方が良さそう。

*/
type InitialStateId<T extends StateDefinition[]> = T[number] extends infer U
? U extends { id: string; factoryArgs: undefined }
? U["id"]
: never
: never;

/**
* ステートマシンを表すクラス。
*
* @template State ステートマシンのステートの型。
* @template Input ステートが処理する入力の型。
* @template Context ステート間で共有されるコンテキストの型。
Expand All @@ -111,6 +115,7 @@ export class StateMachine<
private readonly context: Context;

private currentState: State<StateDefinitions, Input, Context>;
private isDisposed = false;

/**
* ステートマシンの現在のステートのID。
Expand All @@ -120,41 +125,70 @@ export class StateMachine<
}

/**
* @param initialState ステートマシンの初期ステート。
* @param context ステート間で共有されるコンテキスト。
* @param stateFactories ステートのファクトリー関数。
* @param context ステートマシンのコンテキスト。
* @param initialStateId ステートマシンの初期ステートのID。
*/
constructor(
stateFactories: StateFactories<StateDefinitions, Input, Context>,
initialState: State<StateDefinitions, Input, Context>,
context: Context,
initialStateId: InitialStateId<StateDefinitions>,
) {
this.stateFactories = stateFactories;
this.context = context;

this.currentState = initialState;
this.currentState = stateFactories[initialStateId](undefined);
}

/**
* ステートを設定する。
* @param id ステートのID。
* @param factoryArgs ステートのファクトリー関数の引数。
*/
setState<T extends StateId<StateDefinitions>>(
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved
id: T,
factoryArgs: FactoryArgs<StateDefinitions, T>,
) {
if (this.isDisposed) {
throw new Error("This state machine is already disposed.");
}
this.currentState.onExit(this.context);
this.currentState = this.stateFactories[id](factoryArgs);
this.currentState.onEnter(this.context);
}

/**
* 現在のステートを使用して入力を処理し、必要に応じてステートの遷移を行う。
*
* @param input 処理する入力。
*/
process(input: Input) {
if (this.isDisposed) {
throw new Error("This state machine is already disposed.");
}

let nextState: State<StateDefinitions, Input, Context> | undefined =
undefined;

this.currentState.process({
input,
context: this.context,
setNextState: (id, factoryArgs) => {
nextState = this.stateFactories[id](factoryArgs);
},
});

if (nextState != undefined) {
this.currentState.onExit(this.context);
this.currentState = nextState;
this.currentState.onEnter(this.context);
}
}

dispose() {
try {
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved
this.currentState.onExit(this.context);
} finally {
this.isDisposed = true;
}
}
}
Loading