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._actionPromise = null; } emitFinished() { this.emit('actionsFinished'); } get index() { return this._index; } set index(index) { this._index = index; } get() { return this; } tick(context, elapsed) { // Empty resolves immediately. if (this.traversals.length === 0) { this.emitFinished(); return; } // If the action promise ticks, tick it. if (this._actionPromise && this._actionPromise instanceof TickingPromise) { this._actionPromise.tick(elapsed); return; } // Actions execute immediately until a promise is made, or they're all // executed. while (true) { // Run the action. const result = this.traversals[this.index].traverse(context); // Deferred result. if (result instanceof Promise) { this._actionPromise = result; // Handle any errors. result.catch(console.error); result.finally(() => { // Finally, run the prologue. this.prologue(); }); break; } // Immediate result. this.prologue(); // Need to break out immediately if required. if (0 === this.index) { break; } } } parallel(context) { // Map all traversals to results. const results = this.traversals.map((traversal) => { return traversal.traverse(context); }); // Wrap all results in a TickingPromise. return TickingPromise.all(results); } prologue() { // Clear out the action promise. this._actionPromise = null; // Increment and wrap the index. this.index = (this.index + 1) % this.traversals.length; // If rolled over, the actions are finished. if (0 === this.index) { this.emitFinished(); } } serial(context) { return this.tickingPromise(context); } tickingPromise(context) { return new TickingPromise( (resolve) => { this.once('actionsFinished', resolve); }, (elapsed) => { this.tick(context, elapsed); }, ); } }