@driftengine/native-host 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +29 -0
  3. package/README.md +58 -0
  4. package/dist/activation.d.ts +26 -0
  5. package/dist/activation.js +38 -0
  6. package/dist/alphaCopy.d.ts +36 -0
  7. package/dist/alphaCopy.js +178 -0
  8. package/dist/audio.d.ts +55 -0
  9. package/dist/audio.js +193 -0
  10. package/dist/audioParams.d.ts +25 -0
  11. package/dist/audioParams.js +128 -0
  12. package/dist/bridge.d.ts +70 -0
  13. package/dist/bridge.js +170 -0
  14. package/dist/canvas.d.ts +97 -0
  15. package/dist/canvas.js +197 -0
  16. package/dist/canvasStyle.d.ts +23 -0
  17. package/dist/canvasStyle.js +79 -0
  18. package/dist/device.d.ts +34 -0
  19. package/dist/device.js +144 -0
  20. package/dist/dialogs.d.ts +23 -0
  21. package/dist/dialogs.js +85 -0
  22. package/dist/domEvents.d.ts +37 -0
  23. package/dist/domEvents.js +145 -0
  24. package/dist/domTree.d.ts +34 -0
  25. package/dist/domTree.js +194 -0
  26. package/dist/dualRumble.d.ts +19 -0
  27. package/dist/dualRumble.js +47 -0
  28. package/dist/eventClasses.d.ts +12 -0
  29. package/dist/eventClasses.js +35 -0
  30. package/dist/files.d.ts +16 -0
  31. package/dist/files.js +59 -0
  32. package/dist/fullscreen.d.ts +33 -0
  33. package/dist/fullscreen.js +65 -0
  34. package/dist/game.d.ts +25 -0
  35. package/dist/game.js +38 -0
  36. package/dist/gamepads.d.ts +65 -0
  37. package/dist/gamepads.js +178 -0
  38. package/dist/globals.d.ts +24 -0
  39. package/dist/globals.js +37 -0
  40. package/dist/gpu.d.ts +26 -0
  41. package/dist/gpu.js +47 -0
  42. package/dist/images.d.ts +50 -0
  43. package/dist/images.js +111 -0
  44. package/dist/index.d.ts +23 -0
  45. package/dist/index.js +21 -0
  46. package/dist/keyCodes.d.ts +30 -0
  47. package/dist/keyCodes.js +205 -0
  48. package/dist/keyEvents.d.ts +54 -0
  49. package/dist/keyEvents.js +117 -0
  50. package/dist/mediaQueries.d.ts +62 -0
  51. package/dist/mediaQueries.js +152 -0
  52. package/dist/mouseEvents.d.ts +96 -0
  53. package/dist/mouseEvents.js +287 -0
  54. package/dist/page.d.ts +118 -0
  55. package/dist/page.js +302 -0
  56. package/dist/pointerCapture.d.ts +32 -0
  57. package/dist/pointerCapture.js +69 -0
  58. package/dist/present.d.ts +27 -0
  59. package/dist/present.js +83 -0
  60. package/dist/rawPad.d.ts +44 -0
  61. package/dist/rawPad.js +65 -0
  62. package/dist/readback.d.ts +18 -0
  63. package/dist/readback.js +46 -0
  64. package/dist/runtime.d.ts +69 -0
  65. package/dist/runtime.js +103 -0
  66. package/dist/standardPad.d.ts +49 -0
  67. package/dist/standardPad.js +80 -0
  68. package/dist/textHost.d.ts +37 -0
  69. package/dist/textHost.js +43 -0
  70. package/dist/threads.d.ts +27 -0
  71. package/dist/threads.js +59 -0
  72. package/dist/tileFiles.d.ts +16 -0
  73. package/dist/tileFiles.js +36 -0
  74. package/dist/touchEvents.d.ts +67 -0
  75. package/dist/touchEvents.js +210 -0
  76. package/dist/uiEvents.d.ts +157 -0
  77. package/dist/uiEvents.js +206 -0
  78. package/dist/uiTouch.d.ts +59 -0
  79. package/dist/uiTouch.js +75 -0
  80. package/dist/window.d.ts +71 -0
  81. package/dist/window.js +164 -0
  82. package/dist/windowMode.d.ts +19 -0
  83. package/dist/windowMode.js +15 -0
  84. package/dist/windowTitle.d.ts +18 -0
  85. package/dist/windowTitle.js +33 -0
  86. package/dist/workerScope.mjs +48 -0
  87. package/licenses/GPL-3.0.txt +674 -0
  88. package/package.json +83 -0
  89. package/src/activation.ts +42 -0
  90. package/src/alphaCopy.ts +201 -0
  91. package/src/audio.ts +246 -0
  92. package/src/audioParams.ts +150 -0
  93. package/src/bridge.ts +230 -0
  94. package/src/canvas.ts +227 -0
  95. package/src/canvasStyle.ts +89 -0
  96. package/src/device.ts +167 -0
  97. package/src/dialogs.ts +98 -0
  98. package/src/domEvents.ts +170 -0
  99. package/src/domTree.ts +215 -0
  100. package/src/drs.d.ts +9 -0
  101. package/src/dualRumble.ts +58 -0
  102. package/src/eventClasses.ts +46 -0
  103. package/src/files.ts +61 -0
  104. package/src/fixtures/grey.jpg +0 -0
  105. package/src/fixtures/rgb.png +0 -0
  106. package/src/fixtures/tone.opus +0 -0
  107. package/src/fullscreen.ts +71 -0
  108. package/src/game.ts +53 -0
  109. package/src/gamepads.ts +195 -0
  110. package/src/globals.ts +39 -0
  111. package/src/gpu.ts +59 -0
  112. package/src/images.ts +148 -0
  113. package/src/index.ts +24 -0
  114. package/src/keyCodes.ts +203 -0
  115. package/src/keyEvents.ts +135 -0
  116. package/src/mediaQueries.ts +173 -0
  117. package/src/mouseEvents.ts +328 -0
  118. package/src/page.ts +337 -0
  119. package/src/pointerCapture.ts +73 -0
  120. package/src/present.ts +92 -0
  121. package/src/rawPad.ts +85 -0
  122. package/src/readback.ts +61 -0
  123. package/src/runtime.ts +162 -0
  124. package/src/standardPad.ts +126 -0
  125. package/src/textHost.ts +66 -0
  126. package/src/threads.ts +66 -0
  127. package/src/tileFiles.ts +39 -0
  128. package/src/touchEvents.ts +238 -0
  129. package/src/uiEvents.ts +268 -0
  130. package/src/uiTouch.ts +97 -0
  131. package/src/window.ts +192 -0
  132. package/src/windowMode.ts +24 -0
  133. package/src/windowTitle.ts +36 -0
  134. package/src/workerScope.mjs +48 -0
