char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport process extension to typescriptb2fc926

main
3.2 KiB99 linesraw
1import {
2  Array,
3  ArrayPrototypePush as push,
4  ArrayPrototypeSplice as splice,
5  Map,
6  MapPrototypeDelete as drop,
7  MapPrototypeGet as get,
8  MapPrototypeSet as set,
9  TypeError,
10  uncurryThis,
11} from "ext:core/primordials";
12import { markPromiseHandled, safePromise } from "ext:core/promises";
13import { op_signal_bind, op_signal_poll, op_signal_unbind } from "ext:core/ops";
14import { array, required, usv } from "ext:web/idl.js";
15import { result } from "ext:file/errors.js";
16import { reportErrorValue } from "ext:web/events.js";
17
18const indexOf = uncurryThis(Array.prototype.indexOf);
19const slice = uncurryThis(Array.prototype.slice);
20type SignalListener = () => void;
21const bindings = new Map<string, SignalListener[]>();
22let pumping = false;
23// Signals with at least one listener. A bind racing the pump's final
24// poll restarts the loop below instead of stranding its delivery.
25let boundSignals = 0;
26
27/** Delivers each bound signal to its listeners in binding order. */
28async function pump(): Promise<void> {
29  try {
30    while (true) {
31      const name = result(await safePromise(op_signal_poll()));
32      // The isolate binds nothing anymore: end, releasing the event loop.
33      if (name === null) return;
34      const listeners = get(bindings, name);
35      if (listeners === undefined) continue;
36      const snapshot = slice(listeners, 0);
37      for (let i = 0; i < snapshot.length; i++) {
38        // A listener removed mid-dispatch stops firing, like EventTarget.
39        // Copied to a local first: calling it off the array would set
40        // `this` to the array.
41        const listener = snapshot[i];
42        if (indexOf(listeners, listener) === -1) continue;
43        try {
44          listener();
45        } catch (error) {
46          reportErrorValue(error);
47        }
48      }
49    }
50  } finally {
51    pumping = false;
52    if (boundSignals > 0) ensurePump();
53  }
54}
55function ensurePump(): void {
56  if (pumping) return;
57  pumping = true;
58  markPromiseHandled(pump());
59}
60const addSignalListener = {
61  addSignalListener(signal: string, listener: SignalListener): void {
62    required(arguments.length, 2);
63    const name = usv(signal);
64    if (typeof listener !== "function") {
65      throw new TypeError("Signal listener must be a function");
66    }
67    let listeners = get(bindings, name);
68    if (listeners !== undefined && indexOf(listeners, listener) !== -1) {
69      return;
70    }
71    if (listeners === undefined) {
72      // Binding once per signal keeps the Rust binding count (one per
73      // isolate per signal) balanced with unbind-on-empty below.
74      result(op_signal_bind(name));
75      listeners = array();
76      set(bindings, name, listeners);
77      boundSignals++;
78    }
79    push(listeners, listener);
80    ensurePump();
81  },
82}.addSignalListener;
83const removeSignalListener = {
84  removeSignalListener(signal: string, listener: SignalListener): void {
85    required(arguments.length, 2);
86    const name = usv(signal);
87    const listeners = get(bindings, name);
88    if (listeners === undefined) return;
89    const at = indexOf(listeners, listener);
90    if (at === -1) return;
91    splice(listeners, at, 1);
92    if (listeners.length === 0) {
93      drop(bindings, name);
94      boundSignals--;
95      op_signal_unbind(name);
96    }
97  },
98}.removeSignalListener;
99export { addSignalListener, removeSignalListener };