@hkdigital/lib-core 0.4.25 → 0.4.26
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/network/loaders/audio/AudioScene.svelte.d.ts +19 -10
- package/dist/network/loaders/audio/AudioScene.svelte.js +50 -75
- package/dist/network/loaders/image/ImageScene.svelte.d.ts +13 -13
- package/dist/network/loaders/image/ImageScene.svelte.js +56 -83
- package/dist/network/states/NetworkLoader.svelte.js +0 -4
- package/dist/state/machines/finite-state-machine/FiniteStateMachine.svelte.d.ts +2 -2
- package/dist/state/machines/finite-state-machine/FiniteStateMachine.svelte.js +13 -13
- package/dist/state/machines/finite-state-machine/README.md +31 -31
- package/dist/state/machines/finite-state-machine/typedef.d.ts +3 -3
- package/dist/state/machines/finite-state-machine/typedef.js +18 -12
- package/dist/state/machines/loading-state-machine/README.md +25 -25
- package/package.json +1 -1
|
@@ -13,6 +13,19 @@ export default class AudioScene {
|
|
|
13
13
|
loaded: boolean;
|
|
14
14
|
muted: boolean;
|
|
15
15
|
targetGain: number;
|
|
16
|
+
/**
|
|
17
|
+
* Get audio scene loading progress
|
|
18
|
+
*/
|
|
19
|
+
get progress(): {
|
|
20
|
+
totalBytesLoaded: number;
|
|
21
|
+
totalSize: number;
|
|
22
|
+
sourcesLoaded: number;
|
|
23
|
+
numberOfSources: number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Start loading all audio sources
|
|
27
|
+
*/
|
|
28
|
+
load(): void;
|
|
16
29
|
destroy(): void;
|
|
17
30
|
/**
|
|
18
31
|
* Add in-memory audio source
|
|
@@ -28,16 +41,6 @@ export default class AudioScene {
|
|
|
28
41
|
url: string;
|
|
29
42
|
config?: SourceConfig;
|
|
30
43
|
}): void;
|
|
31
|
-
/**
|
|
32
|
-
* Start loading all audio sources
|
|
33
|
-
*/
|
|
34
|
-
load(): void;
|
|
35
|
-
/**
|
|
36
|
-
* Set an audio context to use
|
|
37
|
-
*
|
|
38
|
-
* @param {AudioContext} [audioContext]
|
|
39
|
-
*/
|
|
40
|
-
setAudioContext(audioContext?: AudioContext): void;
|
|
41
44
|
/**
|
|
42
45
|
* Get a source that can be used to play the audio once
|
|
43
46
|
*
|
|
@@ -46,6 +49,12 @@ export default class AudioScene {
|
|
|
46
49
|
* @returns {Promise<AudioBufferSourceNode>}
|
|
47
50
|
*/
|
|
48
51
|
getSourceNode(label: string): Promise<AudioBufferSourceNode>;
|
|
52
|
+
/**
|
|
53
|
+
* Set an audio context to use
|
|
54
|
+
*
|
|
55
|
+
* @param {AudioContext} [audioContext]
|
|
56
|
+
*/
|
|
57
|
+
setAudioContext(audioContext?: AudioContext): void;
|
|
49
58
|
/**
|
|
50
59
|
* Set target gain
|
|
51
60
|
*
|
|
@@ -5,10 +5,7 @@ import { LoadingStateMachine } from '../../../state/machines.js';
|
|
|
5
5
|
import {
|
|
6
6
|
STATE_INITIAL,
|
|
7
7
|
STATE_LOADING,
|
|
8
|
-
STATE_UNLOADING,
|
|
9
8
|
STATE_LOADED,
|
|
10
|
-
STATE_CANCELLED,
|
|
11
|
-
STATE_ERROR,
|
|
12
9
|
LOAD,
|
|
13
10
|
LOADED
|
|
14
11
|
} from '../../../state/machines.js';
|
|
@@ -30,10 +27,9 @@ import AudioLoader from './AudioLoader.svelte.js';
|
|
|
30
27
|
export default class AudioScene {
|
|
31
28
|
#state = new LoadingStateMachine();
|
|
32
29
|
|
|
33
|
-
// @note this exported state is set by
|
|
30
|
+
// @note this exported state is set by onenter
|
|
34
31
|
state = $state(STATE_INITIAL);
|
|
35
32
|
|
|
36
|
-
// @note this exported state is set by $effect's
|
|
37
33
|
loaded = $derived.by(() => {
|
|
38
34
|
return this.state === STATE_LOADED;
|
|
39
35
|
});
|
|
@@ -109,47 +105,33 @@ export default class AudioScene {
|
|
|
109
105
|
}
|
|
110
106
|
});
|
|
111
107
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
case STATE_UNLOADING:
|
|
122
|
-
{
|
|
123
|
-
// console.log('AudioScene:unloading');
|
|
124
|
-
// this.#startUnLoading();
|
|
125
|
-
}
|
|
126
|
-
break;
|
|
127
|
-
|
|
128
|
-
case STATE_LOADED:
|
|
129
|
-
{
|
|
130
|
-
// console.log('AudioScene:loaded');
|
|
131
|
-
|
|
132
|
-
// tODO
|
|
133
|
-
// this.#abortLoading = null;
|
|
134
|
-
}
|
|
135
|
-
break;
|
|
136
|
-
|
|
137
|
-
case STATE_CANCELLED:
|
|
138
|
-
{
|
|
139
|
-
// console.log('AudioScene:cancelled');
|
|
140
|
-
// TODO
|
|
141
|
-
}
|
|
142
|
-
break;
|
|
143
|
-
|
|
144
|
-
case STATE_ERROR:
|
|
145
|
-
{
|
|
146
|
-
console.error('AudioScene:error', state.error);
|
|
147
|
-
}
|
|
148
|
-
break;
|
|
149
|
-
} // end switch
|
|
108
|
+
state.onenter = ( currentState ) => {
|
|
109
|
+
// console.log('onenter', currentState );
|
|
110
|
+
|
|
111
|
+
if(currentState === STATE_LOADING )
|
|
112
|
+
{
|
|
113
|
+
// console.log('AudioScene:loading');
|
|
114
|
+
this.#startLoading();
|
|
115
|
+
}
|
|
150
116
|
|
|
151
117
|
this.state = state.current;
|
|
152
|
-
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* ==== Common loader interface */
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get audio scene loading progress
|
|
125
|
+
*/
|
|
126
|
+
get progress() {
|
|
127
|
+
return this.#progress;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Start loading all audio sources
|
|
132
|
+
*/
|
|
133
|
+
load() {
|
|
134
|
+
this.#state.send(LOAD);
|
|
153
135
|
}
|
|
154
136
|
|
|
155
137
|
destroy() {
|
|
@@ -157,6 +139,8 @@ export default class AudioScene {
|
|
|
157
139
|
// TODO: Unload AUdioLoaders?
|
|
158
140
|
}
|
|
159
141
|
|
|
142
|
+
/* ==== Source definitions */
|
|
143
|
+
|
|
160
144
|
/**
|
|
161
145
|
* Add in-memory audio source
|
|
162
146
|
* - Uses an AudioLoader instance to load audio data from network
|
|
@@ -175,36 +159,7 @@ export default class AudioScene {
|
|
|
175
159
|
this.#memorySources.push({ label, audioLoader, config });
|
|
176
160
|
}
|
|
177
161
|
|
|
178
|
-
|
|
179
|
-
* Start loading all audio sources
|
|
180
|
-
*/
|
|
181
|
-
load() {
|
|
182
|
-
this.#state.send(LOAD);
|
|
183
|
-
|
|
184
|
-
// FIXME: in unit test when moved to startloading it hangs!
|
|
185
|
-
|
|
186
|
-
for (const { audioLoader } of this.#memorySources) {
|
|
187
|
-
audioLoader.load();
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Set an audio context to use
|
|
193
|
-
*
|
|
194
|
-
* @param {AudioContext} [audioContext]
|
|
195
|
-
*/
|
|
196
|
-
setAudioContext( audioContext ) {
|
|
197
|
-
this.#audioContext = audioContext;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async #startLoading() {
|
|
201
|
-
// console.log('#startLoading');
|
|
202
|
-
|
|
203
|
-
// FIXME: in unit test when moved to startloading it hangs!
|
|
204
|
-
// for (const { audioLoader } of this.#memorySources) {
|
|
205
|
-
// audioLoader.load();
|
|
206
|
-
// }
|
|
207
|
-
}
|
|
162
|
+
/* ==== Resource access */
|
|
208
163
|
|
|
209
164
|
/**
|
|
210
165
|
* Get a source that can be used to play the audio once
|
|
@@ -240,6 +195,25 @@ export default class AudioScene {
|
|
|
240
195
|
return sourceNode;
|
|
241
196
|
}
|
|
242
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Set an audio context to use
|
|
200
|
+
*
|
|
201
|
+
* @param {AudioContext} [audioContext]
|
|
202
|
+
*/
|
|
203
|
+
setAudioContext( audioContext ) {
|
|
204
|
+
this.#audioContext = audioContext;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async #startLoading() {
|
|
208
|
+
// console.log('#startLoading');
|
|
209
|
+
|
|
210
|
+
for (const { audioLoader } of this.#memorySources) {
|
|
211
|
+
audioLoader.load();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ==== Audio specific */
|
|
216
|
+
|
|
243
217
|
/**
|
|
244
218
|
* Set target gain
|
|
245
219
|
*
|
|
@@ -281,6 +255,7 @@ export default class AudioScene {
|
|
|
281
255
|
this.setTargetGain(this.#unmutedTargetGain);
|
|
282
256
|
}
|
|
283
257
|
|
|
258
|
+
/* ==== Internals */
|
|
284
259
|
|
|
285
260
|
#getGainNode()
|
|
286
261
|
{
|
|
@@ -322,4 +297,4 @@ export default class AudioScene {
|
|
|
322
297
|
|
|
323
298
|
throw new Error(`Source [${label}] has not been defined`);
|
|
324
299
|
}
|
|
325
|
-
}
|
|
300
|
+
} // end class
|
|
@@ -10,6 +10,19 @@
|
|
|
10
10
|
export default class ImageScene {
|
|
11
11
|
state: string;
|
|
12
12
|
loaded: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Get image scene loading progress
|
|
15
|
+
*/
|
|
16
|
+
get progress(): {
|
|
17
|
+
totalBytesLoaded: number;
|
|
18
|
+
totalSize: number;
|
|
19
|
+
sourcesLoaded: number;
|
|
20
|
+
numberOfSources: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Start loading all image sources
|
|
24
|
+
*/
|
|
25
|
+
load(): void;
|
|
13
26
|
destroy(): void;
|
|
14
27
|
/**
|
|
15
28
|
* Add image source
|
|
@@ -23,19 +36,6 @@ export default class ImageScene {
|
|
|
23
36
|
label: string;
|
|
24
37
|
imageSource: import("../../../config/typedef.js").ImageSource;
|
|
25
38
|
}): void;
|
|
26
|
-
/**
|
|
27
|
-
* Start loading all image sources
|
|
28
|
-
*/
|
|
29
|
-
load(): void;
|
|
30
|
-
/**
|
|
31
|
-
* Get image scene loading progress
|
|
32
|
-
*/
|
|
33
|
-
get progress(): {
|
|
34
|
-
totalBytesLoaded: number;
|
|
35
|
-
totalSize: number;
|
|
36
|
-
sourcesLoaded: number;
|
|
37
|
-
numberOfSources: number;
|
|
38
|
-
};
|
|
39
39
|
/**
|
|
40
40
|
* Get an image loader
|
|
41
41
|
*
|
|
@@ -7,10 +7,7 @@ import { LoadingStateMachine } from '../../../state/machines.js';
|
|
|
7
7
|
import {
|
|
8
8
|
STATE_INITIAL,
|
|
9
9
|
STATE_LOADING,
|
|
10
|
-
STATE_UNLOADING,
|
|
11
10
|
STATE_LOADED,
|
|
12
|
-
STATE_CANCELLED,
|
|
13
|
-
STATE_ERROR,
|
|
14
11
|
LOAD,
|
|
15
12
|
LOADED
|
|
16
13
|
} from '../../../state/machines.js';
|
|
@@ -31,10 +28,9 @@ import ImageLoader from './ImageLoader.svelte.js';
|
|
|
31
28
|
export default class ImageScene {
|
|
32
29
|
#state = new LoadingStateMachine();
|
|
33
30
|
|
|
34
|
-
// @note this exported state is set by
|
|
31
|
+
// @note this exported state is set by onenter
|
|
35
32
|
state = $state(STATE_INITIAL);
|
|
36
33
|
|
|
37
|
-
// @note this exported state is set by $effect's
|
|
38
34
|
loaded = $derived.by(() => {
|
|
39
35
|
return this.state === STATE_LOADED;
|
|
40
36
|
});
|
|
@@ -92,55 +88,42 @@ export default class ImageScene {
|
|
|
92
88
|
}
|
|
93
89
|
} );
|
|
94
90
|
|
|
95
|
-
state.onenter = (
|
|
96
|
-
// console.log('onenter',
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
case STATE_UNLOADING:
|
|
107
|
-
{
|
|
108
|
-
// console.log('ImageScene:unloading');
|
|
109
|
-
// this.#startUnLoading();
|
|
110
|
-
}
|
|
111
|
-
break;
|
|
112
|
-
|
|
113
|
-
case STATE_LOADED:
|
|
114
|
-
{
|
|
115
|
-
// console.log('ImageScene:loaded');
|
|
116
|
-
// TODO
|
|
117
|
-
// this.#abortLoading = null;
|
|
118
|
-
}
|
|
119
|
-
break;
|
|
120
|
-
|
|
121
|
-
case STATE_CANCELLED:
|
|
122
|
-
{
|
|
123
|
-
// console.log('ImageScene:cancelled');
|
|
124
|
-
// TODO
|
|
125
|
-
}
|
|
126
|
-
break;
|
|
127
|
-
|
|
128
|
-
case STATE_ERROR:
|
|
129
|
-
{
|
|
130
|
-
console.log('ImageScene:error', state);
|
|
131
|
-
}
|
|
132
|
-
break;
|
|
133
|
-
} // end switch
|
|
134
|
-
|
|
135
|
-
this.state = state;
|
|
91
|
+
state.onenter = ( currentState ) => {
|
|
92
|
+
// console.log('onenter', currentState );
|
|
93
|
+
|
|
94
|
+
if(currentState === STATE_LOADING )
|
|
95
|
+
{
|
|
96
|
+
// console.log('ImageScene:loading');
|
|
97
|
+
this.#startLoading();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.state = currentState;
|
|
136
101
|
};
|
|
137
102
|
}
|
|
138
103
|
|
|
104
|
+
/* ==== Common loader interface */
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get image scene loading progress
|
|
108
|
+
*/
|
|
109
|
+
get progress() {
|
|
110
|
+
return this.#progress;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Start loading all image sources
|
|
115
|
+
*/
|
|
116
|
+
load() {
|
|
117
|
+
this.#state.send(LOAD);
|
|
118
|
+
}
|
|
119
|
+
|
|
139
120
|
destroy() {
|
|
140
121
|
// TODO: disconnect all image sources?
|
|
141
122
|
// TODO: Unload ImageLoaders?
|
|
142
123
|
}
|
|
143
124
|
|
|
125
|
+
/* ==== Source definitions */
|
|
126
|
+
|
|
144
127
|
/**
|
|
145
128
|
* Add image source
|
|
146
129
|
* - Uses an ImageLoader instance to load image data from network
|
|
@@ -159,42 +142,7 @@ export default class ImageScene {
|
|
|
159
142
|
this.#imageSources.push({ label, imageLoader });
|
|
160
143
|
}
|
|
161
144
|
|
|
162
|
-
|
|
163
|
-
* Start loading all image sources
|
|
164
|
-
*/
|
|
165
|
-
load() {
|
|
166
|
-
this.#state.send(LOAD);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async #startLoading() {
|
|
170
|
-
for (const { imageLoader } of this.#imageSources) {
|
|
171
|
-
imageLoader.load();
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Get Image source
|
|
177
|
-
*
|
|
178
|
-
* @param {string} label
|
|
179
|
-
*
|
|
180
|
-
* @returns {ImageSceneSource}
|
|
181
|
-
*/
|
|
182
|
-
#getImageSceneSource(label) {
|
|
183
|
-
for (const source of this.#imageSources) {
|
|
184
|
-
if (label === source.label) {
|
|
185
|
-
return source;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
throw new Error(`Source [${label}] has not been defined`);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Get image scene loading progress
|
|
194
|
-
*/
|
|
195
|
-
get progress() {
|
|
196
|
-
return this.#progress;
|
|
197
|
-
}
|
|
145
|
+
/* ==== Resource access */
|
|
198
146
|
|
|
199
147
|
/**
|
|
200
148
|
* Get an image loader
|
|
@@ -236,4 +184,29 @@ export default class ImageScene {
|
|
|
236
184
|
|
|
237
185
|
return source.imageLoader.getObjectURL();
|
|
238
186
|
}
|
|
239
|
-
|
|
187
|
+
|
|
188
|
+
async #startLoading() {
|
|
189
|
+
for (const { imageLoader } of this.#imageSources) {
|
|
190
|
+
imageLoader.load();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* ==== Internals */
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get Image source
|
|
198
|
+
*
|
|
199
|
+
* @param {string} label
|
|
200
|
+
*
|
|
201
|
+
* @returns {ImageSceneSource}
|
|
202
|
+
*/
|
|
203
|
+
#getImageSceneSource(label) {
|
|
204
|
+
for (const source of this.#imageSources) {
|
|
205
|
+
if (label === source.label) {
|
|
206
|
+
return source;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
throw new Error(`Source [${label}] has not been defined`);
|
|
211
|
+
}
|
|
212
|
+
} // end class
|
|
@@ -90,10 +90,6 @@ export default class NetworkLoader {
|
|
|
90
90
|
const state = this._state;
|
|
91
91
|
// const progress = this.progress;
|
|
92
92
|
|
|
93
|
-
//
|
|
94
|
-
// ISSUE: $effect is not triggered by this._state changes,
|
|
95
|
-
// using onenter instead
|
|
96
|
-
//
|
|
97
93
|
this._state.onenter = () => {
|
|
98
94
|
switch (state.current) {
|
|
99
95
|
case STATE_LOADING:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @typedef {import('./typedef.js').
|
|
1
|
+
/** @typedef {import('./typedef.js').TransitionData} TransitionData */
|
|
2
2
|
/** @typedef {import('./typedef.js').OnEnterCallback} OnEnterCallback */
|
|
3
3
|
/** @typedef {import('./typedef.js').OnExitCallback} OnExitCallback */
|
|
4
4
|
/**
|
|
@@ -50,7 +50,7 @@ export default class FiniteStateMachine extends EventEmitter {
|
|
|
50
50
|
get current(): any;
|
|
51
51
|
#private;
|
|
52
52
|
}
|
|
53
|
-
export type
|
|
53
|
+
export type TransitionData = import("./typedef.js").TransitionData;
|
|
54
54
|
export type OnEnterCallback = import("./typedef.js").OnEnterCallback;
|
|
55
55
|
export type OnExitCallback = import("./typedef.js").OnExitCallback;
|
|
56
56
|
import EventEmitter from '../../../generic/events/classes/EventEmitter.js';
|
|
@@ -8,7 +8,7 @@ import { isTestEnv } from '../../../util/env.js';
|
|
|
8
8
|
import EventEmitter from '../../../generic/events/classes/EventEmitter.js';
|
|
9
9
|
import { ENTER, EXIT } from './constants.js';
|
|
10
10
|
|
|
11
|
-
/** @typedef {import('./typedef.js').
|
|
11
|
+
/** @typedef {import('./typedef.js').TransitionData} TransitionData */
|
|
12
12
|
/** @typedef {import('./typedef.js').OnEnterCallback} OnEnterCallback */
|
|
13
13
|
/** @typedef {import('./typedef.js').OnExitCallback} OnExitCallback */
|
|
14
14
|
|
|
@@ -57,17 +57,17 @@ export default class FiniteStateMachine extends EventEmitter {
|
|
|
57
57
|
this.states = states;
|
|
58
58
|
|
|
59
59
|
// synthetically trigger _enter for the initial state.
|
|
60
|
-
const
|
|
60
|
+
const initialTransitionData = {
|
|
61
61
|
from: null,
|
|
62
62
|
to: initial,
|
|
63
63
|
event: null,
|
|
64
64
|
args: []
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
this.#executeAction('_enter',
|
|
67
|
+
this.#executeAction('_enter', initialTransitionData);
|
|
68
68
|
|
|
69
69
|
// Emit ENTER event for external listeners for initial state
|
|
70
|
-
this.emit(ENTER, { state: initial,
|
|
70
|
+
this.emit(ENTER, { state: initial, transition: initialTransitionData });
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
@@ -78,24 +78,24 @@ export default class FiniteStateMachine extends EventEmitter {
|
|
|
78
78
|
* @param {any[]} [args]
|
|
79
79
|
*/
|
|
80
80
|
#transition(newState, event, args) {
|
|
81
|
-
/** @type {
|
|
82
|
-
const
|
|
81
|
+
/** @type {TransitionData} */
|
|
82
|
+
const transition = { from: this.#current, to: newState, event, args };
|
|
83
83
|
|
|
84
84
|
// Call onexit callback before leaving current state
|
|
85
|
-
this.onexit?.(this.#current,
|
|
85
|
+
this.onexit?.(this.#current, transition);
|
|
86
86
|
|
|
87
87
|
// Emit EXIT event for external listeners
|
|
88
|
-
this.emit(EXIT, { state: this.#current,
|
|
88
|
+
this.emit(EXIT, { state: this.#current, transition });
|
|
89
89
|
|
|
90
|
-
this.#executeAction('_exit',
|
|
90
|
+
this.#executeAction('_exit', transition);
|
|
91
91
|
this.#current = newState;
|
|
92
|
-
this.#executeAction('_enter',
|
|
92
|
+
this.#executeAction('_enter', transition);
|
|
93
93
|
|
|
94
94
|
// Emit ENTER event for external listeners
|
|
95
|
-
this.emit(ENTER, { state: newState,
|
|
95
|
+
this.emit(ENTER, { state: newState, transition });
|
|
96
96
|
|
|
97
97
|
// Call onenter callback after state change
|
|
98
|
-
this.onenter?.(newState,
|
|
98
|
+
this.onenter?.(newState, transition);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
@@ -116,7 +116,7 @@ export default class FiniteStateMachine extends EventEmitter {
|
|
|
116
116
|
if (isLifecycleFnMeta(args[0])) {
|
|
117
117
|
return action(args[0]);
|
|
118
118
|
} else {
|
|
119
|
-
throw new Error(`Invalid
|
|
119
|
+
throw new Error(`Invalid transition data passed to lifecycle function`);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// Normal state events
|
|
@@ -56,16 +56,16 @@ Each state can define:
|
|
|
56
56
|
```javascript
|
|
57
57
|
const machine = new FiniteStateMachine('idle', {
|
|
58
58
|
idle: {
|
|
59
|
-
_enter: (
|
|
59
|
+
_enter: (transition) => {
|
|
60
60
|
console.log('Entered idle state');
|
|
61
61
|
},
|
|
62
|
-
_exit: (
|
|
62
|
+
_exit: (transition) => {
|
|
63
63
|
console.log('Leaving idle state');
|
|
64
64
|
},
|
|
65
65
|
start: 'running'
|
|
66
66
|
},
|
|
67
67
|
running: {
|
|
68
|
-
_enter: (
|
|
68
|
+
_enter: (transition) => {
|
|
69
69
|
console.log('Started running');
|
|
70
70
|
},
|
|
71
71
|
stop: 'idle'
|
|
@@ -73,14 +73,14 @@ const machine = new FiniteStateMachine('idle', {
|
|
|
73
73
|
});
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
## Callback
|
|
76
|
+
## Callback TransitionData
|
|
77
77
|
|
|
78
|
-
Enter and exit callbacks receive
|
|
78
|
+
Enter and exit callbacks receive transition data about the state change:
|
|
79
79
|
|
|
80
80
|
```javascript
|
|
81
|
-
/** @typedef {import('./typedef.js').
|
|
81
|
+
/** @typedef {import('./typedef.js').TransitionData} TransitionData */
|
|
82
82
|
|
|
83
|
-
//
|
|
83
|
+
// TransitionData structure:
|
|
84
84
|
{
|
|
85
85
|
from: 'previousState', // State being exited
|
|
86
86
|
to: 'newState', // State being entered
|
|
@@ -94,18 +94,18 @@ Enter and exit callbacks receive metadata about the transition:
|
|
|
94
94
|
For better type safety, import the type definitions:
|
|
95
95
|
|
|
96
96
|
```javascript
|
|
97
|
-
/** @typedef {import('./typedef.js').
|
|
97
|
+
/** @typedef {import('./typedef.js').TransitionData} TransitionData */
|
|
98
98
|
/** @typedef {import('./typedef.js').OnEnterCallback} OnEnterCallback */
|
|
99
99
|
/** @typedef {import('./typedef.js').OnExitCallback} OnExitCallback */
|
|
100
100
|
|
|
101
101
|
/** @type {OnEnterCallback} */
|
|
102
|
-
const handleEnter = (
|
|
103
|
-
console.log(`Entering ${
|
|
102
|
+
const handleEnter = (currentState, transition) => {
|
|
103
|
+
console.log(`Entering ${currentState} from ${transition.from}`);
|
|
104
104
|
};
|
|
105
105
|
|
|
106
106
|
/** @type {OnExitCallback} */
|
|
107
|
-
const handleExit = (
|
|
108
|
-
console.log(`Leaving ${
|
|
107
|
+
const handleExit = (currentState, transition) => {
|
|
108
|
+
console.log(`Leaving ${currentState} to ${transition.to}`);
|
|
109
109
|
};
|
|
110
110
|
|
|
111
111
|
machine.onenter = handleEnter;
|
|
@@ -209,8 +209,8 @@ const machine = new FiniteStateMachine('idle', {
|
|
|
209
209
|
error: { retry: 'loading', reset: 'idle' }
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
-
machine.onexit = (
|
|
213
|
-
switch (
|
|
212
|
+
machine.onexit = (currentState, transition) => {
|
|
213
|
+
switch (currentState) {
|
|
214
214
|
case 'loading':
|
|
215
215
|
console.log('Leaving loading state...');
|
|
216
216
|
// Cancel ongoing requests
|
|
@@ -224,8 +224,8 @@ machine.onexit = (state, metadata) => {
|
|
|
224
224
|
}
|
|
225
225
|
};
|
|
226
226
|
|
|
227
|
-
machine.onenter = (
|
|
228
|
-
switch (
|
|
227
|
+
machine.onenter = (currentState, transition) => {
|
|
228
|
+
switch (currentState) {
|
|
229
229
|
case 'loading':
|
|
230
230
|
console.log('Started loading...');
|
|
231
231
|
showSpinner();
|
|
@@ -236,7 +236,7 @@ machine.onenter = (state, metadata) => {
|
|
|
236
236
|
break;
|
|
237
237
|
case 'error':
|
|
238
238
|
console.log('Loading failed');
|
|
239
|
-
showError(
|
|
239
|
+
showError(transition.args[0]);
|
|
240
240
|
break;
|
|
241
241
|
}
|
|
242
242
|
};
|
|
@@ -264,8 +264,8 @@ const machine = new FiniteStateMachine('idle', {
|
|
|
264
264
|
}
|
|
265
265
|
});
|
|
266
266
|
|
|
267
|
-
machine.onexit = (
|
|
268
|
-
machine.onenter = (
|
|
267
|
+
machine.onexit = (currentState) => console.log(`3. onexit ${currentState}`);
|
|
268
|
+
machine.onenter = (currentState) => console.log(`6. onenter ${currentState}`);
|
|
269
269
|
|
|
270
270
|
// Initial state triggers _enter and onenter
|
|
271
271
|
// Output:
|
|
@@ -313,8 +313,8 @@ machine.onexit = (state) => {
|
|
|
313
313
|
}
|
|
314
314
|
};
|
|
315
315
|
|
|
316
|
-
machine.onenter = (
|
|
317
|
-
switch (
|
|
316
|
+
machine.onenter = (label) => {
|
|
317
|
+
switch (label) {
|
|
318
318
|
case 'loading':
|
|
319
319
|
this.#startProcess(); // Start async process immediately
|
|
320
320
|
break;
|
|
@@ -381,8 +381,8 @@ export default class TaskProcessor {
|
|
|
381
381
|
|
|
382
382
|
constructor() {
|
|
383
383
|
// onexit: Handle cleanup when leaving states
|
|
384
|
-
this.#machine.onexit = (
|
|
385
|
-
switch (
|
|
384
|
+
this.#machine.onexit = (currentState) => {
|
|
385
|
+
switch (currentState) {
|
|
386
386
|
case 'processing':
|
|
387
387
|
this.#cancelTasks(); // Cancel ongoing tasks if interrupted
|
|
388
388
|
break;
|
|
@@ -390,8 +390,8 @@ export default class TaskProcessor {
|
|
|
390
390
|
};
|
|
391
391
|
|
|
392
392
|
// onenter: Handle immediate state actions
|
|
393
|
-
this.#machine.onenter = (
|
|
394
|
-
switch (
|
|
393
|
+
this.#machine.onenter = (currentState) => {
|
|
394
|
+
switch (currentState) {
|
|
395
395
|
case 'processing':
|
|
396
396
|
this.#startAllTasks(); // Start processing immediately
|
|
397
397
|
break;
|
|
@@ -399,7 +399,7 @@ export default class TaskProcessor {
|
|
|
399
399
|
this.#notifyComplete(); // Cleanup/notify when done
|
|
400
400
|
break;
|
|
401
401
|
}
|
|
402
|
-
this.state =
|
|
402
|
+
this.state = currentState;
|
|
403
403
|
};
|
|
404
404
|
|
|
405
405
|
// $effect: Monitor reactive completion
|
|
@@ -435,8 +435,8 @@ export default class TaskProcessor {
|
|
|
435
435
|
});
|
|
436
436
|
|
|
437
437
|
// Handle state-specific actions
|
|
438
|
-
machine.onexit = (
|
|
439
|
-
switch (
|
|
438
|
+
machine.onexit = (currentState) => {
|
|
439
|
+
switch (currentState) {
|
|
440
440
|
case 'loading':
|
|
441
441
|
// Cancel any ongoing requests
|
|
442
442
|
cancelRequests();
|
|
@@ -444,8 +444,8 @@ export default class TaskProcessor {
|
|
|
444
444
|
}
|
|
445
445
|
};
|
|
446
446
|
|
|
447
|
-
machine.onenter = (
|
|
448
|
-
switch (
|
|
447
|
+
machine.onenter = (currentState) => {
|
|
448
|
+
switch (currentState) {
|
|
449
449
|
case 'loading':
|
|
450
450
|
loadData();
|
|
451
451
|
break;
|
|
@@ -526,7 +526,7 @@ const auth = new FiniteStateMachine('anonymous', {
|
|
|
526
526
|
failure: 'anonymous'
|
|
527
527
|
},
|
|
528
528
|
authenticated: {
|
|
529
|
-
_enter: (
|
|
529
|
+
_enter: (transition) => console.log('Welcome!'),
|
|
530
530
|
logout: 'anonymous',
|
|
531
531
|
expire: 'anonymous'
|
|
532
532
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Metadata object passed to state transition callbacks
|
|
3
3
|
*/
|
|
4
|
-
export type
|
|
4
|
+
export type TransitionData = {
|
|
5
5
|
/**
|
|
6
6
|
* - The state being exited
|
|
7
7
|
*/
|
|
@@ -22,8 +22,8 @@ export type StateTransitionMetadata = {
|
|
|
22
22
|
/**
|
|
23
23
|
* Callback function called when entering a state
|
|
24
24
|
*/
|
|
25
|
-
export type OnEnterCallback = (
|
|
25
|
+
export type OnEnterCallback = (currentState: string, transition: TransitionData) => void;
|
|
26
26
|
/**
|
|
27
27
|
* Callback function called when exiting a state
|
|
28
28
|
*/
|
|
29
|
-
export type OnExitCallback = (
|
|
29
|
+
export type OnExitCallback = (currentState: string, transition: TransitionData) => void;
|
|
@@ -5,24 +5,30 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* Metadata object passed to state transition callbacks
|
|
7
7
|
*
|
|
8
|
-
* @typedef {object}
|
|
8
|
+
* @typedef {object} TransitionData
|
|
9
9
|
* @property {string} from - The state being exited
|
|
10
10
|
* @property {string} to - The state being entered
|
|
11
11
|
* @property {string} event - The event that triggered the transition
|
|
12
12
|
* @property {any[]} args - Arguments passed to the send() method
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Callback function called when entering a state
|
|
17
|
+
*
|
|
18
|
+
* @callback OnEnterCallback
|
|
19
|
+
* @param {string} currentState - The state being entered
|
|
20
|
+
* @param {TransitionData} transition - Details about the transition
|
|
21
|
+
* @returns {void}
|
|
22
|
+
*/
|
|
20
23
|
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Callback function called when exiting a state
|
|
26
|
+
*
|
|
27
|
+
* @callback OnExitCallback
|
|
28
|
+
* @param {string} currentState - The state being exited
|
|
29
|
+
* @param {TransitionData} transition - Details about the transition
|
|
30
|
+
* @returns {void}
|
|
31
|
+
*/
|
|
26
32
|
|
|
27
|
-
// Export types for
|
|
33
|
+
// Export types for JSdoc
|
|
28
34
|
export {};
|
|
@@ -108,8 +108,8 @@ The `onenter` callback provides a reliable way to react to state changes, design
|
|
|
108
108
|
```javascript
|
|
109
109
|
const loader = new LoadingStateMachine();
|
|
110
110
|
|
|
111
|
-
loader.onenter = (
|
|
112
|
-
switch (
|
|
111
|
+
loader.onenter = (currentState) => {
|
|
112
|
+
switch (currentState) {
|
|
113
113
|
case STATE_LOADING:
|
|
114
114
|
console.log('Started loading...');
|
|
115
115
|
showSpinner();
|
|
@@ -141,8 +141,8 @@ $effect(() => {
|
|
|
141
141
|
});
|
|
142
142
|
|
|
143
143
|
// onenter catches every transition
|
|
144
|
-
loader.onenter = (
|
|
145
|
-
console.log(
|
|
144
|
+
loader.onenter = (currentState) => {
|
|
145
|
+
console.log(currentState); // Sees every state change
|
|
146
146
|
};
|
|
147
147
|
```
|
|
148
148
|
|
|
@@ -188,8 +188,8 @@ const timeoutId = setTimeout(() => {
|
|
|
188
188
|
}
|
|
189
189
|
}, 10000); // 10 second timeout
|
|
190
190
|
|
|
191
|
-
loader.onenter = (
|
|
192
|
-
switch (
|
|
191
|
+
loader.onenter = (currentState) => {
|
|
192
|
+
switch (currentState) {
|
|
193
193
|
case STATE_TIMEOUT:
|
|
194
194
|
console.log('Loading timed out');
|
|
195
195
|
showRetryButton();
|
|
@@ -220,8 +220,8 @@ LoadingStateMachine inherits the `onenter` callback and Svelte reactivity integr
|
|
|
220
220
|
```javascript
|
|
221
221
|
const loader = new LoadingStateMachine();
|
|
222
222
|
|
|
223
|
-
loader.onenter = (
|
|
224
|
-
switch (
|
|
223
|
+
loader.onenter = (currentState) => {
|
|
224
|
+
switch (currentState) {
|
|
225
225
|
case STATE_LOADING:
|
|
226
226
|
this.#startLoading(); // Start async process immediately
|
|
227
227
|
break;
|
|
@@ -267,8 +267,8 @@ export default class MultiSourceLoader {
|
|
|
267
267
|
|
|
268
268
|
constructor() {
|
|
269
269
|
// Handle immediate loading state actions
|
|
270
|
-
this.#loader.onenter = (
|
|
271
|
-
switch (
|
|
270
|
+
this.#loader.onenter = (currentState) => {
|
|
271
|
+
switch (currentState) {
|
|
272
272
|
case STATE_LOADING:
|
|
273
273
|
this.#startAllSources();
|
|
274
274
|
break;
|
|
@@ -282,7 +282,7 @@ export default class MultiSourceLoader {
|
|
|
282
282
|
this.#handleTimeout();
|
|
283
283
|
break;
|
|
284
284
|
}
|
|
285
|
-
this.state =
|
|
285
|
+
this.state = currentState;
|
|
286
286
|
};
|
|
287
287
|
|
|
288
288
|
// Monitor reactive completion
|
|
@@ -319,8 +319,8 @@ export default class MultiSourceLoader {
|
|
|
319
319
|
const loader = new LoadingStateMachine();
|
|
320
320
|
let data = $state(null);
|
|
321
321
|
|
|
322
|
-
loader.onenter = (
|
|
323
|
-
if (
|
|
322
|
+
loader.onenter = (currentState) => {
|
|
323
|
+
if (currentState === STATE_LOADING) {
|
|
324
324
|
loadData();
|
|
325
325
|
}
|
|
326
326
|
};
|
|
@@ -371,8 +371,8 @@ export default class MultiSourceLoader {
|
|
|
371
371
|
const loader = new LoadingStateMachine();
|
|
372
372
|
let abortController = null;
|
|
373
373
|
|
|
374
|
-
loader.onenter = (
|
|
375
|
-
switch (
|
|
374
|
+
loader.onenter = (currentState) => {
|
|
375
|
+
switch (currentState) {
|
|
376
376
|
case STATE_LOADING:
|
|
377
377
|
startLoad();
|
|
378
378
|
break;
|
|
@@ -426,8 +426,8 @@ export default class MultiSourceLoader {
|
|
|
426
426
|
|
|
427
427
|
```javascript
|
|
428
428
|
// ✅ Good - use onenter for reliable side effects
|
|
429
|
-
loader.onenter = (
|
|
430
|
-
if (
|
|
429
|
+
loader.onenter = (currentState) => {
|
|
430
|
+
if (currentState === STATE_LOADING) {
|
|
431
431
|
analytics.track('loading_started');
|
|
432
432
|
}
|
|
433
433
|
};
|
|
@@ -443,8 +443,8 @@ $effect(() => {
|
|
|
443
443
|
### 2. Handle All Error States
|
|
444
444
|
|
|
445
445
|
```javascript
|
|
446
|
-
loader.onenter = (
|
|
447
|
-
switch (
|
|
446
|
+
loader.onenter = (currentState) => {
|
|
447
|
+
switch (currentState) {
|
|
448
448
|
case STATE_ERROR:
|
|
449
449
|
showErrorToast(loader.error.message);
|
|
450
450
|
logError(loader.error);
|
|
@@ -459,8 +459,8 @@ loader.onenter = (state) => {
|
|
|
459
459
|
### 3. Implement Proper Cleanup
|
|
460
460
|
|
|
461
461
|
```javascript
|
|
462
|
-
loader.onenter = (
|
|
463
|
-
switch (
|
|
462
|
+
loader.onenter = (currentState) => {
|
|
463
|
+
switch (currentState) {
|
|
464
464
|
case STATE_LOADING:
|
|
465
465
|
showProgressBar();
|
|
466
466
|
break;
|
|
@@ -500,8 +500,8 @@ loader.send('load');
|
|
|
500
500
|
const resourceLoader = new LoadingStateMachine();
|
|
501
501
|
let resource = null;
|
|
502
502
|
|
|
503
|
-
resourceLoader.onenter = async (
|
|
504
|
-
switch (
|
|
503
|
+
resourceLoader.onenter = async (currentState) => {
|
|
504
|
+
switch (currentState) {
|
|
505
505
|
case STATE_LOADING:
|
|
506
506
|
try {
|
|
507
507
|
resource = await loadResource();
|
|
@@ -529,8 +529,8 @@ const retryLoader = new LoadingStateMachine();
|
|
|
529
529
|
let retryCount = 0;
|
|
530
530
|
const maxRetries = 3;
|
|
531
531
|
|
|
532
|
-
retryLoader.onenter = async (
|
|
533
|
-
switch (
|
|
532
|
+
retryLoader.onenter = async (currentState) => {
|
|
533
|
+
switch (currentState) {
|
|
534
534
|
case STATE_LOADING:
|
|
535
535
|
try {
|
|
536
536
|
await performLoad();
|