src/core/signal.coffee |
|
|---|---|
Use a Table Of Content |
|
Signal |
|
Signals are generally defined as property of an object. And their name generally end with a past tense verb, such as in:
|
class Signal
|
Signal::constructor |
|
Optionally, a signal can have a specific signature. A signature is a collection of argument names such:
If a signature is passed to a signal, every listener added to the signal must then match the signature.
In the case of an asynchronous listener, the callback argument is not considered as being part of the signature. |
constructor: (@signature...) -> @listeners = [] |
The |
@asyncListeners = 0 |
Listeners managementListeners are stored internally as an array with the form:
|
|
Signal::add |
|
You can register a listener with or without a context.
The context is the object that can be accessed through An optional Signals listeners can be asynchronous, in that case the last
argument of the listener must be named
|
add: (listener, context, priority = 0) -> @validate listener |
A listener can be registered several times, but only if the context object is different each time. In other words, the following is possible:
When the following is not:
|
if not @registered listener, context @listeners.push [listener, context, false, priority] @asyncListeners++ if @isAsync listener |
Listeners are sorted according to their order each time a new listener is added. |
@sortListeners() |
Signal::addOnce |
|
Listeners can be registered for only one call. All the others rules are the same. So you can't add the same listener/context couple twice through the two methods. |
addOnce: (listener, context, priority = 0) -> @validate listener if not @registered listener, context @listeners.push [listener, context, true, priority] @asyncListeners++ if @isAsync listener @sortListeners() |
Signal::remove |
|
Listeners can be removed, but only with the context with which they was added to the signal. In this regards, avoid to register listeners without a context. If later in the application a context is forgotten or invalid when removing a listener from this signal, the listener without context will end up being removed. |
remove: (listener, context) -> if @registered listener, context @asyncListeners-- if @isAsync listener @listeners.splice @indexOf(listener, context), 1 |
Signal::removeAll |
|
All listeners can be removed at once if needed.
|
removeAll: -> @listeners = [] @asyncListeners = 0 |
Signal::indexOf |
|
|
indexOf: (listener, context) -> return i for [l,c],i in @listeners when listener is l and context is c -1 |
Signal::registered |
|
Use the |
registered: (listener, context) -> @indexOf(listener, context) isnt -1 |
Signal::hasListeners |
|
Returns true if the signal has listeners. |
hasListeners: -> @listeners.length isnt 0 |
Signal::sortListeners |
|
The listeners are sorted according to their |
sortListeners: -> return if @listeners.length <= 1 @listeners.sort (a, b) -> [pA, pB ] = [ a[3], b[3]] if pA < pB then 1 else if pB < pA then -1 else 0 |
Signal::validate |
|
Throws an error if the passed-in listener's signature doesn't match the signal's one.
|
validate: (listener) -> if @signature.length > 0 re = /[^(]+\(([^)]+)\).*$/m listenerSignature = Function::toString.call(listener).split('\n').shift() signature = listenerSignature.replace(re, '$1') args = signature.split /\s*,\s*/g args.shift() if args.first() is '' args.pop() if args.last() is 'callback' s1 = @signature.join() s2 = args.join() m = "The listener #{listener} doesn't match the signal's signature #{s1}" throw new Error m if s2 isnt s1 |
Signal::isAsync |
|
Returns
|
isAsync: (listener) -> Function::toString.call(listener).indexOf('callback)') != -1 |
Signal Dispatch |
|
Signal::dispatch |
|
Signals are dispatched to all the listeners. All the arguments passed to the dispatch become the signal's message.
Listeners registered for only one call will be removed after the call. Optionally you can pass a callback argument to the dispatch function.
In that case, the callback must be the last argument passed to the
Note: As the dispatch function will automatically consider
the last argument as the callback if its type is |
dispatch: (args..., callback)-> unless typeof callback is 'function' args.push callback callback = null listeners = @listeners.concat() |
If at leat one listener is async, the whole dispatch process is async otherwise the fast route is used. |
if @asyncListeners > 0 next = (callback) => if listeners.length [listener, context, once, priority] = listeners.shift() if @isAsync listener listener.apply context, args.concat => @remove listener, context if once next callback else listener.apply context, args @remove listener, context if once next callback else callback?() next callback else |
The fast route is just a loop over the listeners. At that point, if your listener do async stuff, it will not prevent the dispatching until it's done. |
for [listener, context, once, priority] in listeners listener.apply context, arguments @remove listener, context if once callback?() module.exports = Signal |