@axi-engine/states 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +91 -94
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +117 -130
- package/dist/state-handler.d.ts +8 -0
- package/dist/state-handler.d.ts.map +1 -0
- package/dist/state-handler.js +2 -0
- package/dist/state-handler.js.map +1 -0
- package/dist/state-machine.d.ts +108 -0
- package/dist/state-machine.d.ts.map +1 -0
- package/dist/state-machine.js +136 -0
- package/dist/state-machine.js.map +1 -0
- package/package.json +47 -47
- package/dist/index.cjs +0 -132
- package/dist/index.d.cts +0 -120
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { Emitter } from
|
|
1
|
+
import { Emitter } from '@axi-engine/utils';
|
|
2
2
|
|
|
3
|
-
//#region src/state-handler.d.ts
|
|
4
3
|
type StateHandler<P = void> = P extends void ? () => void | Promise<void> : (payload: P) => void | Promise<void>;
|
|
5
4
|
interface StateHandlerConfig<T, P = void> {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
onEnter?: StateHandler<P>;
|
|
6
|
+
onExit?: () => void | Promise<void>;
|
|
7
|
+
allowedFrom?: T[];
|
|
9
8
|
}
|
|
10
9
|
type StateHandlerRegistration<T, P = void> = StateHandler<P> | StateHandlerConfig<T, P>;
|
|
11
|
-
|
|
12
|
-
//#region src/state-machine.d.ts
|
|
10
|
+
|
|
13
11
|
/**
|
|
14
12
|
* A minimal, type-safe finite state machine.
|
|
15
13
|
* It manages states, transitions, and associated lifecycle hooks (`onEnter`, `onExit`).
|
|
@@ -30,91 +28,90 @@ type StateHandlerRegistration<T, P = void> = StateHandler<P> | StateHandlerConfi
|
|
|
30
28
|
* }
|
|
31
29
|
*/
|
|
32
30
|
declare class StateMachine<T, P = void> {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
31
|
+
/**
|
|
32
|
+
* @protected
|
|
33
|
+
* The internal representation of the current state.
|
|
34
|
+
*/
|
|
35
|
+
protected _state?: T;
|
|
36
|
+
/**
|
|
37
|
+
* @protected
|
|
38
|
+
* A map storing all registered state configurations.
|
|
39
|
+
*/
|
|
40
|
+
protected states: Map<T, StateHandlerConfig<T, P> | undefined>;
|
|
41
|
+
/**
|
|
42
|
+
* Public emitter that fires an event whenever the state changes.
|
|
43
|
+
* The event provides the old state, the new state, and the payload.
|
|
44
|
+
* @see Emitter
|
|
45
|
+
* @example
|
|
46
|
+
* fsm.onChange.subscribe((from, to, payload) => {
|
|
47
|
+
* console.log(`State transitioned from ${from} to ${to}`);
|
|
48
|
+
* });
|
|
49
|
+
*/
|
|
50
|
+
readonly onChange: Emitter<[from?: T | undefined, to?: T | undefined, payload?: P | undefined]>;
|
|
51
|
+
/**
|
|
52
|
+
* Gets the current state of the machine.
|
|
53
|
+
* @returns The current state identifier, or `undefined` if the machine has not been started.
|
|
54
|
+
*/
|
|
55
|
+
get state(): T | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Registers a state and its associated handler or configuration.
|
|
58
|
+
* If a handler is already registered for the given state, it will be overwritten.
|
|
59
|
+
*
|
|
60
|
+
* @param state The identifier for the state to register.
|
|
61
|
+
* @param handler A handler function (`onEnter`) or a full configuration object.
|
|
62
|
+
* @example
|
|
63
|
+
* // Simple registration
|
|
64
|
+
* fsm.register(MyState.Idle, () => console.log('Entering Idle'));
|
|
65
|
+
*
|
|
66
|
+
* // Advanced registration
|
|
67
|
+
* fsm.register(MyState.Walking, {
|
|
68
|
+
* onEnter: () => console.log('Start walking animation'),
|
|
69
|
+
* onExit: () => console.log('Stop walking animation'),
|
|
70
|
+
* allowedFrom: [MyState.Idle]
|
|
71
|
+
* });
|
|
72
|
+
*/
|
|
73
|
+
register(state: T, handler?: StateHandlerRegistration<T, P>): void;
|
|
74
|
+
/**
|
|
75
|
+
* Transitions the machine to a new state.
|
|
76
|
+
* This method is asynchronous to accommodate async `onEnter` and `onExit` handlers.
|
|
77
|
+
* It will execute the `onExit` handler of the old state, then the `onEnter` handler of the new state.
|
|
78
|
+
*
|
|
79
|
+
* @param newState The identifier of the state to transition to.
|
|
80
|
+
* @param payload An optional payload to pass to the new state's `onEnter` handler.
|
|
81
|
+
* @returns A promise that resolves when the transition is complete.
|
|
82
|
+
* @throws {Error} if the `newState` has not been registered.
|
|
83
|
+
* @throws {Error} if the transition from the current state to the `newState` is not allowed by the `allowedFrom` rule.
|
|
84
|
+
* @example
|
|
85
|
+
* try {
|
|
86
|
+
* await fsm.call(PlayerState.Run, { speed: 10 });
|
|
87
|
+
* } catch (e) {
|
|
88
|
+
* console.error('State transition failed:', e.message);
|
|
89
|
+
* }
|
|
90
|
+
*/
|
|
91
|
+
call(newState: T, payload?: P): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Removes a single state configuration from the machine.
|
|
94
|
+
* If the removed state is the currently active one, the machine's state will be reset to `undefined`.
|
|
95
|
+
*
|
|
96
|
+
* @param state The identifier of the state to remove.
|
|
97
|
+
* @returns `true` if the state was found and removed, otherwise `false`.
|
|
98
|
+
* @example
|
|
99
|
+
* fsm.register(MyState.Temp, () => {});
|
|
100
|
+
* // ...
|
|
101
|
+
* const wasRemoved = fsm.unregister(MyState.Temp);
|
|
102
|
+
* console.log('Temporary state removed:', wasRemoved);
|
|
103
|
+
*/
|
|
104
|
+
unregister(state: T): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Removes all registered states and resets the machine to its initial, undefined state.
|
|
107
|
+
* This does not clear `onChange` subscribers.
|
|
108
|
+
* @example
|
|
109
|
+
* fsm.register(MyState.One);
|
|
110
|
+
* fsm.register(MyState.Two);
|
|
111
|
+
* // ...
|
|
112
|
+
* fsm.clear(); // The machine is now empty.
|
|
113
|
+
*/
|
|
114
|
+
clear(): void;
|
|
117
115
|
}
|
|
118
|
-
|
|
119
|
-
export { StateHandler, StateHandlerConfig, StateHandlerRegistration, StateMachine };
|
|
120
|
-
//# sourceMappingURL=index.d.mts.map
|
|
116
|
+
|
|
117
|
+
export { type StateHandler, type StateHandlerConfig, type StateHandlerRegistration, StateMachine };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,133 +1,120 @@
|
|
|
1
|
+
// src/state-machine.ts
|
|
1
2
|
import { Emitter, isNullOrUndefined, isUndefined, throwIf, throwIfEmpty } from "@axi-engine/utils";
|
|
2
|
-
|
|
3
|
-
//#region src/state-machine.ts
|
|
4
|
-
/**
|
|
5
|
-
* A minimal, type-safe finite state machine.
|
|
6
|
-
* It manages states, transitions, and associated lifecycle hooks (`onEnter`, `onExit`).
|
|
7
|
-
*
|
|
8
|
-
* @template T The type used for state identifiers (e.g., a string or an enum).
|
|
9
|
-
* @template P The default payload type for state handlers. Can be overridden per state.
|
|
10
|
-
* @example
|
|
11
|
-
* enum PlayerState { Idle, Walk, Run }
|
|
12
|
-
*
|
|
13
|
-
* const playerFsm = new StateMachine<PlayerState>();
|
|
14
|
-
*
|
|
15
|
-
* playerFsm.register(PlayerState.Idle, () => console.log('Player is now idle.'));
|
|
16
|
-
* playerFsm.register(PlayerState.Walk, () => console.log('Player is walking.'));
|
|
17
|
-
*
|
|
18
|
-
* async function start() {
|
|
19
|
-
* await playerFsm.call(PlayerState.Idle);
|
|
20
|
-
* await playerFsm.call(PlayerState.Walk);
|
|
21
|
-
* }
|
|
22
|
-
*/
|
|
23
3
|
var StateMachine = class {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
4
|
+
/**
|
|
5
|
+
* @protected
|
|
6
|
+
* The internal representation of the current state.
|
|
7
|
+
*/
|
|
8
|
+
_state;
|
|
9
|
+
/**
|
|
10
|
+
* @protected
|
|
11
|
+
* A map storing all registered state configurations.
|
|
12
|
+
*/
|
|
13
|
+
states = /* @__PURE__ */ new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Public emitter that fires an event whenever the state changes.
|
|
16
|
+
* The event provides the old state, the new state, and the payload.
|
|
17
|
+
* @see Emitter
|
|
18
|
+
* @example
|
|
19
|
+
* fsm.onChange.subscribe((from, to, payload) => {
|
|
20
|
+
* console.log(`State transitioned from ${from} to ${to}`);
|
|
21
|
+
* });
|
|
22
|
+
*/
|
|
23
|
+
onChange = new Emitter();
|
|
24
|
+
/**
|
|
25
|
+
* Gets the current state of the machine.
|
|
26
|
+
* @returns The current state identifier, or `undefined` if the machine has not been started.
|
|
27
|
+
*/
|
|
28
|
+
get state() {
|
|
29
|
+
return this._state;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Registers a state and its associated handler or configuration.
|
|
33
|
+
* If a handler is already registered for the given state, it will be overwritten.
|
|
34
|
+
*
|
|
35
|
+
* @param state The identifier for the state to register.
|
|
36
|
+
* @param handler A handler function (`onEnter`) or a full configuration object.
|
|
37
|
+
* @example
|
|
38
|
+
* // Simple registration
|
|
39
|
+
* fsm.register(MyState.Idle, () => console.log('Entering Idle'));
|
|
40
|
+
*
|
|
41
|
+
* // Advanced registration
|
|
42
|
+
* fsm.register(MyState.Walking, {
|
|
43
|
+
* onEnter: () => console.log('Start walking animation'),
|
|
44
|
+
* onExit: () => console.log('Stop walking animation'),
|
|
45
|
+
* allowedFrom: [MyState.Idle]
|
|
46
|
+
* });
|
|
47
|
+
*/
|
|
48
|
+
register(state, handler) {
|
|
49
|
+
if (isUndefined(handler) || typeof handler === "function") {
|
|
50
|
+
this.states.set(state, { onEnter: handler });
|
|
51
|
+
} else {
|
|
52
|
+
this.states.set(state, handler);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Transitions the machine to a new state.
|
|
57
|
+
* This method is asynchronous to accommodate async `onEnter` and `onExit` handlers.
|
|
58
|
+
* It will execute the `onExit` handler of the old state, then the `onEnter` handler of the new state.
|
|
59
|
+
*
|
|
60
|
+
* @param newState The identifier of the state to transition to.
|
|
61
|
+
* @param payload An optional payload to pass to the new state's `onEnter` handler.
|
|
62
|
+
* @returns A promise that resolves when the transition is complete.
|
|
63
|
+
* @throws {Error} if the `newState` has not been registered.
|
|
64
|
+
* @throws {Error} if the transition from the current state to the `newState` is not allowed by the `allowedFrom` rule.
|
|
65
|
+
* @example
|
|
66
|
+
* try {
|
|
67
|
+
* await fsm.call(PlayerState.Run, { speed: 10 });
|
|
68
|
+
* } catch (e) {
|
|
69
|
+
* console.error('State transition failed:', e.message);
|
|
70
|
+
* }
|
|
71
|
+
*/
|
|
72
|
+
async call(newState, payload) {
|
|
73
|
+
const oldState = this._state;
|
|
74
|
+
const oldStateConfig = this._state ? this.states.get(this._state) : void 0;
|
|
75
|
+
const newStateConfig = this.states.get(newState);
|
|
76
|
+
throwIfEmpty(newStateConfig, `State ${String(newState)} is not registered.`);
|
|
77
|
+
throwIf(
|
|
78
|
+
!isNullOrUndefined(newStateConfig.allowedFrom) && !isNullOrUndefined(oldState) && !newStateConfig.allowedFrom.includes(oldState),
|
|
79
|
+
`Transition from ${String(oldState)} to ${String(newState)} is not allowed.`
|
|
80
|
+
);
|
|
81
|
+
await oldStateConfig?.onExit?.();
|
|
82
|
+
this._state = newState;
|
|
83
|
+
await newStateConfig.onEnter?.(payload);
|
|
84
|
+
this.onChange.emit(oldState, newState, payload);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Removes a single state configuration from the machine.
|
|
88
|
+
* If the removed state is the currently active one, the machine's state will be reset to `undefined`.
|
|
89
|
+
*
|
|
90
|
+
* @param state The identifier of the state to remove.
|
|
91
|
+
* @returns `true` if the state was found and removed, otherwise `false`.
|
|
92
|
+
* @example
|
|
93
|
+
* fsm.register(MyState.Temp, () => {});
|
|
94
|
+
* // ...
|
|
95
|
+
* const wasRemoved = fsm.unregister(MyState.Temp);
|
|
96
|
+
* console.log('Temporary state removed:', wasRemoved);
|
|
97
|
+
*/
|
|
98
|
+
unregister(state) {
|
|
99
|
+
if (this._state === state) {
|
|
100
|
+
this._state = void 0;
|
|
101
|
+
}
|
|
102
|
+
return this.states.delete(state);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Removes all registered states and resets the machine to its initial, undefined state.
|
|
106
|
+
* This does not clear `onChange` subscribers.
|
|
107
|
+
* @example
|
|
108
|
+
* fsm.register(MyState.One);
|
|
109
|
+
* fsm.register(MyState.Two);
|
|
110
|
+
* // ...
|
|
111
|
+
* fsm.clear(); // The machine is now empty.
|
|
112
|
+
*/
|
|
113
|
+
clear() {
|
|
114
|
+
this.states.clear();
|
|
115
|
+
this._state = void 0;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
export {
|
|
119
|
+
StateMachine
|
|
129
120
|
};
|
|
130
|
-
|
|
131
|
-
//#endregion
|
|
132
|
-
export { StateMachine };
|
|
133
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type StateHandler<P = void> = P extends void ? () => void | Promise<void> : (payload: P) => void | Promise<void>;
|
|
2
|
+
export interface StateHandlerConfig<T, P = void> {
|
|
3
|
+
onEnter?: StateHandler<P>;
|
|
4
|
+
onExit?: () => void | Promise<void>;
|
|
5
|
+
allowedFrom?: T[];
|
|
6
|
+
}
|
|
7
|
+
export type StateHandlerRegistration<T, P = void> = StateHandler<P> | StateHandlerConfig<T, P>;
|
|
8
|
+
//# sourceMappingURL=state-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-handler.d.ts","sourceRoot":"","sources":["../src/state-handler.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,IAAI,IAC/B,CAAC,SAAS,IAAI,GACZ,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACxB,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAG3C,MAAM,WAAW,kBAAkB,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI;IAC7C,OAAO,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;CACnB;AAED,MAAM,MAAM,wBAAwB,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-handler.js","sourceRoot":"","sources":["../src/state-handler.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { StateHandlerConfig, StateHandlerRegistration } from "./state-handler";
|
|
2
|
+
import { Emitter } from '@axi-engine/utils';
|
|
3
|
+
/**
|
|
4
|
+
* A minimal, type-safe finite state machine.
|
|
5
|
+
* It manages states, transitions, and associated lifecycle hooks (`onEnter`, `onExit`).
|
|
6
|
+
*
|
|
7
|
+
* @template T The type used for state identifiers (e.g., a string or an enum).
|
|
8
|
+
* @template P The default payload type for state handlers. Can be overridden per state.
|
|
9
|
+
* @example
|
|
10
|
+
* enum PlayerState { Idle, Walk, Run }
|
|
11
|
+
*
|
|
12
|
+
* const playerFsm = new StateMachine<PlayerState>();
|
|
13
|
+
*
|
|
14
|
+
* playerFsm.register(PlayerState.Idle, () => console.log('Player is now idle.'));
|
|
15
|
+
* playerFsm.register(PlayerState.Walk, () => console.log('Player is walking.'));
|
|
16
|
+
*
|
|
17
|
+
* async function start() {
|
|
18
|
+
* await playerFsm.call(PlayerState.Idle);
|
|
19
|
+
* await playerFsm.call(PlayerState.Walk);
|
|
20
|
+
* }
|
|
21
|
+
*/
|
|
22
|
+
export declare class StateMachine<T, P = void> {
|
|
23
|
+
/**
|
|
24
|
+
* @protected
|
|
25
|
+
* The internal representation of the current state.
|
|
26
|
+
*/
|
|
27
|
+
protected _state?: T;
|
|
28
|
+
/**
|
|
29
|
+
* @protected
|
|
30
|
+
* A map storing all registered state configurations.
|
|
31
|
+
*/
|
|
32
|
+
protected states: Map<T, StateHandlerConfig<T, P> | undefined>;
|
|
33
|
+
/**
|
|
34
|
+
* Public emitter that fires an event whenever the state changes.
|
|
35
|
+
* The event provides the old state, the new state, and the payload.
|
|
36
|
+
* @see Emitter
|
|
37
|
+
* @example
|
|
38
|
+
* fsm.onChange.subscribe((from, to, payload) => {
|
|
39
|
+
* console.log(`State transitioned from ${from} to ${to}`);
|
|
40
|
+
* });
|
|
41
|
+
*/
|
|
42
|
+
readonly onChange: Emitter<[from?: T | undefined, to?: T | undefined, payload?: P | undefined]>;
|
|
43
|
+
/**
|
|
44
|
+
* Gets the current state of the machine.
|
|
45
|
+
* @returns The current state identifier, or `undefined` if the machine has not been started.
|
|
46
|
+
*/
|
|
47
|
+
get state(): T | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Registers a state and its associated handler or configuration.
|
|
50
|
+
* If a handler is already registered for the given state, it will be overwritten.
|
|
51
|
+
*
|
|
52
|
+
* @param state The identifier for the state to register.
|
|
53
|
+
* @param handler A handler function (`onEnter`) or a full configuration object.
|
|
54
|
+
* @example
|
|
55
|
+
* // Simple registration
|
|
56
|
+
* fsm.register(MyState.Idle, () => console.log('Entering Idle'));
|
|
57
|
+
*
|
|
58
|
+
* // Advanced registration
|
|
59
|
+
* fsm.register(MyState.Walking, {
|
|
60
|
+
* onEnter: () => console.log('Start walking animation'),
|
|
61
|
+
* onExit: () => console.log('Stop walking animation'),
|
|
62
|
+
* allowedFrom: [MyState.Idle]
|
|
63
|
+
* });
|
|
64
|
+
*/
|
|
65
|
+
register(state: T, handler?: StateHandlerRegistration<T, P>): void;
|
|
66
|
+
/**
|
|
67
|
+
* Transitions the machine to a new state.
|
|
68
|
+
* This method is asynchronous to accommodate async `onEnter` and `onExit` handlers.
|
|
69
|
+
* It will execute the `onExit` handler of the old state, then the `onEnter` handler of the new state.
|
|
70
|
+
*
|
|
71
|
+
* @param newState The identifier of the state to transition to.
|
|
72
|
+
* @param payload An optional payload to pass to the new state's `onEnter` handler.
|
|
73
|
+
* @returns A promise that resolves when the transition is complete.
|
|
74
|
+
* @throws {Error} if the `newState` has not been registered.
|
|
75
|
+
* @throws {Error} if the transition from the current state to the `newState` is not allowed by the `allowedFrom` rule.
|
|
76
|
+
* @example
|
|
77
|
+
* try {
|
|
78
|
+
* await fsm.call(PlayerState.Run, { speed: 10 });
|
|
79
|
+
* } catch (e) {
|
|
80
|
+
* console.error('State transition failed:', e.message);
|
|
81
|
+
* }
|
|
82
|
+
*/
|
|
83
|
+
call(newState: T, payload?: P): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Removes a single state configuration from the machine.
|
|
86
|
+
* If the removed state is the currently active one, the machine's state will be reset to `undefined`.
|
|
87
|
+
*
|
|
88
|
+
* @param state The identifier of the state to remove.
|
|
89
|
+
* @returns `true` if the state was found and removed, otherwise `false`.
|
|
90
|
+
* @example
|
|
91
|
+
* fsm.register(MyState.Temp, () => {});
|
|
92
|
+
* // ...
|
|
93
|
+
* const wasRemoved = fsm.unregister(MyState.Temp);
|
|
94
|
+
* console.log('Temporary state removed:', wasRemoved);
|
|
95
|
+
*/
|
|
96
|
+
unregister(state: T): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Removes all registered states and resets the machine to its initial, undefined state.
|
|
99
|
+
* This does not clear `onChange` subscribers.
|
|
100
|
+
* @example
|
|
101
|
+
* fsm.register(MyState.One);
|
|
102
|
+
* fsm.register(MyState.Two);
|
|
103
|
+
* // ...
|
|
104
|
+
* fsm.clear(); // The machine is now empty.
|
|
105
|
+
*/
|
|
106
|
+
clear(): void;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=state-machine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-machine.d.ts","sourceRoot":"","sources":["../src/state-machine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,kBAAkB,EAAE,wBAAwB,EAAC,MAAM,iBAAiB,CAAC;AAC7E,OAAO,EAAC,OAAO,EAAwD,MAAM,mBAAmB,CAAC;AAGjG;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,YAAY,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI;IACnC;;;OAGG;IACH,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAErB;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC,CAAa;IAE3E;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,+EAAkD;IAEnE;;;OAGG;IACH,IAAI,KAAK,IAAI,CAAC,GAAG,SAAS,CAEzB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI;IAQlE;;;;;;;;;;;;;;;;OAgBG;IACG,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBnD;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO;IAO7B;;;;;;;;OAQG;IACH,KAAK,IAAI,IAAI;CAId"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Emitter, isNullOrUndefined, isUndefined, throwIf, throwIfEmpty } from '@axi-engine/utils';
|
|
2
|
+
/**
|
|
3
|
+
* A minimal, type-safe finite state machine.
|
|
4
|
+
* It manages states, transitions, and associated lifecycle hooks (`onEnter`, `onExit`).
|
|
5
|
+
*
|
|
6
|
+
* @template T The type used for state identifiers (e.g., a string or an enum).
|
|
7
|
+
* @template P The default payload type for state handlers. Can be overridden per state.
|
|
8
|
+
* @example
|
|
9
|
+
* enum PlayerState { Idle, Walk, Run }
|
|
10
|
+
*
|
|
11
|
+
* const playerFsm = new StateMachine<PlayerState>();
|
|
12
|
+
*
|
|
13
|
+
* playerFsm.register(PlayerState.Idle, () => console.log('Player is now idle.'));
|
|
14
|
+
* playerFsm.register(PlayerState.Walk, () => console.log('Player is walking.'));
|
|
15
|
+
*
|
|
16
|
+
* async function start() {
|
|
17
|
+
* await playerFsm.call(PlayerState.Idle);
|
|
18
|
+
* await playerFsm.call(PlayerState.Walk);
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
export class StateMachine {
|
|
22
|
+
/**
|
|
23
|
+
* @protected
|
|
24
|
+
* The internal representation of the current state.
|
|
25
|
+
*/
|
|
26
|
+
_state;
|
|
27
|
+
/**
|
|
28
|
+
* @protected
|
|
29
|
+
* A map storing all registered state configurations.
|
|
30
|
+
*/
|
|
31
|
+
states = new Map();
|
|
32
|
+
/**
|
|
33
|
+
* Public emitter that fires an event whenever the state changes.
|
|
34
|
+
* The event provides the old state, the new state, and the payload.
|
|
35
|
+
* @see Emitter
|
|
36
|
+
* @example
|
|
37
|
+
* fsm.onChange.subscribe((from, to, payload) => {
|
|
38
|
+
* console.log(`State transitioned from ${from} to ${to}`);
|
|
39
|
+
* });
|
|
40
|
+
*/
|
|
41
|
+
onChange = new Emitter();
|
|
42
|
+
/**
|
|
43
|
+
* Gets the current state of the machine.
|
|
44
|
+
* @returns The current state identifier, or `undefined` if the machine has not been started.
|
|
45
|
+
*/
|
|
46
|
+
get state() {
|
|
47
|
+
return this._state;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Registers a state and its associated handler or configuration.
|
|
51
|
+
* If a handler is already registered for the given state, it will be overwritten.
|
|
52
|
+
*
|
|
53
|
+
* @param state The identifier for the state to register.
|
|
54
|
+
* @param handler A handler function (`onEnter`) or a full configuration object.
|
|
55
|
+
* @example
|
|
56
|
+
* // Simple registration
|
|
57
|
+
* fsm.register(MyState.Idle, () => console.log('Entering Idle'));
|
|
58
|
+
*
|
|
59
|
+
* // Advanced registration
|
|
60
|
+
* fsm.register(MyState.Walking, {
|
|
61
|
+
* onEnter: () => console.log('Start walking animation'),
|
|
62
|
+
* onExit: () => console.log('Stop walking animation'),
|
|
63
|
+
* allowedFrom: [MyState.Idle]
|
|
64
|
+
* });
|
|
65
|
+
*/
|
|
66
|
+
register(state, handler) {
|
|
67
|
+
if (isUndefined(handler) || typeof handler === 'function') {
|
|
68
|
+
this.states.set(state, { onEnter: handler });
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.states.set(state, handler);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Transitions the machine to a new state.
|
|
76
|
+
* This method is asynchronous to accommodate async `onEnter` and `onExit` handlers.
|
|
77
|
+
* It will execute the `onExit` handler of the old state, then the `onEnter` handler of the new state.
|
|
78
|
+
*
|
|
79
|
+
* @param newState The identifier of the state to transition to.
|
|
80
|
+
* @param payload An optional payload to pass to the new state's `onEnter` handler.
|
|
81
|
+
* @returns A promise that resolves when the transition is complete.
|
|
82
|
+
* @throws {Error} if the `newState` has not been registered.
|
|
83
|
+
* @throws {Error} if the transition from the current state to the `newState` is not allowed by the `allowedFrom` rule.
|
|
84
|
+
* @example
|
|
85
|
+
* try {
|
|
86
|
+
* await fsm.call(PlayerState.Run, { speed: 10 });
|
|
87
|
+
* } catch (e) {
|
|
88
|
+
* console.error('State transition failed:', e.message);
|
|
89
|
+
* }
|
|
90
|
+
*/
|
|
91
|
+
async call(newState, payload) {
|
|
92
|
+
const oldState = this._state;
|
|
93
|
+
const oldStateConfig = this._state ? this.states.get(this._state) : undefined;
|
|
94
|
+
const newStateConfig = this.states.get(newState);
|
|
95
|
+
throwIfEmpty(newStateConfig, `State ${String(newState)} is not registered.`);
|
|
96
|
+
throwIf(!isNullOrUndefined(newStateConfig.allowedFrom) &&
|
|
97
|
+
!isNullOrUndefined(oldState) &&
|
|
98
|
+
!newStateConfig.allowedFrom.includes(oldState), `Transition from ${String(oldState)} to ${String(newState)} is not allowed.`);
|
|
99
|
+
await oldStateConfig?.onExit?.();
|
|
100
|
+
this._state = newState;
|
|
101
|
+
await newStateConfig.onEnter?.(payload);
|
|
102
|
+
this.onChange.emit(oldState, newState, payload);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Removes a single state configuration from the machine.
|
|
106
|
+
* If the removed state is the currently active one, the machine's state will be reset to `undefined`.
|
|
107
|
+
*
|
|
108
|
+
* @param state The identifier of the state to remove.
|
|
109
|
+
* @returns `true` if the state was found and removed, otherwise `false`.
|
|
110
|
+
* @example
|
|
111
|
+
* fsm.register(MyState.Temp, () => {});
|
|
112
|
+
* // ...
|
|
113
|
+
* const wasRemoved = fsm.unregister(MyState.Temp);
|
|
114
|
+
* console.log('Temporary state removed:', wasRemoved);
|
|
115
|
+
*/
|
|
116
|
+
unregister(state) {
|
|
117
|
+
if (this._state === state) {
|
|
118
|
+
this._state = undefined;
|
|
119
|
+
}
|
|
120
|
+
return this.states.delete(state);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Removes all registered states and resets the machine to its initial, undefined state.
|
|
124
|
+
* This does not clear `onChange` subscribers.
|
|
125
|
+
* @example
|
|
126
|
+
* fsm.register(MyState.One);
|
|
127
|
+
* fsm.register(MyState.Two);
|
|
128
|
+
* // ...
|
|
129
|
+
* fsm.clear(); // The machine is now empty.
|
|
130
|
+
*/
|
|
131
|
+
clear() {
|
|
132
|
+
this.states.clear();
|
|
133
|
+
this._state = undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=state-machine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-machine.js","sourceRoot":"","sources":["../src/state-machine.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAGjG;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,YAAY;IACvB;;;OAGG;IACO,MAAM,CAAK;IAErB;;;OAGG;IACO,MAAM,GAAiD,IAAI,GAAG,EAAE,CAAC;IAE3E;;;;;;;;OAQG;IACM,QAAQ,GAAG,IAAI,OAAO,EAAmC,CAAC;IAEnE;;;OAGG;IACH,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,KAAQ,EAAE,OAAwC;QACzD,IAAI,WAAW,CAAC,OAAO,CAAC,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;YAC1D,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,EAAC,OAAO,EAAE,OAAO,EAAC,CAAC,CAAC;QAC7C,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,IAAI,CAAC,QAAW,EAAE,OAAW;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC;QAC7B,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9E,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAEjD,YAAY,CAAC,cAAc,EAAE,SAAS,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC;QAE7E,OAAO,CACL,CAAC,iBAAiB,CAAC,cAAc,CAAC,WAAW,CAAC;YAC9C,CAAC,iBAAiB,CAAC,QAAQ,CAAC;YAC5B,CAAC,cAAc,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAC9C,mBAAmB,MAAM,CAAC,QAAQ,CAAC,OAAO,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAC7E,CAAC;QAEF,MAAM,cAAc,EAAE,MAAM,EAAE,EAAE,CAAC;QAEjC,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC;QAEvB,MAAO,cAAc,CAAC,OAAiD,EAAE,CAAC,OAAO,CAAC,CAAC;QAEnF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,KAAQ;QACjB,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QAC1B,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;IAC1B,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@axi-engine/states",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A minimal, type-safe state machine for the Axi Engine, designed for managing game logic and component states with a simple API.",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "https://github.com/axijs/engine.git",
|
|
9
|
-
"directory": "packages/states"
|
|
10
|
-
},
|
|
11
|
-
"keywords": [
|
|
12
|
-
"axi-engine",
|
|
13
|
-
"typescript",
|
|
14
|
-
"gamedev",
|
|
15
|
-
"game-development",
|
|
16
|
-
"game-engine",
|
|
17
|
-
"state-machine",
|
|
18
|
-
"finite-state-machine",
|
|
19
|
-
"fsm",
|
|
20
|
-
"state-management",
|
|
21
|
-
"events"
|
|
22
|
-
],
|
|
23
|
-
"types": "./dist/index.d.ts",
|
|
24
|
-
"main": "./dist/index.js",
|
|
25
|
-
"module": "./dist/index.mjs",
|
|
26
|
-
"exports": {
|
|
27
|
-
".": {
|
|
28
|
-
"types": "./dist/index.d.ts",
|
|
29
|
-
"import": "./dist/index.mjs",
|
|
30
|
-
"require": "./dist/index.js"
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"scripts": {
|
|
34
|
-
"build": "
|
|
35
|
-
"docs": "typedoc src/index.ts --out docs/api --options ../../typedoc.json",
|
|
36
|
-
"test": "echo 'No tests yet for @axi-engine/states'"
|
|
37
|
-
},
|
|
38
|
-
"files": [
|
|
39
|
-
"dist"
|
|
40
|
-
],
|
|
41
|
-
"devDependencies": {
|
|
42
|
-
"@axi-engine/utils": "^0.
|
|
43
|
-
},
|
|
44
|
-
"peerDependencies": {
|
|
45
|
-
"@axi-engine/utils": "^0.
|
|
46
|
-
}
|
|
47
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@axi-engine/states",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A minimal, type-safe state machine for the Axi Engine, designed for managing game logic and component states with a simple API.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/axijs/engine.git",
|
|
9
|
+
"directory": "packages/states"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"axi-engine",
|
|
13
|
+
"typescript",
|
|
14
|
+
"gamedev",
|
|
15
|
+
"game-development",
|
|
16
|
+
"game-engine",
|
|
17
|
+
"state-machine",
|
|
18
|
+
"finite-state-machine",
|
|
19
|
+
"fsm",
|
|
20
|
+
"state-management",
|
|
21
|
+
"events"
|
|
22
|
+
],
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"module": "./dist/index.mjs",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.mjs",
|
|
30
|
+
"require": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup",
|
|
35
|
+
"docs": "typedoc src/index.ts --out docs/api --options ../../typedoc.json",
|
|
36
|
+
"test": "echo 'No tests yet for @axi-engine/states'"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@axi-engine/utils": "^0.2.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@axi-engine/utils": "^0.2.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/dist/index.cjs
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
let _axi_engine_utils = require("@axi-engine/utils");
|
|
2
|
-
|
|
3
|
-
//#region src/state-machine.ts
|
|
4
|
-
/**
|
|
5
|
-
* A minimal, type-safe finite state machine.
|
|
6
|
-
* It manages states, transitions, and associated lifecycle hooks (`onEnter`, `onExit`).
|
|
7
|
-
*
|
|
8
|
-
* @template T The type used for state identifiers (e.g., a string or an enum).
|
|
9
|
-
* @template P The default payload type for state handlers. Can be overridden per state.
|
|
10
|
-
* @example
|
|
11
|
-
* enum PlayerState { Idle, Walk, Run }
|
|
12
|
-
*
|
|
13
|
-
* const playerFsm = new StateMachine<PlayerState>();
|
|
14
|
-
*
|
|
15
|
-
* playerFsm.register(PlayerState.Idle, () => console.log('Player is now idle.'));
|
|
16
|
-
* playerFsm.register(PlayerState.Walk, () => console.log('Player is walking.'));
|
|
17
|
-
*
|
|
18
|
-
* async function start() {
|
|
19
|
-
* await playerFsm.call(PlayerState.Idle);
|
|
20
|
-
* await playerFsm.call(PlayerState.Walk);
|
|
21
|
-
* }
|
|
22
|
-
*/
|
|
23
|
-
var StateMachine = class {
|
|
24
|
-
/**
|
|
25
|
-
* @protected
|
|
26
|
-
* The internal representation of the current state.
|
|
27
|
-
*/
|
|
28
|
-
_state;
|
|
29
|
-
/**
|
|
30
|
-
* @protected
|
|
31
|
-
* A map storing all registered state configurations.
|
|
32
|
-
*/
|
|
33
|
-
states = /* @__PURE__ */ new Map();
|
|
34
|
-
/**
|
|
35
|
-
* Public emitter that fires an event whenever the state changes.
|
|
36
|
-
* The event provides the old state, the new state, and the payload.
|
|
37
|
-
* @see Emitter
|
|
38
|
-
* @example
|
|
39
|
-
* fsm.onChange.subscribe((from, to, payload) => {
|
|
40
|
-
* console.log(`State transitioned from ${from} to ${to}`);
|
|
41
|
-
* });
|
|
42
|
-
*/
|
|
43
|
-
onChange = new _axi_engine_utils.Emitter();
|
|
44
|
-
/**
|
|
45
|
-
* Gets the current state of the machine.
|
|
46
|
-
* @returns The current state identifier, or `undefined` if the machine has not been started.
|
|
47
|
-
*/
|
|
48
|
-
get state() {
|
|
49
|
-
return this._state;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Registers a state and its associated handler or configuration.
|
|
53
|
-
* If a handler is already registered for the given state, it will be overwritten.
|
|
54
|
-
*
|
|
55
|
-
* @param state The identifier for the state to register.
|
|
56
|
-
* @param handler A handler function (`onEnter`) or a full configuration object.
|
|
57
|
-
* @example
|
|
58
|
-
* // Simple registration
|
|
59
|
-
* fsm.register(MyState.Idle, () => console.log('Entering Idle'));
|
|
60
|
-
*
|
|
61
|
-
* // Advanced registration
|
|
62
|
-
* fsm.register(MyState.Walking, {
|
|
63
|
-
* onEnter: () => console.log('Start walking animation'),
|
|
64
|
-
* onExit: () => console.log('Stop walking animation'),
|
|
65
|
-
* allowedFrom: [MyState.Idle]
|
|
66
|
-
* });
|
|
67
|
-
*/
|
|
68
|
-
register(state, handler) {
|
|
69
|
-
if ((0, _axi_engine_utils.isUndefined)(handler) || typeof handler === "function") this.states.set(state, { onEnter: handler });
|
|
70
|
-
else this.states.set(state, handler);
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Transitions the machine to a new state.
|
|
74
|
-
* This method is asynchronous to accommodate async `onEnter` and `onExit` handlers.
|
|
75
|
-
* It will execute the `onExit` handler of the old state, then the `onEnter` handler of the new state.
|
|
76
|
-
*
|
|
77
|
-
* @param newState The identifier of the state to transition to.
|
|
78
|
-
* @param payload An optional payload to pass to the new state's `onEnter` handler.
|
|
79
|
-
* @returns A promise that resolves when the transition is complete.
|
|
80
|
-
* @throws {Error} if the `newState` has not been registered.
|
|
81
|
-
* @throws {Error} if the transition from the current state to the `newState` is not allowed by the `allowedFrom` rule.
|
|
82
|
-
* @example
|
|
83
|
-
* try {
|
|
84
|
-
* await fsm.call(PlayerState.Run, { speed: 10 });
|
|
85
|
-
* } catch (e) {
|
|
86
|
-
* console.error('State transition failed:', e.message);
|
|
87
|
-
* }
|
|
88
|
-
*/
|
|
89
|
-
async call(newState, payload) {
|
|
90
|
-
const oldState = this._state;
|
|
91
|
-
const oldStateConfig = this._state ? this.states.get(this._state) : void 0;
|
|
92
|
-
const newStateConfig = this.states.get(newState);
|
|
93
|
-
(0, _axi_engine_utils.throwIfEmpty)(newStateConfig, `State ${String(newState)} is not registered.`);
|
|
94
|
-
(0, _axi_engine_utils.throwIf)(!(0, _axi_engine_utils.isNullOrUndefined)(newStateConfig.allowedFrom) && !(0, _axi_engine_utils.isNullOrUndefined)(oldState) && !newStateConfig.allowedFrom.includes(oldState), `Transition from ${String(oldState)} to ${String(newState)} is not allowed.`);
|
|
95
|
-
await oldStateConfig?.onExit?.();
|
|
96
|
-
this._state = newState;
|
|
97
|
-
await newStateConfig.onEnter?.(payload);
|
|
98
|
-
this.onChange.emit(oldState, newState, payload);
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Removes a single state configuration from the machine.
|
|
102
|
-
* If the removed state is the currently active one, the machine's state will be reset to `undefined`.
|
|
103
|
-
*
|
|
104
|
-
* @param state The identifier of the state to remove.
|
|
105
|
-
* @returns `true` if the state was found and removed, otherwise `false`.
|
|
106
|
-
* @example
|
|
107
|
-
* fsm.register(MyState.Temp, () => {});
|
|
108
|
-
* // ...
|
|
109
|
-
* const wasRemoved = fsm.unregister(MyState.Temp);
|
|
110
|
-
* console.log('Temporary state removed:', wasRemoved);
|
|
111
|
-
*/
|
|
112
|
-
unregister(state) {
|
|
113
|
-
if (this._state === state) this._state = void 0;
|
|
114
|
-
return this.states.delete(state);
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Removes all registered states and resets the machine to its initial, undefined state.
|
|
118
|
-
* This does not clear `onChange` subscribers.
|
|
119
|
-
* @example
|
|
120
|
-
* fsm.register(MyState.One);
|
|
121
|
-
* fsm.register(MyState.Two);
|
|
122
|
-
* // ...
|
|
123
|
-
* fsm.clear(); // The machine is now empty.
|
|
124
|
-
*/
|
|
125
|
-
clear() {
|
|
126
|
-
this.states.clear();
|
|
127
|
-
this._state = void 0;
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
//#endregion
|
|
132
|
-
exports.StateMachine = StateMachine;
|
package/dist/index.d.cts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { Emitter } from "@axi-engine/utils";
|
|
2
|
-
|
|
3
|
-
//#region src/state-handler.d.ts
|
|
4
|
-
type StateHandler<P = void> = P extends void ? () => void | Promise<void> : (payload: P) => void | Promise<void>;
|
|
5
|
-
interface StateHandlerConfig<T, P = void> {
|
|
6
|
-
onEnter?: StateHandler<P>;
|
|
7
|
-
onExit?: () => void | Promise<void>;
|
|
8
|
-
allowedFrom?: T[];
|
|
9
|
-
}
|
|
10
|
-
type StateHandlerRegistration<T, P = void> = StateHandler<P> | StateHandlerConfig<T, P>;
|
|
11
|
-
//#endregion
|
|
12
|
-
//#region src/state-machine.d.ts
|
|
13
|
-
/**
|
|
14
|
-
* A minimal, type-safe finite state machine.
|
|
15
|
-
* It manages states, transitions, and associated lifecycle hooks (`onEnter`, `onExit`).
|
|
16
|
-
*
|
|
17
|
-
* @template T The type used for state identifiers (e.g., a string or an enum).
|
|
18
|
-
* @template P The default payload type for state handlers. Can be overridden per state.
|
|
19
|
-
* @example
|
|
20
|
-
* enum PlayerState { Idle, Walk, Run }
|
|
21
|
-
*
|
|
22
|
-
* const playerFsm = new StateMachine<PlayerState>();
|
|
23
|
-
*
|
|
24
|
-
* playerFsm.register(PlayerState.Idle, () => console.log('Player is now idle.'));
|
|
25
|
-
* playerFsm.register(PlayerState.Walk, () => console.log('Player is walking.'));
|
|
26
|
-
*
|
|
27
|
-
* async function start() {
|
|
28
|
-
* await playerFsm.call(PlayerState.Idle);
|
|
29
|
-
* await playerFsm.call(PlayerState.Walk);
|
|
30
|
-
* }
|
|
31
|
-
*/
|
|
32
|
-
declare class StateMachine<T, P = void> {
|
|
33
|
-
/**
|
|
34
|
-
* @protected
|
|
35
|
-
* The internal representation of the current state.
|
|
36
|
-
*/
|
|
37
|
-
protected _state?: T;
|
|
38
|
-
/**
|
|
39
|
-
* @protected
|
|
40
|
-
* A map storing all registered state configurations.
|
|
41
|
-
*/
|
|
42
|
-
protected states: Map<T, StateHandlerConfig<T, P> | undefined>;
|
|
43
|
-
/**
|
|
44
|
-
* Public emitter that fires an event whenever the state changes.
|
|
45
|
-
* The event provides the old state, the new state, and the payload.
|
|
46
|
-
* @see Emitter
|
|
47
|
-
* @example
|
|
48
|
-
* fsm.onChange.subscribe((from, to, payload) => {
|
|
49
|
-
* console.log(`State transitioned from ${from} to ${to}`);
|
|
50
|
-
* });
|
|
51
|
-
*/
|
|
52
|
-
readonly onChange: Emitter<[from?: T | undefined, to?: T | undefined, payload?: P | undefined]>;
|
|
53
|
-
/**
|
|
54
|
-
* Gets the current state of the machine.
|
|
55
|
-
* @returns The current state identifier, or `undefined` if the machine has not been started.
|
|
56
|
-
*/
|
|
57
|
-
get state(): T | undefined;
|
|
58
|
-
/**
|
|
59
|
-
* Registers a state and its associated handler or configuration.
|
|
60
|
-
* If a handler is already registered for the given state, it will be overwritten.
|
|
61
|
-
*
|
|
62
|
-
* @param state The identifier for the state to register.
|
|
63
|
-
* @param handler A handler function (`onEnter`) or a full configuration object.
|
|
64
|
-
* @example
|
|
65
|
-
* // Simple registration
|
|
66
|
-
* fsm.register(MyState.Idle, () => console.log('Entering Idle'));
|
|
67
|
-
*
|
|
68
|
-
* // Advanced registration
|
|
69
|
-
* fsm.register(MyState.Walking, {
|
|
70
|
-
* onEnter: () => console.log('Start walking animation'),
|
|
71
|
-
* onExit: () => console.log('Stop walking animation'),
|
|
72
|
-
* allowedFrom: [MyState.Idle]
|
|
73
|
-
* });
|
|
74
|
-
*/
|
|
75
|
-
register(state: T, handler?: StateHandlerRegistration<T, P>): void;
|
|
76
|
-
/**
|
|
77
|
-
* Transitions the machine to a new state.
|
|
78
|
-
* This method is asynchronous to accommodate async `onEnter` and `onExit` handlers.
|
|
79
|
-
* It will execute the `onExit` handler of the old state, then the `onEnter` handler of the new state.
|
|
80
|
-
*
|
|
81
|
-
* @param newState The identifier of the state to transition to.
|
|
82
|
-
* @param payload An optional payload to pass to the new state's `onEnter` handler.
|
|
83
|
-
* @returns A promise that resolves when the transition is complete.
|
|
84
|
-
* @throws {Error} if the `newState` has not been registered.
|
|
85
|
-
* @throws {Error} if the transition from the current state to the `newState` is not allowed by the `allowedFrom` rule.
|
|
86
|
-
* @example
|
|
87
|
-
* try {
|
|
88
|
-
* await fsm.call(PlayerState.Run, { speed: 10 });
|
|
89
|
-
* } catch (e) {
|
|
90
|
-
* console.error('State transition failed:', e.message);
|
|
91
|
-
* }
|
|
92
|
-
*/
|
|
93
|
-
call(newState: T, payload?: P): Promise<void>;
|
|
94
|
-
/**
|
|
95
|
-
* Removes a single state configuration from the machine.
|
|
96
|
-
* If the removed state is the currently active one, the machine's state will be reset to `undefined`.
|
|
97
|
-
*
|
|
98
|
-
* @param state The identifier of the state to remove.
|
|
99
|
-
* @returns `true` if the state was found and removed, otherwise `false`.
|
|
100
|
-
* @example
|
|
101
|
-
* fsm.register(MyState.Temp, () => {});
|
|
102
|
-
* // ...
|
|
103
|
-
* const wasRemoved = fsm.unregister(MyState.Temp);
|
|
104
|
-
* console.log('Temporary state removed:', wasRemoved);
|
|
105
|
-
*/
|
|
106
|
-
unregister(state: T): boolean;
|
|
107
|
-
/**
|
|
108
|
-
* Removes all registered states and resets the machine to its initial, undefined state.
|
|
109
|
-
* This does not clear `onChange` subscribers.
|
|
110
|
-
* @example
|
|
111
|
-
* fsm.register(MyState.One);
|
|
112
|
-
* fsm.register(MyState.Two);
|
|
113
|
-
* // ...
|
|
114
|
-
* fsm.clear(); // The machine is now empty.
|
|
115
|
-
*/
|
|
116
|
-
clear(): void;
|
|
117
|
-
}
|
|
118
|
-
//#endregion
|
|
119
|
-
export { StateHandler, StateHandlerConfig, StateHandlerRegistration, StateMachine };
|
|
120
|
-
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/state-handler.ts","../src/state-machine.ts"],"mappings":";;;KAAY,YAAA,aACV,CAAA,6BACe,OAAA,UAAA,OAAA,EACD,CAAA,YAAa,OAAA;AAAA,UAGZ,kBAAA;EAAA,OAAA,GACL,YAAA,CAAa,CAAA;EAAA,MAAA,gBACD,OAAA;EAAA,WAAA,GACR,CAAA;AAAA;AAAA,KAGJ,wBAAA,gBAAwC,YAAA,CAAa,CAAA,IAAK,kBAAA,CAAmB,CAAA,EAAG,CAAA;;;;ACW5F;;;;;;;;;;;;;;;;;;cAAa,YAAA;EAAA;;;;EAAA,UAAA,MAAA,GAKQ,CAAA;EAAA;;;;EAAA,UAAA,MAAA,EAMD,GAAA,CAAI,CAAA,EAAG,kBAAA,CAAmB,CAAA,EAAG,CAAA;EAAA;;;;;;;;;EAAA,SAAA,QAAA,EAW9B,OAAA,EAAA,IAAA,GAAA,CAAA,cAAA,EAAA,GAAA,CAAA,cAAA,OAAA,GAAA,CAAA;EAAA;;;;EAAA,IAAA,MAAA,GAMJ,CAAA;EAAA;;;;;;;;;;;;;;;;;EAAA,SAAA,KAAA,EAqBG,CAAA,EAAA,OAAA,GAAa,wBAAA,CAAyB,CAAA,EAAG,CAAA;EAAA;;;;;;;;;;;;;;;;;EAAA,KAAA,QAAA,EAyBpC,CAAA,EAAA,OAAA,GAAa,CAAA,GAAI,OAAA;EAAA;;;;;;;;;;;;EAAA,WAAA,KAAA,EAmCpB,CAAA;EAAA;;;;;;;;;EAAA,MAAA;AAAA"}
|
package/dist/index.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/state-handler.ts","../src/state-machine.ts"],"mappings":";;;KAAY,YAAA,aACV,CAAA,6BACe,OAAA,UAAA,OAAA,EACD,CAAA,YAAa,OAAA;AAAA,UAGZ,kBAAA;EAAA,OAAA,GACL,YAAA,CAAa,CAAA;EAAA,MAAA,gBACD,OAAA;EAAA,WAAA,GACR,CAAA;AAAA;AAAA,KAGJ,wBAAA,gBAAwC,YAAA,CAAa,CAAA,IAAK,kBAAA,CAAmB,CAAA,EAAG,CAAA;;;;ACW5F;;;;;;;;;;;;;;;;;;cAAa,YAAA;EAAA;;;;EAAA,UAAA,MAAA,GAKQ,CAAA;EAAA;;;;EAAA,UAAA,MAAA,EAMD,GAAA,CAAI,CAAA,EAAG,kBAAA,CAAmB,CAAA,EAAG,CAAA;EAAA;;;;;;;;;EAAA,SAAA,QAAA,EAW9B,OAAA,EAAA,IAAA,GAAA,CAAA,cAAA,EAAA,GAAA,CAAA,cAAA,OAAA,GAAA,CAAA;EAAA;;;;EAAA,IAAA,MAAA,GAMJ,CAAA;EAAA;;;;;;;;;;;;;;;;;EAAA,SAAA,KAAA,EAqBG,CAAA,EAAA,OAAA,GAAa,wBAAA,CAAyB,CAAA,EAAG,CAAA;EAAA;;;;;;;;;;;;;;;;;EAAA,KAAA,QAAA,EAyBpC,CAAA,EAAA,OAAA,GAAa,CAAA,GAAI,OAAA;EAAA;;;;;;;;;;;;EAAA,WAAA,KAAA,EAmCpB,CAAA;EAAA;;;;;;;;;EAAA,MAAA;AAAA"}
|
package/dist/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/state-machine.ts"],"sourcesContent":["import {StateHandlerConfig, StateHandlerRegistration} from \"./state-handler\";\r\nimport {Emitter, isNullOrUndefined, isUndefined, throwIf, throwIfEmpty} from '@axi-engine/utils';\r\n\r\n\r\n/**\r\n * A minimal, type-safe finite state machine.\r\n * It manages states, transitions, and associated lifecycle hooks (`onEnter`, `onExit`).\r\n *\r\n * @template T The type used for state identifiers (e.g., a string or an enum).\r\n * @template P The default payload type for state handlers. Can be overridden per state.\r\n * @example\r\n * enum PlayerState { Idle, Walk, Run }\r\n *\r\n * const playerFsm = new StateMachine<PlayerState>();\r\n *\r\n * playerFsm.register(PlayerState.Idle, () => console.log('Player is now idle.'));\r\n * playerFsm.register(PlayerState.Walk, () => console.log('Player is walking.'));\r\n *\r\n * async function start() {\r\n * await playerFsm.call(PlayerState.Idle);\r\n * await playerFsm.call(PlayerState.Walk);\r\n * }\r\n */\r\nexport class StateMachine<T, P = void> {\r\n /**\r\n * @protected\r\n * The internal representation of the current state.\r\n */\r\n protected _state?: T;\r\n\r\n /**\r\n * @protected\r\n * A map storing all registered state configurations.\r\n */\r\n protected states: Map<T, StateHandlerConfig<T, P> | undefined> = new Map();\r\n\r\n /**\r\n * Public emitter that fires an event whenever the state changes.\r\n * The event provides the old state, the new state, and the payload.\r\n * @see Emitter\r\n * @example\r\n * fsm.onChange.subscribe((from, to, payload) => {\r\n * console.log(`State transitioned from ${from} to ${to}`);\r\n * });\r\n */\r\n readonly onChange = new Emitter<[from?: T, to?: T, payload?: P]>();\r\n\r\n /**\r\n * Gets the current state of the machine.\r\n * @returns The current state identifier, or `undefined` if the machine has not been started.\r\n */\r\n get state(): T | undefined {\r\n return this._state;\r\n }\r\n\r\n /**\r\n * Registers a state and its associated handler or configuration.\r\n * If a handler is already registered for the given state, it will be overwritten.\r\n *\r\n * @param state The identifier for the state to register.\r\n * @param handler A handler function (`onEnter`) or a full configuration object.\r\n * @example\r\n * // Simple registration\r\n * fsm.register(MyState.Idle, () => console.log('Entering Idle'));\r\n *\r\n * // Advanced registration\r\n * fsm.register(MyState.Walking, {\r\n * onEnter: () => console.log('Start walking animation'),\r\n * onExit: () => console.log('Stop walking animation'),\r\n * allowedFrom: [MyState.Idle]\r\n * });\r\n */\r\n register(state: T, handler?: StateHandlerRegistration<T, P>): void {\r\n if (isUndefined(handler) || typeof handler === 'function') {\r\n this.states.set(state, {onEnter: handler});\r\n } else {\r\n this.states.set(state, handler);\r\n }\r\n }\r\n\r\n /**\r\n * Transitions the machine to a new state.\r\n * This method is asynchronous to accommodate async `onEnter` and `onExit` handlers.\r\n * It will execute the `onExit` handler of the old state, then the `onEnter` handler of the new state.\r\n *\r\n * @param newState The identifier of the state to transition to.\r\n * @param payload An optional payload to pass to the new state's `onEnter` handler.\r\n * @returns A promise that resolves when the transition is complete.\r\n * @throws {Error} if the `newState` has not been registered.\r\n * @throws {Error} if the transition from the current state to the `newState` is not allowed by the `allowedFrom` rule.\r\n * @example\r\n * try {\r\n * await fsm.call(PlayerState.Run, { speed: 10 });\r\n * } catch (e) {\r\n * console.error('State transition failed:', e.message);\r\n * }\r\n */\r\n async call(newState: T, payload?: P): Promise<void> {\r\n const oldState = this._state;\r\n const oldStateConfig = this._state ? this.states.get(this._state) : undefined;\r\n const newStateConfig = this.states.get(newState);\r\n\r\n throwIfEmpty(newStateConfig, `State ${String(newState)} is not registered.`);\r\n\r\n throwIf(\r\n !isNullOrUndefined(newStateConfig.allowedFrom) &&\r\n !isNullOrUndefined(oldState) &&\r\n !newStateConfig.allowedFrom.includes(oldState),\r\n `Transition from ${String(oldState)} to ${String(newState)} is not allowed.`\r\n );\r\n\r\n await oldStateConfig?.onExit?.();\r\n\r\n this._state = newState;\r\n\r\n await (newStateConfig.onEnter as (payload?: P) => void | Promise<void>)?.(payload);\r\n\r\n this.onChange.emit(oldState, newState, payload);\r\n }\r\n\r\n /**\r\n * Removes a single state configuration from the machine.\r\n * If the removed state is the currently active one, the machine's state will be reset to `undefined`.\r\n *\r\n * @param state The identifier of the state to remove.\r\n * @returns `true` if the state was found and removed, otherwise `false`.\r\n * @example\r\n * fsm.register(MyState.Temp, () => {});\r\n * // ...\r\n * const wasRemoved = fsm.unregister(MyState.Temp);\r\n * console.log('Temporary state removed:', wasRemoved);\r\n */\r\n unregister(state: T): boolean {\r\n if (this._state === state) {\r\n this._state = undefined;\r\n }\r\n return this.states.delete(state);\r\n }\r\n\r\n /**\r\n * Removes all registered states and resets the machine to its initial, undefined state.\r\n * This does not clear `onChange` subscribers.\r\n * @example\r\n * fsm.register(MyState.One);\r\n * fsm.register(MyState.Two);\r\n * // ...\r\n * fsm.clear(); // The machine is now empty.\r\n */\r\n clear(): void {\r\n this.states.clear();\r\n this._state = undefined;\r\n }\r\n}\r\n\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,eAAb,MAAuC;;;;;CAKrC,AAAU;;;;;CAMV,AAAU,yBAAuD,IAAI,KAAK;;;;;;;;;;CAW1E,AAAS,WAAW,IAAI,SAA0C;;;;;CAMlE,IAAI,QAAuB;AACzB,SAAO,KAAK;;;;;;;;;;;;;;;;;;;CAoBd,SAAS,OAAU,SAAgD;AACjE,MAAI,YAAY,QAAQ,IAAI,OAAO,YAAY,WAC7C,MAAK,OAAO,IAAI,OAAO,EAAC,SAAS,SAAQ,CAAC;MAE1C,MAAK,OAAO,IAAI,OAAO,QAAQ;;;;;;;;;;;;;;;;;;;CAqBnC,MAAM,KAAK,UAAa,SAA4B;EAClD,MAAM,WAAW,KAAK;EACtB,MAAM,iBAAiB,KAAK,SAAS,KAAK,OAAO,IAAI,KAAK,OAAO,GAAG;EACpE,MAAM,iBAAiB,KAAK,OAAO,IAAI,SAAS;AAEhD,eAAa,gBAAgB,SAAS,OAAO,SAAS,CAAC,qBAAqB;AAE5E,UACE,CAAC,kBAAkB,eAAe,YAAY,IAC9C,CAAC,kBAAkB,SAAS,IAC5B,CAAC,eAAe,YAAY,SAAS,SAAS,EAC9C,mBAAmB,OAAO,SAAS,CAAC,MAAM,OAAO,SAAS,CAAC,kBAC5D;AAED,QAAM,gBAAgB,UAAU;AAEhC,OAAK,SAAS;AAEd,QAAO,eAAe,UAAoD,QAAQ;AAElF,OAAK,SAAS,KAAK,UAAU,UAAU,QAAQ;;;;;;;;;;;;;;CAejD,WAAW,OAAmB;AAC5B,MAAI,KAAK,WAAW,MAClB,MAAK,SAAS;AAEhB,SAAO,KAAK,OAAO,OAAO,MAAM;;;;;;;;;;;CAYlC,QAAc;AACZ,OAAK,OAAO,OAAO;AACnB,OAAK,SAAS"}
|