package/src/uiTouch.ts ADDED
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Touches, as a browser's page reads them: `Touch`, the `TouchList` three of which a `TouchEvent`
3
+ * carries, and the event. The sibling of `uiEvents.ts`, split out only so each stays one screenful.
4
+ *
5
+ * A `TouchList` indexes, iterates and answers `item`, the three ways a page reads one; the engine's
6
+ * input reads `changedTouches[i]`.
7
+ */
8
+
9
+ import { type ModifierInit, UIEvent } from './uiEvents.ts';
10
+
11
+ export interface TouchInit {
12
+ readonly identifier: number;
13
+ readonly target: EventTarget;
14
+ readonly clientX?: number;
15
+ readonly clientY?: number;
16
+ readonly screenX?: number;
17
+ readonly screenY?: number;
18
+ readonly radiusX?: number;
19
+ readonly radiusY?: number;
20
+ readonly force?: number;
21
+ }
22
+
23
+ export class Touch {
24
+ readonly identifier: number;
25
+ readonly target: EventTarget;
26
+ readonly clientX: number;
27
+ readonly clientY: number;
28
+ readonly screenX: number;
29
+ readonly screenY: number;
30
+ readonly radiusX: number;
31
+ readonly radiusY: number;
32
+ readonly rotationAngle = 0;
33
+ readonly force: number;
34
+ readonly altitudeAngle = Math.PI / 2;
35
+ readonly azimuthAngle = 0;
36
+ readonly touchType = 'direct';
37
+ constructor(init: TouchInit) {
38
+ this.identifier = init.identifier;
39
+ this.target = init.target;
40
+ this.clientX = init.clientX ?? 0;
41
+ this.clientY = init.clientY ?? 0;
42
+ this.screenX = init.screenX ?? 0;
43
+ this.screenY = init.screenY ?? 0;
44
+ this.radiusX = init.radiusX ?? 0;
45
+ this.radiusY = init.radiusY ?? 0;
46
+ this.force = init.force ?? 0;
47
+ }
48
+ get pageX(): number {
49
+ return this.clientX;
50
+ }
51
+ get pageY(): number {
52
+ return this.clientY;
53
+ }
54
+ }
55
+
56
+ export class TouchList {
57
+ readonly length: number;
58
+ [index: number]: Touch;
59
+ constructor(touches: readonly Touch[] = []) {
60
+ this.length = touches.length;
61
+ touches.forEach((touch, at) => {
62
+ this[at] = touch;
63
+ });
64
+ }
65
+ item(index: number): Touch | null {
66
+ return this[index] ?? null;
67
+ }
68
+ *[Symbol.iterator](): IterableIterator<Touch> {
69
+ for (let at = 0; at < this.length; at += 1) yield this[at] as Touch;
70
+ }
71
+ }
72
+
73
+ export interface TouchEventInit extends EventInit, ModifierInit {
74
+ readonly touches?: readonly Touch[];
75
+ readonly targetTouches?: readonly Touch[];
76
+ readonly changedTouches?: readonly Touch[];
77
+ }
78
+
79
+ export class TouchEvent extends UIEvent {
80
+ readonly touches: TouchList;
81
+ readonly targetTouches: TouchList;
82
+ readonly changedTouches: TouchList;
83
+ readonly ctrlKey: boolean;
84
+ readonly shiftKey: boolean;
85
+ readonly altKey: boolean;
86
+ readonly metaKey: boolean;
87
+ constructor(type: string, init: TouchEventInit = {}) {
88
+ super(type, init);
89
+ this.touches = new TouchList(init.touches);
90
+ this.targetTouches = new TouchList(init.targetTouches);
91
+ this.changedTouches = new TouchList(init.changedTouches);
92
+ this.ctrlKey = init.ctrlKey === true;
93
+ this.shiftKey = init.shiftKey === true;
94
+ this.altKey = init.altKey === true;
95
+ this.metaKey = init.metaKey === true;
96
+ }
97
+ }
package/src/window.ts ADDED
@@ -0,0 +1,192 @@
1
+ /**
2
+ * A window, the canvas the engine draws into, and the frame put on the screen.
3
+ *
4
+ * **SDL makes the window and Dawn draws into it**, through `@kmamal/sdl` and `@kmamal/gpu`: the
5
+ * composition chosen for this host on 2026-09-18, because Dawn is the WebGPU implementation inside
6
+ * Chrome and the host's gate is that the engine's frames match the browser's. SDL is also what the
7
+ * later tasks read input, audio and the clipboard from.
8
+ *
9
+ * **The window renderer is made from the engine's device, when the engine configures the canvas.**
10
+ * The engine asks `navigator.gpu` for its own adapter and device, as it does in a browser, so the
11
+ * host does not have one until the engine hands it over — which it does, in `configure`.
12
+ *
13
+ * **Teardown waits for the device.** Destroying the device, the instance or the window with work
14
+ * still in flight trips an assertion inside Dawn and takes the process with it (measured
15
+ * 2026-09-18); after `onSubmittedWorkDone` every order is clean.
16
+ */
17
+
18
+ import gpuModule from '@kmamal/gpu';
19
+ import sdl from '@kmamal/sdl';
20
+
21
+ import { nativeBridge } from './bridge.ts';
22
+ import type { BridgeDisplay, BridgeWindow, NativeBridge } from './bridge.ts';
23
+ import { NativeCanvas } from './canvas.ts';
24
+ import { desktopFiles } from './dialogs.ts';
25
+ import { createNativeTextHost } from './textHost.ts';
26
+ import type { NativeTextHost, TextWindow } from './textHost.ts';
27
+ import { connectDomEvents } from './domEvents.ts';
28
+ import { sdlWindowFlags, type WindowMode } from './windowMode.ts';
29
+ import { windowTitle } from './windowTitle.ts';
30
+ import type { EventWindow } from './domEvents.ts';
31
+ import { nativeGpu } from './gpu.ts';
32
+ import type { HostPage } from './page.ts';
33
+ import { Presenter } from './present.ts';
34
+
35
+ type Instance = ReturnType<typeof gpuModule.create>;
36
+ type WindowRenderer = ReturnType<typeof gpuModule.renderGPUDeviceToWindow>;
37
+ type SdlWindow = ReturnType<typeof sdl.video.createWindow>;
38
+ /* `@kmamal/gpu` declares its own branded device type; the engine's is the DOM's, and they are one object. */
39
+ type DawnDevice = Parameters<typeof gpuModule.renderGPUDeviceToWindow>[0]['device'];
40
+
41
+ export interface HostWindowOptions {
42
+ readonly title: string;
43
+ readonly width: number;
44
+ readonly height: number;
45
+ /** A window that is never shown, for checks that should not flash on a desktop. */
46
+ readonly hidden?: boolean;
47
+ /** As a packaged game's manifest says; windowed and resizable when it says nothing. */
48
+ readonly mode?: WindowMode;
49
+ readonly resizable?: boolean;
50
+ }
51
+
52
+ export class HostWindow {
53
+ readonly canvas: NativeCanvas;
54
+ readonly gpu: GPU;
55
+ private readonly window: SdlWindow;
56
+ private readonly instance: Instance;
57
+ private device: GPUDevice | null = null;
58
+ private renderer: WindowRenderer | null = null;
59
+ private presenter: Presenter | null = null;
60
+ private closed = false;
61
+
62
+ constructor(options: HostWindowOptions) {
63
+ this.window = sdl.video.createWindow({
64
+ title: windowTitle(options.title, sdl.info.drivers.video.current),
65
+ width: options.width,
66
+ height: options.height,
67
+ ...sdlWindowFlags(options.mode, options.resizable),
68
+ visible: options.hidden !== true,
69
+ webgpu: true,
70
+ });
71
+ this.canvas = new NativeCanvas(this.window.pixelWidth, this.window.pixelHeight);
72
+ this.instance = gpuModule.create([]);
73
+ this.gpu = nativeGpu(this.instance as unknown as Parameters<typeof nativeGpu>[0]);
74
+ this.window.on('resize', ({ pixelWidth, pixelHeight }) => {
75
+ this.canvas.resizeTo(pixelWidth, pixelHeight);
76
+ this.renderer?.resize();
77
+ });
78
+ this.window.on('close', () => {
79
+ this.closed = true;
80
+ });
81
+ /*
82
+ * **A window nobody focused receives no keys, and SDL says so nowhere.**
83
+ *
84
+ * `@kmamal/sdl` routes a mouse event by which window is *hovered* and a key event by which one
85
+ * is *focused*, and it learns the second only from a `focus` event it may deliver before
86
+ * anything is listening — a window created and given focus by the desktop in the same breath
87
+ * leaves `Globals.windows.focused` null. The window then draws, answers the mouse, and is deaf:
88
+ * a maintainer reported exactly that on 2026-09-20, with clicks landing and the keyboard doing
89
+ * nothing.
90
+ *
91
+ * Asking for focus outright sets it, which is what an application does when it opens a window
92
+ * anyway. **Only a visible one**: a hidden window is for checks that must not take a desktop's
93
+ * focus away from whoever is using it.
94
+ */
95
+ if (options.hidden !== true) this.window.focus();
96
+ }
97
+
98
+ /**
99
+ * Put this window's canvas in `page`, and its keys, mouse, focus and size into `page`'s events.
100
+ * Hands back what disconnects them.
101
+ */
102
+ connectPage(page: HostPage): () => void {
103
+ page.attach(this.canvas);
104
+ return connectDomEvents(this.window as unknown as EventWindow, sdl.mouse, page, this.canvas);
105
+ }
106
+
107
+ /**
108
+ * The shell's bridge over this window, with its store at `storePath` (see `bridge.ts`). `quit` is
109
+ * how the host hears that the game, or the person closing the window, has ended the session.
110
+ */
111
+ createBridge(storePath: string, quit: () => void): NativeBridge {
112
+ return nativeBridge({
113
+ window: this.window as unknown as BridgeWindow,
114
+ displays: () => sdl.video.displays as unknown as readonly BridgeDisplay[],
115
+ storePath,
116
+ quit,
117
+ files: desktopFiles() ?? undefined,
118
+ });
119
+ }
120
+
121
+ /** The editor's text services over this window's typing and the desktop's clipboard. */
122
+ createTextHost(): NativeTextHost {
123
+ return createNativeTextHost(this.window as unknown as TextWindow, sdl.clipboard);
124
+ }
125
+
126
+ /** Whether the person closed it. */
127
+ /** A new title, made showable as the first one was (`windowTitle.ts`). */
128
+ setTitle(title: string): void {
129
+ this.window.setTitle(windowTitle(title, sdl.info.drivers.video.current));
130
+ }
131
+
132
+ /**
133
+ * Send `event` as SDL sends one, through the window's own emitter, for input a capture replays:
134
+ * it then takes the path a person's does, and counts as a gesture where theirs would.
135
+ */
136
+ replay(type: string, event: object): void {
137
+ (this.window as unknown as { emit(type: string, event: object): void }).emit(type, event);
138
+ }
139
+
140
+ get isClosed(): boolean {
141
+ return this.closed;
142
+ }
143
+
144
+ /**
145
+ * Put the engine's last frame on the screen. Nothing happens before the engine has configured
146
+ * the canvas, or while the window is minimised to nothing.
147
+ */
148
+ present(): void {
149
+ if (this.closed || !this.canvas.visible) return;
150
+ const context = this.canvas.getContext('webgpu') as GPUCanvasContext;
151
+ const configuration = context.getConfiguration();
152
+ if (configuration === null) return;
153
+ if (this.device !== configuration.device) this.adopt(configuration.device);
154
+ const renderer = this.renderer;
155
+ const presenter = this.presenter;
156
+ if (renderer === null || presenter === null) return;
157
+ presenter.present(context.getCurrentTexture(), renderer.getCurrentTextureView());
158
+ renderer.swap();
159
+ }
160
+
161
+ /** Wait for the device, then let everything go in an order Dawn accepts. */
162
+ async close(): Promise<void> {
163
+ await this.device?.queue.onSubmittedWorkDone();
164
+ this.closed = true;
165
+ /*
166
+ * **A window the person already closed is destroyed, and destroying it again throws.**
167
+ *
168
+ * SDL destroys a window when its close button is pressed, so a host that then shuts down in
169
+ * good order — which is the ordinary path, and the only one after a person closes a window —
170
+ * met `Error: window is destroyed` out of `sdl.video.window.destroy` and ended on a stack
171
+ * trace instead of a clean exit. Reported from a maintainer's own machine, 2026-09-20.
172
+ *
173
+ * **What it gives up:** a destroy that fails for some other reason is swallowed too. The
174
+ * instance below is released either way, which is the part that holds the process open.
175
+ */
176
+ try {
177
+ this.window.destroy();
178
+ } catch {
179
+ /* Already gone, which is what closing a window does. */
180
+ }
181
+ gpuModule.destroy(this.instance);
182
+ }
183
+
184
+ private adopt(device: GPUDevice): void {
185
+ this.device = device;
186
+ this.renderer = gpuModule.renderGPUDeviceToWindow({
187
+ device: device as unknown as DawnDevice,
188
+ window: this.window,
189
+ });
190
+ this.presenter = new Presenter(device, this.renderer.getCurrentTexture().format);
191
+ }
192
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The window a packaged game asked for, in SDL's flags: the manifest's `window.mode` and
3
+ * `window.resizable`, which the desktop shell hands to Electron's `BrowserWindow`.
4
+ *
5
+ * **Fullscreen is the desktop's**: `@kmamal/sdl` opens it as `SDL_WINDOW_FULLSCREEN_DESKTOP`,
6
+ * covering the screen at the screen's own resolution, which is what Electron's `fullscreen: true`
7
+ * is. No mode is switched. **Borderless** is a window with no frame, which is Electron's
8
+ * `frame: false`.
9
+ *
10
+ * What it gives up: nothing chooses the display. SDL opens the window on its first display, as
11
+ * Electron does.
12
+ */
13
+
14
+ export type WindowMode = 'windowed' | 'borderless' | 'fullscreen';
15
+
16
+ export interface WindowFlags {
17
+ readonly fullscreen: boolean;
18
+ readonly borderless: boolean;
19
+ readonly resizable: boolean;
20
+ }
21
+
22
+ export function sdlWindowFlags(mode: WindowMode = 'windowed', resizable = true): WindowFlags {
23
+ return { fullscreen: mode === 'fullscreen', borderless: mode === 'borderless', resizable };
24
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A window's title as this SDL can show it.
3
+ *
4
+ * **Under X11 the title SDL writes is in the process's locale, and Node leaves that at C.** SDL
5
+ * 2.32's X11 driver sets `WM_NAME` through `XmbTextListToTextProperty`, so a UTF-8 title's bytes
6
+ * go in as Latin-1, and the `_NET_WM_NAME` it should add in UTF-8 — the one a window manager prefers —
7
+ * is not on the window at all. Read back with `xprop` on 2026-09-19: `WM_NAME(STRING) = "Showroom
8
+ * â\302\200\302\224 DriftEngine"` and no `_NET_WM_NAME`, which the desktop drew as `Showroom —
9
+ * DriftEngine`; reported by the maintainer. So under X11 a title is made ASCII before SDL sees it:
10
+ * accents dropped, dashes and quotes in their ASCII forms, and anything else one `?` rather than
11
+ * three characters of noise.
12
+ *
13
+ * What would make it wrong: an SDL that sets `_NET_WM_NAME`, which `xprop -id <window>
14
+ * _NET_WM_NAME` shows, and this then drops letters for nothing. Wayland's title is UTF-8 and is
15
+ * left alone.
16
+ */
17
+
18
+ const PLAIN = new Map([
19
+ ['–', '-'],
20
+ ['—', '-'],
21
+ ['‘', "'"],
22
+ ['’', "'"],
23
+ ['“', '"'],
24
+ ['”', '"'],
25
+ ['…', '...'],
26
+ ]);
27
+
28
+ /** `title` as `driver` can show it: unchanged, or in ASCII under X11. */
29
+ export function windowTitle(title: string, driver: string): string {
30
+ if (driver !== 'x11') return title;
31
+ return [...title.normalize('NFKD').replace(/\p{M}/gu, '')]
32
+ .map((character) =>
33
+ character.charCodeAt(0) < 0x80 ? character : (PLAIN.get(character) ?? '?'),
34
+ )
35
+ .join('');
36
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The start of every thread the native host makes: a browser worker's scope over `worker_threads`,
3
+ * and then the module the page asked for.
4
+ *
5
+ * **A worker module written for a page reads `self`, `onmessage`, `addEventListener` and a global
6
+ * `postMessage`**, none of which a Node thread has; they are made here over `parentPort`, and a
7
+ * message is handed on with its value as `data`, as a `MessageEvent` carries it.
8
+ *
9
+ * **Messages wait until the module has run**, as a browser holds them until a worker's script has
10
+ * been evaluated: a page usually posts its first message the moment it constructs the worker, and a
11
+ * module that sets `onmessage` after an `await` would otherwise never see it.
12
+ *
13
+ * Plain JavaScript, so that it loads before any loader a host registers has anything to do.
14
+ */
15
+ import { parentPort, workerData } from 'node:worker_threads';
16
+
17
+ const scope = globalThis;
18
+ const listeners = new Set();
19
+ let ready = false;
20
+ const held = [];
21
+
22
+ function deliver(data) {
23
+ const event = { type: 'message', data, target: scope, currentTarget: scope };
24
+ if (typeof scope.onmessage === 'function') scope.onmessage(event);
25
+ for (const listener of listeners) {
26
+ if (typeof listener === 'function') listener(event);
27
+ else listener.handleEvent(event);
28
+ }
29
+ }
30
+
31
+ scope.self = scope;
32
+ scope.onmessage = null;
33
+ scope.postMessage = (value, transfer) => parentPort.postMessage(value, transfer);
34
+ scope.addEventListener = (type, listener) => {
35
+ if (type === 'message') listeners.add(listener);
36
+ };
37
+ scope.removeEventListener = (type, listener) => {
38
+ if (type === 'message') listeners.delete(listener);
39
+ };
40
+ scope.close = () => process.exit(0);
41
+ parentPort.on('message', (data) => {
42
+ if (ready) deliver(data);
43
+ else held.push(data);
44
+ });
45
+
46
+ await import(workerData.url);
47
+ ready = true;
48
+ for (const data of held.splice(0)) deliver(data);