UNPKG

@comunica/core

Version:

Lightweight, semantic and modular actor framework

180 lines 7.08 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Bus = void 0; /** * A publish-subscribe bus for sending actions to actors * to test whether or not they can run an action. * * This bus does not run the action itself, * for that a {@link Mediator} can be used. * * @see Actor * @see Mediator * * @template A The actor type that can subscribe to the sub. * @template I The input type of an actor. * @template T The test type of an actor. * @template O The output type of an actor. * @template TS The test side data type. */ class Bus { /** * All enumerable properties from the `args` object are inherited to this bus. * * @param {IBusArgs} args Arguments object * @param {string} args.name The name for the bus * @throws When required arguments are missing. */ constructor(args) { this.actors = []; this.observers = []; // Mapping from dependency (after) to dependents (before) this.dependencyLinks = new Map(); Object.assign(this, args); this.failMessage = `All actors over bus ${this.name} failed to handle an action`; } /** * Subscribe the given actor to the bus. * After this, the given actor can be unsubscribed from the bus by calling {@link Bus#unsubscribe}. * * An actor that is subscribed multiple times will exist that amount of times in the bus. * * @param {A} actor The actor to subscribe. */ subscribe(actor) { this.actors.push(actor); this.reorderForDependencies(); } /** * Subscribe the given observer to the bus. * After this, the given observer can be unsubscribed from the bus by calling {@link Bus#unsubscribeObserver}. * * An observer that is subscribed multiple times will exist that amount of times in the bus. * * @param {ActionObserver<I, O>} observer The observer to subscribe. */ subscribeObserver(observer) { this.observers.push(observer); } /** * Unsubscribe the given actor from the bus. * * An actor that is subscribed multiple times will be unsubscribed only once. * * @param {A} actor The actor to unsubscribe * @return {boolean} If the given actor was successfully unsubscribed, * otherwise it was not subscribed before. */ unsubscribe(actor) { const index = this.actors.indexOf(actor); if (index >= 0) { this.actors.splice(index, 1); return true; } return false; } /** * Unsubscribe the given observer from the bus. * * An observer that is subscribed multiple times will be unsubscribed only once. * * @param {ActionObserver<I, O>} observer The observer to unsubscribe. * @return {boolean} If the given observer was successfully unsubscribed, * otherwise it was not subscribed before. */ unsubscribeObserver(observer) { const index = this.observers.indexOf(observer); if (index >= 0) { this.observers.splice(index, 1); return true; } return false; } /** * Publish an action to all actors in the bus to test if they can run the action. * * @param {I} action An action to publish * @return {IActorReply<A extends Actor<I, T, O>, I extends IAction, T extends IActorTest, * O extends IActorOutput>[]} * An array of reply objects. Each object contains a reference to the actor, * and a promise to its {@link Actor#test} result. */ publish(action) { return this.actors.map((actor) => ({ actor, reply: actor.test(action) })); } /** * Invoked when an action was run by an actor. * * @param actor The action on which the {@link Actor#run} method was invoked. * @param {I} action The original action input. * @param {Promise<O>} output A promise resolving to the final action output. */ onRun(actor, action, output) { for (const observer of this.observers) { observer.onRun(actor, action, output); } } /** * Indicate that the given actor has the given actor dependencies. * * This will ensure that the given actor will be present in the bus *before* the given dependencies. * * @param {A} dependent A dependent actor that will be placed before the given actors. * @param {A[]} dependencies Actor dependencies that will be placed after the given actor. */ addDependencies(dependent, dependencies) { for (const dependency of dependencies) { let existingDependencies = this.dependencyLinks.get(dependency); if (!existingDependencies) { existingDependencies = []; this.dependencyLinks.set(dependency, existingDependencies); } existingDependencies.push(dependent); } this.reorderForDependencies(); } /** * Reorder the bus based on all present dependencies. */ reorderForDependencies() { if (this.dependencyLinks.size > 0) { const actorsAfter = []; // Temporarily remove all actors that have dependencies for (const actorAfter of this.dependencyLinks.keys()) { const dependentPos = this.actors.indexOf(actorAfter); if (dependentPos >= 0) { this.actors.splice(dependentPos, 1); actorsAfter.push(actorAfter); } } // Iteratively append actors based on the first dependency link // that has all of its dependencies available in the array while (actorsAfter.length > 0) { // Find the first actor that has all of its dependencies available. let activeActorAfterId = -1; for (let i = 0; i < actorsAfter.length; i++) { let validLink = true; for (const dependency of this.dependencyLinks.get(actorsAfter[i])) { if (!this.actors.includes(dependency) && actorsAfter.includes(dependency)) { validLink = false; break; } } if (validLink) { activeActorAfterId = i; break; } } // If none of the pending links are possible, there must be a cyclic dependency if (activeActorAfterId < 0) { throw new Error(`Cyclic dependency links detected in bus ${this.name}`); } // The dependent may not be available (yet), so we don't add it to the array (yet). const activeActorAfter = actorsAfter.splice(activeActorAfterId, 1)[0]; this.actors.push(activeActorAfter); } } } } exports.Bus = Bus; //# sourceMappingURL=Bus.js.map