@swifttui/web 0.0.14 → 0.0.16

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 (92) hide show
  1. package/README.md +24 -10
  2. package/dist/index.d.ts +9 -0
  3. package/dist/index.js +9 -0
  4. package/dist/manifest.d.ts +2 -0
  5. package/dist/manifest.js +2 -0
  6. package/dist/src/AccessibilityTree.js +156 -0
  7. package/dist/src/AccessibilityTree.js.map +1 -0
  8. package/dist/src/BoxDrawingRenderer.js +1106 -0
  9. package/dist/src/BoxDrawingRenderer.js.map +1 -0
  10. package/dist/src/WebHostApp.d.ts +41 -0
  11. package/dist/src/WebHostApp.js +135 -0
  12. package/dist/src/WebHostApp.js.map +1 -0
  13. package/dist/src/WebHostSceneManifest.d.ts +18 -0
  14. package/dist/src/WebHostSceneManifest.js +70 -0
  15. package/dist/src/WebHostSceneManifest.js.map +1 -0
  16. package/dist/src/WebHostSceneRuntime.d.ts +112 -0
  17. package/dist/src/WebHostSceneRuntime.js +651 -0
  18. package/dist/src/WebHostSceneRuntime.js.map +1 -0
  19. package/dist/src/WebHostSurfaceTransport.d.ts +166 -0
  20. package/dist/src/WebHostSurfaceTransport.js +252 -0
  21. package/dist/src/WebHostSurfaceTransport.js.map +1 -0
  22. package/dist/src/WebHostTerminalStyle.d.ts +92 -0
  23. package/dist/src/WebHostTerminalStyle.js +277 -0
  24. package/dist/src/WebHostTerminalStyle.js.map +1 -0
  25. package/dist/src/WebHostTestFixtures.d.ts +5 -0
  26. package/dist/src/WebHostTestFixtures.js +9 -0
  27. package/dist/src/WebHostTestFixtures.js.map +1 -0
  28. package/dist/src/WebSocketSceneBridge.d.ts +53 -0
  29. package/dist/src/WebSocketSceneBridge.js +124 -0
  30. package/dist/src/WebSocketSceneBridge.js.map +1 -0
  31. package/dist/src/wasi/BrowserWASIBridge.d.ts +33 -0
  32. package/dist/src/wasi/BrowserWASIBridge.js +97 -0
  33. package/dist/src/wasi/BrowserWASIBridge.js.map +1 -0
  34. package/dist/src/wasi/SharedInputQueue.d.ts +31 -0
  35. package/dist/src/wasi/SharedInputQueue.js +102 -0
  36. package/dist/src/wasi/SharedInputQueue.js.map +1 -0
  37. package/dist/src/wasi/StdIOPipe.d.ts +15 -0
  38. package/dist/src/wasi/StdIOPipe.js +56 -0
  39. package/dist/src/wasi/StdIOPipe.js.map +1 -0
  40. package/dist/src/wasi/WasiPollScheduler.js +114 -0
  41. package/dist/src/wasi/WasiPollScheduler.js.map +1 -0
  42. package/dist/src/wasi/WasmSceneRuntime.d.ts +23 -0
  43. package/dist/src/wasi/WasmSceneRuntime.js +119 -0
  44. package/dist/src/wasi/WasmSceneRuntime.js.map +1 -0
  45. package/dist/src/wasi/WasmSceneWorker.d.ts +27 -0
  46. package/dist/src/wasi/WasmSceneWorker.js +109 -0
  47. package/dist/src/wasi/WasmSceneWorker.js.map +1 -0
  48. package/dist/testing.d.ts +2 -0
  49. package/dist/testing.js +2 -0
  50. package/dist/wasi-worker.d.ts +2 -0
  51. package/dist/wasi-worker.js +2 -0
  52. package/dist/wasi.d.ts +6 -0
  53. package/dist/wasi.js +6 -0
  54. package/dist/websocket.d.ts +2 -0
  55. package/dist/websocket.js +2 -0
  56. package/package.json +49 -18
  57. package/AGENTS.md +0 -52
  58. package/cli.ts +0 -168
  59. package/index.html +0 -50
  60. package/index.ts +0 -8
  61. package/manifest.ts +0 -1
  62. package/src/AccessibilityTree.ts +0 -262
  63. package/src/BoxDrawingRenderer.ts +0 -585
  64. package/src/PublicEntrypointBoundary.test.ts +0 -20
  65. package/src/WebHostApp.test.ts +0 -222
  66. package/src/WebHostApp.ts +0 -269
  67. package/src/WebHostSceneManifest.test.ts +0 -38
  68. package/src/WebHostSceneManifest.ts +0 -156
  69. package/src/WebHostSceneRuntime.test.ts +0 -1982
  70. package/src/WebHostSceneRuntime.ts +0 -1142
  71. package/src/WebHostSurfaceTransport.test.ts +0 -362
  72. package/src/WebHostSurfaceTransport.ts +0 -691
  73. package/src/WebHostTerminalStyle.test.ts +0 -123
  74. package/src/WebHostTerminalStyle.ts +0 -471
  75. package/src/WebHostTestFixtures.ts +0 -10
  76. package/src/WebSocketSceneBridge.test.ts +0 -198
  77. package/src/WebSocketSceneBridge.ts +0 -233
  78. package/src/browser.ts +0 -59
  79. package/src/wasi/BrowserWASIBridge.test.ts +0 -168
  80. package/src/wasi/BrowserWASIBridge.ts +0 -167
  81. package/src/wasi/SharedInputQueue.test.ts +0 -146
  82. package/src/wasi/SharedInputQueue.ts +0 -199
  83. package/src/wasi/StdIOPipe.ts +0 -72
  84. package/src/wasi/WasiPollScheduler.test.ts +0 -176
  85. package/src/wasi/WasiPollScheduler.ts +0 -305
  86. package/src/wasi/WasmSceneRuntime.ts +0 -205
  87. package/src/wasi/WasmSceneWorker.ts +0 -182
  88. package/testing.ts +0 -1
  89. package/tsconfig.json +0 -29
  90. package/wasi-worker.ts +0 -1
  91. package/wasi.ts +0 -4
  92. package/websocket.ts +0 -1
