UNPKG

11.3 kBtext/coffeescriptView Raw
1# States
2#
3# How it works
4# 1. states should start from "void", there should be no atVoid handler.
5# 2. by calling @error current state and error will be saved
6# and we will turn the state into "panic".
7# 3. at "panic" states, state machine won't turn unless you call @recover()
8# or setState to "void" manually.
9# Latter one won't reset @panicError and @panicState.
10# 4. you should do the error recovering in atPanic handler, or just emit "panic"
11# event to the parent.
12# 5. all the local data should be store on @data, so we can easily recover
13# from the previous shutdown.
14# 6. should be rebust against invalid states jump
15# 7. we have a default atPanit state handler to just emit a "panic" event
16# so the parent of this state machine should come to rescue
17# but in case we know how to recover from the current state, we may over
18# write atPanic handler to suppress the panic event
19# Note: Set state with same stateName of the current state again will do nothing.
20#
21#
22# States using a unique Sole to prevent multiple running context
23# at async action.
24#
25# atFetching:(sole)->
26# asyncFetchin ()=>
27# # check soles to prevent multiple runing context
28# if not @checkSole sole
29# return
30#
31
32if typeof Leaf isnt "undefined"
33 EventEmitter = Leaf.EventEmitter
34 Errors = Leaf.ErrorDoc.create()
35 .define("AlreadyDestroyed")
36 .define("InvalidState")
37 .generate()
38else
39 EventEmitter = (require "eventex").EventEmitter
40 Errors = (require "error-doc").create()
41 .define("AlreadyDestroyed")
42 .define("InvalidState")
43 .generate()
44class States extends EventEmitter
45 @Errors = Errors
46 constructor:()->
47 @state = "void"
48 @_sole = 1
49 @lastException = null
50 @states = {}
51 @rescues = []
52 @data = {}
53 @forceAsync ?= false
54# @_listenBys = []
55 if @_isDebugging
56 @debug()
57 super()
58 declare:(states...)->
59 for state in states
60 @states[state] = state
61 destroy:()->
62 if @isDestroyed
63 return;
64 @emit "destroy"
65 @isDestroyed = true
66 @emit = ()->
67 @on = ()->
68 @once = ()->
69 @removeAllListeners()
70
71 extract:(fields...)->
72 data = {}
73 for item in fields
74 data[item] = @data[item]
75 return data
76 setData:(data)->
77 for prop of data
78 if data.hasOwnProperty prop
79 @data[prop] = data[prop]
80 at:(state,callback)->
81 handlerName = "at"+state[0].toUpperCase()+state.substring(1)
82 this[handlerName] = callback
83 return this
84 _nextTick:(exec)->
85 if typeof setImmediate isnt "undefined"
86 fn = setImmediate
87 else
88 fn = (exec)=>
89 timer = setTimeout ()=>
90 exec()
91 ,4
92 return timer
93 return fn(exec)
94 _clearTick:(timer)->
95 if typeof setImmediate isnt "undefined"
96 fn = clearImmediate
97 else
98 fn = clearTimeout
99 setState:(state,args...)->
100 @_clearTick @_stateTimer
101 if @forceAsync
102 @_stateTimer = @_nextTick ()=>
103 if @_isDebugging
104 @_setState state,args...
105 else
106 @_try ()=>
107 @_setState state,args...
108 else
109 if @_isDebugging
110 @_setState state,args...
111 else
112 @_try ()=>
113 @_setState state,args...
114 _try:(fn)=>
115 try
116 fn()
117 catch e
118 @error e
119 _setState:(state,args...)->
120 @_clearTick @_stateTimer
121 if not state
122 throw new Errors.InvalidState "Can't set invalid states #{state}"
123 if @state is "panic" and state isnt "void"
124 return
125 if @isDestroyed
126 return
127 if @data.feeds
128 for prop,item of @data.feeds
129 item.feedListener = null
130 @_sole += 1
131 @stopWaiting()
132 @previousState = @state
133 @state = state
134 if @_isDebugging and @_debugStateHandler
135 @_debugStateHandler()
136 @emit "state",state,args...
137 @emit "state/#{state}",args...
138 stateHandler = "at"+state[0].toUpperCase()+state.substring(1)
139 if this[stateHandler]
140 sole = @_sole
141 this[stateHandler] ()=>
142 sole isnt @_sole
143 ,args...
144 else if state not in ["void"]
145 if console.warn
146 console.warn "state handler #{stateHandler} not provided"
147 else
148 console.error "state handler #{stateHandler} not provided"
149 error:(error)->
150 @panicError = error
151 @panicState = @state
152 for rescue in @rescues
153 if rescue.state is @panicState and (@panicError instanceof rescue.error or not rescue.error)
154 if @_debugRescueHandler
155 @_debugRescueHandler()
156 @recover()
157 rescue.callback(error)
158 break
159 # does rescue handles all error
160 if @panicError
161 @setState "panic"
162 recover:(recoverState)->
163 # For safety, recover just do a respawn.
164 # So every async call should be ignored,
165 # only if they forgot to check sole.
166 error = @panicError
167 state = @panicState
168 @respawn()
169 if recoverState
170 @setState recoverState
171 return {error,state}
172 rescue:(state,error,callback = ()->)->
173 if not callback
174 throw new Error "rescue should provide callbacks"
175 @rescues.push {state,error,callback}
176 give:(name,items...)->
177 if @_waitingGiveName is name
178 handler = @_waitingGiveHandler
179 @_waitingGiveName = null
180 @_waitingGiveHandler = null
181 if @_isDebugging and @_debugRecieveHandler
182 @_debugRecieveHandler(name,items...)
183 handler.apply this,items
184 return
185 stopWaiting:(name)->
186 if name
187 if @_waitingGiveName is name
188 @_waitingGiveName = null
189 @_waitingGiveHandler = null
190 else
191 throw new Error "not waiting for #{name}"
192 else
193 @_waitingGiveName = null
194 @_waitingGiveHandler = null
195
196 isWaitingFor:(name)->
197 if not name and @_waitingGiveName
198 return true
199 if name is @_waitingGiveName
200 return true
201 return false
202 feed:(name,item)->
203 @data.feeds ?= {}
204 @data.feeds[name] ?= []
205 @data.feeds[name].push(item)
206 if listener = @data.feeds[name].feedListener
207 @data.feeds[name].feedListener = null
208 listener()
209 consumeAll:(name)->
210 if @data.feeds?[name]?
211 length = @data.feeds[name].length or 0
212 @data.feeds[name] = []
213 return length
214 return 0
215 hasFeed:(name)->
216 return @data.feeds?[name]?.length > 0
217 consume:(name)->
218 if not @hasFeed name
219 return null
220 if @data.feeds?[name]?
221 return @data.feeds[name].shift() or true
222 consumeWhenAvailableMergeToLast:(name,callback)->
223 @consumeWhenAvailable name,(detail)=>
224 while last = @consume name
225 continue
226 if last
227 callback(last)
228 else
229 callback detail
230 consumeWhenAvailable:(name,callback)->
231 @data.feeds ?= {}
232 @data.feeds[name] ?= []
233 if @data.feeds[name].length > 0
234 callback @consume(name)
235 else
236 @data.feeds[name].feedListener = ()=>
237 callback @consume(name)
238 #console.error "startve"
239 @emit "starve",name
240 @emit "starve/#{name}"
241 waitFor:(name,handler)->
242 if @_waitingGiveName
243 throw new Error "already waiting for #{@_waitingGiveName} and can't wait for #{name} now"
244 @_waitingGiveName = name
245 @_waitingGiveHandler = handler
246 if @_isDebugging and @_debugWaitHandler
247 @_debugWaitHandler()
248 @emit "wait",name
249 @emit "wait/#{name}"
250 atPanic:()->
251
252 if @_isDebugging and @_debugPanicHandler
253 @_debugPanicHandler()
254 console.error @panicError,@panicState
255 @emit "panic",@panicError,@panicState
256 reset:(data = {})->
257 @data = data
258 @respawn()
259 @emit "reset"
260 getSole:()->
261 return @_sole
262 checkSole:(sole)->
263 return @_sole is sole
264 stale:(sole)->
265 if typeof sole is "function"
266 return sole()
267 return @_sole isnt sole
268 respawn:()->
269 @_sole = @_sole or 1
270 @_sole += 1
271 @_waitingGiveName = null
272 @_waitingGiveHandler = null
273 @panicError = null
274 @panicState = null
275 @setState "void"
276 @_clearTick @_stateTimer
277 @clear()
278# listenBy:(who,event,callback)->
279# owner = null
280# for item in @_listenBys
281# if item.who is who
282# owner = item
283# break
284# if not owner
285# owner = {who:who,cases:[]}
286# @_listenBys.push owner
287# owner.cases.push {event:event,callback:callback}
288# @on event,callback
289# stopListenBy:(who,event)->
290# owner = null
291# for item in @_listenBys
292# if item.who is who
293# owner = item
294# break
295# if not owner
296# return
297# for item,index in owner.cases
298# if item and (item.event is event or not event)
299# @removeListener item.event,item.callback
300# owner.cases[index] = null
301# owner.cases = owner.cases.filter (item)->item
302# if owner.cases.length is 0
303# @_listenBys = @_listenBys.filter (item)->item isnt owner
304 debug:(option = {})->
305 close = option.close
306 @_debugName = option.name or @constructor and @constructor.name or "Anonymouse"
307 _console = option.console or console
308 log = ()->
309 if _console.debug
310 _console.debug.apply _console,arguments
311 else
312 _console.log.apply _console,arguments
313 if close
314 @_isDebugging = false
315 else
316 @_isDebugging = true
317 @_debugStateHandler ?= ()=>
318 log "#{@_debugName or ''} state: #{@state}"
319 @_debugWaitHandler ?= ()=>
320 log "#{@_debugName or ''} waiting: #{@_waitingGiveName}"
321 @_debugRescueHandler ?= ()=>
322 log "#{@_debugName or ''} rescue: #{@panicState} => #{@panicError}"
323 @_debugPanicHandler ?= ()=>
324 log "#{@_debugName or ''} panic: #{JSON.stringify @panicError}"
325 @_debugRecieveHandler ?= (name,data...)=>
326 log "#{@_debugName or ''} recieve: #{name} => #{data.join(" ")}"
327 clear:(handler)->
328 if handler
329 if @_clearHandler
330 throw new Error "already has clear handler"
331 @_clearHandler = handler
332 else
333 _handler = @_clearHandler
334 @_clearHandler = null
335 if _handler
336 _handler()
337if typeof Leaf isnt "undefined"
338 Leaf.States = States
339else
340 module.exports = States