[gjs/ewlsh/nova-repl: 5/7] modules: Add events module for EventEmitter API




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]