UNPKG

76.6 kBJavaScriptView Raw
1// l8.js
2// a modern multi-tasker for javascript
3// https://github.com/JeanHuguesRobert/l8
4//
5// 2012/10/24, JHR, create
6//
7// (c) Jean Hugues Robert
8// Licensed under the MIT license.
9
10// Boilerplate for module loaders. Basically: avoid polluting global space
11(function( define ){ // 'use strict'; // ToDo: figure out why this gets undefined
12define( function(){
13
14/* ----------------------------------------------------------------------------
15 * Debug. The one painfull thing that we want to cure.
16 */
17
18 // DEBUG mode defaults to "on" when nodejs. Please use l8.debug() to change it
19 var DEBUG = (typeof window === 'undefined')
20
21var NoOp = function noop(){}
22
23var TraceStartTask = !DEBUG ? 0 : 0
24// When debugging test cases, this tells when to start outputting traces.
25// Ugly but usefull.
26
27// In node.js, "util" module defines puts(), among others
28var Util = null
29var Bugsnag = null
30try{
31 Util = require( "util")
32 // Bugsnag = require( "bugsnag")
33 // Bugsnag.register( "your-api-key-goes-here")
34 // ToDo: https://bugsnag.com/docs/notifiers/node
35 DEBUG && Util.debug( "entering l8.js")
36}catch( e ){}
37
38var trace = function(){
39// Print trace. Offer an easy breakpoint when output contains "DEBUG".
40// Note: when using native console.log(), it's better to output objects
41// instead of strings because modern debuggers understand objects and can
42// display them intelligently.
43 var buf = []
44 // Output message will have a "l8" prefix so that you know who did it
45 var args = ["l8"]
46 var only_strings = true
47 // If single array argument, just add "l8" prefix
48 if( arguments.length === 1 && arguments[0] instanceof Array){
49 args = args.concat( arguments[0])
50 // Or else make array using arguments, still with "l8" prefix
51 }else{
52 args = args.concat( Array.prototype.slice.call( arguments, 0))
53 }
54 // For each item to display
55 var item
56 var stack
57 for( var ii = 0 ; ii < args.length ; ii++ ){
58 item = args[ii]
59 // Unless empty, skipped
60 if( item ){
61 // When item is object with a nice .toLabel() method, keep it short
62 if( item.toLabel ){
63 item = item.toLabel()
64 // When item is a string or something we cannot handle client side
65 }else if( typeof item === 'string' || !Util ){
66 item = item
67 // When item is complex and Util.inspect() can help
68 }else{
69 stack = item.stack
70 item = Util.inspect( item)
71 }
72 // When we have only string, better concat them
73 if( only_strings && typeof item !== "string" ){
74 only_strings = false
75 }
76 if( item ){
77 buf.push( item)
78 if( stack ){
79 buf.push( Util.inspect( stack))
80 stack = null
81 }
82 }
83 }
84 }
85 try{
86 if( Util ){
87 if( only_strings ){
88 Util.puts( buf = buf.join( ", "))
89 }else{
90 Util.puts( buf = Util.inspect( buf))
91 }
92 }else{
93 console.log( buf)
94 }
95 if( buf.indexOf( "DEBUG") >= 0 ){
96 // please set breakpoint here to debug
97 try{ debugger }catch( e ){}
98 }
99 }catch( e ){
100 // ToDo: host adapted tracing
101 }
102 return buf
103}
104
105var assert = function( cond ){
106 // ToDo: https://github.com/visionmedia/better-assert
107 if( !cond ){
108 trace.apply( this, arguments)
109 trace( "DEBUG assert failure")
110 throw new Error( "Assert failure")
111 }
112}
113
114var de = DEBUG, bug = trace, mand = assert
115// That's my de&&bug darling, also de&&mand()
116
117
118/* ----------------------------------------------------------------------------
119 * Task & Step
120 */
121
122var NextTaskId = 0
123
124function Task( parent, is_fork, is_spawn ){
125// Tasks are like function call activation records, but with a spaghetti stack
126// because more than one child task can be active at the same time.
127// See also http://en.wikipedia.org/wiki/Spaghetti_stack
128// Forked tasks's parent task collect the multiple results, one per fork.
129// Spawn tasks don't block their parent and don't provide a result.
130 this.nextFree = void null // Task allocator reuse objects
131 task_init.call( this, parent, is_fork, is_spawn)
132 return this
133}
134var ProtoTask = Task.prototype
135
136var task_init =
137ProtoTask.init = function( parent, is_fork, is_spawn ){
138 this.id = NextTaskId++ // unique id. .toString() uses it too
139 if( DEBUG ){
140 this.stepCount = 0 // Step ids generator
141 }
142 // Note: initing properties to undefined helps some JIT compilers
143 this.firstStep = void null
144 this.isSingleStep = false
145 this.currentStep = void null // What step the task is on, aka "IP"
146 this.insertionStep = void null // Where steps are usually added
147 this.pausedStep = void null // What step the task is paused on
148 this.isFork = !!is_fork
149 this.wasSpawn = !!is_spawn
150 this.stepResult = void null
151 this.stepError = void null
152 this.isDone = false // False while task is pending
153 this.subtasks = void null // a set, keys are task.id
154 this.subtasksCount = void null // size of that set
155 this.parentTask = parent // aka "caller"
156 this.forkedTasks = void null // Subtask(s) that block this task
157 this.forkedTasksCount = void null // Number of such tasks
158 this.forkResults = void null // Array of these task's result
159 this.forkResultsCount = void null // Number of entries in that array
160 this.forkResultsIndex = void null // in parent's forkResults array
161 this.data = void null // bindings for task local variables
162 this.optional = {} // Some JIT compilers prefer that
163 /*
164 this.optional.wasCanceled = false // "brutal cancel" flag
165 this.optional.shouldStop = false // "gentle cancel" flag
166 this.optional.deferredSteps = null // Go lang style "defer"
167 this.optional.deferredResult = null
168 this.optional.deferredError = null
169 this.optional.successBlock = null
170 this.optional.failureBlock = null
171 this.optional.progressBlock = null
172 this.optional.finalBlock = null
173 this.optional.donePromise = null
174 this.optional.generator = null
175 */
176 if( TraceStartTask && NextTaskId > TraceStartTask )trace( "DEBUG New", this)
177 // Add new task to it's parent's list of pending subtasks
178 // When a done task creates a subtask, the parent task inherit it
179 // The root task is obviously never done, or else this would break
180 while( parent.isDone ){ parent = parent.parentTask }
181 // Parent remembers all pending subtasks, both forked & spawn ones
182 if( !parent.subtasks ){
183 de&&mand( !parent.subtasksCount, parent.subtasksCount)
184 parent.subtasks = {}
185 parent.subtasksCount = 1
186 }else{
187 parent.subtasksCount++
188 }
189 parent.subtasks[this.id] = this
190 // Forked tasks also block their parent and accumulate results
191 if( !is_spawn ){
192 if( !parent.forkedTasks ){
193 // When only one member, direct link, efficient because frequent
194 parent.forkedTasks = this
195 parent.forkedTasksCount = 1
196 }else{
197 de&&mand( is_fork || parent === l8 )
198 parent.forkedTasksCount++
199 // With two members, mutate into an array
200 if( parent.forkedTasksCount === 2 ){
201 parent.forkedTasks = [parent.forkedTasks,this]
202 // More members, push them
203 }else{
204 parent.forkedTasks.push( this)
205 }
206 }
207 // Allocate entry for forked tasks results, set to undefined for now
208 if( is_fork ){
209 if( !parent.forkResults ){
210 parent.forkResults = [void null]
211 parent.forkResultsCount = 1
212 this.forkResultsIndex = 0 // this task's result in parent.forkResults
213 }else{
214 parent.forkResults[
215 this.forkResultsIndex = parent.forkResultsCount++
216 ] = void null
217 }
218 }
219 }
220 // Please see what happens in Task.subtaskDoneEvent(), ie "destructor"
221 if( TraceStartTask && NextTaskId > TraceStartTask )trace( "New", this)
222 return this
223}
224
225function Step( task, block, is_fork, is_repeat ){
226// Tasks execute steps, some steps may create additional steps to execute.
227// Forked steps run in parallel whereas regular steps are sequential. Steps
228// that cannot execute immediatly can block and terminate later when some
229// asynchronous event occurs. WHen a forked step is blocked, the other forked
230// steps are still executed whereas when a regular step blocks, the next
231// steps are blocked too.
232 step_init.call( this, task, block, is_fork, is_repeat)
233 return this
234}
235var ProtoStep = Step.prototype
236
237var step_init =
238ProtoStep.init = function( task, block, is_fork, is_repeat ){
239 if( DEBUG ){
240 this.id = ++task.stepCount
241 this.wasQueued = false
242 this.wasExecuted = false
243 }
244 while( task.isDone ){
245 task = task.parentTask
246 // ToDo: maybe I could create a task "on the fly"?
247 if( task === l8 )throw new Error( "Cannot add step to root l8 task")
248 }
249 this.task = task
250 if( block ){
251 // If step is a promise, step will block until that promise delivers
252 if( !(block instanceof Function) ){
253 block = function(){ task.interpret( block) }
254 }
255 this.block = block
256 }else{
257 this.block = NoOp
258 }
259 this.isFork = is_fork
260 this.isRepeat = is_repeat
261 this.wasSpawn = false
262 this.isBlocking = false // When task is paused on this step
263 // enqueue/dequeue list management
264 //this.previous = null
265 this.next = null
266 var previous = task.insertionStep
267 task.insertionStep = this
268 // When inserting at head
269 if( !previous ){
270 this.next = task.firstStep
271 //if( this.next ){ this.next.previous = this }
272 task.firstStep = task.currentStep = this
273 // When inserting at tail
274 //}else if( !previous.next ){
275 //this.previous = previous
276 //this.previous.next = this
277 // When inserting in the middle of the list
278 }else{
279 //this.previous = previous
280 this.next = previous.next
281 //previous.next.previous = this
282 previous.next = this
283 }
284 if( TraceStartTask && NextTaskId > TraceStartTask ){
285 trace(
286 "New", this,
287 this === task.firstStep ? "first" : ""
288 )
289 }
290 return this
291}
292
293// Bootstrap root task, id 0
294var l8 = new Task( {}/*dummy parent*/)
295l8.parentTask = null
296l8.data = {task:l8}
297l8.proto = ProtoTask
298l8.l8 = l8
299var CurrentStep = new Step( l8, NoOp, false, true) // empty loop
300CurrentStep.isBlocking = true
301l8.currentStep = l8.pausedStep = CurrentStep
302l8.timeNow = null
303l8.dateNow = null
304
305// Browser & nodejs way to schedule code execution in the event loop.
306// Note: you can provide yours if you get an efficient one.
307try{
308 l8.nextTick = process.nextTick
309 l8.nextTick( function(){})
310 l8.module = module
311}catch( e ){
312 l8.nextTick = function next_tick( block ){ setTimeout( block, 0) }
313 l8.nextTick( function(){})
314 l8.module = this
315}
316var L8_NextTick = l8.nextTick
317
318// Some special errors are used to build control structures
319l8.cancelEvent = "cancel"
320l8.breakEvent = "break"
321l8.continueEvent = "continue"
322l8.returnEvent = "return"
323l8.failureEvent = "failure"
324l8.closeEvent = "close"
325
326ProtoTask.debug = function( on ){
327 if( arguments.length ){
328 l8.de = de = DEBUG = on
329 }
330 return DEBUG
331}
332l8.debug( DEBUG)
333ProtoTask.trace = trace
334ProtoTask.bug = trace
335ProtoTask.assert = assert
336ProtoTask.mand = assert
337
338l8.client = !Util
339l8.server = Util
340l8.getDebugFlag = function(){ return de }
341l8.__defineGetter__( "de", l8.getDebugFlag)
342
343
344/* ----------------------------------------------------------------------------
345 * Scheduler, aka "step walker"
346 * process.nextTick() or setTimeout() can do the job but I do some buffering
347 * and that runs faster.
348 */
349
350var NO_SCHEDULER = false // false && !DEBUG
351
352var L8_Execute // ProtoStep.execute, see below
353
354if( !NO_SCHEDULER ){
355
356var L8_QueuedStep = null
357var L8_StepQueue = []
358var L8_IsScheduled = false
359
360var L8_Tick = function tick(){
361 // Update l8.timeNow & l8.dateNow, called often enough.
362 // Fast and somehow usefull to correlate traces about the same event.
363 // ToDo: Use V8/Mozilla Date.now() ?
364 l8.timeNow = (l8.dateNow = new Date()).getTime()
365 var step
366 while( step = L8_QueuedStep ){
367 L8_QueuedStep = L8_StepQueue.shift()
368 //step.execute()
369 L8_Execute( step)
370 }
371 L8_IsScheduled = false
372 // When done, assume code runs from within the "root" task
373 CurrentStep = l8.currentStep
374}
375
376var L8_Scheduler = function scheduler(){
377// Inject the scheduler in the global event loop.
378// It executes queued steps and their next ones.
379 if( !L8_IsScheduled ){
380 de&&mand( L8_QueuedStep)
381 L8_IsScheduled = true
382 L8_NextTick( L8_Tick)
383 }
384}
385
386L8_EnqueueStep = function enqueue_step( step ){
387// Schedule step to execute. Restart scheduler if it is not started.
388 if( DEBUG ){
389 assert( !step.wasQueued || step.isRepeat )
390 step.wasQueued = true
391 }
392 // Store step, efficiently if only one exist, in an array if more is needed
393 if( L8_QueuedStep ){
394 L8_StepQueue.push( step)
395 }else{
396 L8_QueuedStep = step
397 }
398 de&&mand( !step.isBlocking )
399 // Wake up scheduler if necessary, it will eventually execute this step
400 if( !L8_IsScheduled ){
401 L8_IsScheduled = true
402 L8_NextTick( L8_Tick)
403 }
404 // Debug traces
405 if( TraceStartTask && NextTaskId > TraceStartTask ){
406 if( L8_QueuedStep ){
407 L8_QueuedStep.trace( "queued step")
408 var item
409 for( var ii = 0 ; ii < L8_StepQueue.length ; ii++ ){
410 item = L8_StepQueue[ii].trace( "queued step[" + ii + "]")
411 }
412 }
413 }
414}
415
416// when NO_SCHEDULER
417}else{
418
419// The code above does the equivalent of this, but it does it faster.
420var L8_EnqueueStep = function( step ){
421 de&&mand( !step.task.isDone )
422 // Result cannot be a promise because promises pause the task
423 de&&mand( !step.task.stepResult || !step.task.stepResult.then || step.task.stepResult.parentTask )
424 L8_NextTick(
425 // slower: execute.bind( step)
426 function(){
427 //execute.call( step)
428 L8_Execute( step)
429 // When done, assume code runs from within the "root" task
430 CurrentStep = l8.currentStep
431 }
432 )
433}
434l8.__defineGetter__( "timeNow", function(){
435 return (l8.dateNow = new Date()).getTime()
436})
437
438} // endif !NO_SCHEDULER
439
440ProtoStep.trace = function step_trace(){
441 var args = Array.prototype.slice.call( arguments, 0)
442 var task = this.task
443 trace( [this].concat( args).concat([
444 task.isDone ? "task done" : "",
445 this === task.firstStep ? "first" : "",
446 this.isRepeat ? "repeat" : "",
447 this.isFork ? "fork" : "",
448 this.isBlocking ? "pause" : ""
449 ]))
450}
451
452ProtoStep.execute = L8_Execute = function step_execute( that ){
453 if( TraceStartTask && NextTaskId > TraceStartTask ){
454 this.trace( "DEBUG execute")
455 }
456 if( false && DEBUG ){
457 assert( !that.wasExecuted || that.isRepeat )
458 that.wasExecuted = true
459 }
460 var task = that.task
461 if( DEBUG && task.isDone )throw new Error( "BUG, exec done l8 step: " + that)
462 de&&mand( !task.parentTask || task.parentTask.subtasksCount > 0 )
463 if( that.isBlocking ){
464 de&&mand( task.pausedStep === that )
465 return
466 }
467 task.currentStep = that
468 CurrentStep = that
469 // Steps created by this step are queued after the insertionStep
470 task.insertionStep = that
471 var block = that.block
472 // Previous result cannot be a promise because promises pause the task
473 de&&mand( !task.stepResult || !task.stepResult.then || task.stepResult.parentTask )
474 var result
475 // Consume previous fork results if any unless step is a fork itself
476 var results = !that.isFork && task.forkResults
477 if( results ){
478 de&&mand( !task.forkedTasks )
479 task.forkResults = null
480 task.forkResultsCount = 0
481 }
482 // Execute block, set "this" to the current task
483 try{
484 // If step(), don't provide any parameter
485 if( !block.length ){
486 result = block.call( task)
487 // If step( r), provide forks results or last result as a single parameter
488 }else if( block.length === 1 ){
489 if( results ){
490 result = block.call( task, results)
491 }else{
492 result = block.call( task, task.stepResult)
493 }
494 // If step( a, b, ...), use fork results or assume last result is an array
495 }else{
496 result = block.apply(
497 task,
498 (results && results.length > 1)
499 ? results
500 : task.stepResult
501 )
502 }
503 de&&mand( !task.parentTask || task.parentTask.subtasksCount > 0 )
504 // Update last result only when block returned something defined.
505 // Result can be set asynchronously using proceed(), see below
506 if( result !== void null ){
507 task.stepResult = result
508 // If result is a promise, block until promise is done, unless rslt is a
509 // task (waiting for a task must be explicit)
510 if( result.then && !result.parentTask ){
511 return task.wait( result)
512 }
513 }
514 if( DEBUG ){ task.progressing() }
515 }catch( e ){
516 // scheduleNext() will handle the error propagation
517 task.stepError = e
518 if( DEBUG ){
519 that.trace( "task failure", e)
520 if( TraceStartTask && NextTaskId > TraceStartTask ){
521 that.trace( "DEBUG execute failed" + e)
522 }
523 }
524 }
525 // task.insertionStep = null
526 that.scheduleNext()
527}
528
529ProtoStep.scheduleNext = function schedule_next(){
530// Handle progression from step to step, error propagation, task termination
531 var task = this.task
532 if( task.isDone )throw new Error( "Bug, schedule a done l8 task: " + this)
533 de&&mand( !task.parentTask || task.parentTask.subtasksCount > 0 )
534 if( this.isBlocking ){
535 de&&mand( task.pausedStep === this, task.pausedStep)
536 return
537 }
538 var redo = this.isRepeat
539 // Handle "continue" and "break" in loops
540 if( redo && task.stepError ){
541 if( task.stepError === l8.continueEvent ){
542 task.stepError = void null
543 }else if( task.stepError === l8.breakEvent ){
544 redo = false
545 }
546 }
547 // When no error, wait for subtasks if any, else move to next step or loop
548 if( !task.stepError ){
549 // Previous step result cannot be a promise because promises pause the task
550 de&&mand( !task.stepResult || !task.stepResult.then || task.stepResult.parentTask )
551 var next_step = redo ? this : this.next
552 if( next_step ){
553 if( !this.isFork || !next_step.isFork || redo ){
554 // Regular steps wait for forked tasks, fork steps don't
555 if( task.forkedTasks ){
556 this.isBlocking = true
557 task.pausedStep = this
558 return
559 }
560 }
561 if( redo ){
562 if( task === l8 ){
563 this.isBlocking = true
564 task.pausedStep = this
565 return
566 }
567 }
568 if( NO_SCHEDULER ){
569 L8_NextTick( function(){ L8_Execute( next_step) })
570 }else{
571 L8_EnqueueStep( next_step)
572 }
573 de&&mand( task.parentTask || task.parentTask.subtasksCount > 0 )
574 return
575 }else{
576 if( task.forkedTasks ){
577 this.isBlocking = true
578 task.pausedStep = this
579 return
580 }
581 }
582 // When error, cancel all remaining subtasks
583 }else{
584 var subtasks = task.subtasks
585 if( subtasks ){
586 for( var subtask_id in subtasks ){
587 subtasks[subtask_id].cancel()
588 }
589 de&&mand( !task.parentTask || task.parentTask.subtasksCount > 0 )
590 }
591 if( task.forkedTasks ){
592 this.isBlocking = true
593 task.pausedStep = this
594 return
595 }
596 }
597 // Handle deferred steps
598 var steps = task.optional.deferredSteps
599 if( task.optional.deferredSteps ){
600 var step = steps.pop()
601 if( step ){
602 // Save task result before running first deferred step
603 if( !task.optional.deferredResult ){
604 task.optional.deferredResult = task.stepResult
605 task.optional.deferredError = task.stepError
606 }
607 // Schedule the deferred step
608 //task.firstStep = null
609 step = MakeStep( task, step[0]) // ToDo: handle args
610 if( NO_SCHEDULER ){
611 L8_NextTick( function(){ L8_Execute( step) })
612 }else{
613 L8_EnqueueStep( step)
614 }
615 return
616 // Restore "pre-deferred" task result
617 }else{
618 task.stepResult = task.optional.deferredResult
619 task.stepError = task.optional.deferredError
620 }
621 }
622 // When nothing more, handle task termination
623 de&&mand( !task.forkedTasks )
624 this.isBlocking = true
625 task.pausedStep = null
626 // ToDo: let success/failure block run asynch, then done, not before
627 task.isDone = true
628 var exit_repeat = false
629 var is_return = false
630 var block
631 if( task.stepError === l8.returnEvent ){
632 is_return = true
633 task.stepError = void null
634 }else if( task.stepError === l8.breakEvent ){
635 task.stepError = void null
636 exit_repeat = true
637 }
638 task.progressing()
639 var err = task.stepError
640 de&&mand( !task.parentTask || task.parentTask.subtasksCount > 0 )
641 if( err ){
642 if( block = task.optional.failureBlock ){
643 try{
644 block.call( task, err)
645 err = task.stepError = void null
646 }catch( e ){
647 task.stepError = e
648 }
649 }
650 }else{
651 if( block = task.optional.successBlock ){
652 try{
653 block.call( task, task.stepResult)
654 }catch( e ){
655 err = task.stepError = e
656 }
657 }
658 }
659 if( block = task.optional.finalBlock ){
660 try{
661 block.call( task, err, task.stepResult)
662 }catch( e ){
663 err = task.stepError = e
664 }
665 }
666 var promise = task.optional.donePromise
667 if( promise ){
668 if( err ){
669 promise.reject( err)
670 }else{
671 promise.resolve( task.stepResult)
672 }
673 }
674 var parent = task.parentTask
675 if( exit_repeat ){
676 //if( parent ){
677 if( parent.currentStep.isRepeat ){
678 parent.currentStep.isRepeat = false
679 }else{
680 // task.parentTask.raise( l8.breakEvent)
681 task.stepError = l8.breakEvent
682 }
683 //}
684 }else if( is_return && !task.optional.wasCanceled ){
685 task.optional.wasCanceled
686 task.stepError = l8.returnEvent
687 }
688 //if( parent ){ // all tasks (but inactive root one) have a parent
689 de&&mand( parent.subtasksCount > 0 )
690 parent.subtaskDoneEvent( task)
691 //}
692 // Ask the Step allocator to reuse this now done task's steps
693 if( !task.data ){
694 // ToDo: I could free task when binding contains only references, not defs
695 task.firstStep.free()
696 }
697}
698
699ProtoTask.subtaskDoneEvent = function( subtask ){
700// Private. Called by Step.scheduleNextStep() when a subtask is done
701 if( DEBUG && TraceStartTask && NextTaskId > TraceStartTask ){
702 trace( "DEBUG Done subtask", subtask)
703 }
704 // One less pending subtask
705 de&&mand( !this.parentTask || this.parentTask.subtasksCount > 0 )
706 de&&mand( !subtask.forkedTasks )
707 de&&mand( this.subtasksCount > 0, this.subtasksCount)
708 de&&mand( this.subtasks)
709 de&&mand( this.subtasks[subtask.id] === subtask )
710 delete this.subtasks[subtask.id]
711 // Parent task inherits spawn subtasks, unix does the same with processes
712 var list = subtask.subtasks
713 if( list ){
714 subtask.subtasks = null
715 subtask.subtasksCount = 0
716 var item
717 for( var ii in list ){
718 item = list[ii]
719 item.parentTask = this
720 this.subtasks[item.id] = item
721 this.subtasksCount++
722 }
723 }
724 if( --this.subtasksCount === 0 ){
725 this.subtasks = null
726 }
727 // When a fork is done, resume blocked parent and remember result
728 if( !subtask.wasSpawn ){ // && this.parentTast ){
729 // When a forked task fails, parent will cancel the other forks
730 var err = subtask.stepError
731 if( err ){
732 this.stepError = err
733 if( !this.parentTask ){
734 trace( "Unhandled exception", subtask, err)
735 }
736 }else if( subtask.isFork ){
737 // Accumulate forked results, stored at the appropriate index
738 this.forkResults[subtask.forkResultsIndex] = subtask.stepResult
739 }
740 // When all forks succeed, resume blocked parent task
741 // Ditto if one fork fails
742 if( --this.forkedTasksCount <= 0 || err ){
743 // Clear this.forkedTasks when it is empty, code elsewhere expect this
744 if( !this.forkedTasksCount ){
745 this.forkedTasks = null
746 }
747 // As a bonus, deblocking task's result is made available for next step
748 if( !err ){ this.stepResult = subtask.stepResult }
749 // Unless fork terminated early there should be blocked steps
750 var paused_step = this.pausedStep
751 if( paused_step && this !== l8 ){
752 de&&mand( paused_step.task === this )
753 paused_step.isBlocking = false
754 this.pausedStep = null
755 paused_step.scheduleNext()
756 // But if task has no more steps, make task result using forked results
757 }else if( subtask.isFork ){
758 var list = this.forkedTasksResults
759 var len = list ? this.forkedTasksResults.length : 0
760 // ToDo: I need a isTask flag to handle length 1 result lists
761 if( list && len > 1 ){
762 var buf = []
763 var item
764 for( var ii = 0 ; ii < len ; ii++ ){
765 item = list[ii]
766 if( !item.stepError ){
767 buf.push( item.stepResult)
768 }else{
769 buf.push( void null)
770 }
771 }
772 this.stepResult = buf
773 }
774 }
775 }
776 // Some task objects are reuseable. If a reference to the task was held
777 // somewhere, using it is when the task is done is a bug
778 // However, references to long lived spawn tasks are legit
779 subtask.free()
780 }
781}
782
783ProtoTask.step = function step( block, is_fork, is_repeat ){
784// Add a step to execute later
785 var task = this.current
786 MakeStep( task, block, is_fork, is_repeat)
787 return task
788}
789
790ProtoTask.proceed = function( block ){
791// Pause current task and return a callback to be called to resume execution.
792 var task = this.current
793 var step = task.currentStep
794 if( step.isBlocking ){
795 // ToDo: test/allow multiple next()
796 // throw new Error( "Can't walk, not running")
797 }
798 step.isBlocking = true
799 task.pausedStep = step
800 return function walk_cb(){
801 if( task.currentStep !== step ){
802 // ToDo: quid if multiple proceed() fire?
803 throw new Error( "Cannot walk same step again")
804 }
805 var previous_step = CurrentStep
806 CurrentStep = step
807 var result
808 if( arguments.length === 1 ){
809 result = arguments[0]
810 }else{
811 result = arguments
812 }
813 try{
814 // ToDo: block should run as if from next step ?
815 // ToDo: block should run as a new step ?
816 if( block ){
817 result = block.apply( task, arguments)
818 }
819 if( task.currentStep === step ){
820 if( step.isBlocking ){
821 de&&mand( task.pausedStep === step )
822 if( result ){
823 task.stepResult = result
824 // If result is a promise, wait for it
825 if( result.then ){
826 task.wait( result)
827 return
828 }
829 }
830 // resume task
831 step.isBlocking = false
832 task.pausedStep = null
833 step.scheduleNext()
834 }
835 }
836 }catch( e ){
837 task.raise( e)
838 }finally{
839 CurrentStep = previous_step
840 //L8_Scheduler()
841 }
842 }
843}
844
845ProtoTask.__defineGetter__( "walk", function(){
846 return this.proceed( null)
847})
848
849ProtoTask.__defineGetter__( "flow", function(){
850// NodeJs friendly "walk" that checks first result to detect errors and throw
851// error when present, or else filters out first result to set result of step
852// using rest.
853 return function(){
854 var err = arguments[0]
855 if( err )throw err
856 if( arguments.length === 2 )return arguments[1]
857 return Array.splice.call( arguments, 1)
858 }
859})
860
861
862/*
863 * Step allocator. Attempt to reuse some previous steps.
864 */
865
866var NextFreeStep = null
867
868function MakeStep( task, block, is_fork, is_repeat ){
869 var step = NextFreeStep
870 if( step ){
871 NextFreeStep = step.next
872 return step_init.call( step, task, block, is_fork, is_repeat)
873 }
874 return new Step( task, block, is_fork, is_repeat)
875}
876
877ProtoStep.free = function(){
878 if( NextFreeStep ){
879 this.next = NextFreeStep
880 }
881 NextFreeStep = this
882}
883
884/*
885 * Task allocator. Attempt to reuse some previous task objects.
886 */
887
888var NextFreeTask = null
889
890function MakeTask( parent, is_fork, is_spawn ){
891 var task = NextFreeTask
892 if( task ){
893 NextFreeTask = task.nextFree
894 return task_init.call( task, parent, is_fork, is_spawn)
895 }
896 return new Task( parent, is_fork, is_spawn)
897}
898
899ProtoTask.free = function(){
900 this.nextFree = NextFreeTask
901 NextFreeTask = this
902}
903
904/* ----------------------------------------------------------------------------
905 * API
906 */
907
908ProtoTask.toString = ProtoTask.toLabel = function task_to_string(){
909 var label = this === l8 ? "" : this.label
910 label = label ? "[" + label + "]" : ""
911 return "Task/" + this.id + label
912}
913
914ProtoTask.__defineGetter__( "label", function(){
915 return this.get( "label") || ""
916})
917
918ProtoTask.__defineSetter__( "label", function( label ){
919 return this.var( "label", label)
920})
921
922ProtoTask.Task = function task_task( fn ){
923// Build a "task constructor". When such a beast is called, it creates a task
924 if( !(fn instanceof Function) ){
925 var block
926 if( !(fn instanceof Array) || arguments.length > 1 ){
927 block = Array.prototype.slice.call( arguments, 0)
928 }else{
929 block = fn
930 }
931 fn = function(){ this.interpret( block) }
932 }
933 return function (){
934 var parent_task = CurrentStep.task
935 while( parent_task.isDone ){ parent_task = parent_task.parentTask }
936 var args = arguments
937 // Don't create a useless task if parent task is still a "single step" task
938 if( parent_task.isSingleStep && !parent_task.firstStep.next ){
939 MakeStep( parent_task, function(){ return fn.apply( task, args) })
940 return parent_task
941 }
942 var task = MakeTask( parent_task)
943 var next_step = MakeStep( task, function(){ return fn.apply( task, args) })
944 if( NO_SCHEDULER ){
945 L8_NextTick( function(){ L8_Execute( next_step) })
946 }else{
947 L8_EnqueueStep( next_step)
948 }
949 return task
950 }
951}
952
953ProtoTask._task = function( block, forked, paused, detached, repeat ){
954 var task = this.current
955 var new_task
956 // Don't create a useless task if parent task is still a "single step" task
957 // ToDo: fix this
958 if( task.isSingleStep && !task.firstStep.next ){
959 new_task = task
960 }else{
961 new_task = MakeTask( task, forked, detached)
962 }
963 // Mark as reuseable, unless spawn
964 new_task.wasSpawn = detached
965 new_task.isSingleStep = true
966 if( paused ){
967 // Pause task, need a new "first step" for that
968 MakeStep( new_task)
969 new_task.pausedStep = new_task.firstStep
970 new_task.pausedStep.isBlocking = true
971 MakeStep( new_task, block)
972 }else{
973 var next_step = MakeStep( new_task, block)
974 if( NO_SCHEDULER ){
975 L8_NextTick( function(){ L8_Execute( next_step) })
976 }else{
977 L8_EnqueueStep( next_step)
978 }
979 }
980 return new_task
981}
982
983ProtoTask.task = function task_task( block, forked, paused, detached, repeat ){
984// Add a step that will start a new task with some initial step to execute.
985// Such tasks are initially "single step" task. If the single step calls a
986// task constructor, that constructor will get optimized and will reuse the
987// single step task instead of creating a new task.
988 if( TraceStartTask && NextTaskId > TraceStartTask ){
989 trace( this.current.currentStep , "invokes fork()",
990 forked ? "forked" : "",
991 paused ? "paused" : "",
992 detached ? "detached" : "",
993 repeat ? "repeated" : ""
994 )
995 }
996 var task = this.current
997 if( task === l8 ){
998 var task = MakeTask( l8)
999 return task._task( block, forked, paused, detached, repeat)
1000 }
1001 return task.step( function(){
1002 if( TraceStartTask && TraceStartTask >= NextTaskId ){
1003 trace( task.currentStep , "executes scheduled fork",
1004 forked ? "forked" : "",
1005 paused ? "paused" : "",
1006 detached ? "detached" : "",
1007 repeat ? "repeated" : ""
1008 )
1009 }
1010 return task._task( block, forked, paused, detached, repeat)
1011 }, forked, repeat)
1012}
1013
1014ProtoTask.fork = function task_fork( block, starts_paused ){
1015// Add a step that will start a forked task with some initial step to execute
1016 return this.task( block, true, starts_paused)
1017}
1018
1019ProtoTask._fork = function( block, starts_paused ){
1020 return this._task( block, true, starts_paused)
1021}
1022
1023ProtoTask.spawn = function task_spawn( block, starts_paused ){
1024// Add a step that will start a detached task with some initial step to execute
1025 return this.task( block, true, starts_paused, true) // detached
1026}
1027
1028ProtoTask._spawn = function( block, starts_paused ){
1029 return this._task( block, true, starts_paused, true) // detached
1030}
1031
1032ProtoTask.repeat = function task_repeat( block ){
1033// Add a step that will repeately start a new task with a first step to execute
1034 return this.task( block, false, false, false, true) // repeated
1035}
1036
1037ProtoTask.defer = function(){
1038 var task = this.current
1039 var args = arguments
1040 var steps = task.optional.deferredSteps
1041 if( steps ){
1042 step.push( args)
1043 }else{
1044 task.optional.deferredSteps = [args]
1045 }
1046}
1047
1048ProtoTask.__defineGetter__( "current", function(){
1049 return this === l8 ? CurrentStep.task : this
1050})
1051
1052ProtoTask.__defineGetter__( "begin", function(){
1053 return MakeTask( this.current)
1054})
1055
1056ProtoTask.__defineGetter__( "end", function(){
1057 var task = this
1058 var first = task.firstStep
1059 var is_new_step = false
1060 if( !first ){
1061 is_new_step
1062 first = MakeStep( task)
1063 }
1064 // When first step can run immediately
1065 if( !task.forkedTasks ){
1066 L8_EnqueueStep( first)
1067 // When first step is after forks
1068 }else{
1069 // Pause task to wait for forks, need a new "first step" for that
1070 if( !is_new_step ){
1071 var save = task.insertionStep
1072 // Insert at head of list of steps
1073 task.insertionStep = null
1074 MakeStep( task)
1075 task.insertionStep = save
1076 }
1077 task.pausedStep = task.firstStep
1078 task.pausedStep.isBlocking = true
1079 }
1080 // Return parent, makes chaining possible t.begin.step().step().end.step()
1081 return task.parentTask
1082})
1083
1084ProtoTask.__defineGetter__( "done", function(){
1085 return this.current.isDone
1086})
1087
1088ProtoTask.__defineGetter__( "succeed", function(){
1089 var task = this.current
1090 return task.isDone && !task.err
1091})
1092
1093ProtoTask.__defineGetter__( "fail", function(){
1094 var task = this.current
1095 return task.isDone && task.err
1096})
1097
1098ProtoTask.__defineGetter__( "result", function(){
1099 return this.current.stepResult
1100})
1101
1102ProtoTask.__defineSetter__( "result", function( val){
1103 return this.current.stepResult = val
1104})
1105
1106ProtoTask.__defineGetter__( "error", function(){
1107 return this.current.stepError
1108})
1109
1110ProtoTask.__defineGetter__( "stop", function(){
1111 var task = this.current
1112 task.optional.shouldStop = true
1113 return task
1114})
1115
1116ProtoTask.__defineGetter__( "stopping", function(){
1117 var task = this.current
1118 return task.optional.shouldStop && !task.isDone
1119})
1120
1121ProtoTask.__defineGetter__( "stopped", function(){
1122 var task = this.current
1123 return task.optional.shouldStop && task.isDone
1124})
1125
1126ProtoTask.__defineGetter__( "canceled", function(){
1127 return this.current.optional.wasCanceled
1128})
1129
1130ProtoTask.interpret = function task_interpret( steps ){
1131// Add steps according to description.
1132 var task = this.current
1133 if( steps.then ){
1134 this.step( function(){ this.wait( steps) })
1135 return task
1136 }
1137 var block
1138 var len = steps.length
1139 for( var ii = 0 ; ii < len ; ii++ ){
1140 step = steps[ii]
1141 if( step instanceof Function ){
1142 this.step( step)
1143 }else if( step instanceof Array ){
1144 this.task( step)
1145 }else if( step.then ){
1146 (function( promise ){ this.step( function(){ this.wait( promise) }) })
1147 ( step)
1148 }else{
1149 var done = false
1150 if( block = step.step ){ this.step( block); done = true }
1151 if( block = step.task ){ this.task( block); done = true }
1152 if( block = step.repeat ){ this.repeat( block); done = true }
1153 if( block = step.fork ){ this.fork( block); done = true }
1154 if( block = step.progress ){ this.progress( block); done = true }
1155 if( block = step.success ){ this.success( block); done = true }
1156 if( block = step.failure ){ this.failure( block); done = true }
1157 if( block = step.final ){ this.final( block); done = true }
1158 if( block = step.defer ){ this.defer( block); done = true }
1159 if( !done ){
1160 // Immediate value
1161 (function( value ){ this.step( function(){ return value }) })( step)
1162 }
1163 }
1164 }
1165 return task
1166}
1167
1168ProtoTask.__defineGetter__( "tasks", function(){
1169 var buf = []
1170 var tasks = this.subtasks
1171 if( tasks ){
1172 for( var k in tasks ){
1173 buf.push( tasks[k])
1174 }
1175 }
1176 return buf
1177})
1178
1179ProtoTask.__defineGetter__( "parent", function(){
1180 return this.current.parentTask
1181})
1182
1183ProtoTask.__defineGetter__( "root", function(){
1184 var task = this.current
1185 if( !task.parentTask )return task
1186 while( true ){
1187 if( task.parentTask === l8 )return task
1188 task = task.parentTask
1189 }
1190})
1191
1192ProtoTask.__defineGetter__( "paused", function(){
1193 var task = this.current
1194 return !!task.pausedStep
1195})
1196
1197ProtoTask.cancel = function task_cancel(){
1198 var task = this.current
1199 if( task.isDone )return task
1200 var done = false
1201 var on_self = false
1202 while( !done ){
1203 done = true
1204 var tasks = task.tasks
1205 for( var subtask in tasks ){
1206 subtask = tasks[subtask]
1207 if( subtask.optional.wasCanceled )continue
1208 if( subtask.currentStep === CurrentStep ){
1209 on_self = subtask
1210 }else{
1211 done = false
1212 subtask.cancel()
1213 }
1214 }
1215 }
1216 if( !on_self && task !== CurrentStep.task ){
1217 task.optional.wasCanceled = true
1218 task.raise( l8.cancelEvent)
1219 }
1220 return task
1221}
1222
1223ProtoTask.progressing = function(){
1224 if( this.optional.progressBlock ){
1225 try{
1226 this.optional.progressBlock( this)
1227 }catch( e ){
1228 // ToDo
1229 }
1230 }
1231 if( this.optional.promise ){
1232 this.promise.progress()
1233 }
1234}
1235
1236ProtoTask.return = function task_return( val ){
1237 var task = this.current
1238 if( task.isDone ){
1239 throw new Error( "Cannot return(), done l8 task")
1240 }
1241 if( arguments.length === 1 ){ task.stepResult = val }
1242 task.optional.wasCanceled = true
1243 task.raise( l8.returnEvent, false, task.stepResult)
1244}
1245
1246ProtoTask.__defineGetter__( "continue", function task_continue(){
1247 return this.raise( l8.continueEvent)
1248})
1249
1250ProtoTask.__defineGetter__( "break", function task_break(){
1251 return this.raise( l8.breakEvent)
1252})
1253
1254ProtoStep.toString = ProtoStep.toLabel
1255= function(){ return this.task.toString() + "/" + this.id }
1256
1257ProtoTask.final = function( block ){
1258 var task = this.current
1259 task.optional.finalBlock = block
1260 return task
1261}
1262
1263ProtoTask.failure = function( block ){
1264 var task = this.current
1265 task.optional.failureBlock = block
1266 return task
1267}
1268
1269ProtoTask.success = function( block ){
1270 var task = this.current
1271 task.optional.successBlock = block
1272 return task
1273}
1274
1275/* ----------------------------------------------------------------------------
1276 * Trans-compiler
1277 */
1278
1279// l8.compile() may need to be provided a well scoped "eval()" or else it's
1280// result function may lack access to the global variables referenced by the
1281// code to (re)compile. This should be necessary on nodejs only, not in browsers
1282l8.eval = null // l8.eval = function( txt ){ eval( txt) }
1283
1284ProtoTask.compile = function task_compile( code, generator ){
1285// Expand some macros to make a "task constructor" or a "generator constructor".
1286
1287 // Lexer
1288
1289 code = code.toString()
1290 var close = code.lastIndexOf( "}")
1291 code = code.substr( 0, close) + code.substr( close + 1)
1292 code = "\n begin;\n" + code + "\n end;\n"
1293 var ii = 0
1294 var fragment
1295 var fragments = []
1296 code.replace(
1297 / (begin|end|step;|step\([^\)]*\);|task;|task\([^\)]*\);|fork;|fork\([^\)]*\);|repeat;|repeat\([^\)]*\);|progress;|progress\([^\)]*\);|success;|success\([^\)]*\);|failure;|failure\([^\)]*\);|final;|final\([^\)]*\);|defer;|defer\([^\)]*\);)/g,
1298 function( match, keyword, index ){
1299 fragment = code.substring( ii, index - 1)
1300 fragments.push( fragment)
1301 fragment = "~kw~" + keyword
1302 fragments.push( fragment)
1303 ii = index + match.length
1304 }
1305 )
1306
1307 // Parser
1308
1309 function is_empty( code ){
1310 return !code
1311 .replace( /;/g, "")
1312 .replace( /\./g, "")
1313 .replace( /\s/g, "")
1314 .replace( /\r/g, "")
1315 .replace( /\n/g, "")
1316 }
1317
1318 function parse( list, subtree, is_nested ){
1319 var obj
1320 var kw
1321 var params
1322 if( !list.length )return subtree
1323 var head = list.shift()
1324 // trace( head)
1325 if( head == "~kw~end" ){
1326 if( !is_nested ){
1327 throw new Error( "Unexpected 'end' in l8.compile()")
1328 }
1329 return subtree
1330 }
1331 if( head == "~kw~begin" ){
1332 var sub = parse( list, [], true)
1333 subtree.push( {begin: sub})
1334 }else if( head.indexOf( "~kw~") === 0 ){
1335 kw = head.substr( 4).replace( ";", "").replace( /\s/g, "")
1336 params = ""
1337 kw = kw.replace( /\(.*\)/, function( match ){
1338 params = match
1339 return ""
1340 })
1341 obj = {params:params}
1342 obj[kw] = list.shift()
1343 subtree.push( obj)
1344 }else{
1345 subtree.push( {code:head})
1346 }
1347 return parse( list, subtree, is_nested)
1348 }
1349
1350 var tree = parse( fragments, [], false)
1351 var body = tree[1].begin
1352 var head = body[0].code.replace( /;\nfunction/, "function")
1353 delete body[0]
1354
1355 // Code generator
1356
1357 var pushed
1358
1359 function f( params, code ){
1360 params = params || "()"
1361 return "function" + params + "{ "
1362 + code.replace( / +/g, " ").replace( /(\r|\n| )+$/, "")
1363 + " }"
1364 }
1365
1366 function g( buf, kw, params, code ){
1367 if( is_empty( code) ){
1368 pushed = true
1369 return ""
1370 }
1371 //buf.push( "this." + kw + "( " + f( code) + ");\n")
1372 buf.push( kw + "( " + f( params, code) + ")")
1373 pushed = true
1374 }
1375
1376 var previous = null
1377
1378 function gen_block( head, buf, after ){
1379 if( !head )return
1380 var block
1381 if( block = head.begin ){
1382 var body_obj = []
1383 previous = null
1384 generate( block, body_obj)
1385 body_obj = body_obj.join( ".\n")
1386 if( after && (after.fork || after.repeat || after.spawn) ){
1387 buf.push( body_obj)
1388 pushed = true
1389 return
1390 }
1391 // "begin" after "step" is equivalent to "task"
1392 if( after && after.step ){
1393 buf.push( body_obj)
1394 pushed = true
1395 return
1396 }
1397 g( buf, "task", "()", body_obj)
1398 }
1399 else if( block = head.code ){
1400 if( !is_empty( block) ){
1401 buf.push( block + "\nthis")
1402 }
1403 pushed = true
1404 }
1405 else if( block = head.step ){ g( buf, "step", head.params, block) }
1406 else if( block = head.task ){ g( buf, "task", head.params, block) }
1407 else if( block = head.fork ){ g( buf, "fork", head.params, block) }
1408 else if( block = head.spawn ){ g( buf, "spawn", head.params, block) }
1409 else if( block = head.repeat ){ g( buf, "repeat", head.params, block) }
1410 else if( block = head.progress ){ g( buf, "progress", head.params, block) }
1411 else if( block = head.success ){ g( buf, "success", head.params, block) }
1412 else if( block = head.failure ){ g( buf, "failure", head.params, block) }
1413 else if( block = head.final ){ g( buf, "final", head.params, block) }
1414 else if( block = head.defer ){ g( buf, "defer", head.params, block) }
1415 }
1416
1417 function generate( tree, buf ){
1418 if( !tree.length ){
1419 gen_block( previous, buf)
1420 return
1421 }
1422 var head = tree.shift()
1423 if( !head )return generate( tree, buf)
1424 pushed = false
1425 if( head.begin && previous ){
1426 var content
1427 for( var kw in previous ){
1428 if( kw == "params" )continue
1429 content = previous[kw]
1430 }
1431 if( is_empty( content) ){
1432 content = []
1433 var tmp = previous
1434 gen_block( head, content, previous)
1435 previous = tmp
1436 for( kw in previous ){
1437 if( kw == "params" )continue
1438 // "step" + "begin" eqv "task"
1439 if( kw == "step" ){
1440 previous["step"] = null
1441 kw = "task"
1442 }
1443 previous[kw] = content.join( ".\n")
1444 }
1445 head = null
1446 }
1447 }
1448 if( previous ){
1449 gen_block( previous, buf)
1450 if( !pushed ){
1451 //g( buf, "step", previous.code)
1452 if( !is_empty( previous.code) ){
1453 buf.push( previous.code + ";this")
1454 }
1455 pushed = true
1456 }
1457 }
1458 previous = head
1459 generate( tree, buf)
1460 }
1461
1462 //trace( Util.inspect( fragments))
1463 var str = []
1464 str.push( ";this")
1465 generate( body, str)
1466 // trace( Util.inspect( str))
1467 str = str.join( ".\n")
1468 var fn
1469 // Compile code, with user provided "scoped eval" maybe
1470 if( l8.eval ){
1471 fn = l8.eval( "L8_compiled = " + head + str + "}") // WTF, xxx = is needed
1472 // l8.eval = null
1473 }else{
1474 // Remove 'function xx(p1,p2..){' declaration, but remember parameters
1475 var params
1476 head = head.replace(
1477 /function.*\((.*)\).*{/,
1478 function( match, p1 ){
1479 params = p1.replace( / /, "")
1480 return ""
1481 }
1482 )
1483 // Compile code, using "global scope", something that is platform dependant
1484 fn = new Function( params, head + str)
1485 }
1486 return !generator ? l8.Task( fn) : l8.Generator( fn)
1487}
1488
1489l8.compileGenerator = function( code ){
1490 return l8.compile( code, true)
1491}
1492
1493if( false && DEBUG ){
1494var do_something_as_task = function(){
1495 var ii = 0
1496 step; this.sleep( 1000);
1497 fork; do_some_other_task();
1498 fork; another_task();
1499 task; yet();
1500 step( a, b ); use( a); use( b);
1501 step; begin
1502 ii++
1503 step; ha()
1504 end
1505 fork; begin
1506 first()
1507 failure; bad()
1508 end
1509 fork; begin
1510 step; second()
1511 failure; very_bad()
1512 end
1513 begin
1514 step; ok()
1515 failure; ko()
1516 end
1517 repeat; begin
1518 step; act()
1519 step( r ); if( !r ) this.break
1520 end
1521 success; done();
1522 failure; problem();
1523 final; always();
1524}
1525l8.compile( do_something_as_task)
1526} // DEBUG
1527
1528/* ----------------------------------------------------------------------------
1529 * Promise
1530 */
1531
1532function Promise(){
1533// Promise/A compliant. See https://gist.github.com/3889970
1534 this.wasResolved = false
1535 this.resolveValue = void null
1536 this.wasRejected = false
1537 this.rejectReason = void null
1538 this.allHandlers = null
1539 return this
1540}
1541var ProtoPromise = Promise.prototype
1542
1543var P_defer = null // q.js or when.js 's defer(), or angular's $q's one
1544
1545l8.setPromiseFactory = function( factory ){
1546 P_defer = factory
1547}
1548
1549function MakePromise(){
1550 return P_defer ? P_defer() : new Promise()
1551}
1552
1553ProtoTask.promise = function(){ return MakePromise() }
1554
1555ProtoTask.then = function task_then( success, failure, progress ){
1556 var promise = this.optional.donePromise
1557 if( !promise ){
1558 promise = this.optional.donePromise = MakePromise()
1559 }
1560 return promise.then( success, failure, progress)
1561}
1562
1563ProtoTask.callback = function l8_node( promise, cb ){
1564// Register a node style callback to handle a promise completion.
1565// Promise defaults to current thead when not specified.
1566 if( !cb ){
1567 de&&mand( promise instanceof Function )
1568 cb = promise
1569 promise = this.current
1570 }
1571 return promise.then(
1572 function( ok){ cb( null, ok) },
1573 function( ko){ cb( ko) }
1574 )
1575}
1576
1577ProtoPromise.then = function promise_then( success, failure, progress ){
1578 var new_promise = MakePromise()
1579 if( !this.allHandlers ){
1580 this.allHandlers = []
1581 }
1582 this.allHandlers.push({
1583 successBlock: success,
1584 failureBlock: failure,
1585 progressBlock: progress,
1586 nextPromise: new_promise
1587 })
1588 if( this.wasResolved ){
1589 this.resolve( this.resolveValue, true) // force
1590 }else if( this.wasRejected ){
1591 this.reject( this.rejectReason, true) // force
1592 }
1593 return new_promise
1594}
1595
1596ProtoPromise.handleResult = function handle( handler, ok, value ){
1597 var block = ok ? handler.successBlock : handler.failureBlock
1598 var next = handler.nextPromise
1599 if( block ){
1600 try{
1601 var val = block.call( this, value)
1602 if( val && val.then ){
1603 val.then(
1604 function( r ){ ProtoPromise.handleResult( handler, true, r) },
1605 function( e ){ ProtoPromise.handleResult( handler, false, e) }
1606 )
1607 return
1608 }
1609 if( next ){
1610 next.resolve( val)
1611 }
1612 }catch( e ){
1613 if( next ){
1614 next.reject( e)
1615 }
1616 }
1617 }else if( next ){
1618 next.resolve.call( next, value)
1619 }
1620 handler.nextPromise = null
1621 handler.failureBlock = handler.successBlock = handler.progressBlock = null
1622}
1623
1624ProtoPromise.resolve = function promise_resolve( value, force ){
1625 if( !force && (this.wasResolved || this.wasRejected) )return
1626 this.wasResolved = true
1627 this.resolveValue = value
1628 if( !value ){
1629 value = value // ToDo de&&bug( "DEBUG null resolve")
1630 }
1631 if( !this.allHandlers )return
1632 var that = this
1633 function handle( handler, value ){
1634 L8_NextTick( function(){
1635 that.handleResult( handler, true, value)
1636 })
1637 }
1638 for( var ii = 0 ; ii < this.allHandlers.length ; ii++ ){
1639 handle( this.allHandlers[ii], value)
1640 }
1641 this.allHandlers = null
1642 return this
1643}
1644
1645ProtoPromise.reject = function promise_reject( value, force ){
1646 if( !force && (this.wasResolved || this.wasRejected) )return
1647 this.wasRejected = true
1648 this.rejectReason = value
1649 if( !this.allHandlers )return
1650 function handle( handler, value ){
1651 L8_NextTick( function(){
1652 ProtoPromise.handleResult( handler, false, value)
1653 })
1654 }
1655 for( var ii = 0 ; ii < this.allHandlers.length ; ii++ ){
1656 handle( this.allHandlers[ii], value)
1657 }
1658 this.allHandlers = null
1659 return this
1660}
1661
1662ProtoPromise.progress = function promise_progress(){
1663 if( this.wasResolved || this.wasRejected )return
1664 // ToDo: implement this
1665 return this
1666}
1667
1668/* ----------------------------------------------------------------------------
1669 * Task "local" variables.
1670 * Such a variable is stored in a "binding" that subtasks inherit.
1671 */
1672
1673ProtoTask.var = function( attr, val ){
1674// Define a new variable.
1675// Note: please use global() to create variables in the root binding because
1676// l8.var() will actually create a variable in the current task when applied
1677// on the root l8 task.
1678// Note: when a task is done, it's bindings are erased. As a consequence, any
1679// pending spawn task gets inherited by the done task's parent task and cannot
1680// access the erased bindings they previously accessed, resulting in access
1681// attemtps returning typically "undefined" instead of the expected value. To
1682// avoid such a situation, when spawn tasks accessed shared variable from their
1683// parent, please make sure that the parent task does not terminate until all
1684// spawn task are done too. Use .join() for that purpose.
1685 if( attr === "task" )throw( "no 'task' variable, reserved")
1686 var task = this.current
1687 var data = task.data
1688 if( !data ){
1689 data = task.data = {task:task}
1690 }
1691 data[attr] = {value:val,task:task}
1692 return task
1693}
1694
1695ProtoTask.global = function( attr, val ){
1696 if( arguments.length === 1 ){
1697 return this.data[attr] = {value:val,task:l8}
1698 }else{
1699 return this.data[attr]
1700 }
1701}
1702
1703ProtoTask.set = function( attr, val ){
1704// Change the value of an existing task local variable or create a new
1705// variable as .var() would.
1706 if( attr === "task" )throw( "no 'task' l8 variable, reserved")
1707 var task = this.current
1708 var data = task.data
1709 if( !data ){
1710 task.data = {task:task}
1711 }
1712 var target = task
1713 var slot
1714 while( target ){
1715 if( (data = target.data)
1716 && data.hasOwnProperty( attr)
1717 ){
1718 slot = data[attr]
1719 slot.task.data[attr].value = val
1720 if( target != task ){
1721 task.data[attr] = {task:target}
1722 }
1723 return task
1724 }
1725 target = target.parentTask
1726 }
1727 return task.var( attr, val)
1728}
1729
1730ProtoTask.get = function( attr ){
1731// Get the value of a task's variable. If the current task does not define
1732// that variable in it's own binding, follow binding chain in parent task.
1733 if( attr === "task" )throw( "no 'task' l8 variable, reserved")
1734 var task = this.current
1735 var data = task.data
1736 if( !data ){
1737 task.data = {task:task}
1738 }
1739 var target = task
1740 var slot
1741 while( target ){
1742 if( (data = target.data)
1743 && data.hasOwnProperty( attr)
1744 ){
1745 slot = data[attr]
1746 if( target !== task ){
1747 task.data[attr] = {task:target}
1748 }
1749 return slot.task.data[attr].value
1750 }
1751 target = target.parentTask
1752 }
1753 // "undefined" is returned when attribute does not exists
1754}
1755
1756ProtoTask.binding = function( attr ){
1757// Return the "binding" where a variable is stored (or would be stored).
1758// That binding is an object with a "task" property (the binding owner) and
1759// a property for each variable ever accessed by that task or it's subtasks.
1760// That property has a "value" property when that variable is stored directly
1761// inside that binding. Or else it has a "task" property that tells which task
1762// stores the variable's value.
1763 var task = this.current
1764 var data
1765 var target = task
1766 while( target ){
1767 if( !(data = target.data) ){
1768 target = target.parentTask
1769 continue
1770 }
1771 if( !attr )return data
1772 if( data.hasOwnProperty( attr) ){
1773 if( target != task ){
1774 task.data[attr] = {task:target}
1775 }
1776 return data[attr].task.data
1777 }
1778 target = target.parentTask
1779 }
1780 return l8.data
1781}
1782
1783/* ----------------------------------------------------------------------------
1784 * Tasks synchronization
1785 */
1786
1787ProtoTask.wait = function task_wait( promise ){
1788 var task = this.current
1789 var step = task.currentStep
1790 task.pause()
1791 var on_err = function( e ){
1792 if( !task.currentStep === step )return
1793 task.raise( e)
1794 }
1795 var wait_loop = function( r ){
1796 if( !task.currentStep === step )return
1797 if( r && r.then ){
1798 // my promises can't be fulfilled with a promise but others may
1799 r.then( wait_loop, on_err)
1800 return
1801 }
1802 task.stepResult = r
1803 task.resume()
1804 }
1805 promise.then( wait_loop, on_err)
1806 return task
1807}
1808
1809ProtoTask.join = function task_join(){
1810 var task = this.current
1811 var step = task.currentStep
1812 var j = function(){
1813 if( task.subtasksCount ){
1814 for( var subtask in task.subtasks ){
1815 subtask = task.subtasks[subtask]
1816 task.pause()
1817 subtask.then( j, j)
1818 return
1819 }
1820 return
1821 }
1822 if( task.pausedStep === step ){
1823 task.resume()
1824 }
1825 }
1826 j()
1827 return task
1828}
1829
1830ProtoTask.pause = function pause(){
1831// Pause execution of task at current step. Task will resume and execute next
1832// step when resume() is called.
1833 var task = this.current
1834 var step = task.currentStep
1835 if( step.isBlocking ){
1836 throw new Error( "Cannot pause, already blocked l8 task")
1837 }
1838 step.isBlocking = true
1839 task.pausedStep = step
1840 return task
1841}
1842
1843ProtoTask.resume = function task_resume(){
1844// Resume execution of paused task. Execution restarts at step next to the
1845// one where the task was paused.
1846 var task = this.current
1847 if( task.isDone ){
1848 throw new Error( "Cannot resume, done l8 task")
1849 }
1850 var paused_step = task.pausedStep
1851 if( !paused_step ){
1852 throw new Error( "Cannot resume, not paused l8 task")
1853 }
1854 if( !paused_step.isBlocking ){
1855 throw new Error( "Cannot resume, running l8 step")
1856 }
1857 de&&mand( paused_step.task === this )
1858 // Result cannot be a promise because promises pause the task
1859 de&&mand( !task.stepResult || !task.stepResult.then || task.stepResult.parentTask )
1860 task.pausedStep = null
1861 paused_step.isBlocking = false
1862 paused_step.scheduleNext()
1863 return task
1864}
1865
1866ProtoTask.raise = function task_raise( err, dont_throw, val ){
1867// Note: val parameter is needed when err is l8.returnEvent
1868 var task = this.current
1869 de&&mand( task !== l8 )
1870 if( task.isDone )return task
1871 err = task.stepError = err || task.stepError || l8.failureEvent
1872 if( err === l8.returnEvent ){
1873 task.stepResult = val
1874 }
1875 var step = task.currentStep
1876 if( step ){
1877 // If there exists subtasks, forward error to them
1878 var queue = task.forkedTasks
1879 if( queue ){
1880 if( queue instanceof Array ){
1881 for( var subtask in queue ){
1882 queue[subtask].raise( err, dont_throw, val)
1883 }
1884 }else{
1885 queue.raise( err, dont_throw, val)
1886 }
1887 return
1888 }
1889 // error are forwarded to parent, unless catched, in scheduleNext()
1890 if( step.isBlocking ){
1891 step.isBlocking = false
1892 task.pauseStep = null
1893 step.scheduleNext()
1894 }else if( step === CurrentStep ){
1895 if( !dont_throw )throw err
1896 }
1897 }else{
1898 de&&bug( "Unhandled exception", err, err.stack)
1899 }
1900 return task
1901}
1902
1903ProtoTask.sleep = function task_sleep( delay ){
1904 var task = this.current
1905 var step = task.currentStep
1906 task.pause()
1907 setTimeout( function() {
1908 if( !task.currentStep === step )return
1909 task.resume()
1910 }, delay)
1911 return task
1912}
1913
1914/* ----------------------------------------------------------------------------
1915 * Semaphore
1916 */
1917
1918function Semaphore( count ){
1919 this.count = count
1920 this.promiseQueue = []
1921 this.closed = false
1922 return this
1923}
1924var ProtoSemaphore = Semaphore.prototype
1925
1926ProtoTask.semaphore = function( count ){
1927 return new Semaphore( count)
1928}
1929
1930ProtoSemaphore.then = function( callback ){
1931 return this.promise.then( callback)
1932}
1933
1934ProtoSemaphore.__defineGetter__( "promise", function(){
1935 var promise = MakePromise()
1936 if( this.closed ){
1937 promise.reject( l8.CloseEvent)
1938 return promise
1939 }
1940 if( this.count > 0 ){
1941 this.count--
1942 promise.resolve( this)
1943 }else{
1944 this.queue.push( promise)
1945 }
1946 return promise
1947})
1948
1949ProtoSemaphore.release = function(){
1950 this.count++
1951 if( this.closed || this.count <= 0 )return
1952 var step = this.promiseQueue.shift()
1953 if( step ){
1954 this.count--
1955 step.resolve( this)
1956 }
1957 return this
1958}
1959
1960ProtoSemaphore.close = function(){
1961 var list = this.promiseQueue
1962 this.promiseQueue = null
1963 var len = list.length
1964 for( var ii = 0 ; ii < len ; ii++ ){
1965 list[ii].reject( l8.CloseEvent)
1966 }
1967 return this
1968}
1969
1970/* ----------------------------------------------------------------------------
1971 * Mutex
1972 */
1973
1974function Mutex( entered ){
1975 this.entered = entered
1976 this.task = null
1977 this.taskQueue = []
1978 this.closed = false
1979}
1980var ProtoMutex = Mutex.prototype
1981
1982ProtoTask.mutex = function task_mutex( entered ){
1983 return new Mutex( entered)
1984}
1985
1986ProtoMutex.__defineGetter__( "promise", function(){
1987 var promise = MakePromise()
1988 var task = CurrentStep.task
1989 // when no need to queue...
1990 if( !this.entered || this.task === task ){
1991 // ... because same task cannot block itself
1992 if( this.entered ){
1993 promise.reject( new Error( "mutex already entered"))
1994 // ... because nobody's there
1995 }else{
1996 this.entered = true
1997 this.task = task
1998 promise.resolve( this)
1999 }
2000 // when a new task wants to enter asap
2001 }else{
2002 this.queue.push( promise)
2003 }
2004 return promise
2005})
2006
2007ProtoMutex.then = function( callback, errback ){
2008// Duck typing so that Task.wait() works
2009 return this.promise.then( callback, errback)
2010}
2011
2012ProtoMutex.release = function(){
2013 if( !this.entered )return
2014 this.task = null
2015 var promise = this.promiseQueue.shift()
2016 if( promise ){
2017 promise.resolve( this)
2018 }else{
2019 this.entered = false
2020 this.task = null
2021 }
2022}
2023
2024ProtoMutex.close = function(){
2025 var list = this.promiseQueue
2026 this.promiseQueue = null
2027 var len = list.length
2028 for( var ii = 0 ; ii < len ; ii++ ){
2029 list[ii].reject( l8.CloseEvent)
2030 }
2031 return this
2032}
2033
2034/* ----------------------------------------------------------------------------
2035 * Lock
2036 */
2037
2038function Lock( count ){
2039// aka "reentrant mutex"
2040 this.mutex = new Mutex( count > 0 )
2041 this.count = count || 0
2042 this.closed = false
2043}
2044var ProtoLock = Lock.prototype
2045
2046ProtoTask.lock = function task_lock( count ){
2047 return new Lock( count)
2048}
2049
2050ProtoLock.__defineGetter__( "promise", function(){
2051 var that = this
2052 var promise = MakePromise()
2053 if( this.mutex.task === CurrentStep.task ){
2054 this.count++
2055 promise.resolve( that)
2056 }else{
2057 this.mutex.then( function(){
2058 this.count = 1
2059 promise.resolve( that)
2060 })
2061 }
2062 return promise
2063})
2064
2065ProtoLock.then = function lock_then( callback, errback ){
2066 return this.promise.then( callback, errback)
2067}
2068
2069ProtoLock.release = function(){
2070 if( this.count ){
2071 if( --this.count )return
2072 }
2073 this.mutex.release()
2074}
2075
2076ProtoLock.__defineGetter__( "task", function(){
2077 return this.mutex.task
2078})
2079
2080ProtoLock.close = function(){
2081 if( this.closed )return
2082 this.closed = true
2083 this.mutex.close()
2084 return this
2085}
2086
2087/* ----------------------------------------------------------------------------
2088 * Port. Producer/Consumer protocol with no buffering at all.
2089 */
2090
2091function Port(){
2092 this.getPromise = null // "in" promise, ready when ready to .get()
2093 this.putPromise = null // "out" promise, ready when ready to .put()
2094 this.value = null
2095 this.closed = false
2096}
2097var ProtoPort = Port.prototype
2098
2099ProtoTask.port = function task_port(){
2100 return new Port()
2101}
2102
2103ProtoPort.__defineGetter__( "promise", function(){
2104 return this.in
2105})
2106
2107ProtoPort.then = function port_then( callback, errback ){
2108 return this.in.then( callback, errback)
2109}
2110
2111ProtoPort.get = function port_get(){
2112 var that = this
2113 this.out.resolve()
2114 var task = this.current
2115 var step = task.currentStep
2116 task.pause()
2117 this.in.then( function( r ){
2118 if( !that.getPromise )return that.in
2119 that.getPromise = null
2120 that.value = r
2121 if( task.pausedStep === step ){
2122 task.stepResult = r
2123 task.resume()
2124 }
2125 })
2126 return this
2127}
2128
2129ProtoPort.tryGet = function(){
2130// Like .get() but non blocking
2131 if( this.closed
2132 || !this.getPromise
2133 || this.getPromise.wasResolved
2134 )return [false]
2135 this.getPromise = null
2136 return [true, this.value]
2137}
2138
2139ProtoPort.put = function port_put( msg ){
2140 var that = this
2141 this.in.resolve( msg)
2142 var task = this.current
2143 var step = task.currentStep
2144 task.pause()
2145 this.out.then( function(){
2146 if( !that.putPromise )return that.out
2147 that.putPromise = null
2148 if( task.pausedStep === step ){
2149 step.stepResult = that
2150 task.resume()
2151 }
2152 })
2153 return this
2154}
2155
2156ProtoPort.tryPut = function( msg ){
2157// Like .put() but non blocking
2158 if( this.closed
2159 || !this.putPromise
2160 || !this.putPromise.wasResolved
2161 )return false
2162 this.putPromise = null
2163 this.value = msg
2164 return true
2165}
2166
2167ProtoPort.__defineGetter__( "in", function(){
2168 return this.getPromise
2169 ? this.getPromise = MakePromise()
2170 : this.getPromise
2171})
2172
2173ProtoPort.__defineGetter__( "out", function(){
2174 return this.putPromise
2175 ? this.putPromise = MakePromise()
2176 : this.putPromise
2177})
2178
2179/* ----------------------------------------------------------------------------
2180 * MessageQueue. Producer/Consumer protocol with buffering.
2181 */
2182
2183function MessageQueue( capacity ){
2184 this.capacity = capacity || 100000
2185 this.queue = new Array() // ToDo: preallocate this.capacity
2186 this.length = 0
2187 this.getPromise = null // "in" promise, ready when ready to .get()
2188 this.putPromise = null // "out" promise, ready when ready to .put()
2189 this.closed = false
2190}
2191var ProtoMessageQueue = MessageQueue.prototype
2192
2193ProtoTask.queue = function task_queue( capacity ){
2194 return new MessageQueue( capacity)
2195}
2196
2197ProtoMessageQueue.__defineGetter__( "promise", function(){
2198 return this.in
2199})
2200
2201ProtoMessageQueue.then = function message_queue_then( callback, errback ){
2202 return this.in.then( callback, errback)
2203}
2204
2205ProtoMessageQueue.put = function message_queue_put( msg ){
2206 var that = this
2207 var step = CurrentStep
2208 var task = step.task
2209 if( that.closed )return task.break
2210 if( arguments.length > 1 ){
2211 msg = arguments
2212 }
2213 if( this.full ){
2214 task.pause()
2215 this.out.then( function(){
2216 task.queue.push( msg)
2217 if( task.pausedStep === step ){
2218 task.stepResult = msg
2219 task.resume()
2220 }
2221 that.putPromise = null
2222 that.in.resolve()
2223 ++that.length
2224 if( !that.full ){
2225 that.out.resolve()
2226 }
2227 })
2228 }else{
2229 this.queue.push( msg)
2230 this.length++
2231 this.in.resolve()
2232 }
2233}
2234
2235ProtoMessageQueue.tryPut = function message_queue_try_put( msg ){
2236 if( this.closed
2237 || this.full
2238 )return false
2239 this.queue.push( arguments.length > 1 ? arguments : msg)
2240 this.length++
2241 this.in.resolve()
2242 return true
2243}
2244
2245ProtoMessageQueue.get = function message_queue_get(){
2246 var that = this
2247 var step = CurrentStep
2248 var task = step.task
2249 if( that.closed )return task.break
2250 var get = function(){
2251 de&&mand( that.getPromise )
2252 that.getPromise = null
2253 task.stepResult = that.queue.shift()
2254 that.length--
2255 if( !that.empty ){
2256 that.in.resolve()
2257 }
2258 return that
2259 }
2260 if( !this.empty )return get()
2261 var consume = function(){
2262 if( task.pausedStep !== step )return
2263 if( that.closed )return task.break
2264 if( that.empty ){
2265 that.in.then( consume)
2266 return
2267 }
2268 get()
2269 task.resume()
2270 }
2271 task.pause()
2272 this.in.then( consume)
2273 return that
2274}
2275
2276ProtoMessageQueue.tryGet = function message_queue_try_get(){
2277 if( this.closed
2278 || this.empty
2279 )return [false]
2280 var msg = this.queue.shift()
2281 --this.length
2282 if( !this.empty ){
2283 this.in.resolve()
2284 }
2285 return [true, msg]
2286}
2287
2288ProtoMessageQueue.__defineGetter__( "in", function(){
2289 var promise = this.getPromise
2290 if( promise )return promise
2291 this.getPromise = promise = MakePromise()
2292 if( !this.empty ){
2293 promise.resolve()
2294 }
2295 return promise
2296})
2297
2298ProtoMessageQueue.__defineGetter__( "out", function(){
2299 var promise = this.putPromise
2300 if( promise )return promise
2301 this.putPromise = promise = MakePromise()
2302 if( !this.full ){
2303 promise.resolve()
2304 }
2305 return promise
2306})
2307
2308ProtoMessageQueue.__defineGetter__( "empty", function(){
2309 return this.length === 0 || this.closed
2310})
2311
2312ProtoMessageQueue.__defineGetter__( "full", function(){
2313 return this.length >= this.capacity && !this.closed
2314})
2315
2316ProtoMessageQueue.close = function(){
2317 if( this.closed )return this
2318 this.closed = true
2319 if( this.getPromise ){
2320 this.getPromise.resolve()
2321 }
2322 if( this.putPromise ){
2323 this.putPromise.resolve()
2324 }
2325 return true
2326}
2327
2328/* ----------------------------------------------------------------------------
2329 * Generator. next()/yield() protocol
2330 */
2331
2332function Generator(){
2333 this.task = null // generator task, the one that yields
2334 this.getPromise = null // ready when ready to .next()
2335 this.getMessage = null
2336 this.putPromise = null // ready when ready to .yield()
2337 this.putMessage = null
2338 this.closed = false
2339 return this
2340}
2341
2342var ProtoGenerator = Generator.prototype
2343
2344ProtoTask.generator = function task_generator(){
2345 return new Generator()
2346}
2347
2348ProtoTask.Generator = function( block ){
2349// Return a "Generator Constructor", much like l8.Task() does but the returned
2350// value is a Generator Task, not just a regular Task. I.e. it can "yield".
2351 return function(){
2352 var args = arguments
2353 var parent = l8.current
2354 var gen = l8.generator()
2355 var task = MakeTask( parent, false, true) // detached (spawn)
2356 // ToDo: generator task object should be reuseable using task.free()
2357 L8_EnqueueStep( MakeStep( task, function(){
2358 block.apply( task, args)
2359 }))
2360 gen.task = task
2361 var closer = function(){
2362 if( task.optional.generator ){
2363 gen.close()
2364 task.optional.generator = null
2365 }
2366 if( parent.optional.generator ){
2367 gen.close()
2368 parent.optional.generator = null
2369 }
2370 }
2371 task.then( closer, closer)
2372 parent.then( closer, closer)
2373 parent.optional.generator = task.optional.generator = gen
2374 return task
2375 }
2376}
2377
2378ProtoTask.yield = function( val ){
2379 var task = l8.current
2380 var gen
2381 var gen_task = task
2382 while( gen_task ){
2383 gen = gen_task.optional.generator
2384 if( gen ){
2385 gen.yield( val)
2386 return task
2387 }
2388 gen_task = gen_task.parentTask
2389 }
2390 task.raise( new Error( "Cannot yield(), not a l8 generator"))
2391 return task
2392}
2393
2394ProtoTask.next = function( val ){
2395 var task = l8.current
2396 var gen
2397 var gen_task = task
2398 while( gen_task ){
2399 gen = gen_task.optional.generator
2400 if( gen ){
2401 gen.next( val)
2402 return task
2403 }
2404 gen_task = gen_task.parentTask
2405 }
2406 task.raise( new Error( "Cannot generate(), not a l8 generator"))
2407 return task
2408}
2409
2410ProtoGenerator.__defineGetter__( "promise", function(){
2411 return this.get
2412})
2413
2414ProtoGenerator.then = function port_then( callback, errback ){
2415 return this.get.then( callback, errback)
2416}
2417
2418ProtoGenerator.next = function( msg ){
2419 var that = this
2420 var task = l8.current
2421 var step = task.currentStep
2422 // Pause until producer yields
2423 task.pause()
2424 this.get.then( function( get_msg ){
2425 that.getPromise = null
2426 that.put.resolve( that.putMessage = msg )
2427 if( task.pausedStep === step ){
2428 if( that.closed ){
2429 // return task.break
2430 task.stepError = l8.breakEvent
2431 }else{
2432 task.stepResult = get_msg
2433 }
2434 task.resume()
2435 }
2436 })
2437 return this
2438}
2439
2440ProtoGenerator.tryNext = function( msg ){
2441// Like .generate() but never blocks
2442 if( this.closed )return [false]
2443 if( !this.getPromise.wasResolved )return [false]
2444 this.getPromise = null
2445 this.put.resolve( this.putMessage = msg)
2446 return [true, this.getMessage]
2447}
2448
2449ProtoGenerator.yield = function( msg ){
2450 var that = this
2451 this.task = task
2452 this.get.resolve( this.getMessage = msg)
2453 var task = l8.current
2454 var step = task.currentStep
2455 // Pause until consumer calls .next()
2456 task.pause()
2457 this.put.then( function( put_msg ){
2458 that.putPromise = null
2459 if( task.pausedStep === step ){
2460 if( that.closed ){
2461 // return task.break
2462 task.stepError = l8.breakEvent
2463 }else{
2464 task.stepResult = put_msg
2465 }
2466 task.resume()
2467 }
2468 })
2469 return this
2470}
2471
2472ProtoGenerator.tryYield = function( msg ){
2473// Like .yield() but never blocks
2474 if( this.closed )return [false]
2475 if( !this.putPromise.wasResolved )return [false]
2476 this.putPromise = null
2477 this.get.resolve( this.getMessage = msg)
2478 return [true, this.putMessage]
2479}
2480
2481ProtoGenerator.close = function generator_close(){
2482 if( this.closed )return this
2483 this.closed = true
2484 if( this.getPromise ){ this.getPromise.resolve() }
2485 if( this.putPromise ){ this.putPromise.resolve() }
2486 return this
2487}
2488
2489ProtoGenerator.__defineGetter__( "get", function(){
2490 var promise = this.getPromise
2491 if( !promise ){
2492 promise = this.getPromise = MakePromise()
2493 if( this.closed ){
2494 promise.resolve()
2495 }
2496 }
2497 return promise
2498})
2499
2500ProtoGenerator.__defineGetter__( "put", function(){
2501 var promise = this.putPromise
2502 if( !promise ){
2503 promise = this.putPromise = MakePromise()
2504 if( this.closed ){
2505 promise.resolve()
2506 }
2507 }
2508 return promise
2509})
2510
2511
2512/* ----------------------------------------------------------------------------
2513 * Signal
2514 */
2515
2516function Signal(){
2517 this.nextPromise = MakePromise()
2518 this.closed = false
2519}
2520var ProtoSignal = Signal.prototype
2521
2522ProtoTask.signal = function task_signal( on ){
2523 return new Signal( on)
2524}
2525
2526ProtoSignal.__defineGetter__( "promise", function(){
2527// Returns an unresolved promise that .signal() will resolve and .close() will
2528// reject. Returns an already rejected promise if signal was closed.
2529 var promise = this.nextPromise
2530 if( this.closed )return promise
2531 return !promise.wasResolved ? promise : (this.nextPromise = MakePromise())
2532})
2533
2534ProtoMessageQueue.then = function signal_then( callback, errback ){
2535 return this.promise.then( callback, errback)
2536}
2537
2538ProtoSignal.signal = function signal_signal( value ){
2539// Resolve an unresolved promise that .promise will provide. Signals are not
2540// buffered, only the last one is kept.
2541 if( this.nextPromise.wasResolved && !this.closed ){
2542 this.nextPromise = MakePromise()
2543 }
2544 this.nextPromise.resolve( value )
2545}
2546
2547ProtoSignal.close = function signal_close(){
2548 if( this.closed )return
2549 this.closed = true
2550 if( this.nextPromise.wasResolved ){
2551 this.nextPromise = MakePromise()
2552 }
2553 this.nextPromise.reject( l8.closeEvent)
2554}
2555
2556/* ----------------------------------------------------------------------------
2557 * Timeout
2558 */
2559
2560function Timeout( delay ){
2561 var promise = this.timedPromise = MakePromise()
2562 setTimeout( function(){ promise.resolve() }, delay)
2563}
2564var ProtoTimeout = Timeout.prototype
2565
2566ProtoTask.timeout = function( delay ){
2567 return new Timeout( delay)
2568}
2569
2570ProtoTimeout.__defineGetter__( "promise", function(){
2571 return this.timedPromise
2572})
2573
2574ProtoTimeout.then = function( callback, errback ){
2575 return this.timedPromise.then( callback, errback)
2576}
2577
2578
2579/* ----------------------------------------------------------------------------
2580 * Selector
2581 */
2582
2583function Selector( list, is_or ){
2584 this.allPromises = list
2585 this.firePromise = null
2586 this.result = null
2587 this.isOr = is_or // "Or" selectors ignore false results
2588}
2589var ProtoSelector = Selector.prototype
2590
2591ProtoTask.selector = ProtoTask.any = function( ll ){
2592 var list = (arguments.length === 1 && (ll instanceof Array)) ? ll : arguments
2593 return new Selector( list)
2594}
2595
2596ProtoTask.or = function( ll ){
2597 var list = (arguments.length === 1 && (ll instanceof Array)) ? ll : arguments
2598 return new Selector( list, true)
2599}
2600
2601ProtoTask.select = function(){
2602 var selector = new Selector( arguments)
2603 return this.wait( selector)
2604}
2605
2606ProtoSelector.__defineGetter__( "promise", function(){
2607 var promise = this.firePromise
2608 if( promise )return promise
2609 var that = this
2610 var list = this.allPromises
2611 this.firePromise = promise = MakePromise()
2612 var len = list.length
2613 if( !len ){
2614 promise.resolve( null)
2615 return promise
2616 }
2617 var count = 0
2618 function ok( r ){
2619 if( !that.result ){
2620 try{
2621 while( r instanceof Function ){
2622 r = r.call( l8)
2623 }
2624 }catch( e ){
2625 return ko( e)
2626 }
2627 if( r.then ){
2628 r.then( ok, ko)
2629 }else{
2630 count++
2631 if( r || !that.isOr || count === len ){
2632 that.result = that.isOr ? r : [null,r]
2633 promise.resolve( that.result)
2634 }
2635 }
2636 }
2637 }
2638 function ko( e ){
2639 count++
2640 if( !that.result ){
2641 that.result = [e,null]
2642 promise.resolve( that.result)
2643 }
2644 }
2645 var item
2646 var buf = []
2647 for( var ii = 0 ; ii < len ; ii++ ){
2648 item = list[ii]
2649 while( item instanceof Function ){
2650 item = item.call( l8)
2651 }
2652 if( item.then ){
2653 buf.push( item)
2654 }else{
2655 ok( item)
2656 return promise
2657 }
2658 }
2659 if( len = buf.length ){
2660 for( ii = 0 ; ii < len ; ii++ ){
2661 item = buf[ii]
2662 item.then( ok, ko)
2663 }
2664 }
2665 return promise
2666})
2667
2668ProtoSelector.then = function( callback, errback ){
2669 return this.firePromise.then( callback, errback)
2670}
2671
2672/* ----------------------------------------------------------------------------
2673 * Aggregator
2674 */
2675
2676function Aggregator( list, is_and ){
2677 this.allPromises = list
2678 this.results = []
2679 this.result = list.length
2680 this.firePromise = null
2681 this.isAnd = is_and
2682}
2683var ProtoAggregator = Aggregator.prototype
2684
2685ProtoTask.aggregator = ProtoTask.all = function( ll ){
2686 var list = (arguments.length === 1 && (ll instanceof Array)) ? ll : arguments
2687 return new Aggregator( list)
2688}
2689
2690ProtoTask.and = function( ll ){
2691 var list = (arguments.length === 1 && (ll instanceof Array)) ? ll : arguments
2692 return new Aggregator( list, true)
2693}
2694
2695ProtoAggregator.__defineGetter__( "promise", function(){
2696 var promise = this.firePromise
2697 if( promise )return promise
2698 var that = this
2699 var list = this.allPromises
2700 this.firePromise = promise = MakePromise( list.length === 0)
2701 var results = this.results
2702 var len = list.length
2703 if( !len ){
2704 promise.resolve( results)
2705 return promise
2706 }
2707 // ToDo: should respect order, need an index
2708 function ok( r ){
2709 try{
2710 while( r instanceof Function ){
2711 r = r.call( l8)
2712 }
2713 }catch( e ){
2714 return ko( e)
2715 }
2716 if( r.then ){
2717 r.then( ok, ko)
2718 }else{
2719 results.push( [null,r])
2720 if( that.result ){ that.result = r }
2721 if( results.length === list.length ){
2722 promise.resolve( that.isAnd ? that.result : results)
2723 }
2724 }
2725 }
2726 function ko( e ){
2727 results.push( [e,null])
2728 if( results.length === list.length ){
2729 promise.resolve( that.isAnd ? false : results)
2730 }
2731 }
2732 var item
2733 for( var ii = 0 ; ii < len ; ii++ ){
2734 item = list[ii]
2735 while( item instanceof Function ){
2736 item = item.call( l8)
2737 }
2738 if( item.then ){
2739 item.then( ok, ko)
2740 }else{
2741 ok( item)
2742 }
2743 }
2744 return promise
2745})
2746
2747ProtoAggregator.then = function( callback, errback ){
2748 return this.promise.then( callback, errback)
2749}
2750
2751/*
2752 * Misc
2753 */
2754
2755l8.countdown = function( n ){
2756// Exit process with error status 1 after a while.
2757// Display a stressfull message every second until that.
2758 var count_down = n
2759 setInterval(
2760 function(){
2761 de&&bug( "tick " + --count_down)
2762 if( !count_down ){
2763 trace( "exiting, with error status...")
2764 process.exit( 1)
2765 }
2766 },
2767 1000
2768 )
2769}
2770
2771/*
2772 * End boilerplate for module loaders
2773 * Copied from when.js, see https://github.com/cujojs/when/blob/master/when.js
2774 * Go figure what it means...
2775 */
2776
2777return l8
2778}) })(
2779 typeof define == 'function' && define.amd
2780 ? define
2781 : function( factory ){
2782 typeof exports === 'object'
2783 ? (module.exports = factory())
2784 : (this.l8 = factory());
2785 }
2786 );