1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | /*-----------------------------------------------------------------------------
|
4 | | Copyright (c) 2014-2017, PhosphorJS Contributors
|
5 | |
|
6 | | Distributed under the terms of the BSD 3-Clause License.
|
7 | |
|
8 | | The full license is in the file LICENSE, distributed with this software.
|
9 | |----------------------------------------------------------------------------*/
|
10 | var algorithm_1 = require("@phosphor/algorithm");
|
11 | /**
|
12 | * A concrete implementation of `ISignal`.
|
13 | *
|
14 | * #### Example
|
15 | * ```typescript
|
16 | * import { ISignal, Signal } from '@phosphor/signaling';
|
17 | *
|
18 | * class SomeClass {
|
19 | *
|
20 | * constructor(name: string) {
|
21 | * this.name = name;
|
22 | * }
|
23 | *
|
24 | * readonly name: string;
|
25 | *
|
26 | * get valueChanged: ISignal<this, number> {
|
27 | * return this._valueChanged;
|
28 | * }
|
29 | *
|
30 | * get value(): number {
|
31 | * return this._value;
|
32 | * }
|
33 | *
|
34 | * set value(value: number) {
|
35 | * if (value === this._value) {
|
36 | * return;
|
37 | * }
|
38 | * this._value = value;
|
39 | * this._valueChanged.emit(value);
|
40 | * }
|
41 | *
|
42 | * private _value = 0;
|
43 | * private _valueChanged = new Signal<this, number>(this);
|
44 | * }
|
45 | *
|
46 | * function logger(sender: SomeClass, value: number): void {
|
47 | * console.log(sender.name, value);
|
48 | * }
|
49 | *
|
50 | * let m1 = new SomeClass('foo');
|
51 | * let m2 = new SomeClass('bar');
|
52 | *
|
53 | * m1.valueChanged.connect(logger);
|
54 | * m2.valueChanged.connect(logger);
|
55 | *
|
56 | * m1.value = 42; // logs: foo 42
|
57 | * m2.value = 17; // logs: bar 17
|
58 | * ```
|
59 | */
|
60 | var Signal = /** @class */ (function () {
|
61 | /**
|
62 | * Construct a new signal.
|
63 | *
|
64 | * @param sender - The sender which owns the signal.
|
65 | */
|
66 | function Signal(sender) {
|
67 | this.sender = sender;
|
68 | }
|
69 | /**
|
70 | * Connect a slot to the signal.
|
71 | *
|
72 | * @param slot - The slot to invoke when the signal is emitted.
|
73 | *
|
74 | * @param thisArg - The `this` context for the slot. If provided,
|
75 | * this must be a non-primitive object.
|
76 | *
|
77 | * @returns `true` if the connection succeeds, `false` otherwise.
|
78 | */
|
79 | Signal.prototype.connect = function (slot, thisArg) {
|
80 | return Private.connect(this, slot, thisArg);
|
81 | };
|
82 | /**
|
83 | * Disconnect a slot from the signal.
|
84 | *
|
85 | * @param slot - The slot to disconnect from the signal.
|
86 | *
|
87 | * @param thisArg - The `this` context for the slot. If provided,
|
88 | * this must be a non-primitive object.
|
89 | *
|
90 | * @returns `true` if the connection is removed, `false` otherwise.
|
91 | */
|
92 | Signal.prototype.disconnect = function (slot, thisArg) {
|
93 | return Private.disconnect(this, slot, thisArg);
|
94 | };
|
95 | /**
|
96 | * Emit the signal and invoke the connected slots.
|
97 | *
|
98 | * @param args - The args to pass to the connected slots.
|
99 | *
|
100 | * #### Notes
|
101 | * Slots are invoked synchronously in connection order.
|
102 | *
|
103 | * Exceptions thrown by connected slots will be caught and logged.
|
104 | */
|
105 | Signal.prototype.emit = function (args) {
|
106 | Private.emit(this, args);
|
107 | };
|
108 | return Signal;
|
109 | }());
|
110 | exports.Signal = Signal;
|
111 | /**
|
112 | * The namespace for the `Signal` class statics.
|
113 | */
|
114 | (function (Signal) {
|
115 | /**
|
116 | * Remove all connections between a sender and receiver.
|
117 | *
|
118 | * @param sender - The sender object of interest.
|
119 | *
|
120 | * @param receiver - The receiver object of interest.
|
121 | *
|
122 | * #### Notes
|
123 | * If a `thisArg` is provided when connecting a signal, that object
|
124 | * is considered the receiver. Otherwise, the `slot` is considered
|
125 | * the receiver.
|
126 | */
|
127 | function disconnectBetween(sender, receiver) {
|
128 | Private.disconnectBetween(sender, receiver);
|
129 | }
|
130 | Signal.disconnectBetween = disconnectBetween;
|
131 | /**
|
132 | * Remove all connections where the given object is the sender.
|
133 | *
|
134 | * @param sender - The sender object of interest.
|
135 | */
|
136 | function disconnectSender(sender) {
|
137 | Private.disconnectSender(sender);
|
138 | }
|
139 | Signal.disconnectSender = disconnectSender;
|
140 | /**
|
141 | * Remove all connections where the given object is the receiver.
|
142 | *
|
143 | * @param receiver - The receiver object of interest.
|
144 | *
|
145 | * #### Notes
|
146 | * If a `thisArg` is provided when connecting a signal, that object
|
147 | * is considered the receiver. Otherwise, the `slot` is considered
|
148 | * the receiver.
|
149 | */
|
150 | function disconnectReceiver(receiver) {
|
151 | Private.disconnectReceiver(receiver);
|
152 | }
|
153 | Signal.disconnectReceiver = disconnectReceiver;
|
154 | /**
|
155 | * Remove all connections where an object is the sender or receiver.
|
156 | *
|
157 | * @param object - The object of interest.
|
158 | *
|
159 | * #### Notes
|
160 | * If a `thisArg` is provided when connecting a signal, that object
|
161 | * is considered the receiver. Otherwise, the `slot` is considered
|
162 | * the receiver.
|
163 | */
|
164 | function disconnectAll(object) {
|
165 | Private.disconnectAll(object);
|
166 | }
|
167 | Signal.disconnectAll = disconnectAll;
|
168 | /**
|
169 | * Clear all signal data associated with the given object.
|
170 | *
|
171 | * @param object - The object for which the data should be cleared.
|
172 | *
|
173 | * #### Notes
|
174 | * This removes all signal connections and any other signal data
|
175 | * associated with the object.
|
176 | */
|
177 | function clearData(object) {
|
178 | Private.disconnectAll(object);
|
179 | }
|
180 | Signal.clearData = clearData;
|
181 | /**
|
182 | * Get the signal exception handler.
|
183 | *
|
184 | * @returns The current exception handler.
|
185 | *
|
186 | * #### Notes
|
187 | * The default exception handler is `console.error`.
|
188 | */
|
189 | function getExceptionHandler() {
|
190 | return Private.exceptionHandler;
|
191 | }
|
192 | Signal.getExceptionHandler = getExceptionHandler;
|
193 | /**
|
194 | * Set the signal exception handler.
|
195 | *
|
196 | * @param handler - The function to use as the exception handler.
|
197 | *
|
198 | * @returns The old exception handler.
|
199 | *
|
200 | * #### Notes
|
201 | * The exception handler is invoked when a slot throws an exception.
|
202 | */
|
203 | function setExceptionHandler(handler) {
|
204 | var old = Private.exceptionHandler;
|
205 | Private.exceptionHandler = handler;
|
206 | return old;
|
207 | }
|
208 | Signal.setExceptionHandler = setExceptionHandler;
|
209 | })(Signal = exports.Signal || (exports.Signal = {}));
|
210 | exports.Signal = Signal;
|
211 | /**
|
212 | * The namespace for the module implementation details.
|
213 | */
|
214 | var Private;
|
215 | (function (Private) {
|
216 | /**
|
217 | * The signal exception handler function.
|
218 | */
|
219 | Private.exceptionHandler = function (err) {
|
220 | console.error(err);
|
221 | };
|
222 | /**
|
223 | * Connect a slot to a signal.
|
224 | *
|
225 | * @param signal - The signal of interest.
|
226 | *
|
227 | * @param slot - The slot to invoke when the signal is emitted.
|
228 | *
|
229 | * @param thisArg - The `this` context for the slot. If provided,
|
230 | * this must be a non-primitive object.
|
231 | *
|
232 | * @returns `true` if the connection succeeds, `false` otherwise.
|
233 | */
|
234 | function connect(signal, slot, thisArg) {
|
235 | // Coerce a `null` `thisArg` to `undefined`.
|
236 | thisArg = thisArg || undefined;
|
237 | // Ensure the sender's array of receivers is created.
|
238 | var receivers = receiversForSender.get(signal.sender);
|
239 | if (!receivers) {
|
240 | receivers = [];
|
241 | receiversForSender.set(signal.sender, receivers);
|
242 | }
|
243 | // Bail if a matching connection already exists.
|
244 | if (findConnection(receivers, signal, slot, thisArg)) {
|
245 | return false;
|
246 | }
|
247 | // Choose the best object for the receiver.
|
248 | var receiver = thisArg || slot;
|
249 | // Ensure the receiver's array of senders is created.
|
250 | var senders = sendersForReceiver.get(receiver);
|
251 | if (!senders) {
|
252 | senders = [];
|
253 | sendersForReceiver.set(receiver, senders);
|
254 | }
|
255 | // Create a new connection and add it to the end of each array.
|
256 | var connection = { signal: signal, slot: slot, thisArg: thisArg };
|
257 | receivers.push(connection);
|
258 | senders.push(connection);
|
259 | // Indicate a successful connection.
|
260 | return true;
|
261 | }
|
262 | Private.connect = connect;
|
263 | /**
|
264 | * Disconnect a slot from a signal.
|
265 | *
|
266 | * @param signal - The signal of interest.
|
267 | *
|
268 | * @param slot - The slot to disconnect from the signal.
|
269 | *
|
270 | * @param thisArg - The `this` context for the slot. If provided,
|
271 | * this must be a non-primitive object.
|
272 | *
|
273 | * @returns `true` if the connection is removed, `false` otherwise.
|
274 | */
|
275 | function disconnect(signal, slot, thisArg) {
|
276 | // Coerce a `null` `thisArg` to `undefined`.
|
277 | thisArg = thisArg || undefined;
|
278 | // Lookup the list of receivers, and bail if none exist.
|
279 | var receivers = receiversForSender.get(signal.sender);
|
280 | if (!receivers || receivers.length === 0) {
|
281 | return false;
|
282 | }
|
283 | // Bail if no matching connection exits.
|
284 | var connection = findConnection(receivers, signal, slot, thisArg);
|
285 | if (!connection) {
|
286 | return false;
|
287 | }
|
288 | // Choose the best object for the receiver.
|
289 | var receiver = thisArg || slot;
|
290 | // Lookup the array of senders, which is now known to exist.
|
291 | var senders = sendersForReceiver.get(receiver);
|
292 | // Clear the connection and schedule cleanup of the arrays.
|
293 | connection.signal = null;
|
294 | scheduleCleanup(receivers);
|
295 | scheduleCleanup(senders);
|
296 | // Indicate a successful disconnection.
|
297 | return true;
|
298 | }
|
299 | Private.disconnect = disconnect;
|
300 | /**
|
301 | * Remove all connections between a sender and receiver.
|
302 | *
|
303 | * @param sender - The sender object of interest.
|
304 | *
|
305 | * @param receiver - The receiver object of interest.
|
306 | */
|
307 | function disconnectBetween(sender, receiver) {
|
308 | // If there are no receivers, there is nothing to do.
|
309 | var receivers = receiversForSender.get(sender);
|
310 | if (!receivers || receivers.length === 0) {
|
311 | return;
|
312 | }
|
313 | // If there are no senders, there is nothing to do.
|
314 | var senders = sendersForReceiver.get(receiver);
|
315 | if (!senders || senders.length === 0) {
|
316 | return;
|
317 | }
|
318 | // Clear each connection between the sender and receiver.
|
319 | algorithm_1.each(senders, function (connection) {
|
320 | // Skip connections which have already been cleared.
|
321 | if (!connection.signal) {
|
322 | return;
|
323 | }
|
324 | // Clear the connection if it matches the sender.
|
325 | if (connection.signal.sender === sender) {
|
326 | connection.signal = null;
|
327 | }
|
328 | });
|
329 | // Schedule a cleanup of the senders and receivers.
|
330 | scheduleCleanup(receivers);
|
331 | scheduleCleanup(senders);
|
332 | }
|
333 | Private.disconnectBetween = disconnectBetween;
|
334 | /**
|
335 | * Remove all connections where the given object is the sender.
|
336 | *
|
337 | * @param sender - The sender object of interest.
|
338 | */
|
339 | function disconnectSender(sender) {
|
340 | // If there are no receivers, there is nothing to do.
|
341 | var receivers = receiversForSender.get(sender);
|
342 | if (!receivers || receivers.length === 0) {
|
343 | return;
|
344 | }
|
345 | // Clear each receiver connection.
|
346 | algorithm_1.each(receivers, function (connection) {
|
347 | // Skip connections which have already been cleared.
|
348 | if (!connection.signal) {
|
349 | return;
|
350 | }
|
351 | // Choose the best object for the receiver.
|
352 | var receiver = connection.thisArg || connection.slot;
|
353 | // Clear the connection.
|
354 | connection.signal = null;
|
355 | // Cleanup the array of senders, which is now known to exist.
|
356 | scheduleCleanup(sendersForReceiver.get(receiver));
|
357 | });
|
358 | // Schedule a cleanup of the receivers.
|
359 | scheduleCleanup(receivers);
|
360 | }
|
361 | Private.disconnectSender = disconnectSender;
|
362 | /**
|
363 | * Remove all connections where the given object is the receiver.
|
364 | *
|
365 | * @param receiver - The receiver object of interest.
|
366 | */
|
367 | function disconnectReceiver(receiver) {
|
368 | // If there are no senders, there is nothing to do.
|
369 | var senders = sendersForReceiver.get(receiver);
|
370 | if (!senders || senders.length === 0) {
|
371 | return;
|
372 | }
|
373 | // Clear each sender connection.
|
374 | algorithm_1.each(senders, function (connection) {
|
375 | // Skip connections which have already been cleared.
|
376 | if (!connection.signal) {
|
377 | return;
|
378 | }
|
379 | // Lookup the sender for the connection.
|
380 | var sender = connection.signal.sender;
|
381 | // Clear the connection.
|
382 | connection.signal = null;
|
383 | // Cleanup the array of receivers, which is now known to exist.
|
384 | scheduleCleanup(receiversForSender.get(sender));
|
385 | });
|
386 | // Schedule a cleanup of the list of senders.
|
387 | scheduleCleanup(senders);
|
388 | }
|
389 | Private.disconnectReceiver = disconnectReceiver;
|
390 | /**
|
391 | * Remove all connections where an object is the sender or receiver.
|
392 | *
|
393 | * @param object - The object of interest.
|
394 | */
|
395 | function disconnectAll(object) {
|
396 | // Clear and cleanup any receiver connections.
|
397 | var receivers = receiversForSender.get(object);
|
398 | if (receivers && receivers.length > 0) {
|
399 | algorithm_1.each(receivers, function (connection) { connection.signal = null; });
|
400 | scheduleCleanup(receivers);
|
401 | }
|
402 | // Clear and cleanup any sender connections.
|
403 | var senders = sendersForReceiver.get(object);
|
404 | if (senders && senders.length > 0) {
|
405 | algorithm_1.each(senders, function (connection) { connection.signal = null; });
|
406 | scheduleCleanup(senders);
|
407 | }
|
408 | }
|
409 | Private.disconnectAll = disconnectAll;
|
410 | /**
|
411 | * Emit a signal and invoke its connected slots.
|
412 | *
|
413 | * @param signal - The signal of interest.
|
414 | *
|
415 | * @param args - The args to pass to the connected slots.
|
416 | *
|
417 | * #### Notes
|
418 | * Slots are invoked synchronously in connection order.
|
419 | *
|
420 | * Exceptions thrown by connected slots will be caught and logged.
|
421 | */
|
422 | function emit(signal, args) {
|
423 | // If there are no receivers, there is nothing to do.
|
424 | var receivers = receiversForSender.get(signal.sender);
|
425 | if (!receivers || receivers.length === 0) {
|
426 | return;
|
427 | }
|
428 | // Invoke the slots for connections with a matching signal.
|
429 | // Any connections added during emission are not invoked.
|
430 | for (var i = 0, n = receivers.length; i < n; ++i) {
|
431 | var connection = receivers[i];
|
432 | if (connection.signal === signal) {
|
433 | invokeSlot(connection, args);
|
434 | }
|
435 | }
|
436 | }
|
437 | Private.emit = emit;
|
438 | /**
|
439 | * A weak mapping of sender to array of receiver connections.
|
440 | */
|
441 | var receiversForSender = new WeakMap();
|
442 | /**
|
443 | * A weak mapping of receiver to array of sender connections.
|
444 | */
|
445 | var sendersForReceiver = new WeakMap();
|
446 | /**
|
447 | * A set of connection arrays which are pending cleanup.
|
448 | */
|
449 | var dirtySet = new Set();
|
450 | /**
|
451 | * A function to schedule an event loop callback.
|
452 | */
|
453 | var schedule = (function () {
|
454 | var ok = typeof requestAnimationFrame === 'function';
|
455 | // @ts-ignore
|
456 | return ok ? requestAnimationFrame : setImmediate;
|
457 | })();
|
458 | /**
|
459 | * Find a connection which matches the given parameters.
|
460 | */
|
461 | function findConnection(connections, signal, slot, thisArg) {
|
462 | return algorithm_1.find(connections, function (connection) { return (connection.signal === signal &&
|
463 | connection.slot === slot &&
|
464 | connection.thisArg === thisArg); });
|
465 | }
|
466 | /**
|
467 | * Invoke a slot with the given parameters.
|
468 | *
|
469 | * The connection is assumed to be valid.
|
470 | *
|
471 | * Exceptions in the slot will be caught and logged.
|
472 | */
|
473 | function invokeSlot(connection, args) {
|
474 | var signal = connection.signal, slot = connection.slot, thisArg = connection.thisArg;
|
475 | try {
|
476 | slot.call(thisArg, signal.sender, args);
|
477 | }
|
478 | catch (err) {
|
479 | Private.exceptionHandler(err);
|
480 | }
|
481 | }
|
482 | /**
|
483 | * Schedule a cleanup of a connection array.
|
484 | *
|
485 | * This will add the array to the dirty set and schedule a deferred
|
486 | * cleanup of the array contents. On cleanup, any connection with a
|
487 | * `null` signal will be removed from the array.
|
488 | */
|
489 | function scheduleCleanup(array) {
|
490 | if (dirtySet.size === 0) {
|
491 | schedule(cleanupDirtySet);
|
492 | }
|
493 | dirtySet.add(array);
|
494 | }
|
495 | /**
|
496 | * Cleanup the connection lists in the dirty set.
|
497 | *
|
498 | * This function should only be invoked asynchronously, when the
|
499 | * stack frame is guaranteed to not be on the path of user code.
|
500 | */
|
501 | function cleanupDirtySet() {
|
502 | dirtySet.forEach(cleanupConnections);
|
503 | dirtySet.clear();
|
504 | }
|
505 | /**
|
506 | * Cleanup the dirty connections in a connections array.
|
507 | *
|
508 | * This will remove any connection with a `null` signal.
|
509 | *
|
510 | * This function should only be invoked asynchronously, when the
|
511 | * stack frame is guaranteed to not be on the path of user code.
|
512 | */
|
513 | function cleanupConnections(connections) {
|
514 | algorithm_1.ArrayExt.removeAllWhere(connections, isDeadConnection);
|
515 | }
|
516 | /**
|
517 | * Test whether a connection is dead.
|
518 | *
|
519 | * A dead connection has a `null` signal.
|
520 | */
|
521 | function isDeadConnection(connection) {
|
522 | return connection.signal === null;
|
523 | }
|
524 | })(Private || (Private = {}));
|