@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.
- package/README.md +24 -10
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/manifest.d.ts +2 -0
- package/dist/manifest.js +2 -0
- package/dist/src/AccessibilityTree.js +156 -0
- package/dist/src/AccessibilityTree.js.map +1 -0
- package/dist/src/BoxDrawingRenderer.js +1106 -0
- package/dist/src/BoxDrawingRenderer.js.map +1 -0
- package/dist/src/WebHostApp.d.ts +41 -0
- package/dist/src/WebHostApp.js +135 -0
- package/dist/src/WebHostApp.js.map +1 -0
- package/dist/src/WebHostSceneManifest.d.ts +18 -0
- package/dist/src/WebHostSceneManifest.js +70 -0
- package/dist/src/WebHostSceneManifest.js.map +1 -0
- package/dist/src/WebHostSceneRuntime.d.ts +112 -0
- package/dist/src/WebHostSceneRuntime.js +651 -0
- package/dist/src/WebHostSceneRuntime.js.map +1 -0
- package/dist/src/WebHostSurfaceTransport.d.ts +166 -0
- package/dist/src/WebHostSurfaceTransport.js +252 -0
- package/dist/src/WebHostSurfaceTransport.js.map +1 -0
- package/dist/src/WebHostTerminalStyle.d.ts +92 -0
- package/dist/src/WebHostTerminalStyle.js +277 -0
- package/dist/src/WebHostTerminalStyle.js.map +1 -0
- package/dist/src/WebHostTestFixtures.d.ts +5 -0
- package/dist/src/WebHostTestFixtures.js +9 -0
- package/dist/src/WebHostTestFixtures.js.map +1 -0
- package/dist/src/WebSocketSceneBridge.d.ts +53 -0
- package/dist/src/WebSocketSceneBridge.js +124 -0
- package/dist/src/WebSocketSceneBridge.js.map +1 -0
- package/dist/src/wasi/BrowserWASIBridge.d.ts +33 -0
- package/dist/src/wasi/BrowserWASIBridge.js +97 -0
- package/dist/src/wasi/BrowserWASIBridge.js.map +1 -0
- package/dist/src/wasi/SharedInputQueue.d.ts +31 -0
- package/dist/src/wasi/SharedInputQueue.js +102 -0
- package/dist/src/wasi/SharedInputQueue.js.map +1 -0
- package/dist/src/wasi/StdIOPipe.d.ts +15 -0
- package/dist/src/wasi/StdIOPipe.js +56 -0
- package/dist/src/wasi/StdIOPipe.js.map +1 -0
- package/dist/src/wasi/WasiPollScheduler.js +114 -0
- package/dist/src/wasi/WasiPollScheduler.js.map +1 -0
- package/dist/src/wasi/WasmSceneRuntime.d.ts +23 -0
- package/dist/src/wasi/WasmSceneRuntime.js +119 -0
- package/dist/src/wasi/WasmSceneRuntime.js.map +1 -0
- package/dist/src/wasi/WasmSceneWorker.d.ts +27 -0
- package/dist/src/wasi/WasmSceneWorker.js +109 -0
- package/dist/src/wasi/WasmSceneWorker.js.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +2 -0
- package/dist/wasi-worker.d.ts +2 -0
- package/dist/wasi-worker.js +2 -0
- package/dist/wasi.d.ts +6 -0
- package/dist/wasi.js +6 -0
- package/dist/websocket.d.ts +2 -0
- package/dist/websocket.js +2 -0
- package/package.json +49 -18
- package/AGENTS.md +0 -52
- package/cli.ts +0 -168
- package/index.html +0 -50
- package/index.ts +0 -8
- package/manifest.ts +0 -1
- package/src/AccessibilityTree.ts +0 -262
- package/src/BoxDrawingRenderer.ts +0 -585
- package/src/PublicEntrypointBoundary.test.ts +0 -20
- package/src/WebHostApp.test.ts +0 -222
- package/src/WebHostApp.ts +0 -269
- package/src/WebHostSceneManifest.test.ts +0 -38
- package/src/WebHostSceneManifest.ts +0 -156
- package/src/WebHostSceneRuntime.test.ts +0 -1982
- package/src/WebHostSceneRuntime.ts +0 -1142
- package/src/WebHostSurfaceTransport.test.ts +0 -362
- package/src/WebHostSurfaceTransport.ts +0 -691
- package/src/WebHostTerminalStyle.test.ts +0 -123
- package/src/WebHostTerminalStyle.ts +0 -471
- package/src/WebHostTestFixtures.ts +0 -10
- package/src/WebSocketSceneBridge.test.ts +0 -198
- package/src/WebSocketSceneBridge.ts +0 -233
- package/src/browser.ts +0 -59
- package/src/wasi/BrowserWASIBridge.test.ts +0 -168
- package/src/wasi/BrowserWASIBridge.ts +0 -167
- package/src/wasi/SharedInputQueue.test.ts +0 -146
- package/src/wasi/SharedInputQueue.ts +0 -199
- package/src/wasi/StdIOPipe.ts +0 -72
- package/src/wasi/WasiPollScheduler.test.ts +0 -176
- package/src/wasi/WasiPollScheduler.ts +0 -305
- package/src/wasi/WasmSceneRuntime.ts +0 -205
- package/src/wasi/WasmSceneWorker.ts +0 -182
- package/testing.ts +0 -1
- package/tsconfig.json +0 -29
- package/wasi-worker.ts +0 -1
- package/wasi.ts +0 -4
- package/websocket.ts +0 -1
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { StdIOPipe } from "./StdIOPipe.ts";
|
|
2
|
-
import {
|
|
3
|
-
encodeWebHostTerminalRenderStyleBase64,
|
|
4
|
-
type WebHostTerminalStyle,
|
|
5
|
-
} from "../WebHostTerminalStyle.ts";
|
|
6
|
-
import {
|
|
7
|
-
WebHostOutputDecoder,
|
|
8
|
-
encodeRenderStyleControlMessage,
|
|
9
|
-
encodeResizeControlMessage,
|
|
10
|
-
type WebHostOutputSink,
|
|
11
|
-
} from "../WebHostSurfaceTransport.ts";
|
|
12
|
-
|
|
13
|
-
export interface BrowserWASIBridgeOptions {
|
|
14
|
-
sceneId: string;
|
|
15
|
-
columns: number;
|
|
16
|
-
rows: number;
|
|
17
|
-
environment?: Record<string, string>;
|
|
18
|
-
renderStyle?: WebHostTerminalStyle;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type BrowserWASIOutputSink = WebHostOutputSink;
|
|
22
|
-
|
|
23
|
-
export class BrowserWASIBridge {
|
|
24
|
-
readonly stdin = new StdIOPipe();
|
|
25
|
-
readonly stdout = new StdIOPipe();
|
|
26
|
-
readonly stderr = new StdIOPipe();
|
|
27
|
-
readonly environment: Record<string, string>;
|
|
28
|
-
|
|
29
|
-
private detachStdout?: () => void;
|
|
30
|
-
private detachStderr?: () => void;
|
|
31
|
-
private readonly resizeListeners = new Set<(
|
|
32
|
-
columns: number,
|
|
33
|
-
rows: number,
|
|
34
|
-
cellWidth?: number,
|
|
35
|
-
cellHeight?: number
|
|
36
|
-
) => void>();
|
|
37
|
-
private latestResize: {
|
|
38
|
-
columns: number;
|
|
39
|
-
rows: number;
|
|
40
|
-
cellWidth?: number;
|
|
41
|
-
cellHeight?: number;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
constructor(options: BrowserWASIBridgeOptions) {
|
|
45
|
-
this.environment = {
|
|
46
|
-
TUIGUI_MODE: "browser",
|
|
47
|
-
TUIGUI_TRANSPORT: "surface",
|
|
48
|
-
TUIGUI_SURFACE_DELTA: "1",
|
|
49
|
-
TUIGUI_SCENE: options.sceneId,
|
|
50
|
-
TUIGUI_COLUMNS: String(Math.max(1, options.columns)),
|
|
51
|
-
TUIGUI_ROWS: String(Math.max(1, options.rows)),
|
|
52
|
-
...options.environment,
|
|
53
|
-
...(options.renderStyle
|
|
54
|
-
? {
|
|
55
|
-
TUIGUI_RENDER_STYLE: encodeWebHostTerminalRenderStyleBase64(
|
|
56
|
-
options.renderStyle
|
|
57
|
-
),
|
|
58
|
-
}
|
|
59
|
-
: {}),
|
|
60
|
-
};
|
|
61
|
-
this.latestResize = {
|
|
62
|
-
columns: Math.max(1, options.columns),
|
|
63
|
-
rows: Math.max(1, options.rows),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
bindOutput(
|
|
68
|
-
sink: BrowserWASIOutputSink
|
|
69
|
-
): void {
|
|
70
|
-
this.detachStdout?.();
|
|
71
|
-
this.detachStderr?.();
|
|
72
|
-
const decoder = new WebHostOutputDecoder();
|
|
73
|
-
this.detachStdout = this.stdout.subscribe((chunk) => {
|
|
74
|
-
for (const record of decoder.feed(chunk)) {
|
|
75
|
-
switch (record.type) {
|
|
76
|
-
case "surface":
|
|
77
|
-
sink.presentSurface(record.frame);
|
|
78
|
-
break;
|
|
79
|
-
case "clipboard":
|
|
80
|
-
void sink.writeClipboard?.(record.text);
|
|
81
|
-
break;
|
|
82
|
-
case "runtimeIssue":
|
|
83
|
-
sink.notifyRuntimeIssue?.(record.issue);
|
|
84
|
-
break;
|
|
85
|
-
case "frameDiagnostic":
|
|
86
|
-
sink.recordFrameDiagnostic?.(record.diagnostic);
|
|
87
|
-
break;
|
|
88
|
-
case "text":
|
|
89
|
-
sink.writeOutput?.(record.text);
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
this.detachStderr = this.stderr.subscribe((chunk) => {
|
|
95
|
-
sink.writeError?.(new TextDecoder().decode(chunk));
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
resize(
|
|
100
|
-
columns: number,
|
|
101
|
-
rows: number,
|
|
102
|
-
cellWidth?: number,
|
|
103
|
-
cellHeight?: number
|
|
104
|
-
): void {
|
|
105
|
-
const normalizedColumns = Math.max(1, columns);
|
|
106
|
-
const normalizedRows = Math.max(1, rows);
|
|
107
|
-
this.environment.TUIGUI_COLUMNS = String(normalizedColumns);
|
|
108
|
-
this.environment.TUIGUI_ROWS = String(normalizedRows);
|
|
109
|
-
this.latestResize = {
|
|
110
|
-
columns: normalizedColumns,
|
|
111
|
-
rows: normalizedRows,
|
|
112
|
-
cellWidth,
|
|
113
|
-
cellHeight,
|
|
114
|
-
};
|
|
115
|
-
this.stdin.write(encodeResizeControlMessage(columns, rows, cellWidth, cellHeight));
|
|
116
|
-
for (const listener of this.resizeListeners) {
|
|
117
|
-
listener(normalizedColumns, normalizedRows, cellWidth, cellHeight);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
updateRenderStyle(
|
|
122
|
-
style: WebHostTerminalStyle
|
|
123
|
-
): void {
|
|
124
|
-
this.environment.TUIGUI_RENDER_STYLE = encodeWebHostTerminalRenderStyleBase64(style);
|
|
125
|
-
this.stdin.write(encodeRenderStyleControlMessage(style));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
sendInput(
|
|
129
|
-
chunk: Uint8Array
|
|
130
|
-
): void {
|
|
131
|
-
this.stdin.write(chunk);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
subscribeResize(
|
|
135
|
-
listener: (
|
|
136
|
-
columns: number,
|
|
137
|
-
rows: number,
|
|
138
|
-
cellWidth?: number,
|
|
139
|
-
cellHeight?: number
|
|
140
|
-
) => void
|
|
141
|
-
): () => void {
|
|
142
|
-
this.resizeListeners.add(listener);
|
|
143
|
-
listener(
|
|
144
|
-
this.latestResize.columns,
|
|
145
|
-
this.latestResize.rows,
|
|
146
|
-
this.latestResize.cellWidth,
|
|
147
|
-
this.latestResize.cellHeight
|
|
148
|
-
);
|
|
149
|
-
return () => {
|
|
150
|
-
this.resizeListeners.delete(listener);
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
dispose(): void {
|
|
155
|
-
this.detachStdout?.();
|
|
156
|
-
this.detachStderr?.();
|
|
157
|
-
this.resizeListeners.clear();
|
|
158
|
-
this.stdin.close();
|
|
159
|
-
this.stdout.close();
|
|
160
|
-
this.stderr.close();
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export {
|
|
165
|
-
encodeRenderStyleControlMessage,
|
|
166
|
-
encodeResizeControlMessage,
|
|
167
|
-
};
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "bun:test";
|
|
2
|
-
import { Worker } from "node:worker_threads";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
SharedInputQueueReader,
|
|
6
|
-
SharedInputQueueWriter,
|
|
7
|
-
type SharedInputQueueBuffers,
|
|
8
|
-
createSharedInputQueue,
|
|
9
|
-
} from "./SharedInputQueue.ts";
|
|
10
|
-
|
|
11
|
-
test("shared input queue preserves write order across partial reads", () => {
|
|
12
|
-
const queue = createSharedInputQueue(8);
|
|
13
|
-
const writer = new SharedInputQueueWriter(queue);
|
|
14
|
-
const reader = new SharedInputQueueReader(queue);
|
|
15
|
-
|
|
16
|
-
writer.write("abcdef");
|
|
17
|
-
|
|
18
|
-
expect(decode(reader.readAvailable(2))).toBe("ab");
|
|
19
|
-
expect(decode(reader.readAvailable(4))).toBe("cdef");
|
|
20
|
-
expect(reader.readAvailable(4)).toBeUndefined();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test("shared input queue wraps around the ring buffer", () => {
|
|
24
|
-
const queue = createSharedInputQueue(8);
|
|
25
|
-
const writer = new SharedInputQueueWriter(queue);
|
|
26
|
-
const reader = new SharedInputQueueReader(queue);
|
|
27
|
-
|
|
28
|
-
writer.write("abcdef");
|
|
29
|
-
expect(decode(reader.readAvailable(4))).toBe("abcd");
|
|
30
|
-
|
|
31
|
-
writer.write("gh");
|
|
32
|
-
expect(decode(reader.readAvailable(4))).toBe("efgh");
|
|
33
|
-
expect(reader.readAvailable(4)).toBeUndefined();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("shared input queue reports EOF after close once buffered data is drained", () => {
|
|
37
|
-
const queue = createSharedInputQueue(8);
|
|
38
|
-
const writer = new SharedInputQueueWriter(queue);
|
|
39
|
-
const reader = new SharedInputQueueReader(queue);
|
|
40
|
-
|
|
41
|
-
writer.write("ok");
|
|
42
|
-
writer.close();
|
|
43
|
-
|
|
44
|
-
expect(decode(reader.readAvailable(8))).toBe("ok");
|
|
45
|
-
expect(reader.readAvailable(8)).toBeUndefined();
|
|
46
|
-
expect(reader.read(8)).toBeUndefined();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("shared input queue reports readable bytes without consuming them", () => {
|
|
50
|
-
const queue = createSharedInputQueue(8);
|
|
51
|
-
const writer = new SharedInputQueueWriter(queue);
|
|
52
|
-
const reader = new SharedInputQueueReader(queue);
|
|
53
|
-
|
|
54
|
-
expect(reader.availableBytes()).toBe(0);
|
|
55
|
-
|
|
56
|
-
writer.write("abc");
|
|
57
|
-
|
|
58
|
-
expect(reader.availableBytes()).toBe(3);
|
|
59
|
-
expect(decode(reader.readAvailable(2))).toBe("ab");
|
|
60
|
-
expect(reader.availableBytes()).toBe(1);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("shared input queue timed readiness wait wakes on write", async () => {
|
|
64
|
-
const queue = createSharedInputQueue(8);
|
|
65
|
-
const reader = new SharedInputQueueReader(queue);
|
|
66
|
-
const worker = writeInputFromWorker(queue, "x", 10);
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
expect(reader.waitForReadable(250)).toBe("readable");
|
|
70
|
-
expect(decode(reader.readAvailable(1))).toBe("x");
|
|
71
|
-
} finally {
|
|
72
|
-
await worker.terminate();
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("shared input queue timed readiness wait returns timedOut", () => {
|
|
77
|
-
const queue = createSharedInputQueue(8);
|
|
78
|
-
const reader = new SharedInputQueueReader(queue);
|
|
79
|
-
|
|
80
|
-
expect(reader.waitForReadable(1)).toBe("timedOut");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("shared input queue readiness wait wakes on close", async () => {
|
|
84
|
-
const queue = createSharedInputQueue(8);
|
|
85
|
-
const reader = new SharedInputQueueReader(queue);
|
|
86
|
-
const worker = closeInputFromWorker(queue, 10);
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
expect(reader.waitForReadable(250)).toBe("closed");
|
|
90
|
-
} finally {
|
|
91
|
-
await worker.terminate();
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
function decode(
|
|
96
|
-
chunk: Uint8Array | undefined
|
|
97
|
-
): string | undefined {
|
|
98
|
-
return chunk ? new TextDecoder().decode(chunk) : undefined;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function writeInputFromWorker(
|
|
102
|
-
queue: SharedInputQueueBuffers,
|
|
103
|
-
text: string,
|
|
104
|
-
delayMilliseconds: number
|
|
105
|
-
): Worker {
|
|
106
|
-
return new Worker(`
|
|
107
|
-
const { workerData } = require("node:worker_threads");
|
|
108
|
-
const control = new Int32Array(workerData.controlBuffer);
|
|
109
|
-
const data = new Uint8Array(workerData.dataBuffer);
|
|
110
|
-
const bytes = new TextEncoder().encode(workerData.text);
|
|
111
|
-
setTimeout(() => {
|
|
112
|
-
const writeIndex = Atomics.load(control, 1);
|
|
113
|
-
data.set(bytes, writeIndex % data.length);
|
|
114
|
-
Atomics.store(control, 1, writeIndex + bytes.length);
|
|
115
|
-
Atomics.notify(control, 1);
|
|
116
|
-
}, workerData.delayMilliseconds);
|
|
117
|
-
`, {
|
|
118
|
-
eval: true,
|
|
119
|
-
workerData: {
|
|
120
|
-
controlBuffer: queue.controlBuffer,
|
|
121
|
-
dataBuffer: queue.dataBuffer,
|
|
122
|
-
delayMilliseconds,
|
|
123
|
-
text,
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function closeInputFromWorker(
|
|
129
|
-
queue: SharedInputQueueBuffers,
|
|
130
|
-
delayMilliseconds: number
|
|
131
|
-
): Worker {
|
|
132
|
-
return new Worker(`
|
|
133
|
-
const { workerData } = require("node:worker_threads");
|
|
134
|
-
const control = new Int32Array(workerData.controlBuffer);
|
|
135
|
-
setTimeout(() => {
|
|
136
|
-
Atomics.store(control, 2, 1);
|
|
137
|
-
Atomics.notify(control, 1);
|
|
138
|
-
}, workerData.delayMilliseconds);
|
|
139
|
-
`, {
|
|
140
|
-
eval: true,
|
|
141
|
-
workerData: {
|
|
142
|
-
controlBuffer: queue.controlBuffer,
|
|
143
|
-
delayMilliseconds,
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
}
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
const controlSlots = 3;
|
|
2
|
-
|
|
3
|
-
const enum ControlSlot {
|
|
4
|
-
readIndex = 0,
|
|
5
|
-
writeIndex = 1,
|
|
6
|
-
closed = 2,
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const sharedInputQueueDefaultCapacity = 64 * 1024;
|
|
10
|
-
|
|
11
|
-
export interface SharedInputQueueBuffers {
|
|
12
|
-
readonly controlBuffer: SharedArrayBuffer;
|
|
13
|
-
readonly dataBuffer: SharedArrayBuffer;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type SharedInputReadiness = "readable" | "closed" | "timedOut";
|
|
17
|
-
|
|
18
|
-
interface SharedInputQueueState {
|
|
19
|
-
readonly control: Int32Array;
|
|
20
|
-
readonly data: Uint8Array;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function createSharedInputQueue(
|
|
24
|
-
capacity: number = sharedInputQueueDefaultCapacity
|
|
25
|
-
): SharedInputQueueBuffers {
|
|
26
|
-
if (typeof SharedArrayBuffer === "undefined") {
|
|
27
|
-
throw new Error(
|
|
28
|
-
"SharedArrayBuffer is unavailable. Serve the app with COOP/COEP headers so browser WASI stdin can stay live."
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!Number.isInteger(capacity) || capacity <= 0) {
|
|
33
|
-
throw new Error(`Shared input queue capacity must be a positive integer, received ${capacity}.`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
controlBuffer: new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * controlSlots),
|
|
38
|
-
dataBuffer: new SharedArrayBuffer(capacity),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function hydrateSharedInputQueue(
|
|
43
|
-
buffers: SharedInputQueueBuffers
|
|
44
|
-
): SharedInputQueueState {
|
|
45
|
-
return {
|
|
46
|
-
control: new Int32Array(buffers.controlBuffer),
|
|
47
|
-
data: new Uint8Array(buffers.dataBuffer),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export class SharedInputQueueWriter {
|
|
52
|
-
private readonly queue: SharedInputQueueState;
|
|
53
|
-
|
|
54
|
-
constructor(buffers: SharedInputQueueBuffers) {
|
|
55
|
-
this.queue = hydrateSharedInputQueue(buffers);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
write(chunk: Uint8Array | string): void {
|
|
59
|
-
if (Atomics.load(this.queue.control, ControlSlot.closed) !== 0) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const bytes = normalizeChunk(chunk);
|
|
64
|
-
if (bytes.length == 0) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const readIndex = Atomics.load(this.queue.control, ControlSlot.readIndex);
|
|
69
|
-
const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);
|
|
70
|
-
const usedCapacity = writeIndex - readIndex;
|
|
71
|
-
const availableCapacity = this.queue.data.length - usedCapacity;
|
|
72
|
-
|
|
73
|
-
if (bytes.length > availableCapacity) {
|
|
74
|
-
throw new Error(
|
|
75
|
-
`Shared input queue overflow: cannot enqueue ${bytes.length} byte(s) into ${availableCapacity} byte(s) of free space.`
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
writeToRingBuffer(this.queue.data, bytes, writeIndex);
|
|
80
|
-
Atomics.store(this.queue.control, ControlSlot.writeIndex, writeIndex + bytes.length);
|
|
81
|
-
Atomics.notify(this.queue.control, ControlSlot.writeIndex);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
close(): void {
|
|
85
|
-
Atomics.store(this.queue.control, ControlSlot.closed, 1);
|
|
86
|
-
Atomics.notify(this.queue.control, ControlSlot.writeIndex);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export class SharedInputQueueReader {
|
|
91
|
-
private readonly queue: SharedInputQueueState;
|
|
92
|
-
|
|
93
|
-
constructor(buffers: SharedInputQueueBuffers) {
|
|
94
|
-
this.queue = hydrateSharedInputQueue(buffers);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
read(maxBytes: number): Uint8Array | undefined {
|
|
98
|
-
while (true) {
|
|
99
|
-
const next = this.readAvailable(maxBytes);
|
|
100
|
-
if (next) {
|
|
101
|
-
return next;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (this.isClosed()) {
|
|
105
|
-
return undefined;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);
|
|
109
|
-
Atomics.wait(this.queue.control, ControlSlot.writeIndex, writeIndex);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
readAvailable(maxBytes: number): Uint8Array | undefined {
|
|
114
|
-
if (!Number.isInteger(maxBytes) || maxBytes <= 0) {
|
|
115
|
-
return new Uint8Array();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const readIndex = Atomics.load(this.queue.control, ControlSlot.readIndex);
|
|
119
|
-
const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);
|
|
120
|
-
const availableBytes = writeIndex - readIndex;
|
|
121
|
-
|
|
122
|
-
if (availableBytes <= 0) {
|
|
123
|
-
return undefined;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const byteCount = Math.min(maxBytes, availableBytes);
|
|
127
|
-
const chunk = readFromRingBuffer(this.queue.data, readIndex, byteCount);
|
|
128
|
-
Atomics.store(this.queue.control, ControlSlot.readIndex, readIndex + byteCount);
|
|
129
|
-
return chunk;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
availableBytes(): number {
|
|
133
|
-
const readIndex = Atomics.load(this.queue.control, ControlSlot.readIndex);
|
|
134
|
-
const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);
|
|
135
|
-
return Math.max(0, writeIndex - readIndex);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
waitForReadable(
|
|
139
|
-
timeoutMilliseconds?: number
|
|
140
|
-
): SharedInputReadiness {
|
|
141
|
-
while (true) {
|
|
142
|
-
if (this.availableBytes() > 0) {
|
|
143
|
-
return "readable";
|
|
144
|
-
}
|
|
145
|
-
if (this.isClosed()) {
|
|
146
|
-
return "closed";
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);
|
|
150
|
-
const result = Atomics.wait(
|
|
151
|
-
this.queue.control,
|
|
152
|
-
ControlSlot.writeIndex,
|
|
153
|
-
writeIndex,
|
|
154
|
-
timeoutMilliseconds
|
|
155
|
-
);
|
|
156
|
-
if (result === "timed-out") {
|
|
157
|
-
return "timedOut";
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
isClosed(): boolean {
|
|
163
|
-
return Atomics.load(this.queue.control, ControlSlot.closed) !== 0;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function normalizeChunk(
|
|
168
|
-
chunk: Uint8Array | string
|
|
169
|
-
): Uint8Array {
|
|
170
|
-
return typeof chunk == "string" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function writeToRingBuffer(
|
|
174
|
-
buffer: Uint8Array,
|
|
175
|
-
chunk: Uint8Array,
|
|
176
|
-
startIndex: number
|
|
177
|
-
): void {
|
|
178
|
-
const offset = startIndex % buffer.length;
|
|
179
|
-
const firstSegmentLength = Math.min(chunk.length, buffer.length - offset);
|
|
180
|
-
buffer.set(chunk.subarray(0, firstSegmentLength), offset);
|
|
181
|
-
if (firstSegmentLength < chunk.length) {
|
|
182
|
-
buffer.set(chunk.subarray(firstSegmentLength), 0);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function readFromRingBuffer(
|
|
187
|
-
buffer: Uint8Array,
|
|
188
|
-
startIndex: number,
|
|
189
|
-
byteCount: number
|
|
190
|
-
): Uint8Array {
|
|
191
|
-
const chunk = new Uint8Array(byteCount);
|
|
192
|
-
const offset = startIndex % buffer.length;
|
|
193
|
-
const firstSegmentLength = Math.min(byteCount, buffer.length - offset);
|
|
194
|
-
chunk.set(buffer.subarray(offset, offset + firstSegmentLength), 0);
|
|
195
|
-
if (firstSegmentLength < byteCount) {
|
|
196
|
-
chunk.set(buffer.subarray(0, byteCount - firstSegmentLength), firstSegmentLength);
|
|
197
|
-
}
|
|
198
|
-
return chunk;
|
|
199
|
-
}
|
package/src/wasi/StdIOPipe.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
export class StdIOPipe implements AsyncIterable<Uint8Array> {
|
|
2
|
-
private readonly chunks: Uint8Array[] = [];
|
|
3
|
-
private readonly waiters: Array<(value: IteratorResult<Uint8Array>) => void> = [];
|
|
4
|
-
private readonly listeners = new Set<(chunk: Uint8Array) => void>();
|
|
5
|
-
private closed = false;
|
|
6
|
-
|
|
7
|
-
write(chunk: Uint8Array | string): void {
|
|
8
|
-
if (this.closed) {
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk);
|
|
13
|
-
const waiter = this.waiters.shift();
|
|
14
|
-
if (waiter) {
|
|
15
|
-
waiter({ done: false, value: bytes });
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
this.chunks.push(bytes);
|
|
20
|
-
for (const listener of this.listeners) {
|
|
21
|
-
listener(bytes);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
close(): void {
|
|
26
|
-
if (this.closed) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
this.closed = true;
|
|
31
|
-
while (this.waiters.length > 0) {
|
|
32
|
-
const waiter = this.waiters.shift();
|
|
33
|
-
waiter?.({ done: true, value: undefined as never });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async read(): Promise<Uint8Array | undefined> {
|
|
38
|
-
const next = this.chunks.shift();
|
|
39
|
-
if (next) {
|
|
40
|
-
return next;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (this.closed) {
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return await new Promise<Uint8Array | undefined>((resolve) => {
|
|
48
|
-
this.waiters.push((result) => {
|
|
49
|
-
resolve(result.done ? undefined : result.value);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
subscribe(
|
|
55
|
-
listener: (chunk: Uint8Array) => void
|
|
56
|
-
): () => void {
|
|
57
|
-
this.listeners.add(listener);
|
|
58
|
-
return () => {
|
|
59
|
-
this.listeners.delete(listener);
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async *[Symbol.asyncIterator](): AsyncIterator<Uint8Array> {
|
|
64
|
-
while (true) {
|
|
65
|
-
const next = await this.read();
|
|
66
|
-
if (!next) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
yield next;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|