1
- export default function ( state , states = { } ) {
1
+ export default function ( initialState , states = { } ) {
2
2
/*
3
3
* Core Finite State Machine functionality
4
4
* - adheres to Svelte store contract (https://svelte.dev/docs#Store_contract)
@@ -8,6 +8,7 @@ export default function (state, states = {}) {
8
8
*/
9
9
const subscribers = new Set ( ) ;
10
10
let proxy ;
11
+ let state = null ;
11
12
12
13
function subscribe ( callback ) {
13
14
if ( typeof callback !== 'function' ) {
@@ -18,12 +19,43 @@ export default function (state, states = {}) {
18
19
return ( ) => subscribers . delete ( callback ) ;
19
20
}
20
21
22
+ /*
23
+ * API change: subscribers are notified after _enter, not before, because eventless transitions
24
+ * might mean we settle in a new state. We may well transit through several states during the
25
+ * _enter() call.
26
+ *
27
+ * The logic here is intricate. The protocol is that there is always, internally, calls to
28
+ * _exit() followed by _enter(), with the same to and from arguments. The initial _exit()
29
+ * and the final _enter() will use the public event. All calls will have the original event
30
+ * and args -- since we never know in advance whether an event is a final one. If _enter()
31
+ * returns a new state, we then generate a new _exit() and _enter, moving from the previous
32
+ * state to the new one, and repeat. If it does not, then that _enter is the final call.
33
+ */
21
34
function transition ( newState , event , args ) {
22
- const metadata = { from : state , to : newState , event, args } ;
23
- dispatch ( '_exit' , metadata ) ;
24
- state = newState ;
25
- subscribers . forEach ( ( callback ) => callback ( state ) ) ;
26
- dispatch ( '_enter' , metadata ) ;
35
+ let metadata = { from : state , to : newState , event, args } ;
36
+ let startState = state ;
37
+
38
+ // Never exit the null state
39
+ if ( state !== null ) {
40
+ dispatch ( '_exit' , metadata ) ;
41
+ }
42
+
43
+ while ( true ) {
44
+ state = metadata . to ;
45
+ const nextState = dispatch ( '_enter' , metadata ) ;
46
+ if ( ! nextState ) {
47
+ break ;
48
+ }
49
+
50
+ metadata = { from : metadata . to , to : nextState , event, args }
51
+ dispatch ( '_exit' , metadata ) ;
52
+ }
53
+
54
+ // If (and only if) the final state is not the same as the initial state, then we
55
+ // inform the subscribers
56
+ if ( state !== startState ) {
57
+ subscribers . forEach ( ( callback ) => callback ( state ) ) ;
58
+ }
27
59
}
28
60
29
61
function dispatch ( event , ...args ) {
@@ -78,8 +110,10 @@ export default function (state, states = {}) {
78
110
} ) ;
79
111
80
112
/*
81
- * `_enter` initial state and return the proxy object
113
+ * `_enter` initial state and return the proxy object. Note that this may also
114
+ * involve eventless transitions to other states. Note, interestingly, that
115
+ * we are free to notify here, because there will never be subscribers.
82
116
*/
83
- dispatch ( '_enter' , { from : null , to : state , event : null , args : [ ] } ) ;
117
+ transition ( initialState , null , [ ] ) ;
84
118
return proxy ;
85
119
}
0 commit comments