1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.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 | */
|
19 | class 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 | }
|
177 | exports.Bus = Bus;
|
178 | //# sourceMappingURL=Bus.js.map |
\ | No newline at end of file |