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 | if typeof Leaf isnt "undefined"
|
33 | EventEmitter = Leaf.EventEmitter
|
34 | Errors = Leaf.ErrorDoc.create()
|
35 | .define("AlreadyDestroyed")
|
36 | .define("InvalidState")
|
37 | .generate()
|
38 | else
|
39 | EventEmitter = (require "eventex").EventEmitter
|
40 | Errors = (require "error-doc").create()
|
41 | .define("AlreadyDestroyed")
|
42 | .define("InvalidState")
|
43 | .generate()
|
44 | class 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 |
|
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 |
|
160 | if @panicError
|
161 | @setState "panic"
|
162 | recover:(recoverState)->
|
163 |
|
164 |
|
165 |
|
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 |
|
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 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
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()
|
337 | if typeof Leaf isnt "undefined"
|
338 | Leaf.States = States
|
339 | else
|
340 | module.exports = States
|