-
Notifications
You must be signed in to change notification settings - Fork 1
/
graphStateMachine.ls
140 lines (99 loc) · 4.27 KB
/
graphStateMachine.ls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
129
130
131
132
133
134
135
136
require! {
underscore: _
helpers: h
backbone4000: Backbone
colors: colors
'./graph': graph
}
State = exports.State = graph.DirectedGraphNode.extend4000 do
defaults:
name: 'unnamed'
visited: false
active: false
name: 'unnamed'
initialize: (options) ->
_.extend @, options
connectChildren: ->
if @_children?.constructor is String then @_children = []
if @_children?.constructor is Object then @_children = _.keys @_children
if @child then @_children = h.push @_children, @child
_.map (@_children or []), (val,key) ~>
if key@@ isnt Number then val = key
@addChild @root.states[val]
leave: (toState, event) ->
@set active: false
@trigger 'leave', toState, event
visit: (fromState, event) ->
@set visited: true, active: true
@trigger 'visit', fromState, event
changeState: (...args) -> @root.changeState.apply @root, args
findChild: ( searchState ) ->
newState = switch searchState?@@
| undefined => throw new Error "trying to change to undefined state from state #{@name}"
| String => @children.find (state) -> state.name is searchState
| Function => @children.find (state) -> state is searchState
default throw new Error "wrong state search term constructor at #{@name}, (#{it})"
if not newState then throw new Error "I am \"#{@name}\", state not found in my children when using a search term \"#{searchState}\""
newState
State.defineChild = (...classes) ->
newState = @::rootClass.defineState.apply @::rootClass, classes
@addChild newState::name
newState
State.addChild = (name) ->
@::children = h.push @::children, name
GraphStateMachine = exports.GraphStateMachine = Backbone.Model.extend4000 do
stateClass: State
initialize: (options) ->
@smWakeup!
if @initialize_ then @initialize_ options
stateName = options?.state or @get('state') or @state
if stateName then @changeState stateName
smWakeup: (stateName) ->
#
# instantiate states
# TODO this should be done lazily on a per state basis
#
@states = h.dictMap (@states or {}), (state,name) ~>
instantiate = (params={}) ~>
new (@stateClass.extend4000({ name: name }, params)) root : @
stateInstance = switch state@@
| Function => new state root: @
| Boolean => instantiate!
| Object => instantiate state
default => throw new Error "state constructor is wrong (#{it?})"
# this makes states link up between each other
_.map @states, (state, name) -> state.connectChildren()
@on 'change:state', (self, stateName) ~> @changeState stateName
@trigger 'smwakeup'
smSleep: ->
@trigger 'smsleep'
changeState: (toStateName, event) ->
#console.log 'changestaet!', @state, toStateName, event
if fromState = @state
toState = fromState.findChild(toStateName); fromState.leave()
else
toState = @states[toStateName]
if not toState then throw new Error "#{@name} can't find initial state \"#{toStateName}\""
console.log @name, colors.green('changestate'), fromState?name, '->', toState.name, 'event:',event
@set { state: toState.name } silent: true
@state = toState
toState.visit fromState, event
@stateTriggers toState.name, fromState?.name, event
stateTriggers: (toStateName, fromStateName, event) ->
if f = @['state_' + toStateName] then f.call @, fromStateName, event
@trigger 'changestate', toStateName, fromStateName, event
@trigger 'changestate:', toStateName, fromStateName, event
@trigger 'state_' + toStateName, fromStateName, event
ubigraph: (stateName) ->
if not stateName then stateName = _.first _.keys @states
dontbrowserify = 'ubigraph'
ubi = require dontbrowserify
ubi.visualize @states[stateName], ((node) -> node.getChildren()), ((node) -> node.name )
GraphStateMachine.mergers.push Backbone.metaMerger.chainF 'initialize_'
GraphStateMachine.defineState = (...classes) ->
classes.push { rootClass: @ }
stateSubClass = @::stateClass.extend4000.apply @::stateClass, classes
if not @::states then @::states = {}
@::states[stateSubClass::name] = stateSubClass
GraphStateMachine.defineStates = (...states) ->
_.map states, ~> @defineState it