@hkdigital/lib-core 0.4.47 → 0.4.48
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/base/SceneBase.svelte.d.ts +3 -11
- package/dist/network/loaders/base/SceneBase.svelte.js +34 -58
- package/dist/network/states/NetworkLoader.svelte.d.ts +1 -3
- package/dist/network/states/NetworkLoader.svelte.js +41 -19
- package/dist/state/machines/finite-state-machine/FiniteStateMachine.svelte.js +29 -11
- package/dist/state/machines/loading-state-machine/LoadingStateMachine.svelte.js +2 -1
- package/dist/util/svelte/wait/index.js +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export default class SceneBase {
|
|
6
6
|
state: string;
|
|
7
|
+
initial: boolean;
|
|
7
8
|
loaded: boolean;
|
|
9
|
+
/** @type {SceneLoadingProgress} */
|
|
10
|
+
progress: SceneLoadingProgress;
|
|
8
11
|
/**
|
|
9
12
|
* Get the array of sources managed by this scene
|
|
10
13
|
*
|
|
@@ -19,17 +22,6 @@ export default class SceneBase {
|
|
|
19
22
|
* @returns {import('../../states/index.js').NetworkLoader} loader
|
|
20
23
|
*/
|
|
21
24
|
getLoaderFromSource(source: object): import("../../states/index.js").NetworkLoader;
|
|
22
|
-
/**
|
|
23
|
-
* Get scene loading progress
|
|
24
|
-
*/
|
|
25
|
-
get progress(): import("./typedef.js").SceneLoadingProgress;
|
|
26
|
-
/**
|
|
27
|
-
* Get scene abort progress
|
|
28
|
-
*/
|
|
29
|
-
get abortProgress(): {
|
|
30
|
-
sourcesAborted: number;
|
|
31
|
-
numberOfSources: number;
|
|
32
|
-
};
|
|
33
25
|
/**
|
|
34
26
|
* Start loading all sources
|
|
35
27
|
*/
|
|
@@ -27,12 +27,22 @@ export default class SceneBase {
|
|
|
27
27
|
// @note this exported state is set by onenter
|
|
28
28
|
state = $state(STATE_INITIAL);
|
|
29
29
|
|
|
30
|
+
initial = $derived.by(() => {
|
|
31
|
+
return this.state === STATE_INITIAL;
|
|
32
|
+
});
|
|
33
|
+
|
|
30
34
|
loaded = $derived.by(() => {
|
|
31
35
|
return this.state === STATE_LOADED;
|
|
32
36
|
});
|
|
33
37
|
|
|
38
|
+
// aborted = $derived.by(() => {
|
|
39
|
+
// return this.state === STATE_ABORTED;
|
|
40
|
+
// });
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
34
44
|
/** @type {SceneLoadingProgress} */
|
|
35
|
-
|
|
45
|
+
progress = $derived.by(() => {
|
|
36
46
|
let totalSize = 0;
|
|
37
47
|
let totalBytesLoaded = 0;
|
|
38
48
|
let sourcesLoaded = 0;
|
|
@@ -62,36 +72,24 @@ export default class SceneBase {
|
|
|
62
72
|
};
|
|
63
73
|
});
|
|
64
74
|
|
|
65
|
-
#abortProgress = $derived.by(() => {
|
|
66
|
-
let sourcesAborted = 0;
|
|
67
|
-
const sources = this.sources;
|
|
68
|
-
const numberOfSources = sources.length;
|
|
69
|
-
|
|
70
|
-
for (let j = 0; j < numberOfSources; j++) {
|
|
71
|
-
const source = sources[j];
|
|
72
|
-
const loader = this.getLoaderFromSource(source);
|
|
73
|
-
const loaderState = loader.state;
|
|
74
|
-
|
|
75
|
-
if (loaderState === STATE_ABORTED || loaderState === STATE_ERROR) {
|
|
76
|
-
sourcesAborted++;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
sourcesAborted,
|
|
82
|
-
numberOfSources
|
|
83
|
-
};
|
|
84
|
-
});
|
|
85
75
|
|
|
86
76
|
/**
|
|
87
77
|
* Construct SceneBase
|
|
88
78
|
*/
|
|
89
79
|
constructor() {
|
|
90
|
-
|
|
80
|
+
this.#state.onenter = (currentState) => {
|
|
81
|
+
if (currentState === STATE_LOADING) {
|
|
82
|
+
this.#startLoading();
|
|
83
|
+
} else if (currentState === STATE_ABORTING) {
|
|
84
|
+
this.#startAbort();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.state = currentState;
|
|
88
|
+
};
|
|
91
89
|
|
|
92
90
|
$effect(() => {
|
|
93
91
|
if (this.state === STATE_LOADING) {
|
|
94
|
-
const { sourcesLoaded, numberOfSources } = this
|
|
92
|
+
const { sourcesLoaded, numberOfSources } = this.progress;
|
|
95
93
|
|
|
96
94
|
if (sourcesLoaded === numberOfSources && numberOfSources > 0) {
|
|
97
95
|
this.#state.send(LOADED);
|
|
@@ -99,20 +97,13 @@ export default class SceneBase {
|
|
|
99
97
|
}
|
|
100
98
|
});
|
|
101
99
|
|
|
102
|
-
$effect(() => {
|
|
103
|
-
if (this.state === STATE_ABORTING) {
|
|
104
|
-
const { sourcesAborted, numberOfSources } = this.#abortProgress;
|
|
105
|
-
|
|
106
|
-
if (sourcesAborted === numberOfSources && numberOfSources > 0) {
|
|
107
|
-
this.#state.send(ABORTED);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
100
|
|
|
112
101
|
$effect(() => {
|
|
113
|
-
if (this
|
|
102
|
+
if (this.state === STATE_LOADING) {
|
|
103
|
+
|
|
114
104
|
// Check if any source failed during loading
|
|
115
105
|
const sources = this.sources;
|
|
106
|
+
|
|
116
107
|
for (const source of sources) {
|
|
117
108
|
const loader = this.getLoaderFromSource(source);
|
|
118
109
|
if (loader.state === STATE_ERROR) {
|
|
@@ -121,18 +112,9 @@ export default class SceneBase {
|
|
|
121
112
|
}
|
|
122
113
|
}
|
|
123
114
|
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
state.onenter = (currentState) => {
|
|
127
|
-
if (currentState === STATE_LOADING) {
|
|
128
|
-
this.#startLoading();
|
|
129
|
-
} else if (currentState === STATE_ABORTING) {
|
|
130
|
-
this.#startAbort();
|
|
131
|
-
}
|
|
132
115
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
116
|
+
});
|
|
117
|
+
} // end constructor
|
|
136
118
|
|
|
137
119
|
/* ==== Abstract methods - must be implemented by subclasses */
|
|
138
120
|
|
|
@@ -159,20 +141,6 @@ export default class SceneBase {
|
|
|
159
141
|
|
|
160
142
|
/* ==== Common loader interface */
|
|
161
143
|
|
|
162
|
-
/**
|
|
163
|
-
* Get scene loading progress
|
|
164
|
-
*/
|
|
165
|
-
get progress() {
|
|
166
|
-
return this.#progress;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Get scene abort progress
|
|
171
|
-
*/
|
|
172
|
-
get abortProgress() {
|
|
173
|
-
return this.#abortProgress;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
144
|
/**
|
|
177
145
|
* Start loading all sources
|
|
178
146
|
*/
|
|
@@ -302,5 +270,13 @@ export default class SceneBase {
|
|
|
302
270
|
const loader = this.getLoaderFromSource(source);
|
|
303
271
|
loader.abort();
|
|
304
272
|
}
|
|
273
|
+
|
|
274
|
+
// Defer ABORTED transition to avoid re-entrant state machine calls
|
|
275
|
+
setTimeout(() => {
|
|
276
|
+
// Only transition to ABORTED if still in ABORTING state
|
|
277
|
+
if (this.#state.current === STATE_ABORTING) {
|
|
278
|
+
this.#state.send(ABORTED);
|
|
279
|
+
}
|
|
280
|
+
}, 0);
|
|
305
281
|
}
|
|
306
282
|
}
|
|
@@ -13,8 +13,7 @@ export default class NetworkLoader {
|
|
|
13
13
|
constructor({ url }: {
|
|
14
14
|
url: string;
|
|
15
15
|
});
|
|
16
|
-
|
|
17
|
-
state: any;
|
|
16
|
+
state: string;
|
|
18
17
|
initial: boolean;
|
|
19
18
|
loaded: boolean;
|
|
20
19
|
/** @type {string|null} */
|
|
@@ -94,4 +93,3 @@ export default class NetworkLoader {
|
|
|
94
93
|
getObjectURL(): string;
|
|
95
94
|
#private;
|
|
96
95
|
}
|
|
97
|
-
import { LoadingStateMachine } from '../../state/machines.js';
|
|
@@ -29,20 +29,34 @@ import { ERROR_NOT_LOADED, ERROR_TRANSFERRED } from './constants.js';
|
|
|
29
29
|
* - Loaded data can be transferred to an AudioBufferSourceNode
|
|
30
30
|
*/
|
|
31
31
|
export default class NetworkLoader {
|
|
32
|
-
|
|
32
|
+
#state = $state(new LoadingStateMachine());
|
|
33
33
|
|
|
34
|
-
state = $derived.by(() => {
|
|
35
|
-
|
|
36
|
-
});
|
|
34
|
+
// state = $derived.by(() => {
|
|
35
|
+
// return this.#state.current;
|
|
36
|
+
// });
|
|
37
|
+
|
|
38
|
+
// initial = $derived.by(() => {
|
|
39
|
+
// return this.#state.current === STATE_INITIAL;
|
|
40
|
+
// });
|
|
41
|
+
|
|
42
|
+
// loaded = $derived.by(() => {
|
|
43
|
+
// return this.#state.current === STATE_LOADED;
|
|
44
|
+
// });
|
|
45
|
+
|
|
46
|
+
state = $state(STATE_INITIAL);
|
|
37
47
|
|
|
38
48
|
initial = $derived.by(() => {
|
|
39
|
-
return this.
|
|
49
|
+
return this.state === STATE_INITIAL;
|
|
40
50
|
});
|
|
41
51
|
|
|
42
52
|
loaded = $derived.by(() => {
|
|
43
|
-
return this.
|
|
53
|
+
return this.state === STATE_LOADED;
|
|
44
54
|
});
|
|
45
55
|
|
|
56
|
+
// aborted = $derived.by(() => {
|
|
57
|
+
// return this.state === STATE_ABORTED;
|
|
58
|
+
// });
|
|
59
|
+
|
|
46
60
|
/** @type {string|null} */
|
|
47
61
|
_url = null;
|
|
48
62
|
|
|
@@ -86,11 +100,10 @@ export default class NetworkLoader {
|
|
|
86
100
|
|
|
87
101
|
this._url = url;
|
|
88
102
|
|
|
89
|
-
|
|
90
|
-
|
|
103
|
+
this.#state.onenter = (currentState) => {
|
|
104
|
+
this.state = currentState;
|
|
91
105
|
|
|
92
|
-
|
|
93
|
-
switch (state.current) {
|
|
106
|
+
switch (currentState) {
|
|
94
107
|
case STATE_LOADING:
|
|
95
108
|
{
|
|
96
109
|
this.#load();
|
|
@@ -116,8 +129,17 @@ export default class NetworkLoader {
|
|
|
116
129
|
this._abortLoading();
|
|
117
130
|
this._abortLoading = null;
|
|
118
131
|
}
|
|
119
|
-
|
|
120
|
-
|
|
132
|
+
|
|
133
|
+
//
|
|
134
|
+
// _abortLoading has been called (is set)
|
|
135
|
+
// => Transition to state ABORTED (deferred to avoid re-entrant call)
|
|
136
|
+
//
|
|
137
|
+
setTimeout(() => {
|
|
138
|
+
// Only transition to ABORTED if still in ABORTING state
|
|
139
|
+
if (this.#state.current === STATE_ABORTING) {
|
|
140
|
+
this.#state.send(ABORTED);
|
|
141
|
+
}
|
|
142
|
+
}, 0);
|
|
121
143
|
}
|
|
122
144
|
break;
|
|
123
145
|
|
|
@@ -134,14 +156,14 @@ export default class NetworkLoader {
|
|
|
134
156
|
* Start loading all network data
|
|
135
157
|
*/
|
|
136
158
|
load() {
|
|
137
|
-
this.
|
|
159
|
+
this.#state.send(LOAD);
|
|
138
160
|
}
|
|
139
161
|
|
|
140
162
|
/**
|
|
141
163
|
* Unoad all network data
|
|
142
164
|
*/
|
|
143
165
|
unload() {
|
|
144
|
-
this.
|
|
166
|
+
this.#state.send(UNLOAD);
|
|
145
167
|
}
|
|
146
168
|
|
|
147
169
|
/**
|
|
@@ -150,7 +172,7 @@ export default class NetworkLoader {
|
|
|
150
172
|
* - Aborts network requests and transitions to STATE_ABORTING
|
|
151
173
|
*/
|
|
152
174
|
abort() {
|
|
153
|
-
this.
|
|
175
|
+
this.#state.send(ABORT);
|
|
154
176
|
}
|
|
155
177
|
|
|
156
178
|
/**
|
|
@@ -303,9 +325,9 @@ export default class NetworkLoader {
|
|
|
303
325
|
// this._size = this._buffer.byteLength;
|
|
304
326
|
// }
|
|
305
327
|
|
|
306
|
-
this.
|
|
328
|
+
this.#state.send(LOADED);
|
|
307
329
|
} catch (e) {
|
|
308
|
-
this.
|
|
330
|
+
this.#state.send(ERROR, e);
|
|
309
331
|
}
|
|
310
332
|
}
|
|
311
333
|
|
|
@@ -325,9 +347,9 @@ export default class NetworkLoader {
|
|
|
325
347
|
this._headers = null;
|
|
326
348
|
this._buffer = null;
|
|
327
349
|
|
|
328
|
-
this.
|
|
350
|
+
this.#state.send(INITIAL);
|
|
329
351
|
} catch (e) {
|
|
330
|
-
this.
|
|
352
|
+
this.#state.send(ERROR, e);
|
|
331
353
|
}
|
|
332
354
|
}
|
|
333
355
|
} // end class
|
|
@@ -45,6 +45,9 @@ export default class FiniteStateMachine extends EventEmitter {
|
|
|
45
45
|
/** @type {boolean} */
|
|
46
46
|
#enableConsoleWarnings = !isTestEnv;
|
|
47
47
|
|
|
48
|
+
/** @type {boolean} */
|
|
49
|
+
#isTransitioning = false;
|
|
50
|
+
|
|
48
51
|
/**
|
|
49
52
|
* Constructor
|
|
50
53
|
*
|
|
@@ -81,21 +84,27 @@ export default class FiniteStateMachine extends EventEmitter {
|
|
|
81
84
|
/** @type {TransitionData} */
|
|
82
85
|
const transition = { from: this.#current, to: newState, event, args };
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
this.#isTransitioning = true;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// Call onexit callback before leaving current state
|
|
91
|
+
this.onexit?.(this.#current, transition);
|
|
86
92
|
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
// Emit EXIT event for external listeners
|
|
94
|
+
this.emit(EXIT, { state: this.#current, transition });
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
this.#executeAction('_exit', transition);
|
|
97
|
+
this.#current = newState;
|
|
98
|
+
this.#executeAction('_enter', transition);
|
|
93
99
|
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
// Emit ENTER event for external listeners
|
|
101
|
+
this.emit(ENTER, { state: newState, transition });
|
|
96
102
|
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
// Call onenter callback after state change
|
|
104
|
+
this.onenter?.(newState, transition);
|
|
105
|
+
} finally {
|
|
106
|
+
this.#isTransitioning = false;
|
|
107
|
+
}
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
/**
|
|
@@ -147,6 +156,15 @@ export default class FiniteStateMachine extends EventEmitter {
|
|
|
147
156
|
* @param {any[]} args
|
|
148
157
|
*/
|
|
149
158
|
send(event, ...args) {
|
|
159
|
+
if (this.#isTransitioning) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Cannot send event '${event}' while state machine is transitioning. ` +
|
|
162
|
+
`This indicates a re-entrant call from within onenter, onexit, or ` +
|
|
163
|
+
`lifecycle callbacks (_enter/_exit). Consider using setTimeout() to ` +
|
|
164
|
+
`defer the event or restructure to avoid nested state transitions.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
150
168
|
const newState = this.#executeAction(event, ...args);
|
|
151
169
|
|
|
152
170
|
if (newState && newState !== this.#current) {
|
|
@@ -61,11 +61,12 @@ export default class LoadingStateMachine extends FiniteStateMachine {
|
|
|
61
61
|
},
|
|
62
62
|
[STATE_ABORTING]: {
|
|
63
63
|
[ERROR]: STATE_ERROR,
|
|
64
|
+
[LOADED]: STATE_LOADED, // A load signal might still come during ABORT
|
|
64
65
|
[ABORTED]: STATE_ABORTED
|
|
65
66
|
},
|
|
66
67
|
[STATE_ABORTED]: {
|
|
67
68
|
[LOAD]: STATE_LOADING,
|
|
68
|
-
[LOADED]: STATE_LOADED,
|
|
69
|
+
[LOADED]: STATE_LOADED, // A load signal might still come after ABORT
|
|
69
70
|
[UNLOAD]: STATE_UNLOADING
|
|
70
71
|
},
|
|
71
72
|
[STATE_TIMEOUT]: {
|