1
+ //
2
+ // HierarchicalStateMachine.swift
3
+ // SwiftState
4
+ //
5
+ // Created by Yasuhiro Inami on 2014/10/13.
6
+ // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved.
7
+ //
8
+
9
+ import Foundation
10
+
11
+ public typealias HSM = HierarchicalStateMachine < String , String >
12
+
13
+ //
14
+ // NOTE:
15
+ // When subclassing StateMachine<StateType, StateEventType>,
16
+ // don't directly set String as a replacement of StateType (generic type)
17
+ // due to Xcode6.1-GM2 generics bug(?) causing EXC_I386_GPFLT when overriding method e.g. `hasRoute()`.
18
+ //
19
+ // Ideally, class `HierarchicalStateMachine` should be declared as following:
20
+ //
21
+ // `public class HierarchicalStateMachine: StateMachine<String, String>`
22
+ //
23
+ // To avoid above issue, we use `typealias HSM` instead.
24
+ //
25
+
26
+ /// nestable StateMachine with StateType as String
27
+ public class HierarchicalStateMachine < S: StateType , E: StateEventType > : StateMachine < S , E > , Printable
28
+ {
29
+ private var _submachines = [ String : HSM] ( )
30
+
31
+ public let name : String
32
+
33
+ /// init with submachines
34
+ public init ( name: String , submachines: [ HSM ] ? = nil , state: State , initClosure: ( StateMachine < State , Event > -> Void ) ? = nil )
35
+ {
36
+ self . name = name
37
+
38
+ if let submachines = submachines {
39
+ for submachine in submachines {
40
+ self . _submachines [ submachine. name] = submachine
41
+ }
42
+ }
43
+
44
+ super. init ( state: state, initClosure: initClosure)
45
+ }
46
+
47
+ public var description : String
48
+ {
49
+ return self . name
50
+ }
51
+
52
+ ///
53
+ /// Converts dot-chained state sent from mainMachine into (submachine, substate) tuple.
54
+ /// e.g.
55
+ ///
56
+ /// - state="MainState1" will return (nil, "MainState1")
57
+ /// - state="SubMachine1.State1" will return (submachine1, "State1")
58
+ /// - state="" (nil) will return (nil, nil)
59
+ ///
60
+ private func _submachineTupleForState( state: State ) -> ( HSM ? , HSM . State )
61
+ {
62
+ assert ( state is HSM . State , " HSM state must be String. " )
63
+
64
+ let components = split ( state as HSM . State , { $0 == " . " } , maxSplit: 1 )
65
+
66
+ switch components. count {
67
+ case 2 :
68
+ let submachineName = components [ 0 ]
69
+ let substate = components [ 1 ]
70
+ return ( self . _submachines [ submachineName] , substate)
71
+
72
+ case 1 :
73
+ let state = components [ 0 ]
74
+ return ( nil , state)
75
+
76
+ default :
77
+ // NOTE: reaches here when state="" (empty) as AnyState
78
+ return ( nil , nil )
79
+ }
80
+ }
81
+
82
+ public override var state : State
83
+ {
84
+ // NOTE: returning `substate` is not reliable (as a spec), so replace it with actual `submachine.state` instead
85
+ let ( submachine, substate) = self . _submachineTupleForState ( self . _state)
86
+
87
+ if let submachine = submachine {
88
+ self . _state = " \( submachine. name) . \( submachine. state) " as State
89
+ }
90
+
91
+ return self . _state
92
+ }
93
+
94
+ public override func hasRoute( transition: Transition , forEvent event: Event = nil ) -> Bool
95
+ {
96
+ let ( fromSubmachine, fromSubstate) = self . _submachineTupleForState ( transition. fromState)
97
+ let ( toSubmachine, toSubstate) = self . _submachineTupleForState ( transition. toState)
98
+
99
+ // check submachine-internal routes
100
+ if fromSubmachine != nil && toSubmachine != nil && fromSubmachine === toSubmachine {
101
+ return fromSubmachine!. hasRoute ( fromSubstate => toSubstate, forEvent: nil )
102
+ }
103
+
104
+ return super. hasRoute ( transition, forEvent: event)
105
+ }
106
+
107
+ internal override func _addRoute( var route: Route , forEvent event: Event = nil ) -> RouteID
108
+ {
109
+ let originalCondition = route. condition
110
+
111
+ let condition : Condition = { transition -> Bool in
112
+
113
+ let ( fromSubmachine, fromSubstate) = self . _submachineTupleForState ( route. transition. fromState)
114
+ let ( toSubmachine, toSubstate) = self . _submachineTupleForState ( route. transition. toState)
115
+
116
+ //
117
+ // For external-route, don't let mainMachine switch to submachine.state=toSubstate
118
+ // when current submachine.state is not toSubstate.
119
+ //
120
+ // e.g. ignore `"MainState0" => "Sub1.State1"` transition when
121
+ // `mainMachine.state="MainState0"` but `submachine.state="State2"` (not "State1")
122
+ //
123
+ if toSubmachine != nil && toSubmachine!. state != toSubstate && fromSubmachine !== toSubmachine {
124
+ return false
125
+ }
126
+
127
+ return originalCondition ? ( transition: transition) ?? true
128
+ }
129
+
130
+ route = Route ( transition: route. transition, condition: condition)
131
+
132
+ return super. _addRoute ( route, forEvent: event)
133
+ }
134
+
135
+ // TODO: apply mainMachine's events to submachines
136
+ internal override func _tryState( state: State , userInfo: Any ? = nil , forEvent event: Event ) -> Bool
137
+ {
138
+ assert ( state is HSM . State , " HSM state must be String. " )
139
+
140
+ let fromState = self . state
141
+ let toState = state
142
+ let transition = fromState => toState
143
+
144
+ let ( fromSubmachine, fromSubstate) = self . _submachineTupleForState ( fromState)
145
+ let ( toSubmachine, toSubstate) = self . _submachineTupleForState ( toState)
146
+
147
+ // try changing submachine-internal state
148
+ if fromSubmachine != nil && toSubmachine != nil && fromSubmachine === toSubmachine {
149
+
150
+ if toSubmachine!. canTryState ( toSubstate, forEvent: event as HSM . Event ) {
151
+
152
+ //
153
+ // NOTE:
154
+ // Change mainMachine's state first to invoke its handlers
155
+ // before changing toSubmachine's state because mainMachine.state relies on it.
156
+ //
157
+ super. _tryState ( toState, userInfo: userInfo, forEvent: event)
158
+
159
+ toSubmachine! <- ( toSubstate as HSM . State)
160
+
161
+ return true
162
+ }
163
+
164
+ }
165
+
166
+ return super. _tryState ( toState, userInfo: userInfo, forEvent: event)
167
+ }
168
+ }
0 commit comments