[gjs/ewlsh/nova-repl: 5/7] modules: Add events module for EventEmitter API
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/ewlsh/nova-repl: 5/7] modules: Add events module for EventEmitter API
- Date: Sun, 30 Jan 2022 02:55:09 +0000 (UTC)
commit 49e4431057d59d1824604d2660ea746db9cda07a
Author: Evan Welsh <contact evanwelsh com>
Date: Sun Jan 23 23:16:22 2022 -0800
modules: Add events module for EventEmitter API
modules/esm/events.js | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 104 insertions(+)
---
diff --git a/modules/esm/events.js b/modules/esm/events.js
new file mode 100644
index 000000000..0eb646428
--- /dev/null
+++ b/modules/esm/events.js
@@ -0,0 +1,104 @@
+export class EventEmitter {
+ #signalConnections = [];
+ #nextConnectionId = 1;
+
+ connect(name, callback) {
+ // be paranoid about callback arg since we'd start to throw from emit()
+ // if it was messed up
+ if (typeof callback !== 'function')
+ throw new Error('When connecting signal must give a callback that is a function');
+
+ let id = this.#nextConnectionId;
+ this.#nextConnectionId += 1;
+
+ // this makes it O(n) in total connections to emit, but I think
+ // it's right to optimize for low memory and reentrancy-safety
+ // rather than speed
+ this.#signalConnections.push({
+ id,
+ name,
+ callback,
+ 'disconnected': false,
+ });
+ return id;
+ }
+
+ disconnect(id) {
+ let i;
+ let length = this.#signalConnections.length;
+ for (i = 0; i < length; ++i) {
+ let connection = this.#signalConnections[i];
+ if (connection.id === id) {
+ if (connection.disconnected)
+ throw new Error(`Signal handler id ${id} already disconnected`);
+
+ // set a flag to deal with removal during emission
+ connection.disconnected = true;
+ this.#signalConnections.splice(i, 1);
+
+ return;
+ }
+ }
+
+ throw new Error(`No signal connection ${id} found`);
+ }
+
+ signalHandlerIsConnected(id) {
+ const {length} = this.#signalConnections;
+ for (let i = 0; i < length; ++i) {
+ const connection = this.#signalConnections[i];
+ if (connection.id === id)
+ return !connection.disconnected;
+ }
+
+ return false;
+ }
+
+ disconnectAll() {
+ while (this.#signalConnections.length > 0)
+ this.disconnect(this.#signalConnections[0].id);
+ }
+
+ emit(name, ...args) {
+ // To deal with re-entrancy (removal/addition while
+ // emitting), we copy out a list of what was connected
+ // at emission start; and just before invoking each
+ // handler we check its disconnected flag.
+ let handlers = [];
+ let i;
+ let length = this.#signalConnections.length;
+ for (i = 0; i < length; ++i) {
+ let connection = this.#signalConnections[i];
+ if (connection.name === name)
+ handlers.push(connection);
+ }
+
+ // create arg array which is emitter + everything passed in except
+ // signal name. Would be more convenient not to pass emitter to
+ // the callback, but trying to be 100% consistent with GObject
+ // which does pass it in. Also if we pass in the emitter here,
+ // people don't create closures with the emitter in them,
+ // which would be a cycle.
+ let argArray = [this, ...args];
+
+ length = handlers.length;
+ for (i = 0; i < length; ++i) {
+ let connection = handlers[i];
+ if (!connection.disconnected) {
+ try {
+ // since we pass "null" for this, the global object will be used.
+ let ret = connection.callback.apply(null, argArray);
+
+ // if the callback returns true, we don't call the next
+ // signal handlers
+ if (ret === true)
+ break;
+ } catch (e) {
+ // just log any exceptions so that callbacks can't disrupt
+ // signal emission
+ logError(e, `Exception in callback for signal: ${name}`);
+ }
+ }
+ }
+ }
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]