import {compose, EventEmitter, TickingPromise} from '@avocado/core'; import {Traversal} from './traversal'; import {Traversals} from './traversals'; const decorate = compose( EventEmitter, ); export class Actions extends decorate(Traversals) { static type() { return 'actions'; } constructor() { super(); this._index = 0; this.pending = null; } emitFinished() { this.emit('actionsFinished'); } get index() { return this._index; } set index(index) { this._index = index; } get() { return this; } tick(context, elapsed) { if (this.traversals.length === 0) { this.emitFinished(); return; } if (this.pending) { if (this.pending instanceof TickingPromise) { this.pending.tick(elapsed); } return; } // Actions execute immediately until a promise is made, or they're all // executed. while (true) { const result = this.traversals[this.index].traverse(context); if (result instanceof Promise) { result.then(() => { this.prologue(); }); result.catch((error) => { console.error(error); this.prologue(); }); this.pending = result; break; } this.prologue(); if (0 === this.index) { break; } } } parallel(context) { let immediate = true; const results = this.traversals.map((traversal) => { const result = traversal.traverse(context); if (result instanceof Promise) { immediate = false; } results.push(result); }); return immediate ? results : TickingPromise.all(results); } prologue() { this.pending = null; if (0 === (this.index = (this.index + 1) % this.traversals.length)) { this.emitFinished(); } } serial(context) { // Immediate tick. Maybe we can avoid a promise? this.tick(context, 0); // If it's pending, we have to return a ticking promise. if (this.pending) { return this.tickingPromise(context); } } tickingPromise(context) { return new TickingPromise( (resolve) => { this.once('actionsFinished', () => { resolve(); }); }, (elapsed) => { this.tick(context, elapsed); }, ); } }