diff --git a/assembly/events/index.ts b/assembly/events/index.ts new file mode 100644 index 0000000..8514a08 --- /dev/null +++ b/assembly/events/index.ts @@ -0,0 +1,104 @@ +import { BLOCK_OVERHEAD, BLOCK } from "rt/common"; + +export abstract class EventEmitter { + public static EventMap: Map> = new Map>(); + public static registerEventCallback(event: string): void { + if (!isFunction()) ERROR("Cannot register event callback of type U where U is not a function."); + if (!isVoid>()) ERROR("Cannot register event callback of type U where ReturnType is not void."); + if (!EventEmitter.EventMap.has(idof())) EventEmitter.EventMap.set(idof(), new Map()); + let ClassEvents = EventEmitter.EventMap.get(idof()); + if (ClassEvents.has(event)) throw new Error("EventMap already contains a definition for event: " + event); + ClassEvents.set(event, idof() | (lengthof() << 32)); + } + + private _events: Map = new Map(); + + public on(event: string, callback: T): EventEmitter { + if (!isFunction()) ERROR("EventEmitter#on can only be called with a callback of type T where T is a static function."); + let rtId = changetype(changetype(this) - BLOCK_OVERHEAD).rtId; + let EventMap = EventEmitter.EventMap; + if (!EventMap.has(rtId)) throw new Error("Cannot attach events to an EventEmitter with no EventMap definitions."); + let ClassEvents = EventMap.get(rtId); + if (!ClassEvents.has(event)) throw new Error("Event does not exist: " + event); + let classEventSignature = ClassEvents.get(event); + let classEventCallbackID = (classEventSignature & 0xFFFFFFFF); + assert(idof() == classEventCallbackID); + if (!this._events.has(event)) this._events.set(event, new Array()); + let eventList = this._events.get(event); + eventList.push(changetype(callback)); + return this; + } + + public emit + (event: string, a: A = null, b: B = null, c: C = null, d: D = null, e: E = null, f: F = null, g: G = null, h: H = null, i: I = null, j: J = null): EventEmitter { + if (this._events.has(event)) { + let rtId = changetype(changetype(this) - BLOCK_OVERHEAD).rtId; + let ClassEvents = EventEmitter.EventMap.get(rtId); + assert(ClassEvents.has(event), "No event definition."); + let classEventSignature = ClassEvents.get(event); + let classEventArgLength = (classEventSignature >> 32) & 0xFFFFFFFF; + let classEventCallbackID = (classEventSignature & 0xFFFFFFFF); + let list = this._events.get(event); + let length = list.length; + switch (classEventArgLength) { + case 0: { + assert(classEventCallbackID == idof<() => void>()); + for (let z = 0; z < length; z++) call_indirect(unchecked(list[z])); + break; + } + case 1: { + assert(classEventCallbackID == idof<(a: A) => void>()); + for (let z = 0; z < length; z++) call_indirect(unchecked(list[z]), a); + break; + } + case 2: { + assert(classEventCallbackID == idof<(a: A, b: B) => void>()); + for (let z = 0; z < length; z++) call_indirect(unchecked(list[z]), a, b); + break; + } + case 3: { + assert(classEventCallbackID == idof<(a: A, b: B, c: C) => void>()); + for (let z = 0; z < length; z++) call_indirect(unchecked(list[z]), a, b, c); + break; + } + case 4: { + assert(classEventCallbackID == idof<(a: A, b: B, c: C, d: D) => void>()); + for (let z = 0; z < length; z++) call_indirect(unchecked(list[z]), a, b, c, d); + break; + } + case 5: { + assert(classEventCallbackID == idof<(a: A, b: B, c: C, d: D, e: E) => void>()); + for (let z = 0; z < length; z++) call_indirect(unchecked(list[z]), a, b, c, d, e); + break; + } + case 6: { + assert(classEventCallbackID == idof<(a: A, b: B, c: C, d: D, e: E, f: F) => void>()); + for (let z = 0; z < length; z++) call_indirect(unchecked(list[z]), a, b, c, d, e, f); + break; + } + case 7: { + assert(classEventCallbackID == idof<(a: A, b: B, c: C, d: D, e: E, f: F, g: G) => void>()); + for (let z = 0; z < length; z++) call_indirect(unchecked(list[z]), a, b, c, d, e, f, g); + break; + } + case 8: { + assert(classEventCallbackID == idof<(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => void>()); + for (let z = 0; z < length; z++) call_indirect(unchecked(list[z]), a, b, c, d, e, f, g, h); + break; + } + case 9: { + assert(classEventCallbackID == idof<(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => void>()); + for (let z = 0; z < length; z++) call_indirect(unchecked(list[z]), a, b, c, d, e, f, g, h, i); + break; + } + case 10: { + assert(classEventCallbackID == idof<(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => void>()); + for (let z = 0; z < length; z++) call_indirect(unchecked(list[z]), a, b, c, d, e, f, g, h, i, j); + break; + } + default: assert(false); + } + } + return this; + } +} \ No newline at end of file diff --git a/assembly/node.d.ts b/assembly/node.d.ts index d6da2cb..b874f7e 100644 --- a/assembly/node.d.ts +++ b/assembly/node.d.ts @@ -14,3 +14,12 @@ declare class Buffer extends Uint8Array { /** Reads a signed integer at the designated offset. */ readInt8(offset?: i32): i8; } + +export abstract class EventEmitter { + /** Call this function globally to setup a callback on your EventEmitter type `T`, with callback type `U`, and eventName `event`. */ + public static registerEventCallback(event: string): void; + /** Register a callback event of type `T` with eventName `event`. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. */ + public on(event: string, callback: T): this; + /** Emit a callback event with the given parameters (up to 10.) */ + public emit(...args: T): this; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1d4efe9..b4520b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,8 +38,8 @@ } }, "assemblyscript": { - "version": "github:assemblyscript/assemblyscript#4c938f7689c39e3a1ad813356781e2689fd4ec70", - "from": "github:assemblyscript/assemblyscript", + "version": "github:jtenner/assemblyscript#dd04610acdfea0fd914ab5dba02412f788174728", + "from": "github:jtenner/assemblyscript#builtins-dist", "dev": true, "requires": { "@protobufjs/utf8": "^1.1.0", @@ -233,9 +233,9 @@ "dev": true }, "source-map-support": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", - "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", diff --git a/package.json b/package.json index ff4bc6b..cb65b0c 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@as-pect/core": "^2.3.1", - "assemblyscript": "github:assemblyscript/assemblyscript", + "assemblyscript": "github:jtenner/assemblyscript#builtins-dist", "glob": "^7.1.4", "wasi": "github:devsnek/node-wasi" }, diff --git a/tests/events.spec.ts b/tests/events.spec.ts new file mode 100644 index 0000000..fba978a --- /dev/null +++ b/tests/events.spec.ts @@ -0,0 +1,39 @@ +import { EventEmitter } from "events"; + +let calls: i32 = 0; + +type i32Callback = (a: i32) => void; +type i64Callback = (a: i64, b: i64) => void; +class CustomEventEmitter extends EventEmitter {} + +EventEmitter.registerEventCallback("data"); +EventEmitter.registerEventCallback("data2"); + +describe("events", () => { + test("events", () => { + let t = new CustomEventEmitter(); + calls = 0; + t.on("data", (wrongName: i32) => { + calls += 1; + expect(wrongName).toBe(42); + }); + t.emit("data", 42,); + t.emit("data", 42); + t.emit("data", 42); + expect(calls).toBe(3); + }); + + test("events2", () => { + let t = new CustomEventEmitter(); + calls = 0; + t.on("data2", (a: i64, b: i64) => { + calls += 1; + expect(a).toBe(42); + expect(b).toBe(42); + }); + t.emit("data2", 42, 42); + t.emit("data2", 42, 42); + t.emit("data2", 42, 42); + expect(calls).toBe(3); + }); +}); \ No newline at end of file