@swifttui/web 0.1.11 → 0.1.13
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/dist/index.d.ts +2 -2
- package/dist/src/WebHostApp.d.ts +20 -1
- package/dist/src/WebHostApp.js +30 -2
- package/dist/src/WebHostApp.js.map +1 -1
- package/dist/src/WebHostSceneRuntime.d.ts +26 -0
- package/dist/src/WebHostSceneRuntime.js +27 -0
- package/dist/src/WebHostSceneRuntime.js.map +1 -1
- package/dist/src/wasi/MainThreadWasmExecutor.d.ts +8 -0
- package/dist/src/wasi/MainThreadWasmExecutor.js +16 -1
- package/dist/src/wasi/MainThreadWasmExecutor.js.map +1 -1
- package/dist/src/wasi/WasiPollScheduler.js +6 -0
- package/dist/src/wasi/WasiPollScheduler.js.map +1 -1
- package/dist/src/wasi/WasmEngineCapabilities.js +1 -0
- package/dist/src/wasi/WasmEngineCapabilities.js.map +1 -1
- package/dist/src/wasi/WasmRuntimePause.d.ts +63 -0
- package/dist/src/wasi/WasmRuntimePause.js +130 -0
- package/dist/src/wasi/WasmRuntimePause.js.map +1 -0
- package/dist/src/wasi/WasmSceneRuntime.js +14 -1
- package/dist/src/wasi/WasmSceneRuntime.js.map +1 -1
- package/dist/src/wasi/WasmSceneWorker.d.ts +7 -0
- package/dist/src/wasi/WasmSceneWorker.js +11 -4
- package/dist/src/wasi/WasmSceneWorker.js.map +1 -1
- package/dist/wasi.d.ts +2 -1
- package/dist/wasi.js +2 -1
- package/package.json +1 -1
|
@@ -1 +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 WasiPollReadableState {\n availableBytes(): number;\n isClosed(): boolean;\n}\n\nexport interface WasiPollReadableSource extends WasiPollReadableState {\n waitForReadable(timeoutMilliseconds?: number): SharedInputReadiness;\n}\n\nexport interface SuspendingWasiPollReadableSource extends WasiPollReadableState {\n waitForReadableAsync(timeoutMilliseconds?: number): Promise<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\nexport interface SuspendingWasiPollSchedulerOptions {\n memory(): WebAssembly.Memory | undefined;\n stdin: SuspendingWasiPollReadableSource;\n fallbackPoll(\n inPtr: number,\n outPtr: number,\n nsubscriptions: number,\n neventsPtr?: number\n ): number;\n nowMilliseconds?(): number;\n}\n\n/**\n * The JSPI (main-thread) counterpart of `WasiPollScheduler`: identical\n * subscription semantics, but the blocking waits become awaited promises so\n * the surrounding `poll_oneoff` import can be wrapped in\n * `WebAssembly.Suspending` and run without a worker or SharedArrayBuffer.\n */\nexport class SuspendingWasiPollScheduler {\n private readonly memory: SuspendingWasiPollSchedulerOptions[\"memory\"];\n private readonly stdin: SuspendingWasiPollReadableSource;\n private readonly fallbackPoll: SuspendingWasiPollSchedulerOptions[\"fallbackPoll\"];\n private readonly nowMilliseconds: () => number;\n\n constructor(options: SuspendingWasiPollSchedulerOptions) {\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 async pollOneOff(\n inPtr: number,\n outPtr: number,\n nsubscriptions: number,\n neventsPtr?: number\n ): Promise<number> {\n const memory = this.memory();\n if (!memory || nsubscriptions <= 0) {\n return this.fallbackPoll(inPtr, outPtr, nsubscriptions, neventsPtr);\n }\n\n const subscriptions = readSubscriptions(\n new DataView(memory.buffer),\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 // Re-derive the view each pass: memory.buffer detaches when the wasm\n // grows memory while we were suspended.\n const view = new DataView(memory.buffer);\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 await this.stdin.waitForReadableAsync(timeoutMilliseconds);\n } else if (timeoutMilliseconds !== undefined) {\n await new Promise((resolve) =>\n setTimeout(resolve, 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: WasiPollReadableState,\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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"}
|
|
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 WasiPollReadableState {\n availableBytes(): number;\n isClosed(): boolean;\n}\n\nexport interface WasiPollReadableSource extends WasiPollReadableState {\n waitForReadable(timeoutMilliseconds?: number): SharedInputReadiness;\n}\n\nexport interface SuspendingWasiPollReadableSource extends WasiPollReadableState {\n waitForReadableAsync(timeoutMilliseconds?: number): Promise<SharedInputReadiness>;\n}\n\n/**\n * Host-controlled suspension seam for the blocking (worker) scheduler. The\n * gate is consulted between waits: while the host holds the runtime paused,\n * `blockWhilePaused` parks the worker thread and — via the shared pausable\n * clock — excludes the parked time from every clock deadline, so pending\n * timeouts resume with their remaining time intact.\n */\nexport interface WasiPollPauseGate {\n blockWhilePaused(): void;\n}\n\n/** The awaited counterpart of {@link WasiPollPauseGate} for the JSPI path. */\nexport interface SuspendingWasiPollPauseGate {\n waitWhilePaused(): Promise<void>;\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 pauseGate?: WasiPollPauseGate;\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 pauseGate?: WasiPollPauseGate;\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 this.pauseGate = options.pauseGate;\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 this.pauseGate?.blockWhilePaused();\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\nexport interface SuspendingWasiPollSchedulerOptions {\n memory(): WebAssembly.Memory | undefined;\n stdin: SuspendingWasiPollReadableSource;\n fallbackPoll(\n inPtr: number,\n outPtr: number,\n nsubscriptions: number,\n neventsPtr?: number\n ): number;\n nowMilliseconds?(): number;\n pauseGate?: SuspendingWasiPollPauseGate;\n}\n\n/**\n * The JSPI (main-thread) counterpart of `WasiPollScheduler`: identical\n * subscription semantics, but the blocking waits become awaited promises so\n * the surrounding `poll_oneoff` import can be wrapped in\n * `WebAssembly.Suspending` and run without a worker or SharedArrayBuffer.\n */\nexport class SuspendingWasiPollScheduler {\n private readonly memory: SuspendingWasiPollSchedulerOptions[\"memory\"];\n private readonly stdin: SuspendingWasiPollReadableSource;\n private readonly fallbackPoll: SuspendingWasiPollSchedulerOptions[\"fallbackPoll\"];\n private readonly nowMilliseconds: () => number;\n private readonly pauseGate?: SuspendingWasiPollPauseGate;\n\n constructor(options: SuspendingWasiPollSchedulerOptions) {\n this.memory = options.memory;\n this.stdin = options.stdin;\n this.fallbackPoll = options.fallbackPoll;\n this.nowMilliseconds = options.nowMilliseconds ?? (() => performance.now());\n this.pauseGate = options.pauseGate;\n }\n\n async pollOneOff(\n inPtr: number,\n outPtr: number,\n nsubscriptions: number,\n neventsPtr?: number\n ): Promise<number> {\n const memory = this.memory();\n if (!memory || nsubscriptions <= 0) {\n return this.fallbackPoll(inPtr, outPtr, nsubscriptions, neventsPtr);\n }\n\n const subscriptions = readSubscriptions(\n new DataView(memory.buffer),\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 await this.pauseGate?.waitWhilePaused();\n const ready = readySubscriptions(subscriptions, this.stdin, this.nowMilliseconds());\n if (ready.length > 0) {\n // Re-derive the view each pass: memory.buffer detaches when the wasm\n // grows memory while we were suspended.\n const view = new DataView(memory.buffer);\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 await this.stdin.waitForReadableAsync(timeoutMilliseconds);\n } else if (timeoutMilliseconds !== undefined) {\n await new Promise((resolve) =>\n setTimeout(resolve, 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: WasiPollReadableState,\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":";;AAIA,MAAM,yBAAyB;AAC/B,MAAM,kBAAkB;AACxB,MAAM,iCAAiC;AA2DvC,IAAa,oBAAb,MAA+B;CAC7B;CACA;CACA;CACA;CACA;CACA,aAA8B,IAAI,WAChC,IAAI,kBAAkB,WAAW,iBAAiB,CACpD;CAEA,YAAY,SAAmC;EAC7C,KAAK,SAAS,QAAQ;EACtB,KAAK,QAAQ,QAAQ;EACrB,KAAK,eAAe,QAAQ;EAC5B,KAAK,kBAAkB,QAAQ,0BAA0B,YAAY,IAAI;EACzE,KAAK,YAAY,QAAQ;CAC3B;CAEA,WACE,OACA,QACA,gBACA,YACQ;EACR,MAAM,SAAS,KAAK,OAAO;EAC3B,IAAI,CAAC,UAAU,kBAAkB,GAC/B,OAAO,KAAK,aAAa,OAAO,QAAQ,gBAAgB,UAAU;EAGpE,MAAM,OAAO,IAAI,SAAS,OAAO,MAAM;EACvC,MAAM,gBAAgB,kBACpB,MACA,OACA,gBACA,KAAK,gBAAgB,CACvB;EACA,IAAI,kBAAkB,KAAA,GACpB,OAAO,KAAK,aAAa,OAAO,QAAQ,gBAAgB,UAAU;EAGpE,OAAO,MAAM;GACX,KAAK,WAAW,iBAAiB;GACjC,MAAM,QAAQ,mBAAmB,eAAe,KAAK,OAAO,KAAK,gBAAgB,CAAC;GAClF,IAAI,MAAM,SAAS,GAAG;IACpB,YAAY,MAAM,QAAQ,OAAO,KAAK,KAAK;IAC3C,IAAI,eAAe,KAAA,GACjB,KAAK,UAAU,YAAY,MAAM,QAAQ,IAAI;IAE/C,OAAO,KAAK;GACd;GAEA,MAAM,sBAAsB,iCAC1B,eACA,KAAK,gBAAgB,CACvB;GACA,IAAI,sBAAsB,aAAa,GACrC,KAAK,MAAM,gBAAgB,mBAAmB;QACzC,IAAI,wBAAwB,KAAA,GACjC,QAAQ,KACN,KAAK,YACL,GACA,GACA,KAAK,IAAI,qBAAqB,8BAA8B,CAC9D;QAEA,OAAO,KAAK,aAAa,OAAO,QAAQ,gBAAgB,UAAU;EAEtE;CACF;AACF;;;;;;;AAqBA,IAAa,8BAAb,MAAyC;CACvC;CACA;CACA;CACA;CACA;CAEA,YAAY,SAA6C;EACvD,KAAK,SAAS,QAAQ;EACtB,KAAK,QAAQ,QAAQ;EACrB,KAAK,eAAe,QAAQ;EAC5B,KAAK,kBAAkB,QAAQ,0BAA0B,YAAY,IAAI;EACzE,KAAK,YAAY,QAAQ;CAC3B;CAEA,MAAM,WACJ,OACA,QACA,gBACA,YACiB;EACjB,MAAM,SAAS,KAAK,OAAO;EAC3B,IAAI,CAAC,UAAU,kBAAkB,GAC/B,OAAO,KAAK,aAAa,OAAO,QAAQ,gBAAgB,UAAU;EAGpE,MAAM,gBAAgB,kBACpB,IAAI,SAAS,OAAO,MAAM,GAC1B,OACA,gBACA,KAAK,gBAAgB,CACvB;EACA,IAAI,kBAAkB,KAAA,GACpB,OAAO,KAAK,aAAa,OAAO,QAAQ,gBAAgB,UAAU;EAGpE,OAAO,MAAM;GACX,MAAM,KAAK,WAAW,gBAAgB;GACtC,MAAM,QAAQ,mBAAmB,eAAe,KAAK,OAAO,KAAK,gBAAgB,CAAC;GAClF,IAAI,MAAM,SAAS,GAAG;IAGpB,MAAM,OAAO,IAAI,SAAS,OAAO,MAAM;IACvC,YAAY,MAAM,QAAQ,OAAO,KAAK,KAAK;IAC3C,IAAI,eAAe,KAAA,GACjB,KAAK,UAAU,YAAY,MAAM,QAAQ,IAAI;IAE/C,OAAO,KAAK;GACd;GAEA,MAAM,sBAAsB,iCAC1B,eACA,KAAK,gBAAgB,CACvB;GACA,IAAI,sBAAsB,aAAa,GACrC,MAAM,KAAK,MAAM,qBAAqB,mBAAmB;QACpD,IAAI,wBAAwB,KAAA,GACjC,MAAM,IAAI,SAAS,YACjB,WAAW,SAAS,KAAK,IAAI,qBAAqB,8BAA8B,CAAC,CACnF;QAEA,OAAO,KAAK,aAAa,OAAO,QAAQ,gBAAgB,UAAU;EAEtE;CACF;AACF;AAEA,SAAS,kBACP,MACA,OACA,gBACA,iBACqC;CACrC,MAAM,gBAAyC,CAAC;CAChD,KAAK,IAAI,QAAQ,GAAG,QAAQ,gBAAgB,SAAS,GAAG;EACtD,MAAM,eAAe,KAAK,aAAa,WACrC,MACA,QAAQ,QAAQ,sBAClB;EACA,QAAQ,aAAa,WAArB;GACA,KAAK,KAAK;IACR,IAAI,CAAC,mBAAmB,aAAa,OAAO,GAC1C;IAEF,cAAc,KAAK;KACjB,MAAM;KACN,UAAU,aAAa;KACvB,SAAS,aAAa;KACtB,sBAAsB,0BAA0B,cAAc,eAAe;IAC/E,CAAC;IACD;GACF,KAAK,KAAK;IACR,IAAI,aAAa,YAAY,KAAK,UAChC;IAEF,cAAc,KAAK;KACjB,MAAM;KACN,UAAU,aAAa;KACvB,IAAI,aAAa;IACnB,CAAC;IACD;GACF,SACE;EACF;CACF;CACA,OAAO;AACT;AAEA,SAAS,mBACP,SACS;CACT,OAAO,YAAY,KAAK,qBAAqB,YAAY,KAAK;AAChE;AAEA,SAAS,iCACP,eACA,iBACoB;CACpB,IAAI;CACJ,KAAK,MAAM,gBAAgB,eAAe;EACxC,IAAI,aAAa,SAAS,SACxB;EAEF,MAAM,YAAY,2BAA2B,cAAc,eAAe;EAC1E,sBAAsB,wBAAwB,KAAA,IAC1C,YACA,KAAK,IAAI,qBAAqB,SAAS;CAC7C;CACA,OAAO;AACT;AAEA,SAAS,mBACP,eACA,OACA,iBACyB;CACzB,OAAO,cAAc,QAAQ,iBAAiB;EAC5C,QAAQ,aAAa,MAArB;GACA,KAAK,SACH,OAAO,2BAA2B,cAAc,eAAe,KAAK;GACtE,KAAK,UACH,OAAO,MAAM,eAAe,IAAI,KAAK,MAAM,SAAS;EACtD;CACF,CAAC;AACH;AAEA,SAAS,sBACP,eACS;CACT,OAAO,cAAc,MAAM,iBAAiB,aAAa,SAAS,QAAQ;AAC5E;AAEA,SAAS,2BACP,cACA,iBACQ;CACR,OAAO,KAAK,IAAI,GAAG,aAAa,uBAAuB,wBACrD,aAAa,SACb,eACF,CAAC;AACH;AAEA,SAAS,0BACP,cACA,iBACQ;CACR,KAAK,aAAa,QAAQ,KAAK,8CAA8C,GAC3E,OAAO,OAAO,aAAa,OAAO,IAAI;CAExC,OAAO,wBAAwB,aAAa,SAAS,eAAe,IAChE,OAAO,aAAa,OAAO,IAAI;AACrC;AAEA,SAAS,wBACP,SACA,iBACQ;CACR,IAAI,YAAY,KAAK,kBACnB,OAAO,KAAK,IAAI;CAElB,OAAO;AACT;AAEA,SAAS,YACP,MACA,QACA,eACA,OACM;CACN,cAAc,SAAS,cAAc,UAAU;EAC7C,MAAM,YAAY,aAAa,SAAS,UACpC,KAAK,kBACL,KAAK;EACT,MAAM,SAAS,SAAS,QAAQ;EAChC,IAAI,KAAK,MACP,aAAa,UACb,KAAK,eACL,SACF,CAAC,CAAC,YAAY,MAAM,MAAM;EAC1B,IAAI,aAAa,SAAS,UAAU;GAClC,MAAM,iBAAiB,KAAK,IAAI,GAAG,MAAM,eAAe,CAAC;GACzD,KAAK,aAAa,SAAS,IAAI,OAAO,cAAc,GAAG,IAAI;GAC3D,IAAI,mBAAmB,KAAK,MAAM,SAAS,GACzC,KAAK,UAAU,SAAS,IAAI,KAAK,kCAAkC,IAAI;EAE3E;CACF,CAAC;AACH"}
|
|
@@ -30,6 +30,7 @@ function resolveWasmEngineCapabilities(signals = collectWasmEngineProbeSignals()
|
|
|
30
30
|
* `SWIFTTUI_STACK_LEAN_PROFILE` (or a tuning override) always wins.
|
|
31
31
|
*/
|
|
32
32
|
function stackProfileEnvironmentDefaults(capabilities) {
|
|
33
|
+
if (capabilities.engine === "v8") return { SWIFTTUI_STACK_LEAN_PROFILE: "0" };
|
|
33
34
|
return {};
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WasmEngineCapabilities.js","names":[],"sources":["../../../src/wasi/WasmEngineCapabilities.ts"],"sourcesContent":["// Runtime detection of the browser's JS/wasm engine family and the wasm\n// capabilities that decide how the SwiftTUI WASI runtime should execute.\n//\n// JavaScriptCore runs wasm calls on the host thread's native stack, and\n// Darwin worker threads get ~1/16 of the main-thread stack budget, so\n// SwiftTUI's WASI build defaults to its stack-lean resolve profile\n// (`SWIFTTUI_STACK_LEAN_PROFILE`, depth-capped chunked resolve). That profile\n// costs steady-state pipeline time, and engines with roomy worker stacks\n// don't need it. Detection is deliberately asymmetric: a wrongly applied lean\n// profile only costs speed, while wrongly disabling it on a small-stack\n// engine overflows the wasm stack and kills the app — so lean stays\n// recommended unless the engine is confidently V8, the only family with a\n// measured, comfortable worker budget. Gecko is measured (Firefox, live,\n// 2026-07) to NOT fit the non-lean shape in its worker: it must keep the\n// lean profile in worker mode.\n//\n// Engine classification reads Error mechanics rather than user-agent\n// strings: V8 formats stack frames as ` at fn (url)`, JSC and Gecko as\n// `fn@url`, and the two are split by engine-specific Error instance\n// properties (Gecko `fileName`, JSC `sourceURL`). Trunk WebKit (STP ≥ 238)\n// no longer exposes `sourceURL` on constructed Errors, so JSC\n// classification there rides the `fn@url` stack-shape fallback. Non-browser\n// JSC hosts that emulate V8 stack frames for Node compatibility (e.g. Bun)\n// classify as \"v8\"; the probe targets browser engines, where the formats\n// don't cross.\n\nexport type WasmEngineFamily = \"v8\" | \"jsc\" | \"gecko\" | \"unknown\";\n\nexport interface WasmEngineProbeSignals {\n errorStack: string;\n errorHasGeckoFileName: boolean;\n errorHasJSCSourceURL: boolean;\n wasmSuspendingType: string;\n wasmPromisingType: string;\n}\n\nexport interface WasmEngineCapabilities {\n engine: WasmEngineFamily;\n /**\n * WebAssembly JavaScript Promise Integration (`WebAssembly.Suspending` +\n * `WebAssembly.promising`). When true, the main-thread execution mode\n * (`MainThreadWasmExecutor`) can suspend on stdin/timers instead of\n * blocking a worker on `Atomics.wait`.\n */\n supportsJSPI: boolean;\n /**\n * Whether the SwiftTUI WASI build should keep its stack-lean resolve\n * profile on this engine.\n */\n stackLeanRecommended: boolean;\n}\n\nexport function collectWasmEngineProbeSignals(): WasmEngineProbeSignals {\n const probe = new Error(\"wasm-engine-probe\");\n const wasm = (\n globalThis as {\n WebAssembly?: { Suspending?: unknown; promising?: unknown };\n }\n ).WebAssembly;\n return {\n errorStack: typeof probe.stack === \"string\" ? probe.stack : \"\",\n errorHasGeckoFileName: \"fileName\" in probe,\n errorHasJSCSourceURL: \"sourceURL\" in probe,\n wasmSuspendingType: typeof wasm?.Suspending,\n wasmPromisingType: typeof wasm?.promising,\n };\n}\n\nexport function classifyWasmEngineFamily(\n signals: WasmEngineProbeSignals\n): WasmEngineFamily {\n if (/^\\s*at /m.test(signals.errorStack)) {\n return \"v8\";\n }\n if (signals.errorHasGeckoFileName) {\n return \"gecko\";\n }\n if (signals.errorHasJSCSourceURL || /^[^\\n]*@/m.test(signals.errorStack)) {\n return \"jsc\";\n }\n return \"unknown\";\n}\n\nexport function resolveWasmEngineCapabilities(\n signals: WasmEngineProbeSignals = collectWasmEngineProbeSignals()\n): WasmEngineCapabilities {\n const engine = classifyWasmEngineFamily(signals);\n return {\n engine,\n supportsJSPI:\n signals.wasmSuspendingType === \"function\" &&\n signals.wasmPromisingType === \"function\",\n stackLeanRecommended: engine !== \"v8\",\n };\n}\n\n/**\n * WASI environment defaults implied by the engine capabilities. Spread these\n * *before* caller-provided environment entries so an explicit\n * `SWIFTTUI_STACK_LEAN_PROFILE` (or a tuning override) always wins.\n */\nexport function stackProfileEnvironmentDefaults(\n capabilities: WasmEngineCapabilities\n): Record<string, string> {\n //
|
|
1
|
+
{"version":3,"file":"WasmEngineCapabilities.js","names":[],"sources":["../../../src/wasi/WasmEngineCapabilities.ts"],"sourcesContent":["// Runtime detection of the browser's JS/wasm engine family and the wasm\n// capabilities that decide how the SwiftTUI WASI runtime should execute.\n//\n// JavaScriptCore runs wasm calls on the host thread's native stack, and\n// Darwin worker threads get ~1/16 of the main-thread stack budget, so\n// SwiftTUI's WASI build defaults to its stack-lean resolve profile\n// (`SWIFTTUI_STACK_LEAN_PROFILE`, depth-capped chunked resolve). That profile\n// costs steady-state pipeline time, and engines with roomy worker stacks\n// don't need it. Detection is deliberately asymmetric: a wrongly applied lean\n// profile only costs speed, while wrongly disabling it on a small-stack\n// engine overflows the wasm stack and kills the app — so lean stays\n// recommended unless the engine is confidently V8, the only family with a\n// measured, comfortable worker budget. Gecko is measured (Firefox, live,\n// 2026-07) to NOT fit the non-lean shape in its worker: it must keep the\n// lean profile in worker mode.\n//\n// Engine classification reads Error mechanics rather than user-agent\n// strings: V8 formats stack frames as ` at fn (url)`, JSC and Gecko as\n// `fn@url`, and the two are split by engine-specific Error instance\n// properties (Gecko `fileName`, JSC `sourceURL`). Trunk WebKit (STP ≥ 238)\n// no longer exposes `sourceURL` on constructed Errors, so JSC\n// classification there rides the `fn@url` stack-shape fallback. Non-browser\n// JSC hosts that emulate V8 stack frames for Node compatibility (e.g. Bun)\n// classify as \"v8\"; the probe targets browser engines, where the formats\n// don't cross.\n\nexport type WasmEngineFamily = \"v8\" | \"jsc\" | \"gecko\" | \"unknown\";\n\nexport interface WasmEngineProbeSignals {\n errorStack: string;\n errorHasGeckoFileName: boolean;\n errorHasJSCSourceURL: boolean;\n wasmSuspendingType: string;\n wasmPromisingType: string;\n}\n\nexport interface WasmEngineCapabilities {\n engine: WasmEngineFamily;\n /**\n * WebAssembly JavaScript Promise Integration (`WebAssembly.Suspending` +\n * `WebAssembly.promising`). When true, the main-thread execution mode\n * (`MainThreadWasmExecutor`) can suspend on stdin/timers instead of\n * blocking a worker on `Atomics.wait`.\n */\n supportsJSPI: boolean;\n /**\n * Whether the SwiftTUI WASI build should keep its stack-lean resolve\n * profile on this engine.\n */\n stackLeanRecommended: boolean;\n}\n\nexport function collectWasmEngineProbeSignals(): WasmEngineProbeSignals {\n const probe = new Error(\"wasm-engine-probe\");\n const wasm = (\n globalThis as {\n WebAssembly?: { Suspending?: unknown; promising?: unknown };\n }\n ).WebAssembly;\n return {\n errorStack: typeof probe.stack === \"string\" ? probe.stack : \"\",\n errorHasGeckoFileName: \"fileName\" in probe,\n errorHasJSCSourceURL: \"sourceURL\" in probe,\n wasmSuspendingType: typeof wasm?.Suspending,\n wasmPromisingType: typeof wasm?.promising,\n };\n}\n\nexport function classifyWasmEngineFamily(\n signals: WasmEngineProbeSignals\n): WasmEngineFamily {\n if (/^\\s*at /m.test(signals.errorStack)) {\n return \"v8\";\n }\n if (signals.errorHasGeckoFileName) {\n return \"gecko\";\n }\n if (signals.errorHasJSCSourceURL || /^[^\\n]*@/m.test(signals.errorStack)) {\n return \"jsc\";\n }\n return \"unknown\";\n}\n\nexport function resolveWasmEngineCapabilities(\n signals: WasmEngineProbeSignals = collectWasmEngineProbeSignals()\n): WasmEngineCapabilities {\n const engine = classifyWasmEngineFamily(signals);\n return {\n engine,\n supportsJSPI:\n signals.wasmSuspendingType === \"function\" &&\n signals.wasmPromisingType === \"function\",\n stackLeanRecommended: engine !== \"v8\",\n };\n}\n\n/**\n * WASI environment defaults implied by the engine capabilities. Spread these\n * *before* caller-provided environment entries so an explicit\n * `SWIFTTUI_STACK_LEAN_PROFILE` (or a tuning override) always wins.\n */\nexport function stackProfileEnvironmentDefaults(\n capabilities: WasmEngineCapabilities\n): Record<string, string> {\n // V8 workers run non-lean by default: the measured worker stack budget\n // fits the full-depth resolve, and per-frame pipeline cost roughly\n // halves versus the lean profile. The 0.1.9 regression that forced the\n // lean-everywhere hold was NOT lean-vs-non-lean publication behavior —\n // it was completed-frame *disposal* under supersession (visual-only\n // drops + pre-start cancels saturating at the starvation floor), fixed\n // by the `async-no-cancel` render-mode default in `BrowserWASIBridge`;\n // live non-lean + async-no-cancel measures the same distinct-generation\n // coverage as lean at ~2x less per-frame CPU.\n //\n // JSC stays lean (Darwin worker threads get ~1/16 of the main-thread\n // stack). Gecko stays lean by *measurement*, not caution: Firefox live\n // (2026-07) overflows the non-lean shape in its worker.\n if (capabilities.engine === \"v8\") {\n return { SWIFTTUI_STACK_LEAN_PROFILE: \"0\" };\n }\n return {};\n}\n\n/**\n * Environment defaults for the main-thread (JSPI) execution mode, where the\n * wasm runs on the page's thread and gets its far larger stack budget\n * (measured ~12.7× the worker's on trunk WebKit).\n */\nexport function mainThreadStackProfileEnvironmentDefaults(\n capabilities: WasmEngineCapabilities\n): Record<string, string> {\n // HOLD: the main-thread (JSPI) stack budget fits non-lean on JSC and V8\n // (measured), but the JSC main-thread lane has not been soaked non-lean\n // in production, and JSPI slices the native stack — Safari 27's depth\n // budgets must be re-measured per release before this default can flip.\n // Callers can still force the profile via `SWIFTTUI_STACK_LEAN_PROFILE`.\n void capabilities;\n return {};\n}\n\nexport interface JSPIConstructors {\n Suspending: new (fn: (...args: never[]) => unknown) => unknown;\n promising: (fn: unknown) => (...args: unknown[]) => Promise<unknown>;\n}\n\n/** Typed access to the JSPI surface, or undefined where unsupported. */\nexport function jspiConstructors(): JSPIConstructors | undefined {\n const wasm = globalThis.WebAssembly as unknown as Partial<JSPIConstructors> | undefined;\n if (\n typeof wasm?.Suspending === \"function\" &&\n typeof wasm?.promising === \"function\"\n ) {\n return wasm as JSPIConstructors;\n }\n return undefined;\n}\n"],"mappings":";AAoDA,SAAgB,gCAAwD;CACtE,MAAM,wBAAQ,IAAI,MAAM,mBAAmB;CAC3C,MAAM,OACJ,WAGA;CACF,OAAO;EACL,YAAY,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ;EAC5D,uBAAuB,cAAc;EACrC,sBAAsB,eAAe;EACrC,oBAAoB,OAAO,MAAM;EACjC,mBAAmB,OAAO,MAAM;CAClC;AACF;AAEA,SAAgB,yBACd,SACkB;CAClB,IAAI,WAAW,KAAK,QAAQ,UAAU,GACpC,OAAO;CAET,IAAI,QAAQ,uBACV,OAAO;CAET,IAAI,QAAQ,wBAAwB,YAAY,KAAK,QAAQ,UAAU,GACrE,OAAO;CAET,OAAO;AACT;AAEA,SAAgB,8BACd,UAAkC,8BAA8B,GACxC;CACxB,MAAM,SAAS,yBAAyB,OAAO;CAC/C,OAAO;EACL;EACA,cACE,QAAQ,uBAAuB,cAC/B,QAAQ,sBAAsB;EAChC,sBAAsB,WAAW;CACnC;AACF;;;;;;AAOA,SAAgB,gCACd,cACwB;CAcxB,IAAI,aAAa,WAAW,MAC1B,OAAO,EAAE,6BAA6B,IAAI;CAE5C,OAAO,CAAC;AACV;;;;;;AAOA,SAAgB,0CACd,cACwB;CAOxB,OAAO,CAAC;AACV;;AAQA,SAAgB,mBAAiD;CAC/D,MAAM,OAAO,WAAW;CACxB,IACE,OAAO,MAAM,eAAe,cAC5B,OAAO,MAAM,cAAc,YAE3B,OAAO;AAGX"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//#region src/wasi/WasmRuntimePause.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A monotonic clock that excludes time spent paused. `nowMilliseconds()`
|
|
4
|
+
* mirrors `performance.now()` minus every accumulated pause, so WASI clock
|
|
5
|
+
* reads (`clock_time_get`) and `poll_oneoff` deadline math both observe a
|
|
6
|
+
* frozen clock across a pause: pending timeouts keep their remaining time,
|
|
7
|
+
* no expired-deadline catch-up burst fires on resume, and app-side animation
|
|
8
|
+
* clocks do not jump.
|
|
9
|
+
*/
|
|
10
|
+
declare class PausableMonotonicClock {
|
|
11
|
+
private readonly rawNowMilliseconds;
|
|
12
|
+
private accumulatedPauseMilliseconds;
|
|
13
|
+
constructor(rawNowMilliseconds?: () => number);
|
|
14
|
+
nowMilliseconds(): number;
|
|
15
|
+
nowNanoseconds(): bigint;
|
|
16
|
+
addPausedMilliseconds(milliseconds: number): void;
|
|
17
|
+
get pausedMilliseconds(): number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Creates the shared pause cell the main thread uses to suspend a wasm scene
|
|
21
|
+
* worker. One Int32 slot: 0 = running, 1 = paused. The worker parks on the
|
|
22
|
+
* cell between `poll_oneoff` waits, so a paused scene costs zero CPU.
|
|
23
|
+
*/
|
|
24
|
+
declare function createWasmPauseCell(): SharedArrayBuffer;
|
|
25
|
+
declare function setWasmPauseCellPaused(cell: SharedArrayBuffer, paused: boolean): void;
|
|
26
|
+
declare function isWasmPauseCellPaused(cell: SharedArrayBuffer): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Worker-side pause gate: blocks the calling (worker) thread while the shared
|
|
29
|
+
* pause cell is set, then credits the parked wall time to the pausable clock.
|
|
30
|
+
* Must never run on a browser main thread — `Atomics.wait` is worker-only.
|
|
31
|
+
*/
|
|
32
|
+
declare class WorkerWasmPauseGate {
|
|
33
|
+
private readonly flags;
|
|
34
|
+
private readonly clock;
|
|
35
|
+
private readonly rawNowMilliseconds;
|
|
36
|
+
constructor(cell: SharedArrayBuffer, clock: PausableMonotonicClock, rawNowMilliseconds?: () => number);
|
|
37
|
+
blockWhilePaused(): void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Main-thread (JSPI) pause gate: the awaited counterpart of
|
|
41
|
+
* `WorkerWasmPauseGate`. While paused, `waitWhilePaused` suspends on a promise
|
|
42
|
+
* that `setPaused(false)` resolves, then credits the parked time to the clock.
|
|
43
|
+
*/
|
|
44
|
+
declare class MainThreadWasmPauseGate {
|
|
45
|
+
private readonly clock;
|
|
46
|
+
private readonly rawNowMilliseconds;
|
|
47
|
+
private paused;
|
|
48
|
+
private resumeWaiters;
|
|
49
|
+
constructor(clock: PausableMonotonicClock, rawNowMilliseconds?: () => number);
|
|
50
|
+
get isPaused(): boolean;
|
|
51
|
+
setPaused(paused: boolean): void;
|
|
52
|
+
waitWhilePaused(): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Redirects the WASI `clock_time_get` import's MONOTONIC reads through the
|
|
56
|
+
* pausable clock so app-side time agrees with the pause-aware `poll_oneoff`
|
|
57
|
+
* deadline math. Non-monotonic clocks (REALTIME) keep the shim's behavior —
|
|
58
|
+
* wall-clock time genuinely advances across a pause.
|
|
59
|
+
*/
|
|
60
|
+
declare function installPausableClockTimeGet(wasiImport: Record<string, unknown>, memory: () => WebAssembly.Memory | undefined, clock: PausableMonotonicClock): void;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { MainThreadWasmPauseGate, PausableMonotonicClock, WorkerWasmPauseGate, createWasmPauseCell, installPausableClockTimeGet, isWasmPauseCellPaused, setWasmPauseCellPaused };
|
|
63
|
+
//# sourceMappingURL=WasmRuntimePause.d.ts.map
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { wasi } from "@bjorn3/browser_wasi_shim";
|
|
2
|
+
//#region src/wasi/WasmRuntimePause.ts
|
|
3
|
+
const pausedFlagValue = 1;
|
|
4
|
+
/**
|
|
5
|
+
* A monotonic clock that excludes time spent paused. `nowMilliseconds()`
|
|
6
|
+
* mirrors `performance.now()` minus every accumulated pause, so WASI clock
|
|
7
|
+
* reads (`clock_time_get`) and `poll_oneoff` deadline math both observe a
|
|
8
|
+
* frozen clock across a pause: pending timeouts keep their remaining time,
|
|
9
|
+
* no expired-deadline catch-up burst fires on resume, and app-side animation
|
|
10
|
+
* clocks do not jump.
|
|
11
|
+
*/
|
|
12
|
+
var PausableMonotonicClock = class {
|
|
13
|
+
rawNowMilliseconds;
|
|
14
|
+
accumulatedPauseMilliseconds = 0;
|
|
15
|
+
constructor(rawNowMilliseconds = () => performance.now()) {
|
|
16
|
+
this.rawNowMilliseconds = rawNowMilliseconds;
|
|
17
|
+
}
|
|
18
|
+
nowMilliseconds() {
|
|
19
|
+
return this.rawNowMilliseconds() - this.accumulatedPauseMilliseconds;
|
|
20
|
+
}
|
|
21
|
+
nowNanoseconds() {
|
|
22
|
+
return BigInt(Math.round(this.nowMilliseconds() * 1e6));
|
|
23
|
+
}
|
|
24
|
+
addPausedMilliseconds(milliseconds) {
|
|
25
|
+
if (milliseconds > 0) this.accumulatedPauseMilliseconds += milliseconds;
|
|
26
|
+
}
|
|
27
|
+
get pausedMilliseconds() {
|
|
28
|
+
return this.accumulatedPauseMilliseconds;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Creates the shared pause cell the main thread uses to suspend a wasm scene
|
|
33
|
+
* worker. One Int32 slot: 0 = running, 1 = paused. The worker parks on the
|
|
34
|
+
* cell between `poll_oneoff` waits, so a paused scene costs zero CPU.
|
|
35
|
+
*/
|
|
36
|
+
function createWasmPauseCell() {
|
|
37
|
+
if (typeof SharedArrayBuffer === "undefined") throw new Error("SharedArrayBuffer is unavailable. Serve the app with COOP/COEP headers so worker-mode pause can work.");
|
|
38
|
+
return new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
|
|
39
|
+
}
|
|
40
|
+
function setWasmPauseCellPaused(cell, paused) {
|
|
41
|
+
const flags = new Int32Array(cell);
|
|
42
|
+
Atomics.store(flags, 0, paused ? pausedFlagValue : 0);
|
|
43
|
+
if (!paused) Atomics.notify(flags, 0);
|
|
44
|
+
}
|
|
45
|
+
function isWasmPauseCellPaused(cell) {
|
|
46
|
+
return Atomics.load(new Int32Array(cell), 0) === pausedFlagValue;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Worker-side pause gate: blocks the calling (worker) thread while the shared
|
|
50
|
+
* pause cell is set, then credits the parked wall time to the pausable clock.
|
|
51
|
+
* Must never run on a browser main thread — `Atomics.wait` is worker-only.
|
|
52
|
+
*/
|
|
53
|
+
var WorkerWasmPauseGate = class {
|
|
54
|
+
flags;
|
|
55
|
+
clock;
|
|
56
|
+
rawNowMilliseconds;
|
|
57
|
+
constructor(cell, clock, rawNowMilliseconds = () => performance.now()) {
|
|
58
|
+
this.flags = new Int32Array(cell);
|
|
59
|
+
this.clock = clock;
|
|
60
|
+
this.rawNowMilliseconds = rawNowMilliseconds;
|
|
61
|
+
}
|
|
62
|
+
blockWhilePaused() {
|
|
63
|
+
let pausedMilliseconds = 0;
|
|
64
|
+
while (Atomics.load(this.flags, 0) === pausedFlagValue) {
|
|
65
|
+
const parkStart = this.rawNowMilliseconds();
|
|
66
|
+
Atomics.wait(this.flags, 0, pausedFlagValue);
|
|
67
|
+
pausedMilliseconds += this.rawNowMilliseconds() - parkStart;
|
|
68
|
+
}
|
|
69
|
+
this.clock.addPausedMilliseconds(pausedMilliseconds);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Main-thread (JSPI) pause gate: the awaited counterpart of
|
|
74
|
+
* `WorkerWasmPauseGate`. While paused, `waitWhilePaused` suspends on a promise
|
|
75
|
+
* that `setPaused(false)` resolves, then credits the parked time to the clock.
|
|
76
|
+
*/
|
|
77
|
+
var MainThreadWasmPauseGate = class {
|
|
78
|
+
clock;
|
|
79
|
+
rawNowMilliseconds;
|
|
80
|
+
paused = false;
|
|
81
|
+
resumeWaiters = [];
|
|
82
|
+
constructor(clock, rawNowMilliseconds = () => performance.now()) {
|
|
83
|
+
this.clock = clock;
|
|
84
|
+
this.rawNowMilliseconds = rawNowMilliseconds;
|
|
85
|
+
}
|
|
86
|
+
get isPaused() {
|
|
87
|
+
return this.paused;
|
|
88
|
+
}
|
|
89
|
+
setPaused(paused) {
|
|
90
|
+
if (this.paused === paused) return;
|
|
91
|
+
this.paused = paused;
|
|
92
|
+
if (!paused) {
|
|
93
|
+
const waiters = this.resumeWaiters;
|
|
94
|
+
this.resumeWaiters = [];
|
|
95
|
+
for (const resume of waiters) resume();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async waitWhilePaused() {
|
|
99
|
+
let pausedMilliseconds = 0;
|
|
100
|
+
while (this.paused) {
|
|
101
|
+
const parkStart = this.rawNowMilliseconds();
|
|
102
|
+
await new Promise((resolve) => {
|
|
103
|
+
this.resumeWaiters.push(resolve);
|
|
104
|
+
});
|
|
105
|
+
pausedMilliseconds += this.rawNowMilliseconds() - parkStart;
|
|
106
|
+
}
|
|
107
|
+
this.clock.addPausedMilliseconds(pausedMilliseconds);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Redirects the WASI `clock_time_get` import's MONOTONIC reads through the
|
|
112
|
+
* pausable clock so app-side time agrees with the pause-aware `poll_oneoff`
|
|
113
|
+
* deadline math. Non-monotonic clocks (REALTIME) keep the shim's behavior —
|
|
114
|
+
* wall-clock time genuinely advances across a pause.
|
|
115
|
+
*/
|
|
116
|
+
function installPausableClockTimeGet(wasiImport, memory, clock) {
|
|
117
|
+
const original = wasiImport.clock_time_get;
|
|
118
|
+
if (typeof original !== "function") return;
|
|
119
|
+
wasiImport.clock_time_get = (clockid, precision, timePtr) => {
|
|
120
|
+
if (clockid !== wasi.CLOCKID_MONOTONIC) return original(clockid, precision, timePtr);
|
|
121
|
+
const currentMemory = memory();
|
|
122
|
+
if (!currentMemory) return original(clockid, precision, timePtr);
|
|
123
|
+
new DataView(currentMemory.buffer).setBigUint64(timePtr, clock.nowNanoseconds(), true);
|
|
124
|
+
return wasi.ERRNO_SUCCESS;
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
export { MainThreadWasmPauseGate, PausableMonotonicClock, WorkerWasmPauseGate, createWasmPauseCell, installPausableClockTimeGet, isWasmPauseCellPaused, setWasmPauseCellPaused };
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=WasmRuntimePause.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WasmRuntimePause.js","names":[],"sources":["../../../src/wasi/WasmRuntimePause.ts"],"sourcesContent":["import { wasi } from \"@bjorn3/browser_wasi_shim\";\n\nconst pausedFlagValue = 1;\n\n/**\n * A monotonic clock that excludes time spent paused. `nowMilliseconds()`\n * mirrors `performance.now()` minus every accumulated pause, so WASI clock\n * reads (`clock_time_get`) and `poll_oneoff` deadline math both observe a\n * frozen clock across a pause: pending timeouts keep their remaining time,\n * no expired-deadline catch-up burst fires on resume, and app-side animation\n * clocks do not jump.\n */\nexport class PausableMonotonicClock {\n private readonly rawNowMilliseconds: () => number;\n private accumulatedPauseMilliseconds = 0;\n\n constructor(rawNowMilliseconds: () => number = () => performance.now()) {\n this.rawNowMilliseconds = rawNowMilliseconds;\n }\n\n nowMilliseconds(): number {\n return this.rawNowMilliseconds() - this.accumulatedPauseMilliseconds;\n }\n\n nowNanoseconds(): bigint {\n return BigInt(Math.round(this.nowMilliseconds() * 1e6));\n }\n\n addPausedMilliseconds(\n milliseconds: number\n ): void {\n if (milliseconds > 0) {\n this.accumulatedPauseMilliseconds += milliseconds;\n }\n }\n\n get pausedMilliseconds(): number {\n return this.accumulatedPauseMilliseconds;\n }\n}\n\n/**\n * Creates the shared pause cell the main thread uses to suspend a wasm scene\n * worker. One Int32 slot: 0 = running, 1 = paused. The worker parks on the\n * cell between `poll_oneoff` waits, so a paused scene costs zero CPU.\n */\nexport function createWasmPauseCell(): SharedArrayBuffer {\n if (typeof SharedArrayBuffer === \"undefined\") {\n throw new Error(\n \"SharedArrayBuffer is unavailable. Serve the app with COOP/COEP headers so worker-mode pause can work.\"\n );\n }\n return new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);\n}\n\nexport function setWasmPauseCellPaused(\n cell: SharedArrayBuffer,\n paused: boolean\n): void {\n const flags = new Int32Array(cell);\n Atomics.store(flags, 0, paused ? pausedFlagValue : 0);\n if (!paused) {\n Atomics.notify(flags, 0);\n }\n}\n\nexport function isWasmPauseCellPaused(\n cell: SharedArrayBuffer\n): boolean {\n return Atomics.load(new Int32Array(cell), 0) === pausedFlagValue;\n}\n\n/**\n * Worker-side pause gate: blocks the calling (worker) thread while the shared\n * pause cell is set, then credits the parked wall time to the pausable clock.\n * Must never run on a browser main thread — `Atomics.wait` is worker-only.\n */\nexport class WorkerWasmPauseGate {\n private readonly flags: Int32Array;\n private readonly clock: PausableMonotonicClock;\n private readonly rawNowMilliseconds: () => number;\n\n constructor(\n cell: SharedArrayBuffer,\n clock: PausableMonotonicClock,\n rawNowMilliseconds: () => number = () => performance.now()\n ) {\n this.flags = new Int32Array(cell);\n this.clock = clock;\n this.rawNowMilliseconds = rawNowMilliseconds;\n }\n\n blockWhilePaused(): void {\n let pausedMilliseconds = 0;\n while (Atomics.load(this.flags, 0) === pausedFlagValue) {\n const parkStart = this.rawNowMilliseconds();\n Atomics.wait(this.flags, 0, pausedFlagValue);\n pausedMilliseconds += this.rawNowMilliseconds() - parkStart;\n }\n this.clock.addPausedMilliseconds(pausedMilliseconds);\n }\n}\n\n/**\n * Main-thread (JSPI) pause gate: the awaited counterpart of\n * `WorkerWasmPauseGate`. While paused, `waitWhilePaused` suspends on a promise\n * that `setPaused(false)` resolves, then credits the parked time to the clock.\n */\nexport class MainThreadWasmPauseGate {\n private readonly clock: PausableMonotonicClock;\n private readonly rawNowMilliseconds: () => number;\n private paused = false;\n private resumeWaiters: Array<() => void> = [];\n\n constructor(\n clock: PausableMonotonicClock,\n rawNowMilliseconds: () => number = () => performance.now()\n ) {\n this.clock = clock;\n this.rawNowMilliseconds = rawNowMilliseconds;\n }\n\n get isPaused(): boolean {\n return this.paused;\n }\n\n setPaused(\n paused: boolean\n ): void {\n if (this.paused === paused) {\n return;\n }\n this.paused = paused;\n if (!paused) {\n const waiters = this.resumeWaiters;\n this.resumeWaiters = [];\n for (const resume of waiters) {\n resume();\n }\n }\n }\n\n async waitWhilePaused(): Promise<void> {\n let pausedMilliseconds = 0;\n while (this.paused) {\n const parkStart = this.rawNowMilliseconds();\n await new Promise<void>((resolve) => {\n this.resumeWaiters.push(resolve);\n });\n pausedMilliseconds += this.rawNowMilliseconds() - parkStart;\n }\n this.clock.addPausedMilliseconds(pausedMilliseconds);\n }\n}\n\ntype ClockTimeGet = (clockid: number, precision: bigint, timePtr: number) => number;\n\n/**\n * Redirects the WASI `clock_time_get` import's MONOTONIC reads through the\n * pausable clock so app-side time agrees with the pause-aware `poll_oneoff`\n * deadline math. Non-monotonic clocks (REALTIME) keep the shim's behavior —\n * wall-clock time genuinely advances across a pause.\n */\nexport function installPausableClockTimeGet(\n wasiImport: Record<string, unknown>,\n memory: () => WebAssembly.Memory | undefined,\n clock: PausableMonotonicClock\n): void {\n const original = wasiImport.clock_time_get as ClockTimeGet | undefined;\n if (typeof original !== \"function\") {\n return;\n }\n wasiImport.clock_time_get = (\n clockid: number,\n precision: bigint,\n timePtr: number\n ): number => {\n if (clockid !== wasi.CLOCKID_MONOTONIC) {\n return original(clockid, precision, timePtr);\n }\n const currentMemory = memory();\n if (!currentMemory) {\n return original(clockid, precision, timePtr);\n }\n new DataView(currentMemory.buffer).setBigUint64(timePtr, clock.nowNanoseconds(), true);\n return wasi.ERRNO_SUCCESS;\n };\n}\n"],"mappings":";;AAEA,MAAM,kBAAkB;;;;;;;;;AAUxB,IAAa,yBAAb,MAAoC;CAClC;CACA,+BAAuC;CAEvC,YAAY,2BAAyC,YAAY,IAAI,GAAG;EACtE,KAAK,qBAAqB;CAC5B;CAEA,kBAA0B;EACxB,OAAO,KAAK,mBAAmB,IAAI,KAAK;CAC1C;CAEA,iBAAyB;EACvB,OAAO,OAAO,KAAK,MAAM,KAAK,gBAAgB,IAAI,GAAG,CAAC;CACxD;CAEA,sBACE,cACM;EACN,IAAI,eAAe,GACjB,KAAK,gCAAgC;CAEzC;CAEA,IAAI,qBAA6B;EAC/B,OAAO,KAAK;CACd;AACF;;;;;;AAOA,SAAgB,sBAAyC;CACvD,IAAI,OAAO,sBAAsB,aAC/B,MAAM,IAAI,MACR,uGACF;CAEF,OAAO,IAAI,kBAAkB,WAAW,iBAAiB;AAC3D;AAEA,SAAgB,uBACd,MACA,QACM;CACN,MAAM,QAAQ,IAAI,WAAW,IAAI;CACjC,QAAQ,MAAM,OAAO,GAAG,SAAS,kBAAkB,CAAC;CACpD,IAAI,CAAC,QACH,QAAQ,OAAO,OAAO,CAAC;AAE3B;AAEA,SAAgB,sBACd,MACS;CACT,OAAO,QAAQ,KAAK,IAAI,WAAW,IAAI,GAAG,CAAC,MAAM;AACnD;;;;;;AAOA,IAAa,sBAAb,MAAiC;CAC/B;CACA;CACA;CAEA,YACE,MACA,OACA,2BAAyC,YAAY,IAAI,GACzD;EACA,KAAK,QAAQ,IAAI,WAAW,IAAI;EAChC,KAAK,QAAQ;EACb,KAAK,qBAAqB;CAC5B;CAEA,mBAAyB;EACvB,IAAI,qBAAqB;EACzB,OAAO,QAAQ,KAAK,KAAK,OAAO,CAAC,MAAM,iBAAiB;GACtD,MAAM,YAAY,KAAK,mBAAmB;GAC1C,QAAQ,KAAK,KAAK,OAAO,GAAG,eAAe;GAC3C,sBAAsB,KAAK,mBAAmB,IAAI;EACpD;EACA,KAAK,MAAM,sBAAsB,kBAAkB;CACrD;AACF;;;;;;AAOA,IAAa,0BAAb,MAAqC;CACnC;CACA;CACA,SAAiB;CACjB,gBAA2C,CAAC;CAE5C,YACE,OACA,2BAAyC,YAAY,IAAI,GACzD;EACA,KAAK,QAAQ;EACb,KAAK,qBAAqB;CAC5B;CAEA,IAAI,WAAoB;EACtB,OAAO,KAAK;CACd;CAEA,UACE,QACM;EACN,IAAI,KAAK,WAAW,QAClB;EAEF,KAAK,SAAS;EACd,IAAI,CAAC,QAAQ;GACX,MAAM,UAAU,KAAK;GACrB,KAAK,gBAAgB,CAAC;GACtB,KAAK,MAAM,UAAU,SACnB,OAAO;EAEX;CACF;CAEA,MAAM,kBAAiC;EACrC,IAAI,qBAAqB;EACzB,OAAO,KAAK,QAAQ;GAClB,MAAM,YAAY,KAAK,mBAAmB;GAC1C,MAAM,IAAI,SAAe,YAAY;IACnC,KAAK,cAAc,KAAK,OAAO;GACjC,CAAC;GACD,sBAAsB,KAAK,mBAAmB,IAAI;EACpD;EACA,KAAK,MAAM,sBAAsB,kBAAkB;CACrD;AACF;;;;;;;AAUA,SAAgB,4BACd,YACA,QACA,OACM;CACN,MAAM,WAAW,WAAW;CAC5B,IAAI,OAAO,aAAa,YACtB;CAEF,WAAW,kBACT,SACA,WACA,YACW;EACX,IAAI,YAAY,KAAK,mBACnB,OAAO,SAAS,SAAS,WAAW,OAAO;EAE7C,MAAM,gBAAgB,OAAO;EAC7B,IAAI,CAAC,eACH,OAAO,SAAS,SAAS,WAAW,OAAO;EAE7C,IAAI,SAAS,cAAc,MAAM,CAAC,CAAC,aAAa,SAAS,MAAM,eAAe,GAAG,IAAI;EACrF,OAAO,KAAK;CACd;AACF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mainThreadStackProfileEnvironmentDefaults, resolveWasmEngineCapabilities } from "./WasmEngineCapabilities.js";
|
|
2
2
|
import { WebHostSceneRuntime } from "../WebHostSceneRuntime.js";
|
|
3
|
+
import { createWasmPauseCell, setWasmPauseCellPaused } from "./WasmRuntimePause.js";
|
|
3
4
|
import { MainThreadWasmExecutor } from "./MainThreadWasmExecutor.js";
|
|
4
5
|
import { SharedInputQueueWriter, createSharedInputQueue } from "./SharedInputQueue.js";
|
|
5
6
|
//#region src/wasi/WasmSceneRuntime.ts
|
|
@@ -27,18 +28,22 @@ var WasmSceneRuntime = class extends WebHostSceneRuntime {
|
|
|
27
28
|
inputWriter;
|
|
28
29
|
inputRouter;
|
|
29
30
|
sharedQueueError;
|
|
31
|
+
pauseCell;
|
|
30
32
|
detachBridgeInputListener;
|
|
31
33
|
detachResizeListener;
|
|
32
34
|
worker;
|
|
33
35
|
executor;
|
|
34
36
|
didMount = false;
|
|
37
|
+
suspended = false;
|
|
35
38
|
constructor(options, wasmURL, factoryOptions) {
|
|
36
39
|
let inputQueue;
|
|
37
40
|
let inputWriter;
|
|
38
41
|
let sharedQueueError;
|
|
42
|
+
let pauseCell;
|
|
39
43
|
try {
|
|
40
44
|
inputQueue = createSharedInputQueue();
|
|
41
45
|
inputWriter = new SharedInputQueueWriter(inputQueue);
|
|
46
|
+
pauseCell = createWasmPauseCell();
|
|
42
47
|
} catch (error) {
|
|
43
48
|
sharedQueueError = error;
|
|
44
49
|
}
|
|
@@ -62,6 +67,12 @@ var WasmSceneRuntime = class extends WebHostSceneRuntime {
|
|
|
62
67
|
this.inputWriter = inputWriter;
|
|
63
68
|
this.inputRouter = inputRouter;
|
|
64
69
|
this.sharedQueueError = sharedQueueError;
|
|
70
|
+
this.pauseCell = pauseCell;
|
|
71
|
+
}
|
|
72
|
+
onRuntimeSuspensionChange(suspended) {
|
|
73
|
+
this.suspended = suspended;
|
|
74
|
+
if (this.pauseCell) setWasmPauseCellPaused(this.pauseCell, suspended);
|
|
75
|
+
this.executor?.setSuspended(suspended);
|
|
65
76
|
}
|
|
66
77
|
async mount() {
|
|
67
78
|
await super.mount();
|
|
@@ -111,7 +122,8 @@ var WasmSceneRuntime = class extends WebHostSceneRuntime {
|
|
|
111
122
|
type: "start",
|
|
112
123
|
wasmURL: this.wasmURL.href,
|
|
113
124
|
environment,
|
|
114
|
-
inputQueue: this.inputQueue
|
|
125
|
+
inputQueue: this.inputQueue,
|
|
126
|
+
pauseCell: this.pauseCell
|
|
115
127
|
};
|
|
116
128
|
this.worker.postMessage(message);
|
|
117
129
|
}
|
|
@@ -143,6 +155,7 @@ var WasmSceneRuntime = class extends WebHostSceneRuntime {
|
|
|
143
155
|
});
|
|
144
156
|
this.executor = executor;
|
|
145
157
|
this.inputRouter.route = (chunk) => executor.sendInput(chunk);
|
|
158
|
+
executor.setSuspended(this.suspended);
|
|
146
159
|
executor.start();
|
|
147
160
|
}
|
|
148
161
|
handleWorkerMessage(message) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WasmSceneRuntime.js","names":[],"sources":["../../../src/wasi/WasmSceneRuntime.ts"],"sourcesContent":["import {\n WebHostSceneRuntime,\n type WebHostSceneRuntimeOptions,\n} from \"../WebHostSceneRuntime.ts\";\nimport {\n encodeResizeControlMessage,\n type BrowserWASIBridge,\n} from \"./BrowserWASIBridge.ts\";\n\nimport { MainThreadWasmExecutor } from \"./MainThreadWasmExecutor.ts\";\nimport {\n SharedInputQueueWriter,\n createSharedInputQueue,\n type SharedInputQueueBuffers,\n} from \"./SharedInputQueue.ts\";\nimport {\n mainThreadStackProfileEnvironmentDefaults,\n resolveWasmEngineCapabilities,\n type WasmEngineCapabilities,\n} from \"./WasmEngineCapabilities.ts\";\n\nconst workerModuleURL = new URL(\"./wasm-scene-worker.js\", import.meta.url);\n\ninterface WorkerStartMessage {\n type: \"start\";\n wasmURL: string;\n environment: Record<string, string>;\n inputQueue: SharedInputQueueBuffers;\n}\n\ninterface WorkerOutputMessage {\n type: \"stdout\" | \"stderr\";\n chunk: Uint8Array;\n}\n\ninterface WorkerExitMessage {\n type: \"exit\";\n code: number;\n}\n\ninterface WorkerErrorMessage {\n type: \"error\";\n message: string;\n}\n\ntype WorkerMessage = WorkerOutputMessage | WorkerExitMessage | WorkerErrorMessage;\n\nexport interface WasmSceneResizeEvent {\n sceneId: string;\n columns: number;\n rows: number;\n cellWidth?: number;\n cellHeight?: number;\n}\n\nexport interface WasmSceneRuntimeHandle {\n readonly descriptor: WebHostSceneRuntime[\"descriptor\"];\n sendInput(chunk: Uint8Array): void;\n}\n\nexport type WasmExecutionMode = \"worker\" | \"main-thread\";\nexport type WasmExecutionModePreference = WasmExecutionMode | \"auto\";\n\nexport interface WasmSceneRuntimeFactoryOptions {\n onSceneResize?(event: WasmSceneResizeEvent): void;\n onRuntimeCreated?(runtime: WasmSceneRuntimeHandle): void;\n workerModuleURL?: string | URL;\n /**\n * How to execute the wasm app. \"worker\" is the classic path\n * (`Atomics.wait` stdin, needs SharedArrayBuffer/COOP/COEP). \"main-thread\"\n * runs on the page's thread via WebAssembly JSPI — larger stack budget (no\n * stack-lean profile on measured engines), no COOP/COEP requirement, at\n * the cost of sharing the main thread. \"auto\" (default) picks main-thread\n * only where workers cannot run (SharedArrayBuffer unavailable and JSPI\n * present); workers everywhere else.\n */\n executionMode?: WasmExecutionModePreference;\n}\n\nexport function resolveWasmExecutionMode(\n preference: WasmExecutionModePreference,\n capabilities: WasmEngineCapabilities,\n sharedInputQueueAvailable: boolean\n): WasmExecutionMode {\n if (preference !== \"auto\") {\n return preference;\n }\n if (!capabilities.supportsJSPI) {\n return \"worker\";\n }\n // Workers stay the auto default even on JSPI-capable engines: main-thread\n // execution shares the page's thread, and its stack-budget advantage only\n // pays off once the non-lean profile is production-ready (see\n // `stackProfileEnvironmentDefaults`). JSPI's auto role today is running\n // where workers cannot — pages without cross-origin isolation.\n if (!sharedInputQueueAvailable) {\n return \"main-thread\";\n }\n return \"worker\";\n}\n\nexport function createWasmSceneRuntimeFactory(\n wasmURL: URL,\n factoryOptions: WasmSceneRuntimeFactoryOptions = {}\n): (options: WebHostSceneRuntimeOptions) => WebHostSceneRuntime {\n return (options) => {\n const runtime = new WasmSceneRuntime(options, wasmURL, factoryOptions);\n factoryOptions.onRuntimeCreated?.(runtime);\n return runtime;\n };\n}\n\nclass WasmSceneRuntime extends WebHostSceneRuntime {\n private readonly bridge?: BrowserWASIBridge;\n private readonly wasmURL: URL;\n private readonly onSceneResize?: (event: WasmSceneResizeEvent) => void;\n private readonly workerModuleURL: string | URL;\n private readonly executionModePreference: WasmExecutionModePreference;\n private readonly inputQueue?: SharedInputQueueBuffers;\n private readonly inputWriter?: SharedInputQueueWriter;\n private readonly inputRouter: { route(chunk: Uint8Array): void };\n private readonly sharedQueueError?: unknown;\n\n private detachBridgeInputListener?: () => void;\n private detachResizeListener?: () => void;\n private worker?: Worker;\n private executor?: MainThreadWasmExecutor;\n private didMount = false;\n\n constructor(\n options: WebHostSceneRuntimeOptions,\n wasmURL: URL,\n factoryOptions: WasmSceneRuntimeFactoryOptions\n ) {\n let inputQueue: SharedInputQueueBuffers | undefined;\n let inputWriter: SharedInputQueueWriter | undefined;\n let sharedQueueError: unknown;\n\n try {\n inputQueue = createSharedInputQueue();\n inputWriter = new SharedInputQueueWriter(inputQueue);\n } catch (error) {\n // Not fatal here: the main-thread (JSPI) mode runs without\n // SharedArrayBuffer. Surfaced at mount if the worker mode needs it.\n sharedQueueError = error;\n }\n\n const inputRouter = {\n route: (chunk: Uint8Array): void => {\n try {\n inputWriter?.write(chunk);\n } catch (error) {\n console.error(\"[SwiftTUIWeb] failed to enqueue terminal input\", error);\n }\n },\n };\n\n super({\n ...options,\n onInput: (chunk) => inputRouter.route(chunk),\n });\n\n this.bridge = options.bridge;\n this.wasmURL = wasmURL;\n this.onSceneResize = factoryOptions.onSceneResize;\n this.workerModuleURL = factoryOptions.workerModuleURL ?? workerModuleURL;\n this.executionModePreference = factoryOptions.executionMode ?? \"auto\";\n this.inputQueue = inputQueue;\n this.inputWriter = inputWriter;\n this.inputRouter = inputRouter;\n this.sharedQueueError = sharedQueueError;\n }\n\n override async mount(): Promise<void> {\n await super.mount();\n if (this.didMount) {\n return;\n }\n\n this.didMount = true;\n this.detachBridgeInputListener = this.bridge?.stdin.subscribe((chunk) => {\n this.inputRouter.route(chunk);\n });\n this.detachResizeListener = this.bridge?.subscribeResize((columns, rows, cellWidth, cellHeight) => {\n this.onSceneResize?.({\n sceneId: this.descriptor.id,\n columns,\n rows,\n cellWidth,\n cellHeight,\n });\n });\n\n const initialColumns = Number(this.bridge?.environment.TUIGUI_COLUMNS ?? \"0\") || 0;\n const initialRows = Number(this.bridge?.environment.TUIGUI_ROWS ?? \"0\") || 0;\n if (!this.bridge && initialColumns > 0 && initialRows > 0) {\n this.onSceneResize?.({\n sceneId: this.descriptor.id,\n columns: initialColumns,\n rows: initialRows,\n });\n }\n\n if (!this.bridge) {\n this.writeOutput(\n \"\\r\\nSwiftTUI WASI browser runtime requires a WASI bridge.\\r\\n\"\n );\n return;\n }\n\n const mode = resolveWasmExecutionMode(\n this.executionModePreference,\n resolveWasmEngineCapabilities(),\n this.inputQueue !== undefined && this.inputWriter !== undefined\n );\n if (mode === \"main-thread\") {\n this.startMainThreadExecutor();\n return;\n }\n\n if (!this.inputQueue || !this.inputWriter) {\n if (this.sharedQueueError !== undefined) {\n console.error(\n \"[SwiftTUIWeb] failed to create shared stdin queue\",\n this.sharedQueueError\n );\n }\n this.writeOutput(\n \"\\r\\nSwiftTUI WASI browser runtime requires SharedArrayBuffer-backed stdin. Serve the app with COOP/COEP headers.\\r\\n\"\n );\n return;\n }\n\n this.worker = new Worker(this.workerModuleURL, { type: \"module\" });\n this.worker.addEventListener(\"message\", (event: MessageEvent<WorkerMessage>) => {\n this.handleWorkerMessage(event.data);\n });\n this.worker.addEventListener(\"error\", (event) => {\n this.bridge?.stderr.write(\n `\\nSwiftTUI WASI worker failed: ${event.message || \"unknown worker error\"}\\n`\n );\n });\n\n const environment = { ...this.bridge.environment };\n\n const message: WorkerStartMessage = {\n type: \"start\",\n wasmURL: this.wasmURL.href,\n environment,\n inputQueue: this.inputQueue,\n };\n this.worker.postMessage(message);\n }\n\n override dispose(): void {\n this.detachBridgeInputListener?.();\n this.detachResizeListener?.();\n this.inputWriter?.close();\n this.worker?.terminate();\n this.executor?.dispose();\n super.dispose();\n }\n\n private startMainThreadExecutor(): void {\n const bridge = this.bridge;\n if (!bridge) {\n return;\n }\n const executor = new MainThreadWasmExecutor({\n wasmURL: this.wasmURL.href,\n environment: {\n ...mainThreadStackProfileEnvironmentDefaults(resolveWasmEngineCapabilities()),\n ...bridge.environment,\n },\n onStdout: (chunk) => bridge.stdout.write(chunk),\n onStderr: (chunk) => bridge.stderr.write(chunk),\n onExit: (code) => {\n if (code !== 0) {\n bridge.stderr.write(`\\nSwiftTUI WASI app exited with code ${code}.\\n`);\n }\n },\n onError: (message) => {\n bridge.stderr.write(`\\nFailed to start SwiftTUI WASI app: ${message}\\n`);\n },\n });\n this.executor = executor;\n this.inputRouter.route = (chunk) => executor.sendInput(chunk);\n executor.start();\n }\n\n private handleWorkerMessage(\n message: WorkerMessage\n ): void {\n switch (message.type) {\n case \"stdout\":\n this.bridge?.stdout.write(message.chunk);\n break;\n case \"stderr\":\n this.bridge?.stderr.write(message.chunk);\n break;\n case \"exit\":\n if (message.code !== 0) {\n this.bridge?.stderr.write(`\\nSwiftTUI WASI app exited with code ${message.code}.\\n`);\n }\n break;\n case \"error\":\n this.bridge?.stderr.write(`\\nFailed to start SwiftTUI WASI app: ${message.message}\\n`);\n break;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WasmSceneRuntime.js","names":[],"sources":["../../../src/wasi/WasmSceneRuntime.ts"],"sourcesContent":["import {\n WebHostSceneRuntime,\n type WebHostSceneRuntimeOptions,\n} from \"../WebHostSceneRuntime.ts\";\nimport {\n encodeResizeControlMessage,\n type BrowserWASIBridge,\n} from \"./BrowserWASIBridge.ts\";\n\nimport { MainThreadWasmExecutor } from \"./MainThreadWasmExecutor.ts\";\nimport {\n SharedInputQueueWriter,\n createSharedInputQueue,\n type SharedInputQueueBuffers,\n} from \"./SharedInputQueue.ts\";\nimport {\n mainThreadStackProfileEnvironmentDefaults,\n resolveWasmEngineCapabilities,\n type WasmEngineCapabilities,\n} from \"./WasmEngineCapabilities.ts\";\nimport { createWasmPauseCell, setWasmPauseCellPaused } from \"./WasmRuntimePause.ts\";\n\nconst workerModuleURL = new URL(\"./wasm-scene-worker.js\", import.meta.url);\n\ninterface WorkerStartMessage {\n type: \"start\";\n wasmURL: string;\n environment: Record<string, string>;\n inputQueue: SharedInputQueueBuffers;\n pauseCell?: SharedArrayBuffer;\n}\n\ninterface WorkerOutputMessage {\n type: \"stdout\" | \"stderr\";\n chunk: Uint8Array;\n}\n\ninterface WorkerExitMessage {\n type: \"exit\";\n code: number;\n}\n\ninterface WorkerErrorMessage {\n type: \"error\";\n message: string;\n}\n\ntype WorkerMessage = WorkerOutputMessage | WorkerExitMessage | WorkerErrorMessage;\n\nexport interface WasmSceneResizeEvent {\n sceneId: string;\n columns: number;\n rows: number;\n cellWidth?: number;\n cellHeight?: number;\n}\n\nexport interface WasmSceneRuntimeHandle {\n readonly descriptor: WebHostSceneRuntime[\"descriptor\"];\n sendInput(chunk: Uint8Array): void;\n}\n\nexport type WasmExecutionMode = \"worker\" | \"main-thread\";\nexport type WasmExecutionModePreference = WasmExecutionMode | \"auto\";\n\nexport interface WasmSceneRuntimeFactoryOptions {\n onSceneResize?(event: WasmSceneResizeEvent): void;\n onRuntimeCreated?(runtime: WasmSceneRuntimeHandle): void;\n workerModuleURL?: string | URL;\n /**\n * How to execute the wasm app. \"worker\" is the classic path\n * (`Atomics.wait` stdin, needs SharedArrayBuffer/COOP/COEP). \"main-thread\"\n * runs on the page's thread via WebAssembly JSPI — larger stack budget (no\n * stack-lean profile on measured engines), no COOP/COEP requirement, at\n * the cost of sharing the main thread. \"auto\" (default) picks main-thread\n * only where workers cannot run (SharedArrayBuffer unavailable and JSPI\n * present); workers everywhere else.\n */\n executionMode?: WasmExecutionModePreference;\n}\n\nexport function resolveWasmExecutionMode(\n preference: WasmExecutionModePreference,\n capabilities: WasmEngineCapabilities,\n sharedInputQueueAvailable: boolean\n): WasmExecutionMode {\n if (preference !== \"auto\") {\n return preference;\n }\n if (!capabilities.supportsJSPI) {\n return \"worker\";\n }\n // Workers stay the auto default even on JSPI-capable engines: main-thread\n // execution shares the page's thread, and its stack-budget advantage only\n // pays off once the non-lean profile is production-ready (see\n // `stackProfileEnvironmentDefaults`). JSPI's auto role today is running\n // where workers cannot — pages without cross-origin isolation.\n if (!sharedInputQueueAvailable) {\n return \"main-thread\";\n }\n return \"worker\";\n}\n\nexport function createWasmSceneRuntimeFactory(\n wasmURL: URL,\n factoryOptions: WasmSceneRuntimeFactoryOptions = {}\n): (options: WebHostSceneRuntimeOptions) => WebHostSceneRuntime {\n return (options) => {\n const runtime = new WasmSceneRuntime(options, wasmURL, factoryOptions);\n factoryOptions.onRuntimeCreated?.(runtime);\n return runtime;\n };\n}\n\nclass WasmSceneRuntime extends WebHostSceneRuntime {\n private readonly bridge?: BrowserWASIBridge;\n private readonly wasmURL: URL;\n private readonly onSceneResize?: (event: WasmSceneResizeEvent) => void;\n private readonly workerModuleURL: string | URL;\n private readonly executionModePreference: WasmExecutionModePreference;\n private readonly inputQueue?: SharedInputQueueBuffers;\n private readonly inputWriter?: SharedInputQueueWriter;\n private readonly inputRouter: { route(chunk: Uint8Array): void };\n private readonly sharedQueueError?: unknown;\n private readonly pauseCell?: SharedArrayBuffer;\n\n private detachBridgeInputListener?: () => void;\n private detachResizeListener?: () => void;\n private worker?: Worker;\n private executor?: MainThreadWasmExecutor;\n private didMount = false;\n private suspended = false;\n\n constructor(\n options: WebHostSceneRuntimeOptions,\n wasmURL: URL,\n factoryOptions: WasmSceneRuntimeFactoryOptions\n ) {\n let inputQueue: SharedInputQueueBuffers | undefined;\n let inputWriter: SharedInputQueueWriter | undefined;\n let sharedQueueError: unknown;\n let pauseCell: SharedArrayBuffer | undefined;\n\n try {\n inputQueue = createSharedInputQueue();\n inputWriter = new SharedInputQueueWriter(inputQueue);\n pauseCell = createWasmPauseCell();\n } catch (error) {\n // Not fatal here: the main-thread (JSPI) mode runs without\n // SharedArrayBuffer. Surfaced at mount if the worker mode needs it.\n sharedQueueError = error;\n }\n\n const inputRouter = {\n route: (chunk: Uint8Array): void => {\n try {\n inputWriter?.write(chunk);\n } catch (error) {\n console.error(\"[SwiftTUIWeb] failed to enqueue terminal input\", error);\n }\n },\n };\n\n super({\n ...options,\n onInput: (chunk) => inputRouter.route(chunk),\n });\n\n this.bridge = options.bridge;\n this.wasmURL = wasmURL;\n this.onSceneResize = factoryOptions.onSceneResize;\n this.workerModuleURL = factoryOptions.workerModuleURL ?? workerModuleURL;\n this.executionModePreference = factoryOptions.executionMode ?? \"auto\";\n this.inputQueue = inputQueue;\n this.inputWriter = inputWriter;\n this.inputRouter = inputRouter;\n this.sharedQueueError = sharedQueueError;\n this.pauseCell = pauseCell;\n }\n\n protected override onRuntimeSuspensionChange(\n suspended: boolean\n ): void {\n this.suspended = suspended;\n if (this.pauseCell) {\n setWasmPauseCellPaused(this.pauseCell, suspended);\n }\n this.executor?.setSuspended(suspended);\n }\n\n override async mount(): Promise<void> {\n await super.mount();\n if (this.didMount) {\n return;\n }\n\n this.didMount = true;\n this.detachBridgeInputListener = this.bridge?.stdin.subscribe((chunk) => {\n this.inputRouter.route(chunk);\n });\n this.detachResizeListener = this.bridge?.subscribeResize((columns, rows, cellWidth, cellHeight) => {\n this.onSceneResize?.({\n sceneId: this.descriptor.id,\n columns,\n rows,\n cellWidth,\n cellHeight,\n });\n });\n\n const initialColumns = Number(this.bridge?.environment.TUIGUI_COLUMNS ?? \"0\") || 0;\n const initialRows = Number(this.bridge?.environment.TUIGUI_ROWS ?? \"0\") || 0;\n if (!this.bridge && initialColumns > 0 && initialRows > 0) {\n this.onSceneResize?.({\n sceneId: this.descriptor.id,\n columns: initialColumns,\n rows: initialRows,\n });\n }\n\n if (!this.bridge) {\n this.writeOutput(\n \"\\r\\nSwiftTUI WASI browser runtime requires a WASI bridge.\\r\\n\"\n );\n return;\n }\n\n const mode = resolveWasmExecutionMode(\n this.executionModePreference,\n resolveWasmEngineCapabilities(),\n this.inputQueue !== undefined && this.inputWriter !== undefined\n );\n if (mode === \"main-thread\") {\n this.startMainThreadExecutor();\n return;\n }\n\n if (!this.inputQueue || !this.inputWriter) {\n if (this.sharedQueueError !== undefined) {\n console.error(\n \"[SwiftTUIWeb] failed to create shared stdin queue\",\n this.sharedQueueError\n );\n }\n this.writeOutput(\n \"\\r\\nSwiftTUI WASI browser runtime requires SharedArrayBuffer-backed stdin. Serve the app with COOP/COEP headers.\\r\\n\"\n );\n return;\n }\n\n this.worker = new Worker(this.workerModuleURL, { type: \"module\" });\n this.worker.addEventListener(\"message\", (event: MessageEvent<WorkerMessage>) => {\n this.handleWorkerMessage(event.data);\n });\n this.worker.addEventListener(\"error\", (event) => {\n this.bridge?.stderr.write(\n `\\nSwiftTUI WASI worker failed: ${event.message || \"unknown worker error\"}\\n`\n );\n });\n\n const environment = { ...this.bridge.environment };\n\n const message: WorkerStartMessage = {\n type: \"start\",\n wasmURL: this.wasmURL.href,\n environment,\n inputQueue: this.inputQueue,\n pauseCell: this.pauseCell,\n };\n this.worker.postMessage(message);\n }\n\n override dispose(): void {\n this.detachBridgeInputListener?.();\n this.detachResizeListener?.();\n this.inputWriter?.close();\n this.worker?.terminate();\n this.executor?.dispose();\n super.dispose();\n }\n\n private startMainThreadExecutor(): void {\n const bridge = this.bridge;\n if (!bridge) {\n return;\n }\n const executor = new MainThreadWasmExecutor({\n wasmURL: this.wasmURL.href,\n environment: {\n ...mainThreadStackProfileEnvironmentDefaults(resolveWasmEngineCapabilities()),\n ...bridge.environment,\n },\n onStdout: (chunk) => bridge.stdout.write(chunk),\n onStderr: (chunk) => bridge.stderr.write(chunk),\n onExit: (code) => {\n if (code !== 0) {\n bridge.stderr.write(`\\nSwiftTUI WASI app exited with code ${code}.\\n`);\n }\n },\n onError: (message) => {\n bridge.stderr.write(`\\nFailed to start SwiftTUI WASI app: ${message}\\n`);\n },\n });\n this.executor = executor;\n this.inputRouter.route = (chunk) => executor.sendInput(chunk);\n executor.setSuspended(this.suspended);\n executor.start();\n }\n\n private handleWorkerMessage(\n message: WorkerMessage\n ): void {\n switch (message.type) {\n case \"stdout\":\n this.bridge?.stdout.write(message.chunk);\n break;\n case \"stderr\":\n this.bridge?.stderr.write(message.chunk);\n break;\n case \"exit\":\n if (message.code !== 0) {\n this.bridge?.stderr.write(`\\nSwiftTUI WASI app exited with code ${message.code}.\\n`);\n }\n break;\n case \"error\":\n this.bridge?.stderr.write(`\\nFailed to start SwiftTUI WASI app: ${message.message}\\n`);\n break;\n }\n }\n}\n"],"mappings":";;;;;;AAsBA,MAAM,kBAAkB,IAAI,IAAI,0BAA0B,OAAO,KAAK,GAAG;AA2DzE,SAAgB,yBACd,YACA,cACA,2BACmB;CACnB,IAAI,eAAe,QACjB,OAAO;CAET,IAAI,CAAC,aAAa,cAChB,OAAO;CAOT,IAAI,CAAC,2BACH,OAAO;CAET,OAAO;AACT;AAEA,SAAgB,8BACd,SACA,iBAAiD,CAAC,GACY;CAC9D,QAAQ,YAAY;EAClB,MAAM,UAAU,IAAI,iBAAiB,SAAS,SAAS,cAAc;EACrE,eAAe,mBAAmB,OAAO;EACzC,OAAO;CACT;AACF;AAEA,IAAM,mBAAN,cAA+B,oBAAoB;CACjD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA,WAAmB;CACnB,YAAoB;CAEpB,YACE,SACA,SACA,gBACA;EACA,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EAEJ,IAAI;GACF,aAAa,uBAAuB;GACpC,cAAc,IAAI,uBAAuB,UAAU;GACnD,YAAY,oBAAoB;EAClC,SAAS,OAAO;GAGd,mBAAmB;EACrB;EAEA,MAAM,cAAc,EAClB,QAAQ,UAA4B;GAClC,IAAI;IACF,aAAa,MAAM,KAAK;GAC1B,SAAS,OAAO;IACd,QAAQ,MAAM,kDAAkD,KAAK;GACvE;EACF,EACF;EAEA,MAAM;GACJ,GAAG;GACH,UAAU,UAAU,YAAY,MAAM,KAAK;EAC7C,CAAC;EAED,KAAK,SAAS,QAAQ;EACtB,KAAK,UAAU;EACf,KAAK,gBAAgB,eAAe;EACpC,KAAK,kBAAkB,eAAe,mBAAmB;EACzD,KAAK,0BAA0B,eAAe,iBAAiB;EAC/D,KAAK,aAAa;EAClB,KAAK,cAAc;EACnB,KAAK,cAAc;EACnB,KAAK,mBAAmB;EACxB,KAAK,YAAY;CACnB;CAEA,0BACE,WACM;EACN,KAAK,YAAY;EACjB,IAAI,KAAK,WACP,uBAAuB,KAAK,WAAW,SAAS;EAElD,KAAK,UAAU,aAAa,SAAS;CACvC;CAEA,MAAe,QAAuB;EACpC,MAAM,MAAM,MAAM;EAClB,IAAI,KAAK,UACP;EAGF,KAAK,WAAW;EAChB,KAAK,4BAA4B,KAAK,QAAQ,MAAM,WAAW,UAAU;GACvE,KAAK,YAAY,MAAM,KAAK;EAC9B,CAAC;EACD,KAAK,uBAAuB,KAAK,QAAQ,iBAAiB,SAAS,MAAM,WAAW,eAAe;GACjG,KAAK,gBAAgB;IACnB,SAAS,KAAK,WAAW;IACzB;IACA;IACA;IACA;GACF,CAAC;EACH,CAAC;EAED,MAAM,iBAAiB,OAAO,KAAK,QAAQ,YAAY,kBAAkB,GAAG,KAAK;EACjF,MAAM,cAAc,OAAO,KAAK,QAAQ,YAAY,eAAe,GAAG,KAAK;EAC3E,IAAI,CAAC,KAAK,UAAU,iBAAiB,KAAK,cAAc,GACtD,KAAK,gBAAgB;GACnB,SAAS,KAAK,WAAW;GACzB,SAAS;GACT,MAAM;EACR,CAAC;EAGH,IAAI,CAAC,KAAK,QAAQ;GAChB,KAAK,YACH,+DACF;GACA;EACF;EAOA,IALa,yBACX,KAAK,yBACL,8BAA8B,GAC9B,KAAK,eAAe,KAAA,KAAa,KAAK,gBAAgB,KAAA,CAEjD,MAAM,eAAe;GAC1B,KAAK,wBAAwB;GAC7B;EACF;EAEA,IAAI,CAAC,KAAK,cAAc,CAAC,KAAK,aAAa;GACzC,IAAI,KAAK,qBAAqB,KAAA,GAC5B,QAAQ,MACN,qDACA,KAAK,gBACP;GAEF,KAAK,YACH,sHACF;GACA;EACF;EAEA,KAAK,SAAS,IAAI,OAAO,KAAK,iBAAiB,EAAE,MAAM,SAAS,CAAC;EACjE,KAAK,OAAO,iBAAiB,YAAY,UAAuC;GAC9E,KAAK,oBAAoB,MAAM,IAAI;EACrC,CAAC;EACD,KAAK,OAAO,iBAAiB,UAAU,UAAU;GAC/C,KAAK,QAAQ,OAAO,MAClB,kCAAkC,MAAM,WAAW,uBAAuB,GAC5E;EACF,CAAC;EAED,MAAM,cAAc,EAAE,GAAG,KAAK,OAAO,YAAY;EAEjD,MAAM,UAA8B;GAClC,MAAM;GACN,SAAS,KAAK,QAAQ;GACtB;GACA,YAAY,KAAK;GACjB,WAAW,KAAK;EAClB;EACA,KAAK,OAAO,YAAY,OAAO;CACjC;CAEA,UAAyB;EACvB,KAAK,4BAA4B;EACjC,KAAK,uBAAuB;EAC5B,KAAK,aAAa,MAAM;EACxB,KAAK,QAAQ,UAAU;EACvB,KAAK,UAAU,QAAQ;EACvB,MAAM,QAAQ;CAChB;CAEA,0BAAwC;EACtC,MAAM,SAAS,KAAK;EACpB,IAAI,CAAC,QACH;EAEF,MAAM,WAAW,IAAI,uBAAuB;GAC1C,SAAS,KAAK,QAAQ;GACtB,aAAa;IACX,GAAG,0CAA0C,8BAA8B,CAAC;IAC5E,GAAG,OAAO;GACZ;GACA,WAAW,UAAU,OAAO,OAAO,MAAM,KAAK;GAC9C,WAAW,UAAU,OAAO,OAAO,MAAM,KAAK;GAC9C,SAAS,SAAS;IAChB,IAAI,SAAS,GACX,OAAO,OAAO,MAAM,wCAAwC,KAAK,IAAI;GAEzE;GACA,UAAU,YAAY;IACpB,OAAO,OAAO,MAAM,wCAAwC,QAAQ,GAAG;GACzE;EACF,CAAC;EACD,KAAK,WAAW;EAChB,KAAK,YAAY,SAAS,UAAU,SAAS,UAAU,KAAK;EAC5D,SAAS,aAAa,KAAK,SAAS;EACpC,SAAS,MAAM;CACjB;CAEA,oBACE,SACM;EACN,QAAQ,QAAQ,MAAhB;GACA,KAAK;IACH,KAAK,QAAQ,OAAO,MAAM,QAAQ,KAAK;IACvC;GACF,KAAK;IACH,KAAK,QAAQ,OAAO,MAAM,QAAQ,KAAK;IACvC;GACF,KAAK;IACH,IAAI,QAAQ,SAAS,GACnB,KAAK,QAAQ,OAAO,MAAM,wCAAwC,QAAQ,KAAK,IAAI;IAErF;GACF,KAAK;IACH,KAAK,QAAQ,OAAO,MAAM,wCAAwC,QAAQ,QAAQ,GAAG;IACrF;EACF;CACF;AACF"}
|
|
@@ -5,6 +5,13 @@ interface StartWasmSceneWorkerMessage {
|
|
|
5
5
|
wasmURL: string;
|
|
6
6
|
environment: Record<string, string>;
|
|
7
7
|
inputQueue: SharedInputQueueBuffers;
|
|
8
|
+
/**
|
|
9
|
+
* Optional shared pause cell (see `createWasmPauseCell`). When present, the
|
|
10
|
+
* main thread can suspend this scene: the worker parks between poll waits
|
|
11
|
+
* and the app's monotonic clock freezes for the paused span. Absent on
|
|
12
|
+
* messages from older runtimes — the worker then runs unpausable, as before.
|
|
13
|
+
*/
|
|
14
|
+
pauseCell?: SharedArrayBuffer;
|
|
8
15
|
}
|
|
9
16
|
interface OutputWasmSceneWorkerMessage {
|
|
10
17
|
type: "stdout" | "stderr";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { WasiPollScheduler } from "./WasiPollScheduler.js";
|
|
2
|
+
import { PausableMonotonicClock, WorkerWasmPauseGate, installPausableClockTimeGet } from "./WasmRuntimePause.js";
|
|
2
3
|
import { SharedInputQueueReader } from "./SharedInputQueue.js";
|
|
3
4
|
import { ConsoleStdout, Fd, WASI, wasi } from "@bjorn3/browser_wasi_shim";
|
|
4
5
|
//#region src/wasi/WasmSceneWorker.ts
|
|
@@ -56,13 +57,19 @@ var BlockingInputFileDescriptor = class extends Fd {
|
|
|
56
57
|
return this.reader.isClosed();
|
|
57
58
|
}
|
|
58
59
|
};
|
|
59
|
-
function installWasiPollScheduler(wasiBridge, stdin) {
|
|
60
|
+
function installWasiPollScheduler(wasiBridge, stdin, pauseCell) {
|
|
60
61
|
const originalPoll = wasiBridge.wasiImport.poll_oneoff;
|
|
61
62
|
if (typeof originalPoll !== "function") return;
|
|
63
|
+
const memory = () => wasiBridge.inst?.exports.memory;
|
|
64
|
+
const pauseClock = pauseCell ? new PausableMonotonicClock() : void 0;
|
|
65
|
+
const pauseGate = pauseCell && pauseClock ? new WorkerWasmPauseGate(pauseCell, pauseClock) : void 0;
|
|
66
|
+
if (pauseClock) installPausableClockTimeGet(wasiBridge.wasiImport, memory, pauseClock);
|
|
62
67
|
const scheduler = new WasiPollScheduler({
|
|
63
|
-
memory
|
|
68
|
+
memory,
|
|
64
69
|
stdin,
|
|
65
|
-
fallbackPoll: (inPtr, outPtr, nsubscriptions, neventsPtr) => originalPoll(inPtr, outPtr, nsubscriptions, neventsPtr)
|
|
70
|
+
fallbackPoll: (inPtr, outPtr, nsubscriptions, neventsPtr) => originalPoll(inPtr, outPtr, nsubscriptions, neventsPtr),
|
|
71
|
+
nowMilliseconds: pauseClock ? () => pauseClock.nowMilliseconds() : void 0,
|
|
72
|
+
pauseGate
|
|
66
73
|
});
|
|
67
74
|
wasiBridge.wasiImport.poll_oneoff = (inPtr, outPtr, nsubscriptions, neventsPtr) => scheduler.pollOneOff(inPtr, outPtr, nsubscriptions, neventsPtr);
|
|
68
75
|
}
|
|
@@ -84,7 +91,7 @@ async function startWasmScene(message) {
|
|
|
84
91
|
});
|
|
85
92
|
})
|
|
86
93
|
]);
|
|
87
|
-
installWasiPollScheduler(wasiBridge, stdin);
|
|
94
|
+
installWasiPollScheduler(wasiBridge, stdin, message.pauseCell);
|
|
88
95
|
const response = await fetch(message.wasmURL);
|
|
89
96
|
if (!response.ok) throw new Error(`failed to load ${message.wasmURL}: ${response.status} ${response.statusText}`);
|
|
90
97
|
const module = await WebAssembly.compile(await response.arrayBuffer());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WasmSceneWorker.js","names":[],"sources":["../../../src/wasi/WasmSceneWorker.ts"],"sourcesContent":["import { ConsoleStdout, Fd, WASI, wasi } from \"@bjorn3/browser_wasi_shim\";\nimport {\n SharedInputQueueReader,\n type SharedInputQueueBuffers,\n} from \"./SharedInputQueue.ts\";\nimport { WasiPollScheduler } from \"./WasiPollScheduler.ts\";\n\nexport interface StartWasmSceneWorkerMessage {\n type: \"start\";\n wasmURL: string;\n environment: Record<string, string>;\n inputQueue: SharedInputQueueBuffers;\n}\n\nexport interface OutputWasmSceneWorkerMessage {\n type: \"stdout\" | \"stderr\";\n chunk: Uint8Array;\n}\n\nexport interface ExitWasmSceneWorkerMessage {\n type: \"exit\";\n code: number;\n}\n\nexport interface ErrorWasmSceneWorkerMessage {\n type: \"error\";\n message: string;\n}\n\nexport type WasmSceneWorkerMessage = StartWasmSceneWorkerMessage;\nexport type WasmSceneWorkerResponse =\n | OutputWasmSceneWorkerMessage\n | ExitWasmSceneWorkerMessage\n | ErrorWasmSceneWorkerMessage;\n\nexport function startWasmSceneWorker(): void {\n globalThis.addEventListener(\"message\", (event: MessageEvent<WasmSceneWorkerMessage>) => {\n if (event.data.type !== \"start\") {\n return;\n }\n\n void startWasmScene(event.data);\n });\n}\n\nclass BlockingInputFileDescriptor extends Fd {\n private readonly reader: SharedInputQueueReader;\n private readonly fdstat = (() => {\n const fdstat = new wasi.Fdstat(wasi.FILETYPE_CHARACTER_DEVICE, 0);\n fdstat.fs_rights_base = BigInt(wasi.RIGHTS_FD_READ);\n return fdstat;\n })();\n\n constructor(inputQueue: SharedInputQueueBuffers) {\n super();\n this.reader = new SharedInputQueueReader(inputQueue);\n }\n\n override fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat } {\n return {\n ret: wasi.ERRNO_SUCCESS,\n fdstat: this.fdstat,\n };\n }\n\n override fd_filestat_get(): { ret: number; filestat: wasi.Filestat } {\n return {\n ret: wasi.ERRNO_SUCCESS,\n filestat: new wasi.Filestat(0n, wasi.FILETYPE_CHARACTER_DEVICE, 0n),\n };\n }\n\n override fd_read(size: number): { ret: number; data: Uint8Array } {\n const chunk = this.reader.readAvailable(size);\n if (chunk) {\n return {\n ret: wasi.ERRNO_SUCCESS,\n data: chunk,\n };\n }\n\n if (this.reader.isClosed()) {\n return {\n ret: wasi.ERRNO_SUCCESS,\n data: new Uint8Array(),\n };\n }\n\n return {\n ret: wasi.ERRNO_AGAIN,\n data: new Uint8Array(),\n };\n }\n\n availableBytes(): number {\n return this.reader.availableBytes();\n }\n\n waitForReadable(\n timeoutMilliseconds?: number\n ) {\n return this.reader.waitForReadable(timeoutMilliseconds);\n }\n\n isClosed(): boolean {\n return this.reader.isClosed();\n }\n}\n\nfunction installWasiPollScheduler(\n wasiBridge: WASI,\n stdin: BlockingInputFileDescriptor\n): void {\n const originalPoll = wasiBridge.wasiImport.poll_oneoff;\n if (typeof originalPoll !== \"function\") {\n return;\n }\n\n const
|
|
1
|
+
{"version":3,"file":"WasmSceneWorker.js","names":[],"sources":["../../../src/wasi/WasmSceneWorker.ts"],"sourcesContent":["import { ConsoleStdout, Fd, WASI, wasi } from \"@bjorn3/browser_wasi_shim\";\nimport {\n SharedInputQueueReader,\n type SharedInputQueueBuffers,\n} from \"./SharedInputQueue.ts\";\nimport { WasiPollScheduler } from \"./WasiPollScheduler.ts\";\nimport {\n PausableMonotonicClock,\n WorkerWasmPauseGate,\n installPausableClockTimeGet,\n} from \"./WasmRuntimePause.ts\";\n\nexport interface StartWasmSceneWorkerMessage {\n type: \"start\";\n wasmURL: string;\n environment: Record<string, string>;\n inputQueue: SharedInputQueueBuffers;\n /**\n * Optional shared pause cell (see `createWasmPauseCell`). When present, the\n * main thread can suspend this scene: the worker parks between poll waits\n * and the app's monotonic clock freezes for the paused span. Absent on\n * messages from older runtimes — the worker then runs unpausable, as before.\n */\n pauseCell?: SharedArrayBuffer;\n}\n\nexport interface OutputWasmSceneWorkerMessage {\n type: \"stdout\" | \"stderr\";\n chunk: Uint8Array;\n}\n\nexport interface ExitWasmSceneWorkerMessage {\n type: \"exit\";\n code: number;\n}\n\nexport interface ErrorWasmSceneWorkerMessage {\n type: \"error\";\n message: string;\n}\n\nexport type WasmSceneWorkerMessage = StartWasmSceneWorkerMessage;\nexport type WasmSceneWorkerResponse =\n | OutputWasmSceneWorkerMessage\n | ExitWasmSceneWorkerMessage\n | ErrorWasmSceneWorkerMessage;\n\nexport function startWasmSceneWorker(): void {\n globalThis.addEventListener(\"message\", (event: MessageEvent<WasmSceneWorkerMessage>) => {\n if (event.data.type !== \"start\") {\n return;\n }\n\n void startWasmScene(event.data);\n });\n}\n\nclass BlockingInputFileDescriptor extends Fd {\n private readonly reader: SharedInputQueueReader;\n private readonly fdstat = (() => {\n const fdstat = new wasi.Fdstat(wasi.FILETYPE_CHARACTER_DEVICE, 0);\n fdstat.fs_rights_base = BigInt(wasi.RIGHTS_FD_READ);\n return fdstat;\n })();\n\n constructor(inputQueue: SharedInputQueueBuffers) {\n super();\n this.reader = new SharedInputQueueReader(inputQueue);\n }\n\n override fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat } {\n return {\n ret: wasi.ERRNO_SUCCESS,\n fdstat: this.fdstat,\n };\n }\n\n override fd_filestat_get(): { ret: number; filestat: wasi.Filestat } {\n return {\n ret: wasi.ERRNO_SUCCESS,\n filestat: new wasi.Filestat(0n, wasi.FILETYPE_CHARACTER_DEVICE, 0n),\n };\n }\n\n override fd_read(size: number): { ret: number; data: Uint8Array } {\n const chunk = this.reader.readAvailable(size);\n if (chunk) {\n return {\n ret: wasi.ERRNO_SUCCESS,\n data: chunk,\n };\n }\n\n if (this.reader.isClosed()) {\n return {\n ret: wasi.ERRNO_SUCCESS,\n data: new Uint8Array(),\n };\n }\n\n return {\n ret: wasi.ERRNO_AGAIN,\n data: new Uint8Array(),\n };\n }\n\n availableBytes(): number {\n return this.reader.availableBytes();\n }\n\n waitForReadable(\n timeoutMilliseconds?: number\n ) {\n return this.reader.waitForReadable(timeoutMilliseconds);\n }\n\n isClosed(): boolean {\n return this.reader.isClosed();\n }\n}\n\nfunction installWasiPollScheduler(\n wasiBridge: WASI,\n stdin: BlockingInputFileDescriptor,\n pauseCell: SharedArrayBuffer | undefined\n): void {\n const originalPoll = wasiBridge.wasiImport.poll_oneoff;\n if (typeof originalPoll !== \"function\") {\n return;\n }\n\n const memory = (): WebAssembly.Memory | undefined =>\n wasiBridge.inst?.exports.memory as WebAssembly.Memory | undefined;\n\n const pauseClock = pauseCell ? new PausableMonotonicClock() : undefined;\n const pauseGate =\n pauseCell && pauseClock ? new WorkerWasmPauseGate(pauseCell, pauseClock) : undefined;\n if (pauseClock) {\n installPausableClockTimeGet(wasiBridge.wasiImport, memory, pauseClock);\n }\n\n const scheduler = new WasiPollScheduler({\n memory,\n stdin,\n fallbackPoll: (inPtr, outPtr, nsubscriptions, neventsPtr) =>\n originalPoll(inPtr, outPtr, nsubscriptions, neventsPtr),\n nowMilliseconds: pauseClock ? () => pauseClock.nowMilliseconds() : undefined,\n pauseGate,\n });\n wasiBridge.wasiImport.poll_oneoff = (inPtr, outPtr, nsubscriptions, neventsPtr) =>\n scheduler.pollOneOff(inPtr, outPtr, nsubscriptions, neventsPtr);\n}\n\nasync function startWasmScene(\n message: StartWasmSceneWorkerMessage\n): Promise<void> {\n try {\n const stdin = new BlockingInputFileDescriptor(message.inputQueue);\n const wasiBridge = new WASI(\n [\"app.wasm\"],\n Object.entries(message.environment).map(([key, value]) => `${key}=${value}`),\n [\n stdin,\n new ConsoleStdout((chunk) => {\n postWorkerMessage({\n type: \"stdout\",\n chunk,\n });\n }),\n new ConsoleStdout((chunk) => {\n postWorkerMessage({\n type: \"stderr\",\n chunk,\n });\n }),\n ]\n );\n installWasiPollScheduler(wasiBridge, stdin, message.pauseCell);\n\n const response = await fetch(message.wasmURL);\n if (!response.ok) {\n throw new Error(`failed to load ${message.wasmURL}: ${response.status} ${response.statusText}`);\n }\n\n const module = await WebAssembly.compile(await response.arrayBuffer());\n const instance = await WebAssembly.instantiate(module, {\n wasi_snapshot_preview1: wasiBridge.wasiImport,\n });\n\n const exitCode = wasiBridge.start(instance as WebAssembly.Instance);\n postWorkerMessage({\n type: \"exit\",\n code: exitCode,\n });\n } catch (error) {\n postWorkerMessage({\n type: \"error\",\n message: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nfunction postWorkerMessage(\n message: WasmSceneWorkerResponse\n): void {\n globalThis.postMessage(message);\n}\n"],"mappings":"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"}
|
package/dist/wasi.d.ts
CHANGED
|
@@ -4,5 +4,6 @@ import { JSPIConstructors, WasmEngineCapabilities, WasmEngineFamily, WasmEngineP
|
|
|
4
4
|
import { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink } from "./src/wasi/BrowserWASIBridge.js";
|
|
5
5
|
import { SharedInputQueueBuffers, SharedInputQueueReader, SharedInputQueueWriter, SharedInputReadiness, createSharedInputQueue, hydrateSharedInputQueue, sharedInputQueueDefaultCapacity } from "./src/wasi/SharedInputQueue.js";
|
|
6
6
|
import { MainThreadWasmExecutor, MainThreadWasmExecutorOptions } from "./src/wasi/MainThreadWasmExecutor.js";
|
|
7
|
+
import { MainThreadWasmPauseGate, PausableMonotonicClock, WorkerWasmPauseGate, createWasmPauseCell, installPausableClockTimeGet, isWasmPauseCellPaused, setWasmPauseCellPaused } from "./src/wasi/WasmRuntimePause.js";
|
|
7
8
|
import { WasmExecutionMode, WasmExecutionModePreference, WasmSceneResizeEvent, WasmSceneRuntimeFactoryOptions, WasmSceneRuntimeHandle, createWasmSceneRuntimeFactory, resolveWasmExecutionMode } from "./src/wasi/WasmSceneRuntime.js";
|
|
8
|
-
export { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink, JSPIConstructors, MainThreadWasmExecutor, MainThreadWasmExecutorOptions, SharedInputQueueBuffers, SharedInputQueueReader, SharedInputQueueWriter, SharedInputReadiness, StdIOPipe, WasmEngineCapabilities, WasmEngineFamily, WasmEngineProbeSignals, WasmExecutionMode, WasmExecutionModePreference, WasmSceneResizeEvent, WasmSceneRuntimeFactoryOptions, WasmSceneRuntimeHandle, classifyWasmEngineFamily, collectWasmEngineProbeSignals, createSharedInputQueue, createWasmSceneRuntimeFactory, encodeRenderStyleControlMessage, encodeResizeControlMessage, hydrateSharedInputQueue, jspiConstructors, mainThreadStackProfileEnvironmentDefaults, resolveWasmEngineCapabilities, resolveWasmExecutionMode, sharedInputQueueDefaultCapacity, stackProfileEnvironmentDefaults };
|
|
9
|
+
export { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink, JSPIConstructors, MainThreadWasmExecutor, MainThreadWasmExecutorOptions, MainThreadWasmPauseGate, PausableMonotonicClock, SharedInputQueueBuffers, SharedInputQueueReader, SharedInputQueueWriter, SharedInputReadiness, StdIOPipe, WasmEngineCapabilities, WasmEngineFamily, WasmEngineProbeSignals, WasmExecutionMode, WasmExecutionModePreference, WasmSceneResizeEvent, WasmSceneRuntimeFactoryOptions, WasmSceneRuntimeHandle, WorkerWasmPauseGate, classifyWasmEngineFamily, collectWasmEngineProbeSignals, createSharedInputQueue, createWasmPauseCell, createWasmSceneRuntimeFactory, encodeRenderStyleControlMessage, encodeResizeControlMessage, hydrateSharedInputQueue, installPausableClockTimeGet, isWasmPauseCellPaused, jspiConstructors, mainThreadStackProfileEnvironmentDefaults, resolveWasmEngineCapabilities, resolveWasmExecutionMode, setWasmPauseCellPaused, sharedInputQueueDefaultCapacity, stackProfileEnvironmentDefaults };
|
package/dist/wasi.js
CHANGED
|
@@ -2,7 +2,8 @@ import { StdIOPipe } from "./src/wasi/StdIOPipe.js";
|
|
|
2
2
|
import { classifyWasmEngineFamily, collectWasmEngineProbeSignals, jspiConstructors, mainThreadStackProfileEnvironmentDefaults, resolveWasmEngineCapabilities, stackProfileEnvironmentDefaults } from "./src/wasi/WasmEngineCapabilities.js";
|
|
3
3
|
import { encodeRenderStyleControlMessage, encodeResizeControlMessage } from "./src/WebHostSurfaceTransport.js";
|
|
4
4
|
import { BrowserWASIBridge } from "./src/wasi/BrowserWASIBridge.js";
|
|
5
|
+
import { MainThreadWasmPauseGate, PausableMonotonicClock, WorkerWasmPauseGate, createWasmPauseCell, installPausableClockTimeGet, isWasmPauseCellPaused, setWasmPauseCellPaused } from "./src/wasi/WasmRuntimePause.js";
|
|
5
6
|
import { MainThreadWasmExecutor } from "./src/wasi/MainThreadWasmExecutor.js";
|
|
6
7
|
import { SharedInputQueueReader, SharedInputQueueWriter, createSharedInputQueue, hydrateSharedInputQueue, sharedInputQueueDefaultCapacity } from "./src/wasi/SharedInputQueue.js";
|
|
7
8
|
import { createWasmSceneRuntimeFactory, resolveWasmExecutionMode } from "./src/wasi/WasmSceneRuntime.js";
|
|
8
|
-
export { BrowserWASIBridge, MainThreadWasmExecutor, SharedInputQueueReader, SharedInputQueueWriter, StdIOPipe, classifyWasmEngineFamily, collectWasmEngineProbeSignals, createSharedInputQueue, createWasmSceneRuntimeFactory, encodeRenderStyleControlMessage, encodeResizeControlMessage, hydrateSharedInputQueue, jspiConstructors, mainThreadStackProfileEnvironmentDefaults, resolveWasmEngineCapabilities, resolveWasmExecutionMode, sharedInputQueueDefaultCapacity, stackProfileEnvironmentDefaults };
|
|
9
|
+
export { BrowserWASIBridge, MainThreadWasmExecutor, MainThreadWasmPauseGate, PausableMonotonicClock, SharedInputQueueReader, SharedInputQueueWriter, StdIOPipe, WorkerWasmPauseGate, classifyWasmEngineFamily, collectWasmEngineProbeSignals, createSharedInputQueue, createWasmPauseCell, createWasmSceneRuntimeFactory, encodeRenderStyleControlMessage, encodeResizeControlMessage, hydrateSharedInputQueue, installPausableClockTimeGet, isWasmPauseCellPaused, jspiConstructors, mainThreadStackProfileEnvironmentDefaults, resolveWasmEngineCapabilities, resolveWasmExecutionMode, setWasmPauseCellPaused, sharedInputQueueDefaultCapacity, stackProfileEnvironmentDefaults };
|