@@ -0,0 +1,33 @@
1
+ import { WebHostTerminalStyle } from "../WebHostTerminalStyle.js";
2
+ import { WebHostOutputSink, encodeRenderStyleControlMessage, encodeResizeControlMessage } from "../WebHostSurfaceTransport.js";
3
+ import { StdIOPipe } from "./StdIOPipe.js";
4
+
5
+ //#region src/wasi/BrowserWASIBridge.d.ts
6
+ interface BrowserWASIBridgeOptions {
7
+ sceneId: string;
8
+ columns: number;
9
+ rows: number;
10
+ environment?: Record<string, string>;
11
+ renderStyle?: WebHostTerminalStyle;
12
+ }
13
+ type BrowserWASIOutputSink = WebHostOutputSink;
14
+ declare class BrowserWASIBridge {
15
+ readonly stdin: StdIOPipe;
16
+ readonly stdout: StdIOPipe;
17
+ readonly stderr: StdIOPipe;
18
+ readonly environment: Record<string, string>;
19
+ private detachStdout?;
20
+ private detachStderr?;
21
+ private readonly resizeListeners;
22
+ private latestResize;
23
+ constructor(options: BrowserWASIBridgeOptions);
24
+ bindOutput(sink: BrowserWASIOutputSink): void;
25
+ resize(columns: number, rows: number, cellWidth?: number, cellHeight?: number): void;
26
+ updateRenderStyle(style: WebHostTerminalStyle): void;
27
+ sendInput(chunk: Uint8Array): void;
28
+ subscribeResize(listener: (columns: number, rows: number, cellWidth?: number, cellHeight?: number) => void): () => void;
29
+ dispose(): void;
30
+ }
31
+ //#endregion
32
+ export { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink };
33
+ //# sourceMappingURL=BrowserWASIBridge.d.ts.map
@@ -0,0 +1,97 @@
1
+ import { StdIOPipe } from "./StdIOPipe.js";
2
+ import { encodeWebHostTerminalRenderStyleBase64 } from "../WebHostTerminalStyle.js";
3
+ import { WebHostOutputDecoder, encodeRenderStyleControlMessage, encodeResizeControlMessage } from "../WebHostSurfaceTransport.js";
4
+ //#region src/wasi/BrowserWASIBridge.ts
5
+ var BrowserWASIBridge = class {
6
+ stdin = new StdIOPipe();
7
+ stdout = new StdIOPipe();
8
+ stderr = new StdIOPipe();
9
+ environment;
10
+ detachStdout;
11
+ detachStderr;
12
+ resizeListeners = /* @__PURE__ */ new Set();
13
+ latestResize;
14
+ constructor(options) {
15
+ this.environment = {
16
+ TUIGUI_MODE: "browser",
17
+ TUIGUI_TRANSPORT: "surface",
18
+ TUIGUI_SURFACE_DELTA: "1",
19
+ TUIGUI_SCENE: options.sceneId,
20
+ TUIGUI_COLUMNS: String(Math.max(1, options.columns)),
21
+ TUIGUI_ROWS: String(Math.max(1, options.rows)),
22
+ ...options.environment,
23
+ ...options.renderStyle ? { TUIGUI_RENDER_STYLE: encodeWebHostTerminalRenderStyleBase64(options.renderStyle) } : {}
24
+ };
25
+ this.latestResize = {
26
+ columns: Math.max(1, options.columns),
27
+ rows: Math.max(1, options.rows)
28
+ };
29
+ }
30
+ bindOutput(sink) {
31
+ this.detachStdout?.();
32
+ this.detachStderr?.();
33
+ const decoder = new WebHostOutputDecoder();
34
+ this.detachStdout = this.stdout.subscribe((chunk) => {
35
+ for (const record of decoder.feed(chunk)) switch (record.type) {
36
+ case "surface":
37
+ sink.presentSurface(record.frame);
38
+ break;
39
+ case "clipboard":
40
+ sink.writeClipboard?.(record.text);
41
+ break;
42
+ case "runtimeIssue":
43
+ sink.notifyRuntimeIssue?.(record.issue);
44
+ break;
45
+ case "frameDiagnostic":
46
+ sink.recordFrameDiagnostic?.(record.diagnostic);
47
+ break;
48
+ case "text":
49
+ sink.writeOutput?.(record.text);
50
+ break;
51
+ }
52
+ });
53
+ this.detachStderr = this.stderr.subscribe((chunk) => {
54
+ sink.writeError?.(new TextDecoder().decode(chunk));
55
+ });
56
+ }
57
+ resize(columns, rows, cellWidth, cellHeight) {
58
+ const normalizedColumns = Math.max(1, columns);
59
+ const normalizedRows = Math.max(1, rows);
60
+ this.environment.TUIGUI_COLUMNS = String(normalizedColumns);
61
+ this.environment.TUIGUI_ROWS = String(normalizedRows);
62
+ this.latestResize = {
63
+ columns: normalizedColumns,
64
+ rows: normalizedRows,
65
+ cellWidth,
66
+ cellHeight
67
+ };
68
+ this.stdin.write(encodeResizeControlMessage(columns, rows, cellWidth, cellHeight));
69
+ for (const listener of this.resizeListeners) listener(normalizedColumns, normalizedRows, cellWidth, cellHeight);
70
+ }
71
+ updateRenderStyle(style) {
72
+ this.environment.TUIGUI_RENDER_STYLE = encodeWebHostTerminalRenderStyleBase64(style);
73
+ this.stdin.write(encodeRenderStyleControlMessage(style));
74
+ }
75
+ sendInput(chunk) {
76
+ this.stdin.write(chunk);
77
+ }
78
+ subscribeResize(listener) {
79
+ this.resizeListeners.add(listener);
80
+ listener(this.latestResize.columns, this.latestResize.rows, this.latestResize.cellWidth, this.latestResize.cellHeight);
81
+ return () => {
82
+ this.resizeListeners.delete(listener);
83
+ };
84
+ }
85
+ dispose() {
86
+ this.detachStdout?.();
87
+ this.detachStderr?.();
88
+ this.resizeListeners.clear();
89
+ this.stdin.close();
90
+ this.stdout.close();
91
+ this.stderr.close();
92
+ }
93
+ };
94
+ //#endregion
95
+ export { BrowserWASIBridge };
96
+
97
+ //# sourceMappingURL=BrowserWASIBridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserWASIBridge.js","names":[],"sources":["../../../src/wasi/BrowserWASIBridge.ts"],"sourcesContent":["import { StdIOPipe } from \"./StdIOPipe.ts\";\nimport {\n encodeWebHostTerminalRenderStyleBase64,\n type WebHostTerminalStyle,\n} from \"../WebHostTerminalStyle.ts\";\nimport {\n WebHostOutputDecoder,\n encodeRenderStyleControlMessage,\n encodeResizeControlMessage,\n type WebHostOutputSink,\n} from \"../WebHostSurfaceTransport.ts\";\n\nexport interface BrowserWASIBridgeOptions {\n sceneId: string;\n columns: number;\n rows: number;\n environment?: Record<string, string>;\n renderStyle?: WebHostTerminalStyle;\n}\n\nexport type BrowserWASIOutputSink = WebHostOutputSink;\n\nexport class BrowserWASIBridge {\n readonly stdin = new StdIOPipe();\n readonly stdout = new StdIOPipe();\n readonly stderr = new StdIOPipe();\n readonly environment: Record<string, string>;\n\n private detachStdout?: () => void;\n private detachStderr?: () => void;\n private readonly resizeListeners = new Set<(\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n ) => void>();\n private latestResize: {\n columns: number;\n rows: number;\n cellWidth?: number;\n cellHeight?: number;\n };\n\n constructor(options: BrowserWASIBridgeOptions) {\n this.environment = {\n TUIGUI_MODE: \"browser\",\n TUIGUI_TRANSPORT: \"surface\",\n TUIGUI_SURFACE_DELTA: \"1\",\n TUIGUI_SCENE: options.sceneId,\n TUIGUI_COLUMNS: String(Math.max(1, options.columns)),\n TUIGUI_ROWS: String(Math.max(1, options.rows)),\n ...options.environment,\n ...(options.renderStyle\n ? {\n TUIGUI_RENDER_STYLE: encodeWebHostTerminalRenderStyleBase64(\n options.renderStyle\n ),\n }\n : {}),\n };\n this.latestResize = {\n columns: Math.max(1, options.columns),\n rows: Math.max(1, options.rows),\n };\n }\n\n bindOutput(\n sink: BrowserWASIOutputSink\n ): void {\n this.detachStdout?.();\n this.detachStderr?.();\n const decoder = new WebHostOutputDecoder();\n this.detachStdout = this.stdout.subscribe((chunk) => {\n for (const record of decoder.feed(chunk)) {\n switch (record.type) {\n case \"surface\":\n sink.presentSurface(record.frame);\n break;\n case \"clipboard\":\n void sink.writeClipboard?.(record.text);\n break;\n case \"runtimeIssue\":\n sink.notifyRuntimeIssue?.(record.issue);\n break;\n case \"frameDiagnostic\":\n sink.recordFrameDiagnostic?.(record.diagnostic);\n break;\n case \"text\":\n sink.writeOutput?.(record.text);\n break;\n }\n }\n });\n this.detachStderr = this.stderr.subscribe((chunk) => {\n sink.writeError?.(new TextDecoder().decode(chunk));\n });\n }\n\n resize(\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n ): void {\n const normalizedColumns = Math.max(1, columns);\n const normalizedRows = Math.max(1, rows);\n this.environment.TUIGUI_COLUMNS = String(normalizedColumns);\n this.environment.TUIGUI_ROWS = String(normalizedRows);\n this.latestResize = {\n columns: normalizedColumns,\n rows: normalizedRows,\n cellWidth,\n cellHeight,\n };\n this.stdin.write(encodeResizeControlMessage(columns, rows, cellWidth, cellHeight));\n for (const listener of this.resizeListeners) {\n listener(normalizedColumns, normalizedRows, cellWidth, cellHeight);\n }\n }\n\n updateRenderStyle(\n style: WebHostTerminalStyle\n ): void {\n this.environment.TUIGUI_RENDER_STYLE = encodeWebHostTerminalRenderStyleBase64(style);\n this.stdin.write(encodeRenderStyleControlMessage(style));\n }\n\n sendInput(\n chunk: Uint8Array\n ): void {\n this.stdin.write(chunk);\n }\n\n subscribeResize(\n listener: (\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n ) => void\n ): () => void {\n this.resizeListeners.add(listener);\n listener(\n this.latestResize.columns,\n this.latestResize.rows,\n this.latestResize.cellWidth,\n this.latestResize.cellHeight\n );\n return () => {\n this.resizeListeners.delete(listener);\n };\n }\n\n dispose(): void {\n this.detachStdout?.();\n this.detachStderr?.();\n this.resizeListeners.clear();\n this.stdin.close();\n this.stdout.close();\n this.stderr.close();\n }\n}\n\nexport {\n encodeRenderStyleControlMessage,\n encodeResizeControlMessage,\n};\n"],"mappings":";;;;AAsBA,IAAa,oBAAb,MAA+B;CAC7B,QAAiB,IAAI,UAAU;CAC/B,SAAkB,IAAI,UAAU;CAChC,SAAkB,IAAI,UAAU;CAChC;CAEA;CACA;CACA,kCAAmC,IAAI,IAK5B;CACX;CAOA,YAAY,SAAmC;EAC7C,KAAK,cAAc;GACjB,aAAa;GACb,kBAAkB;GAClB,sBAAsB;GACtB,cAAc,QAAQ;GACtB,gBAAgB,OAAO,KAAK,IAAI,GAAG,QAAQ,OAAO,CAAC;GACnD,aAAa,OAAO,KAAK,IAAI,GAAG,QAAQ,IAAI,CAAC;GAC7C,GAAG,QAAQ;GACX,GAAI,QAAQ,cACR,EACE,qBAAqB,uCACnB,QAAQ,WACV,EACF,IACA,CAAC;EACP;EACA,KAAK,eAAe;GAClB,SAAS,KAAK,IAAI,GAAG,QAAQ,OAAO;GACpC,MAAM,KAAK,IAAI,GAAG,QAAQ,IAAI;EAChC;CACF;CAEA,WACE,MACM;EACN,KAAK,eAAe;EACpB,KAAK,eAAe;EACpB,MAAM,UAAU,IAAI,qBAAqB;EACzC,KAAK,eAAe,KAAK,OAAO,WAAW,UAAU;GACnD,KAAK,MAAM,UAAU,QAAQ,KAAK,KAAK,GACrC,QAAQ,OAAO,MAAf;IACA,KAAK;KACH,KAAK,eAAe,OAAO,KAAK;KAChC;IACF,KAAK;KACH,KAAU,iBAAiB,OAAO,IAAI;KACtC;IACF,KAAK;KACH,KAAK,qBAAqB,OAAO,KAAK;KACtC;IACF,KAAK;KACH,KAAK,wBAAwB,OAAO,UAAU;KAC9C;IACF,KAAK;KACH,KAAK,cAAc,OAAO,IAAI;KAC9B;GACF;EAEJ,CAAC;EACD,KAAK,eAAe,KAAK,OAAO,WAAW,UAAU;GACnD,KAAK,aAAa,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,CAAC;EACnD,CAAC;CACH;CAEA,OACE,SACA,MACA,WACA,YACM;EACN,MAAM,oBAAoB,KAAK,IAAI,GAAG,OAAO;EAC7C,MAAM,iBAAiB,KAAK,IAAI,GAAG,IAAI;EACvC,KAAK,YAAY,iBAAiB,OAAO,iBAAiB;EAC1D,KAAK,YAAY,cAAc,OAAO,cAAc;EACpD,KAAK,eAAe;GAClB,SAAS;GACT,MAAM;GACN;GACA;EACF;EACA,KAAK,MAAM,MAAM,2BAA2B,SAAS,MAAM,WAAW,UAAU,CAAC;EACjF,KAAK,MAAM,YAAY,KAAK,iBAC1B,SAAS,mBAAmB,gBAAgB,WAAW,UAAU;CAErE;CAEA,kBACE,OACM;EACN,KAAK,YAAY,sBAAsB,uCAAuC,KAAK;EACnF,KAAK,MAAM,MAAM,gCAAgC,KAAK,CAAC;CACzD;CAEA,UACE,OACM;EACN,KAAK,MAAM,MAAM,KAAK;CACxB;CAEA,gBACE,UAMY;EACZ,KAAK,gBAAgB,IAAI,QAAQ;EACjC,SACE,KAAK,aAAa,SAClB,KAAK,aAAa,MAClB,KAAK,aAAa,WAClB,KAAK,aAAa,UACpB;EACA,aAAa;GACX,KAAK,gBAAgB,OAAO,QAAQ;EACtC;CACF;CAEA,UAAgB;EACd,KAAK,eAAe;EACpB,KAAK,eAAe;EACpB,KAAK,gBAAgB,MAAM;EAC3B,KAAK,MAAM,MAAM;EACjB,KAAK,OAAO,MAAM;EAClB,KAAK,OAAO,MAAM;CACpB;AACF"}
@@ -0,0 +1,31 @@
1
+ //#region src/wasi/SharedInputQueue.d.ts
2
+ declare const sharedInputQueueDefaultCapacity: number;
3
+ interface SharedInputQueueBuffers {
4
+ readonly controlBuffer: SharedArrayBuffer;
5
+ readonly dataBuffer: SharedArrayBuffer;
6
+ }
7
+ type SharedInputReadiness = "readable" | "closed" | "timedOut";
8
+ interface SharedInputQueueState {
9
+ readonly control: Int32Array;
10
+ readonly data: Uint8Array;
11
+ }
12
+ declare function createSharedInputQueue(capacity?: number): SharedInputQueueBuffers;
13
+ declare function hydrateSharedInputQueue(buffers: SharedInputQueueBuffers): SharedInputQueueState;
14
+ declare class SharedInputQueueWriter {
15
+ private readonly queue;
16
+ constructor(buffers: SharedInputQueueBuffers);
17
+ write(chunk: Uint8Array | string): void;
18
+ close(): void;
19
+ }
20
+ declare class SharedInputQueueReader {
21
+ private readonly queue;
22
+ constructor(buffers: SharedInputQueueBuffers);
23
+ read(maxBytes: number): Uint8Array | undefined;
24
+ readAvailable(maxBytes: number): Uint8Array | undefined;
25
+ availableBytes(): number;
26
+ waitForReadable(timeoutMilliseconds?: number): SharedInputReadiness;
27
+ isClosed(): boolean;
28
+ }
29
+ //#endregion
30
+ export { SharedInputQueueBuffers, SharedInputQueueReader, SharedInputQueueWriter, SharedInputReadiness, createSharedInputQueue, hydrateSharedInputQueue, sharedInputQueueDefaultCapacity };
31
+ //# sourceMappingURL=SharedInputQueue.d.ts.map
@@ -0,0 +1,102 @@
1
+ //#region src/wasi/SharedInputQueue.ts
2
+ const controlSlots = 3;
3
+ const sharedInputQueueDefaultCapacity = 64 * 1024;
4
+ function createSharedInputQueue(capacity = sharedInputQueueDefaultCapacity) {
5
+ if (typeof SharedArrayBuffer === "undefined") throw new Error("SharedArrayBuffer is unavailable. Serve the app with COOP/COEP headers so browser WASI stdin can stay live.");
6
+ if (!Number.isInteger(capacity) || capacity <= 0) throw new Error(`Shared input queue capacity must be a positive integer, received ${capacity}.`);
7
+ return {
8
+ controlBuffer: new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * controlSlots),
9
+ dataBuffer: new SharedArrayBuffer(capacity)
10
+ };
11
+ }
12
+ function hydrateSharedInputQueue(buffers) {
13
+ return {
14
+ control: new Int32Array(buffers.controlBuffer),
15
+ data: new Uint8Array(buffers.dataBuffer)
16
+ };
17
+ }
18
+ var SharedInputQueueWriter = class {
19
+ queue;
20
+ constructor(buffers) {
21
+ this.queue = hydrateSharedInputQueue(buffers);
22
+ }
23
+ write(chunk) {
24
+ if (Atomics.load(this.queue.control, 2) !== 0) return;
25
+ const bytes = normalizeChunk(chunk);
26
+ if (bytes.length == 0) return;
27
+ const readIndex = Atomics.load(this.queue.control, 0);
28
+ const writeIndex = Atomics.load(this.queue.control, 1);
29
+ const usedCapacity = writeIndex - readIndex;
30
+ const availableCapacity = this.queue.data.length - usedCapacity;
31
+ if (bytes.length > availableCapacity) throw new Error(`Shared input queue overflow: cannot enqueue ${bytes.length} byte(s) into ${availableCapacity} byte(s) of free space.`);
32
+ writeToRingBuffer(this.queue.data, bytes, writeIndex);
33
+ Atomics.store(this.queue.control, 1, writeIndex + bytes.length);
34
+ Atomics.notify(this.queue.control, 1);
35
+ }
36
+ close() {
37
+ Atomics.store(this.queue.control, 2, 1);
38
+ Atomics.notify(this.queue.control, 1);
39
+ }
40
+ };
41
+ var SharedInputQueueReader = class {
42
+ queue;
43
+ constructor(buffers) {
44
+ this.queue = hydrateSharedInputQueue(buffers);
45
+ }
46
+ read(maxBytes) {
47
+ while (true) {
48
+ const next = this.readAvailable(maxBytes);
49
+ if (next) return next;
50
+ if (this.isClosed()) return;
51
+ const writeIndex = Atomics.load(this.queue.control, 1);
52
+ Atomics.wait(this.queue.control, 1, writeIndex);
53
+ }
54
+ }
55
+ readAvailable(maxBytes) {
56
+ if (!Number.isInteger(maxBytes) || maxBytes <= 0) return new Uint8Array();
57
+ const readIndex = Atomics.load(this.queue.control, 0);
58
+ const availableBytes = Atomics.load(this.queue.control, 1) - readIndex;
59
+ if (availableBytes <= 0) return;
60
+ const byteCount = Math.min(maxBytes, availableBytes);
61
+ const chunk = readFromRingBuffer(this.queue.data, readIndex, byteCount);
62
+ Atomics.store(this.queue.control, 0, readIndex + byteCount);
63
+ return chunk;
64
+ }
65
+ availableBytes() {
66
+ const readIndex = Atomics.load(this.queue.control, 0);
67
+ const writeIndex = Atomics.load(this.queue.control, 1);
68
+ return Math.max(0, writeIndex - readIndex);
69
+ }
70
+ waitForReadable(timeoutMilliseconds) {
71
+ while (true) {
72
+ if (this.availableBytes() > 0) return "readable";
73
+ if (this.isClosed()) return "closed";
74
+ const writeIndex = Atomics.load(this.queue.control, 1);
75
+ if (Atomics.wait(this.queue.control, 1, writeIndex, timeoutMilliseconds) === "timed-out") return "timedOut";
76
+ }
77
+ }
78
+ isClosed() {
79
+ return Atomics.load(this.queue.control, 2) !== 0;
80
+ }
81
+ };
82
+ function normalizeChunk(chunk) {
83
+ return typeof chunk == "string" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk);
84
+ }
85
+ function writeToRingBuffer(buffer, chunk, startIndex) {
86
+ const offset = startIndex % buffer.length;
87
+ const firstSegmentLength = Math.min(chunk.length, buffer.length - offset);
88
+ buffer.set(chunk.subarray(0, firstSegmentLength), offset);
89
+ if (firstSegmentLength < chunk.length) buffer.set(chunk.subarray(firstSegmentLength), 0);
90
+ }
91
+ function readFromRingBuffer(buffer, startIndex, byteCount) {
92
+ const chunk = new Uint8Array(byteCount);
93
+ const offset = startIndex % buffer.length;
94
+ const firstSegmentLength = Math.min(byteCount, buffer.length - offset);
95
+ chunk.set(buffer.subarray(offset, offset + firstSegmentLength), 0);
96
+ if (firstSegmentLength < byteCount) chunk.set(buffer.subarray(0, byteCount - firstSegmentLength), firstSegmentLength);
97
+ return chunk;
98
+ }
99
+ //#endregion
100
+ export { SharedInputQueueReader, SharedInputQueueWriter, createSharedInputQueue, hydrateSharedInputQueue, sharedInputQueueDefaultCapacity };
101
+
102
+ //# sourceMappingURL=SharedInputQueue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SharedInputQueue.js","names":[],"sources":["../../../src/wasi/SharedInputQueue.ts"],"sourcesContent":["const controlSlots = 3;\n\nconst enum ControlSlot {\n readIndex = 0,\n writeIndex = 1,\n closed = 2,\n}\n\nexport const sharedInputQueueDefaultCapacity = 64 * 1024;\n\nexport interface SharedInputQueueBuffers {\n readonly controlBuffer: SharedArrayBuffer;\n readonly dataBuffer: SharedArrayBuffer;\n}\n\nexport type SharedInputReadiness = \"readable\" | \"closed\" | \"timedOut\";\n\ninterface SharedInputQueueState {\n readonly control: Int32Array;\n readonly data: Uint8Array;\n}\n\nexport function createSharedInputQueue(\n capacity: number = sharedInputQueueDefaultCapacity\n): SharedInputQueueBuffers {\n if (typeof SharedArrayBuffer === \"undefined\") {\n throw new Error(\n \"SharedArrayBuffer is unavailable. Serve the app with COOP/COEP headers so browser WASI stdin can stay live.\"\n );\n }\n\n if (!Number.isInteger(capacity) || capacity <= 0) {\n throw new Error(`Shared input queue capacity must be a positive integer, received ${capacity}.`);\n }\n\n return {\n controlBuffer: new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * controlSlots),\n dataBuffer: new SharedArrayBuffer(capacity),\n };\n}\n\nexport function hydrateSharedInputQueue(\n buffers: SharedInputQueueBuffers\n): SharedInputQueueState {\n return {\n control: new Int32Array(buffers.controlBuffer),\n data: new Uint8Array(buffers.dataBuffer),\n };\n}\n\nexport class SharedInputQueueWriter {\n private readonly queue: SharedInputQueueState;\n\n constructor(buffers: SharedInputQueueBuffers) {\n this.queue = hydrateSharedInputQueue(buffers);\n }\n\n write(chunk: Uint8Array | string): void {\n if (Atomics.load(this.queue.control, ControlSlot.closed) !== 0) {\n return;\n }\n\n const bytes = normalizeChunk(chunk);\n if (bytes.length == 0) {\n return;\n }\n\n const readIndex = Atomics.load(this.queue.control, ControlSlot.readIndex);\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n const usedCapacity = writeIndex - readIndex;\n const availableCapacity = this.queue.data.length - usedCapacity;\n\n if (bytes.length > availableCapacity) {\n throw new Error(\n `Shared input queue overflow: cannot enqueue ${bytes.length} byte(s) into ${availableCapacity} byte(s) of free space.`\n );\n }\n\n writeToRingBuffer(this.queue.data, bytes, writeIndex);\n Atomics.store(this.queue.control, ControlSlot.writeIndex, writeIndex + bytes.length);\n Atomics.notify(this.queue.control, ControlSlot.writeIndex);\n }\n\n close(): void {\n Atomics.store(this.queue.control, ControlSlot.closed, 1);\n Atomics.notify(this.queue.control, ControlSlot.writeIndex);\n }\n}\n\nexport class SharedInputQueueReader {\n private readonly queue: SharedInputQueueState;\n\n constructor(buffers: SharedInputQueueBuffers) {\n this.queue = hydrateSharedInputQueue(buffers);\n }\n\n read(maxBytes: number): Uint8Array | undefined {\n while (true) {\n const next = this.readAvailable(maxBytes);\n if (next) {\n return next;\n }\n\n if (this.isClosed()) {\n return undefined;\n }\n\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n Atomics.wait(this.queue.control, ControlSlot.writeIndex, writeIndex);\n }\n }\n\n readAvailable(maxBytes: number): Uint8Array | undefined {\n if (!Number.isInteger(maxBytes) || maxBytes <= 0) {\n return new Uint8Array();\n }\n\n const readIndex = Atomics.load(this.queue.control, ControlSlot.readIndex);\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n const availableBytes = writeIndex - readIndex;\n\n if (availableBytes <= 0) {\n return undefined;\n }\n\n const byteCount = Math.min(maxBytes, availableBytes);\n const chunk = readFromRingBuffer(this.queue.data, readIndex, byteCount);\n Atomics.store(this.queue.control, ControlSlot.readIndex, readIndex + byteCount);\n return chunk;\n }\n\n availableBytes(): number {\n const readIndex = Atomics.load(this.queue.control, ControlSlot.readIndex);\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n return Math.max(0, writeIndex - readIndex);\n }\n\n waitForReadable(\n timeoutMilliseconds?: number\n ): SharedInputReadiness {\n while (true) {\n if (this.availableBytes() > 0) {\n return \"readable\";\n }\n if (this.isClosed()) {\n return \"closed\";\n }\n\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n const result = Atomics.wait(\n this.queue.control,\n ControlSlot.writeIndex,\n writeIndex,\n timeoutMilliseconds\n );\n if (result === \"timed-out\") {\n return \"timedOut\";\n }\n }\n }\n\n isClosed(): boolean {\n return Atomics.load(this.queue.control, ControlSlot.closed) !== 0;\n }\n}\n\nfunction normalizeChunk(\n chunk: Uint8Array | string\n): Uint8Array {\n return typeof chunk == \"string\" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk);\n}\n\nfunction writeToRingBuffer(\n buffer: Uint8Array,\n chunk: Uint8Array,\n startIndex: number\n): void {\n const offset = startIndex % buffer.length;\n const firstSegmentLength = Math.min(chunk.length, buffer.length - offset);\n buffer.set(chunk.subarray(0, firstSegmentLength), offset);\n if (firstSegmentLength < chunk.length) {\n buffer.set(chunk.subarray(firstSegmentLength), 0);\n }\n}\n\nfunction readFromRingBuffer(\n buffer: Uint8Array,\n startIndex: number,\n byteCount: number\n): Uint8Array {\n const chunk = new Uint8Array(byteCount);\n const offset = startIndex % buffer.length;\n const firstSegmentLength = Math.min(byteCount, buffer.length - offset);\n chunk.set(buffer.subarray(offset, offset + firstSegmentLength), 0);\n if (firstSegmentLength < byteCount) {\n chunk.set(buffer.subarray(0, byteCount - firstSegmentLength), firstSegmentLength);\n }\n return chunk;\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ //#region src/wasi/StdIOPipe.d.ts
2
+ declare class StdIOPipe implements AsyncIterable<Uint8Array> {
3
+ private readonly chunks;
4
+ private readonly waiters;
5
+ private readonly listeners;
6
+ private closed;
7
+ write(chunk: Uint8Array | string): void;
8
+ close(): void;
9
+ read(): Promise<Uint8Array | undefined>;
10
+ subscribe(listener: (chunk: Uint8Array) => void): () => void;
11
+ [Symbol.asyncIterator](): AsyncIterator<Uint8Array>;
12
+ }
13
+ //#endregion
14
+ export { StdIOPipe };
15
+ //# sourceMappingURL=StdIOPipe.d.ts.map
@@ -0,0 +1,56 @@
1
+ //#region src/wasi/StdIOPipe.ts
2
+ var StdIOPipe = class {
3
+ chunks = [];
4
+ waiters = [];
5
+ listeners = /* @__PURE__ */ new Set();
6
+ closed = false;
7
+ write(chunk) {
8
+ if (this.closed) return;
9
+ const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk);
10
+ const waiter = this.waiters.shift();
11
+ if (waiter) {
12
+ waiter({
13
+ done: false,
14
+ value: bytes
15
+ });
16
+ return;
17
+ }
18
+ this.chunks.push(bytes);
19
+ for (const listener of this.listeners) listener(bytes);
20
+ }
21
+ close() {
22
+ if (this.closed) return;
23
+ this.closed = true;
24
+ while (this.waiters.length > 0) this.waiters.shift()?.({
25
+ done: true,
26
+ value: void 0
27
+ });
28
+ }
29
+ async read() {
30
+ const next = this.chunks.shift();
31
+ if (next) return next;
32
+ if (this.closed) return;
33
+ return await new Promise((resolve) => {
34
+ this.waiters.push((result) => {
35
+ resolve(result.done ? void 0 : result.value);
36
+ });
37
+ });
38
+ }
39
+ subscribe(listener) {
40
+ this.listeners.add(listener);
41
+ return () => {
42
+ this.listeners.delete(listener);
43
+ };
44
+ }
45
+ async *[Symbol.asyncIterator]() {
46
+ while (true) {
47
+ const next = await this.read();
48
+ if (!next) return;
49
+ yield next;
50
+ }
51
+ }
52
+ };
53
+ //#endregion
54
+ export { StdIOPipe };
55
+
56
+ //# sourceMappingURL=StdIOPipe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StdIOPipe.js","names":[],"sources":["../../../src/wasi/StdIOPipe.ts"],"sourcesContent":["export class StdIOPipe implements AsyncIterable<Uint8Array> {\n private readonly chunks: Uint8Array[] = [];\n private readonly waiters: Array<(value: IteratorResult<Uint8Array>) => void> = [];\n private readonly listeners = new Set<(chunk: Uint8Array) => void>();\n private closed = false;\n\n write(chunk: Uint8Array | string): void {\n if (this.closed) {\n return;\n }\n\n const bytes = typeof chunk === \"string\" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk);\n const waiter = this.waiters.shift();\n if (waiter) {\n waiter({ done: false, value: bytes });\n return;\n }\n\n this.chunks.push(bytes);\n for (const listener of this.listeners) {\n listener(bytes);\n }\n }\n\n close(): void {\n if (this.closed) {\n return;\n }\n\n this.closed = true;\n while (this.waiters.length > 0) {\n const waiter = this.waiters.shift();\n waiter?.({ done: true, value: undefined as never });\n }\n }\n\n async read(): Promise<Uint8Array | undefined> {\n const next = this.chunks.shift();\n if (next) {\n return next;\n }\n\n if (this.closed) {\n return undefined;\n }\n\n return await new Promise<Uint8Array | undefined>((resolve) => {\n this.waiters.push((result) => {\n resolve(result.done ? undefined : result.value);\n });\n });\n }\n\n subscribe(\n listener: (chunk: Uint8Array) => void\n ): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n async *[Symbol.asyncIterator](): AsyncIterator<Uint8Array> {\n while (true) {\n const next = await this.read();\n if (!next) {\n return;\n }\n yield next;\n }\n }\n}\n"],"mappings":";AAAA,IAAa,YAAb,MAA4D;CAC1D,SAAwC,CAAC;CACzC,UAA+E,CAAC;CAChF,4BAA6B,IAAI,IAAiC;CAClE,SAAiB;CAEjB,MAAM,OAAkC;EACtC,IAAI,KAAK,QACP;EAGF,MAAM,QAAQ,OAAO,UAAU,WAAW,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,IAAI,IAAI,WAAW,KAAK;EAChG,MAAM,SAAS,KAAK,QAAQ,MAAM;EAClC,IAAI,QAAQ;GACV,OAAO;IAAE,MAAM;IAAO,OAAO;GAAM,CAAC;GACpC;EACF;EAEA,KAAK,OAAO,KAAK,KAAK;EACtB,KAAK,MAAM,YAAY,KAAK,WAC1B,SAAS,KAAK;CAElB;CAEA,QAAc;EACZ,IAAI,KAAK,QACP;EAGF,KAAK,SAAS;EACd,OAAO,KAAK,QAAQ,SAAS,GAE3B,KADoB,QAAQ,MACvB,CAAC,GAAG;GAAE,MAAM;GAAM,OAAO,KAAA;EAAmB,CAAC;CAEtD;CAEA,MAAM,OAAwC;EAC5C,MAAM,OAAO,KAAK,OAAO,MAAM;EAC/B,IAAI,MACF,OAAO;EAGT,IAAI,KAAK,QACP;EAGF,OAAO,MAAM,IAAI,SAAiC,YAAY;GAC5D,KAAK,QAAQ,MAAM,WAAW;IAC5B,QAAQ,OAAO,OAAO,KAAA,IAAY,OAAO,KAAK;GAChD,CAAC;EACH,CAAC;CACH;CAEA,UACE,UACY;EACZ,KAAK,UAAU,IAAI,QAAQ;EAC3B,aAAa;GACX,KAAK,UAAU,OAAO,QAAQ;EAChC;CACF;CAEA,QAAQ,OAAO,iBAA4C;EACzD,OAAO,MAAM;GACX,MAAM,OAAO,MAAM,KAAK,KAAK;GAC7B,IAAI,CAAC,MACH;GAEF,MAAM;EACR;CACF;AACF"}
@@ -0,0 +1,114 @@
1
+ import { wasi } from "@bjorn3/browser_wasi_shim";
2
+ //#region src/wasi/WasiPollScheduler.ts
3
+ const subscriptionByteLength = 48;
4
+ const eventByteLength = 32;
5
+ const maximumAtomicsWaitMilliseconds = 2147483647;
6
+ var WasiPollScheduler = class {
7
+ memory;
8
+ stdin;
9
+ fallbackPoll;
10
+ nowMilliseconds;
11
+ waitBuffer = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
12
+ constructor(options) {
13
+ this.memory = options.memory;
14
+ this.stdin = options.stdin;
15
+ this.fallbackPoll = options.fallbackPoll;
16
+ this.nowMilliseconds = options.nowMilliseconds ?? (() => performance.now());
17
+ }
18
+ pollOneOff(inPtr, outPtr, nsubscriptions, neventsPtr) {
19
+ const memory = this.memory();
20
+ if (!memory || nsubscriptions <= 0) return this.fallbackPoll(inPtr, outPtr, nsubscriptions, neventsPtr);
21
+ const view = new DataView(memory.buffer);
22
+ const subscriptions = readSubscriptions(view, inPtr, nsubscriptions, this.nowMilliseconds());
23
+ if (subscriptions === void 0) return this.fallbackPoll(inPtr, outPtr, nsubscriptions, neventsPtr);
24
+ while (true) {
25
+ const ready = readySubscriptions(subscriptions, this.stdin, this.nowMilliseconds());
26
+ if (ready.length > 0) {
27
+ writeEvents(view, outPtr, ready, this.stdin);
28
+ if (neventsPtr !== void 0) view.setUint32(neventsPtr, ready.length, true);
29
+ return wasi.ERRNO_SUCCESS;
30
+ }
31
+ const timeoutMilliseconds = shortestClockTimeoutMilliseconds(subscriptions, this.nowMilliseconds());
32
+ if (hasFdReadSubscription(subscriptions)) this.stdin.waitForReadable(timeoutMilliseconds);
33
+ else if (timeoutMilliseconds !== void 0) Atomics.wait(this.waitBuffer, 0, 0, Math.min(timeoutMilliseconds, maximumAtomicsWaitMilliseconds));
34
+ else return this.fallbackPoll(inPtr, outPtr, nsubscriptions, neventsPtr);
35
+ }
36
+ }
37
+ };
38
+ function readSubscriptions(view, inPtr, nsubscriptions, nowMilliseconds) {
39
+ const subscriptions = [];
40
+ for (let index = 0; index < nsubscriptions; index += 1) {
41
+ const subscription = wasi.Subscription.read_bytes(view, inPtr + index * subscriptionByteLength);
42
+ switch (subscription.eventtype) {
43
+ case wasi.EVENTTYPE_CLOCK:
44
+ if (!isSupportedClockId(subscription.clockid)) return;
45
+ subscriptions.push({
46
+ type: "clock",
47
+ userdata: subscription.userdata,
48
+ clockid: subscription.clockid,
49
+ deadlineMilliseconds: clockDeadlineMilliseconds(subscription, nowMilliseconds)
50
+ });
51
+ break;
52
+ case wasi.EVENTTYPE_FD_READ:
53
+ if (subscription.clockid !== wasi.FD_STDIN) return;
54
+ subscriptions.push({
55
+ type: "fdRead",
56
+ userdata: subscription.userdata,
57
+ fd: subscription.clockid
58
+ });
59
+ break;
60
+ default: return;
61
+ }
62
+ }
63
+ return subscriptions;
64
+ }
65
+ function isSupportedClockId(clockid) {
66
+ return clockid === wasi.CLOCKID_MONOTONIC || clockid === wasi.CLOCKID_REALTIME;
67
+ }
68
+ function shortestClockTimeoutMilliseconds(subscriptions, nowMilliseconds) {
69
+ let timeoutMilliseconds;
70
+ for (const subscription of subscriptions) {
71
+ if (subscription.type !== "clock") continue;
72
+ const remaining = clockRemainingMilliseconds(subscription, nowMilliseconds);
73
+ timeoutMilliseconds = timeoutMilliseconds === void 0 ? remaining : Math.min(timeoutMilliseconds, remaining);
74
+ }
75
+ return timeoutMilliseconds;
76
+ }
77
+ function readySubscriptions(subscriptions, stdin, nowMilliseconds) {
78
+ return subscriptions.filter((subscription) => {
79
+ switch (subscription.type) {
80
+ case "clock": return clockRemainingMilliseconds(subscription, nowMilliseconds) <= 0;
81
+ case "fdRead": return stdin.availableBytes() > 0 || stdin.isClosed();
82
+ }
83
+ });
84
+ }
85
+ function hasFdReadSubscription(subscriptions) {
86
+ return subscriptions.some((subscription) => subscription.type === "fdRead");
87
+ }
88
+ function clockRemainingMilliseconds(subscription, nowMilliseconds) {
89
+ return Math.max(0, subscription.deadlineMilliseconds - nowMillisecondsForClock(subscription.clockid, nowMilliseconds));
90
+ }
91
+ function clockDeadlineMilliseconds(subscription, nowMilliseconds) {
92
+ if ((subscription.flags & wasi.SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME) !== 0) return Number(subscription.timeout) / 1e6;
93
+ return nowMillisecondsForClock(subscription.clockid, nowMilliseconds) + Number(subscription.timeout) / 1e6;
94
+ }
95
+ function nowMillisecondsForClock(clockid, nowMilliseconds) {
96
+ if (clockid === wasi.CLOCKID_REALTIME) return Date.now();
97
+ return nowMilliseconds;
98
+ }
99
+ function writeEvents(view, outPtr, subscriptions, stdin) {
100
+ subscriptions.forEach((subscription, index) => {
101
+ const eventtype = subscription.type === "clock" ? wasi.EVENTTYPE_CLOCK : wasi.EVENTTYPE_FD_READ;
102
+ const offset = outPtr + index * eventByteLength;
103
+ new wasi.Event(subscription.userdata, wasi.ERRNO_SUCCESS, eventtype).write_bytes(view, offset);
104
+ if (subscription.type === "fdRead") {
105
+ const availableBytes = Math.max(0, stdin.availableBytes());
106
+ view.setBigUint64(offset + 16, BigInt(availableBytes), true);
107
+ if (availableBytes === 0 && stdin.isClosed()) view.setUint16(offset + 24, wasi.EVENTRWFLAGS_FD_READWRITE_HANGUP, true);
108
+ }
109
+ });
110
+ }
111
+ //#endregion
112
+ export { WasiPollScheduler };
113
+
114
+ //# sourceMappingURL=WasiPollScheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WasiPollScheduler.js","names":[],"sources":["../../../src/wasi/WasiPollScheduler.ts"],"sourcesContent":["import { wasi } from \"@bjorn3/browser_wasi_shim\";\n\nimport type { SharedInputReadiness } from \"./SharedInputQueue.ts\";\n\nconst subscriptionByteLength = 48;\nconst eventByteLength = 32;\nconst maximumAtomicsWaitMilliseconds = 2_147_483_647;\n\ninterface ClockSubscription {\n readonly type: \"clock\";\n readonly userdata: bigint;\n readonly clockid: number;\n readonly deadlineMilliseconds: number;\n}\n\ninterface FdReadSubscription {\n readonly type: \"fdRead\";\n readonly userdata: bigint;\n readonly fd: number;\n}\n\ntype SupportedSubscription = ClockSubscription | FdReadSubscription;\n\nexport interface WasiPollReadableSource {\n availableBytes(): number;\n isClosed(): boolean;\n waitForReadable(timeoutMilliseconds?: number): SharedInputReadiness;\n}\n\nexport interface WasiPollSchedulerOptions {\n memory(): WebAssembly.Memory | undefined;\n stdin: WasiPollReadableSource;\n fallbackPoll(\n inPtr: number,\n outPtr: number,\n nsubscriptions: number,\n neventsPtr?: number\n ): number;\n nowMilliseconds?(): number;\n}\n\nexport class WasiPollScheduler {\n private readonly memory: WasiPollSchedulerOptions[\"memory\"];\n private readonly stdin: WasiPollReadableSource;\n private readonly fallbackPoll: WasiPollSchedulerOptions[\"fallbackPoll\"];\n private readonly nowMilliseconds: () => number;\n private readonly waitBuffer = new Int32Array(\n new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT)\n );\n\n constructor(options: WasiPollSchedulerOptions) {\n this.memory = options.memory;\n this.stdin = options.stdin;\n this.fallbackPoll = options.fallbackPoll;\n this.nowMilliseconds = options.nowMilliseconds ?? (() => performance.now());\n }\n\n pollOneOff(\n inPtr: number,\n outPtr: number,\n nsubscriptions: number,\n neventsPtr?: number\n ): number {\n const memory = this.memory();\n if (!memory || nsubscriptions <= 0) {\n return this.fallbackPoll(inPtr, outPtr, nsubscriptions, neventsPtr);\n }\n\n const view = new DataView(memory.buffer);\n const subscriptions = readSubscriptions(\n view,\n inPtr,\n nsubscriptions,\n this.nowMilliseconds()\n );\n if (subscriptions === undefined) {\n return this.fallbackPoll(inPtr, outPtr, nsubscriptions, neventsPtr);\n }\n\n while (true) {\n const ready = readySubscriptions(subscriptions, this.stdin, this.nowMilliseconds());\n if (ready.length > 0) {\n writeEvents(view, outPtr, ready, this.stdin);\n if (neventsPtr !== undefined) {\n view.setUint32(neventsPtr, ready.length, true);\n }\n return wasi.ERRNO_SUCCESS;\n }\n\n const timeoutMilliseconds = shortestClockTimeoutMilliseconds(\n subscriptions,\n this.nowMilliseconds()\n );\n if (hasFdReadSubscription(subscriptions)) {\n this.stdin.waitForReadable(timeoutMilliseconds);\n } else if (timeoutMilliseconds !== undefined) {\n Atomics.wait(\n this.waitBuffer,\n 0,\n 0,\n Math.min(timeoutMilliseconds, maximumAtomicsWaitMilliseconds)\n );\n } else {\n return this.fallbackPoll(inPtr, outPtr, nsubscriptions, neventsPtr);\n }\n }\n }\n}\n\nfunction readSubscriptions(\n view: DataView,\n inPtr: number,\n nsubscriptions: number,\n nowMilliseconds: number\n): SupportedSubscription[] | undefined {\n const subscriptions: SupportedSubscription[] = [];\n for (let index = 0; index < nsubscriptions; index += 1) {\n const subscription = wasi.Subscription.read_bytes(\n view,\n inPtr + index * subscriptionByteLength\n );\n switch (subscription.eventtype) {\n case wasi.EVENTTYPE_CLOCK:\n if (!isSupportedClockId(subscription.clockid)) {\n return undefined;\n }\n subscriptions.push({\n type: \"clock\",\n userdata: subscription.userdata,\n clockid: subscription.clockid,\n deadlineMilliseconds: clockDeadlineMilliseconds(subscription, nowMilliseconds),\n });\n break;\n case wasi.EVENTTYPE_FD_READ:\n if (subscription.clockid !== wasi.FD_STDIN) {\n return undefined;\n }\n subscriptions.push({\n type: \"fdRead\",\n userdata: subscription.userdata,\n fd: subscription.clockid,\n });\n break;\n default:\n return undefined;\n }\n }\n return subscriptions;\n}\n\nfunction isSupportedClockId(\n clockid: number\n): boolean {\n return clockid === wasi.CLOCKID_MONOTONIC || clockid === wasi.CLOCKID_REALTIME;\n}\n\nfunction shortestClockTimeoutMilliseconds(\n subscriptions: readonly SupportedSubscription[],\n nowMilliseconds: number\n): number | undefined {\n let timeoutMilliseconds: number | undefined;\n for (const subscription of subscriptions) {\n if (subscription.type !== \"clock\") {\n continue;\n }\n const remaining = clockRemainingMilliseconds(subscription, nowMilliseconds);\n timeoutMilliseconds = timeoutMilliseconds === undefined\n ? remaining\n : Math.min(timeoutMilliseconds, remaining);\n }\n return timeoutMilliseconds;\n}\n\nfunction readySubscriptions(\n subscriptions: readonly SupportedSubscription[],\n stdin: WasiPollReadableSource,\n nowMilliseconds: number\n): SupportedSubscription[] {\n return subscriptions.filter((subscription) => {\n switch (subscription.type) {\n case \"clock\":\n return clockRemainingMilliseconds(subscription, nowMilliseconds) <= 0;\n case \"fdRead\":\n return stdin.availableBytes() > 0 || stdin.isClosed();\n }\n });\n}\n\nfunction hasFdReadSubscription(\n subscriptions: readonly SupportedSubscription[]\n): boolean {\n return subscriptions.some((subscription) => subscription.type === \"fdRead\");\n}\n\nfunction clockRemainingMilliseconds(\n subscription: ClockSubscription,\n nowMilliseconds: number\n): number {\n return Math.max(0, subscription.deadlineMilliseconds - nowMillisecondsForClock(\n subscription.clockid,\n nowMilliseconds\n ));\n}\n\nfunction clockDeadlineMilliseconds(\n subscription: wasi.Subscription,\n nowMilliseconds: number\n): number {\n if ((subscription.flags & wasi.SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME) !== 0) {\n return Number(subscription.timeout) / 1_000_000;\n }\n return nowMillisecondsForClock(subscription.clockid, nowMilliseconds)\n + Number(subscription.timeout) / 1_000_000;\n}\n\nfunction nowMillisecondsForClock(\n clockid: number,\n nowMilliseconds: number\n): number {\n if (clockid === wasi.CLOCKID_REALTIME) {\n return Date.now();\n }\n return nowMilliseconds;\n}\n\nfunction writeEvents(\n view: DataView,\n outPtr: number,\n subscriptions: readonly SupportedSubscription[],\n stdin: WasiPollReadableSource\n): void {\n subscriptions.forEach((subscription, index) => {\n const eventtype = subscription.type === \"clock\"\n ? wasi.EVENTTYPE_CLOCK\n : wasi.EVENTTYPE_FD_READ;\n const offset = outPtr + index * eventByteLength;\n new wasi.Event(\n subscription.userdata,\n wasi.ERRNO_SUCCESS,\n eventtype\n ).write_bytes(view, offset);\n if (subscription.type === \"fdRead\") {\n const availableBytes = Math.max(0, stdin.availableBytes());\n view.setBigUint64(offset + 16, BigInt(availableBytes), true);\n if (availableBytes === 0 && stdin.isClosed()) {\n view.setUint16(offset + 24, wasi.EVENTRWFLAGS_FD_READWRITE_HANGUP, true);\n }\n }\n });\n}\n\nexport function writeClockSubscriptionForTesting(\n view: DataView,\n offset: number,\n subscription: {\n userdata: bigint;\n timeoutNanoseconds: bigint;\n clockid?: number;\n flags?: number;\n }\n): void {\n clearRecord(view, offset, subscriptionByteLength);\n view.setBigUint64(offset, subscription.userdata, true);\n view.setUint8(offset + 8, wasi.EVENTTYPE_CLOCK);\n view.setUint32(offset + 16, subscription.clockid ?? wasi.CLOCKID_MONOTONIC, true);\n view.setBigUint64(offset + 24, subscription.timeoutNanoseconds, true);\n view.setUint16(offset + 36, subscription.flags ?? 0, true);\n}\n\nexport function writeFdReadSubscriptionForTesting(\n view: DataView,\n offset: number,\n subscription: {\n userdata: bigint;\n fd: number;\n }\n): void {\n clearRecord(view, offset, subscriptionByteLength);\n view.setBigUint64(offset, subscription.userdata, true);\n view.setUint8(offset + 8, wasi.EVENTTYPE_FD_READ);\n view.setUint32(offset + 16, subscription.fd, true);\n}\n\nexport function readPollEventsForTesting(\n view: DataView,\n offset: number,\n count: number\n): Array<{ userdata: bigint; errno: number; eventtype: number }> {\n return Array.from({ length: count }, (_, index) => {\n const eventOffset = offset + index * eventByteLength;\n return {\n userdata: view.getBigUint64(eventOffset, true),\n errno: view.getUint16(eventOffset + 8, true),\n eventtype: view.getUint8(eventOffset + 10),\n };\n });\n}\n\nfunction clearRecord(\n view: DataView,\n offset: number,\n byteLength: number\n): void {\n new Uint8Array(view.buffer, offset, byteLength).fill(0);\n}\n"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { WebHostSceneRuntime, WebHostSceneRuntimeOptions } from "../WebHostSceneRuntime.js";
2
+
3
+ //#region src/wasi/WasmSceneRuntime.d.ts
4
+ interface WasmSceneResizeEvent {
5
+ sceneId: string;
6
+ columns: number;
7
+ rows: number;
8
+ cellWidth?: number;
9
+ cellHeight?: number;
10
+ }
11
+ interface WasmSceneRuntimeHandle {
12
+ readonly descriptor: WebHostSceneRuntime["descriptor"];
13
+ sendInput(chunk: Uint8Array): void;
14
+ }
15
+ interface WasmSceneRuntimeFactoryOptions {
16
+ onSceneResize?(event: WasmSceneResizeEvent): void;
17
+ onRuntimeCreated?(runtime: WasmSceneRuntimeHandle): void;
18
+ workerModuleURL?: string | URL;
19
+ }
20
+ declare function createWasmSceneRuntimeFactory(wasmURL: URL, factoryOptions?: WasmSceneRuntimeFactoryOptions): (options: WebHostSceneRuntimeOptions) => WebHostSceneRuntime;
21
+ //#endregion
22
+ export { WasmSceneResizeEvent, WasmSceneRuntimeFactoryOptions, WasmSceneRuntimeHandle, createWasmSceneRuntimeFactory };
23
+ //# sourceMappingURL=WasmSceneRuntime.d.ts.map