UNPKG

6.95 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Bus = void 0;
4/**
5 * A publish-subscribe bus for sending actions to actors
6 * to test whether or not they can run an action.
7 *
8 * This bus does not run the action itself,
9 * for that a {@link Mediator} can be used.
10 *
11 * @see Actor
12 * @see Mediator
13 *
14 * @template A The actor type that can subscribe to the sub.
15 * @template I The input type of an actor.
16 * @template T The test type of an actor.
17 * @template O The output type of an actor.
18 */
19class Bus {
20 /**
21 * All enumerable properties from the `args` object are inherited to this bus.
22 *
23 * @param {IBusArgs} args Arguments object
24 * @param {string} args.name The name for the bus
25 * @throws When required arguments are missing.
26 */
27 constructor(args) {
28 this.actors = [];
29 this.observers = [];
30 // Mapping from dependency (after) to dependents (before)
31 this.dependencyLinks = new Map();
32 Object.assign(this, args);
33 }
34 /**
35 * Subscribe the given actor to the bus.
36 * After this, the given actor can be unsubscribed from the bus by calling {@link Bus#unsubscribe}.
37 *
38 * An actor that is subscribed multiple times will exist that amount of times in the bus.
39 *
40 * @param {A} actor The actor to subscribe.
41 */
42 subscribe(actor) {
43 this.actors.push(actor);
44 this.reorderForDependencies();
45 }
46 /**
47 * Subscribe the given observer to the bus.
48 * After this, the given observer can be unsubscribed from the bus by calling {@link Bus#unsubscribeObserver}.
49 *
50 * An observer that is subscribed multiple times will exist that amount of times in the bus.
51 *
52 * @param {ActionObserver<I, O>} observer The observer to subscribe.
53 */
54 subscribeObserver(observer) {
55 this.observers.push(observer);
56 }
57 /**
58 * Unsubscribe the given actor from the bus.
59 *
60 * An actor that is subscribed multiple times will be unsubscribed only once.
61 *
62 * @param {A} actor The actor to unsubscribe
63 * @return {boolean} If the given actor was successfully unsubscribed,
64 * otherwise it was not subscribed before.
65 */
66 unsubscribe(actor) {
67 const index = this.actors.indexOf(actor);
68 if (index >= 0) {
69 this.actors.splice(index, 1);
70 return true;
71 }
72 return false;
73 }
74 /**
75 * Unsubscribe the given observer from the bus.
76 *
77 * An observer that is subscribed multiple times will be unsubscribed only once.
78 *
79 * @param {ActionObserver<I, O>} observer The observer to unsubscribe.
80 * @return {boolean} If the given observer was successfully unsubscribed,
81 * otherwise it was not subscribed before.
82 */
83 unsubscribeObserver(observer) {
84 const index = this.observers.indexOf(observer);
85 if (index >= 0) {
86 this.observers.splice(index, 1);
87 return true;
88 }
89 return false;
90 }
91 /**
92 * Publish an action to all actors in the bus to test if they can run the action.
93 *
94 * @param {I} action An action to publish
95 * @return {IActorReply<A extends Actor<I, T, O>, I extends IAction, T extends IActorTest,
96 * O extends IActorOutput>[]}
97 * An array of reply objects. Each object contains a reference to the actor,
98 * and a promise to its {@link Actor#test} result.
99 */
100 publish(action) {
101 return this.actors.map((actor) => ({ actor, reply: actor.test(action) }));
102 }
103 /**
104 * Invoked when an action was run by an actor.
105 *
106 * @param actor The action on which the {@link Actor#run} method was invoked.
107 * @param {I} action The original action input.
108 * @param {Promise<O>} output A promise resolving to the final action output.
109 */
110 onRun(actor, action, output) {
111 for (const observer of this.observers) {
112 observer.onRun(actor, action, output);
113 }
114 }
115 /**
116 * Indicate that the given actor has the given actor dependencies.
117 *
118 * This will ensure that the given actor will be present in the bus *before* the given dependencies.
119 *
120 * @param {A} dependent A dependent actor that will be placed before the given actors.
121 * @param {A[]} dependencies Actor dependencies that will be placed after the given actor.
122 */
123 addDependencies(dependent, dependencies) {
124 for (const dependency of dependencies) {
125 let existingDependencies = this.dependencyLinks.get(dependency);
126 if (!existingDependencies) {
127 existingDependencies = [];
128 this.dependencyLinks.set(dependency, existingDependencies);
129 }
130 existingDependencies.push(dependent);
131 }
132 this.reorderForDependencies();
133 }
134 /**
135 * Reorder the bus based on all present dependencies.
136 */
137 reorderForDependencies() {
138 if (this.dependencyLinks.size > 0) {
139 const actorsAfter = [];
140 // Temporarily remove all actors that have dependencies
141 for (const actorAfter of this.dependencyLinks.keys()) {
142 const dependentPos = this.actors.indexOf(actorAfter);
143 if (dependentPos >= 0) {
144 this.actors.splice(dependentPos, 1);
145 actorsAfter.push(actorAfter);
146 }
147 }
148 // Iteratively append actors based on the first dependency link
149 // that has all of its dependencies available in the array
150 while (actorsAfter.length > 0) {
151 // Find the first actor that has all of its dependencies available.
152 let activeActorAfterId = -1;
153 for (let i = 0; i < actorsAfter.length; i++) {
154 let validLink = true;
155 for (const dependency of this.dependencyLinks.get(actorsAfter[i])) {
156 if (!this.actors.includes(dependency) && actorsAfter.includes(dependency)) {
157 validLink = false;
158 break;
159 }
160 }
161 if (validLink) {
162 activeActorAfterId = i;
163 break;
164 }
165 }
166 // If none of the pending links are possible, there must be a cyclic dependency
167 if (activeActorAfterId < 0) {
168 throw new Error(`Cyclic dependency links detected in bus ${this.name}`);
169 }
170 // The dependent may not be available (yet), so we don't add it to the array (yet).
171 const activeActorAfter = actorsAfter.splice(activeActorAfterId, 1)[0];
172 this.actors.push(activeActorAfter);
173 }
174 }
175 }
176}
177exports.Bus = Bus;
178//# sourceMappingURL=Bus.js.map
\No newline at end of file