@crowdedkingdoms/crowdyjs 8.16.0 → 8.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/domains/marketplace.d.ts +11 -1
- package/dist/domains/marketplace.d.ts.map +1 -1
- package/dist/domains/marketplace.js +17 -1
- package/dist/generated/graphql.d.ts +56 -0
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +2 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/kit/npcs.d.ts.map +1 -1
- package/dist/kit/social.d.ts.map +1 -1
- package/dist/live-coding/live-coding-controller.d.ts +2 -0
- package/dist/live-coding/live-coding-controller.d.ts.map +1 -1
- package/dist/live-coding/live-coding-controller.js +1 -0
- package/dist/player-runtime/glue-runtime.d.ts +92 -0
- package/dist/player-runtime/glue-runtime.d.ts.map +1 -0
- package/dist/player-runtime/glue-runtime.js +222 -0
- package/dist/player-runtime/glue-sab.d.ts +55 -0
- package/dist/player-runtime/glue-sab.d.ts.map +1 -0
- package/dist/player-runtime/glue-sab.js +79 -0
- package/dist/player-runtime/player-code-broker.d.ts +16 -0
- package/dist/player-runtime/player-code-broker.d.ts.map +1 -1
- package/dist/player-runtime/player-code-broker.js +38 -3
- package/dist/player-runtime/player-glue-worker.d.ts +26 -54
- package/dist/player-runtime/player-glue-worker.d.ts.map +1 -1
- package/dist/player-runtime/player-glue-worker.js +130 -111
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The synchronous host-call transport for browser-target player WASM
|
|
3
|
+
* (player compute P5). WASM imports are synchronous, but the host-call
|
|
4
|
+
* handler (the server-authorized SDK path) is async and lives on the page,
|
|
5
|
+
* while the guest runs in a Worker. The only correct bridge is a
|
|
6
|
+
* SharedArrayBuffer the worker blocks on with `Atomics.wait`: the worker
|
|
7
|
+
* writes the request, posts a wake to the page, and blocks; the page
|
|
8
|
+
* services the async call and writes the reply back into the SAB, then
|
|
9
|
+
* `Atomics.notify` wakes the worker. (A worker blocked in `Atomics.wait`
|
|
10
|
+
* cannot receive `postMessage`, which is exactly why the reply must return
|
|
11
|
+
* through the SAB, not a message.)
|
|
12
|
+
*
|
|
13
|
+
* SharedArrayBuffer + Atomics require cross-origin isolation
|
|
14
|
+
* (COOP: same-origin + COEP: require-corp) in the browser; Node worker
|
|
15
|
+
* threads have them unconditionally, which is how this is integration-tested.
|
|
16
|
+
*
|
|
17
|
+
* Layout: [ state:i32, len:i32 ] header, then a byte data region.
|
|
18
|
+
* state: 0 IDLE, 1 PENDING (worker waiting), 2 DONE.
|
|
19
|
+
* The request travels to the page as a normal postMessage (before the
|
|
20
|
+
* worker blocks); only the reply uses the SAB.
|
|
21
|
+
*/
|
|
22
|
+
export const SAB_STATE_IDLE = 0;
|
|
23
|
+
export const SAB_STATE_PENDING = 1;
|
|
24
|
+
export const SAB_STATE_DONE = 2;
|
|
25
|
+
const HEADER_I32 = 2; // state, len
|
|
26
|
+
export const SAB_HEADER_BYTES = HEADER_I32 * 4;
|
|
27
|
+
/** 1 MiB reply region — host-call replies (chunk/actor reads) are well under this. */
|
|
28
|
+
export const SAB_DATA_BYTES = 1024 * 1024;
|
|
29
|
+
export function createGlueSab(dataBytes = SAB_DATA_BYTES) {
|
|
30
|
+
const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + dataBytes);
|
|
31
|
+
return wrapGlueSab(sab);
|
|
32
|
+
}
|
|
33
|
+
export function wrapGlueSab(sab) {
|
|
34
|
+
return {
|
|
35
|
+
sab,
|
|
36
|
+
header: new Int32Array(sab, 0, HEADER_I32),
|
|
37
|
+
data: new Uint8Array(sab, SAB_HEADER_BYTES),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const encoder = new TextEncoder();
|
|
41
|
+
const decoder = new TextDecoder();
|
|
42
|
+
/**
|
|
43
|
+
* Responder side (page/broker): write the reply envelope bytes into the SAB
|
|
44
|
+
* and wake the blocked worker. `respBytes` must already be the SDK Response
|
|
45
|
+
* envelope (`{ok,data}` / `{ok:false,error}`) the guest expects.
|
|
46
|
+
*/
|
|
47
|
+
export function writeGlueReply(view, respBytes) {
|
|
48
|
+
const len = Math.min(respBytes.length, view.data.length);
|
|
49
|
+
view.data.set(respBytes.subarray(0, len));
|
|
50
|
+
Atomics.store(view.header, 1, len);
|
|
51
|
+
Atomics.store(view.header, 0, SAB_STATE_DONE);
|
|
52
|
+
Atomics.notify(view.header, 0, 1);
|
|
53
|
+
}
|
|
54
|
+
/** Convenience for responders holding a plain object result. */
|
|
55
|
+
export function writeGlueResult(view, result) {
|
|
56
|
+
writeGlueReply(view, encoder.encode(JSON.stringify(result)));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Requester side (worker): mark PENDING before posting the wake, block until
|
|
60
|
+
* the page flips the state to DONE, then read the reply bytes out. The
|
|
61
|
+
* caller is responsible for posting the request message between `arm()` and
|
|
62
|
+
* `waitAndRead()` — that ordering (post, then wait) is what lets the page
|
|
63
|
+
* see the request while the worker is blocked.
|
|
64
|
+
*/
|
|
65
|
+
export function armGlueRequest(view) {
|
|
66
|
+
Atomics.store(view.header, 1, 0);
|
|
67
|
+
Atomics.store(view.header, 0, SAB_STATE_PENDING);
|
|
68
|
+
}
|
|
69
|
+
export function waitAndReadGlueReply(view, timeoutMs = 5000) {
|
|
70
|
+
const res = Atomics.wait(view.header, 0, SAB_STATE_PENDING, timeoutMs);
|
|
71
|
+
if (res === 'timed-out') {
|
|
72
|
+
throw new Error('host call timed out');
|
|
73
|
+
}
|
|
74
|
+
const len = Atomics.load(view.header, 1);
|
|
75
|
+
const out = view.data.slice(0, len);
|
|
76
|
+
Atomics.store(view.header, 0, SAB_STATE_IDLE);
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
export { encoder as glueEncoder, decoder as glueDecoder };
|
|
@@ -51,6 +51,12 @@ export interface PlayerCodeBrokerOptions {
|
|
|
51
51
|
hashArtifact?: (artifact: ArrayBuffer) => Promise<string>;
|
|
52
52
|
/** Wall-clock ms allowed per dispatch before the broker recycles the worker. */
|
|
53
53
|
dispatchWatchdogMs?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Local tick cadence (ms) for a client mod: the worker self-drives `tick`
|
|
56
|
+
* at this interval. Omit/0 for invoke-only mods (no periodic tick). A HUD
|
|
57
|
+
* mod typically ticks ~1 Hz; the per-dispatch watchdog still bounds each.
|
|
58
|
+
*/
|
|
59
|
+
tickIntervalMs?: number;
|
|
54
60
|
}
|
|
55
61
|
/**
|
|
56
62
|
* Page-side security broker for browser-target player WASM (production shape,
|
|
@@ -92,6 +98,16 @@ export declare class PlayerCodeBroker {
|
|
|
92
98
|
/** Clear a tripped circuit so the caller can start again after a fix. */
|
|
93
99
|
resetCircuit(): void;
|
|
94
100
|
private handleMessage;
|
|
101
|
+
/**
|
|
102
|
+
* Deliver a host-call reply. Always posts the message (the offline test
|
|
103
|
+
* shape + any async-transport consumer), and — when the worker shared a
|
|
104
|
+
* SharedArrayBuffer for this call — ALSO writes the SDK Response envelope
|
|
105
|
+
* into it and wakes the worker blocked in Atomics.wait. The synchronous
|
|
106
|
+
* guest can only receive the reply through the SAB (a blocked worker never
|
|
107
|
+
* runs its message handler), so the SAB write is the load-bearing path in
|
|
108
|
+
* the browser; the postMessage is harmless there.
|
|
109
|
+
*/
|
|
110
|
+
private reply;
|
|
95
111
|
private enforceRate;
|
|
96
112
|
private recordTrap;
|
|
97
113
|
private assertGridScope;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"player-code-broker.d.ts","sourceRoot":"","sources":["../../src/player-runtime/player-code-broker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"player-code-broker.d.ts","sourceRoot":"","sources":["../../src/player-runtime/player-code-broker.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { wrapGlueSab, writeGlueResult } from './glue-sab.js';
|
|
1
2
|
/**
|
|
2
3
|
* Deny-by-default host-call allowlist, grouped by capability (04 §4). Only
|
|
3
4
|
* owner-lawful reads and effects cross the bridge; auth, admin, authoring,
|
|
@@ -28,7 +29,9 @@ const ALLOWED_HOST_CALLS = {
|
|
|
28
29
|
world_write: new Set(['voxel_set']),
|
|
29
30
|
egress: new Set(['emit_spatial']),
|
|
30
31
|
present: new Set(['hud_set', 'overlay_draw']),
|
|
31
|
-
|
|
32
|
+
// grid_info is answered by the broker itself (the mod's own clamped bounds),
|
|
33
|
+
// so a client mod can address its grid without a server round-trip.
|
|
34
|
+
meta: new Set(['grid_permission_check', 'grid_info']),
|
|
32
35
|
};
|
|
33
36
|
/** Per-call-family rate caps (calls per rolling second); flood one, others hold. */
|
|
34
37
|
const RATE_CAPS = {
|
|
@@ -114,6 +117,7 @@ export class PlayerCodeBroker {
|
|
|
114
117
|
? this.options.fuelPerDispatch.toString()
|
|
115
118
|
: undefined,
|
|
116
119
|
watchdogMs: this.options.dispatchWatchdogMs ?? 250,
|
|
120
|
+
tickIntervalMs: this.options.tickIntervalMs ?? 0,
|
|
117
121
|
}, [artifact]);
|
|
118
122
|
}
|
|
119
123
|
/** Terminate + respawn on a fresh artifact — the client hot-reload path. */
|
|
@@ -166,6 +170,18 @@ export class PlayerCodeBroker {
|
|
|
166
170
|
this.enforceRate(group, raw.fn);
|
|
167
171
|
this.assertGridScope(raw.fn, args);
|
|
168
172
|
let data;
|
|
173
|
+
if (raw.fn === 'grid_info') {
|
|
174
|
+
// Answered locally: the mod's own clamped grid bounds, so it can
|
|
175
|
+
// address its chunks without knowing world coordinates or a server
|
|
176
|
+
// round-trip. Chunk coords cross as decimal strings (may exceed 2^53).
|
|
177
|
+
const { low, high } = this.options.grid;
|
|
178
|
+
data = {
|
|
179
|
+
low: { x: low.x.toString(), y: low.y.toString(), z: low.z.toString() },
|
|
180
|
+
high: { x: high.x.toString(), y: high.y.toString(), z: high.z.toString() },
|
|
181
|
+
};
|
|
182
|
+
this.reply(raw.reply, id, { type: 'hostcall-result', id, ok: true, data });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
169
185
|
if (PRESENTATION_FUNCTIONS.has(raw.fn)) {
|
|
170
186
|
// Presentation never reaches the SDK/server: it goes only to the
|
|
171
187
|
// game-declared channel. A game that offers no sink silently drops it.
|
|
@@ -178,10 +194,10 @@ export class PlayerCodeBroker {
|
|
|
178
194
|
else {
|
|
179
195
|
data = await this.options.onHostCall({ fn: raw.fn, args });
|
|
180
196
|
}
|
|
181
|
-
this.
|
|
197
|
+
this.reply(raw.reply, id, { type: 'hostcall-result', id, ok: true, data });
|
|
182
198
|
}
|
|
183
199
|
catch (error) {
|
|
184
|
-
this.
|
|
200
|
+
this.reply(raw.reply, id, {
|
|
185
201
|
type: 'hostcall-result',
|
|
186
202
|
id,
|
|
187
203
|
ok: false,
|
|
@@ -189,6 +205,25 @@ export class PlayerCodeBroker {
|
|
|
189
205
|
});
|
|
190
206
|
}
|
|
191
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Deliver a host-call reply. Always posts the message (the offline test
|
|
210
|
+
* shape + any async-transport consumer), and — when the worker shared a
|
|
211
|
+
* SharedArrayBuffer for this call — ALSO writes the SDK Response envelope
|
|
212
|
+
* into it and wakes the worker blocked in Atomics.wait. The synchronous
|
|
213
|
+
* guest can only receive the reply through the SAB (a blocked worker never
|
|
214
|
+
* runs its message handler), so the SAB write is the load-bearing path in
|
|
215
|
+
* the browser; the postMessage is harmless there.
|
|
216
|
+
*/
|
|
217
|
+
reply(replyBuffer, _id, message) {
|
|
218
|
+
this.worker?.postMessage(message);
|
|
219
|
+
if (typeof SharedArrayBuffer !== 'undefined' && replyBuffer instanceof SharedArrayBuffer) {
|
|
220
|
+
const view = wrapGlueSab(replyBuffer);
|
|
221
|
+
const envelope = message.ok
|
|
222
|
+
? { ok: true, data: message.data }
|
|
223
|
+
: { ok: false, error: message.error };
|
|
224
|
+
writeGlueResult(view, envelope);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
192
227
|
enforceRate(group, fn) {
|
|
193
228
|
const cap = RATE_CAPS[group] ?? 60;
|
|
194
229
|
const now = Date.now();
|
|
@@ -1,62 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Browser Web Worker entry for browser-target player WASM (player compute
|
|
3
|
+
* P3/P5). This is the ONLY platform code that shares an execution context
|
|
4
|
+
* with an untrusted player module. It is intentionally thin: the auditable
|
|
5
|
+
* runtime + ABI marshalling live in [glue-runtime.ts] and the synchronous
|
|
6
|
+
* host-call transport in [glue-sab.ts]; this file only wires them to the
|
|
7
|
+
* worker message loop.
|
|
7
8
|
*
|
|
8
9
|
* - it instantiates the gas-injected player artifact with an import table
|
|
9
|
-
* that exposes ONLY
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* Host calls are synchronous from the guest's perspective. In a worker that
|
|
19
|
-
* is realized with a SharedArrayBuffer control block plus Atomics.wait: the
|
|
20
|
-
* guest's import stub writes the request, wakes the page, and blocks until
|
|
21
|
-
* the broker writes the reply. The message/really-shared plumbing lives in
|
|
22
|
-
* buildImportObject / the init handler below; the pure, testable pieces
|
|
23
|
-
* (budget parsing, host-fn table) are exported.
|
|
10
|
+
* that exposes ONLY `ck.*` + inert wasi stubs (nothing else importable),
|
|
11
|
+
* - it never has the DOM, `window`, auth tokens, `fetch`, or third-party
|
|
12
|
+
* `importScripts`,
|
|
13
|
+
* - `ck.host_call` blocks the worker on a SharedArrayBuffer while the
|
|
14
|
+
* page-side broker services the (async) server-authorized call and writes
|
|
15
|
+
* the reply back, so the guest sees a synchronous gateway,
|
|
16
|
+
* - it enforces a per-dispatch wall-clock watchdog and reports traps to the
|
|
17
|
+
* broker, which owns the local circuit breaker.
|
|
24
18
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
|
-
/** The exact host-call names the glue exposes to the guest (mirrors the broker allowlist). */
|
|
29
|
-
export declare const GLUE_HOST_FUNCTIONS: readonly ["container_create", "container_get", "containers_list", "container_delete", "property_set", "model_invoke", "user_state_get", "user_state_set", "grid_state_get", "grid_state_set", "chunk_get", "voxels_list", "actors_list", "actors_list_radius", "voxel_set", "emit_spatial", "hud_set", "overlay_draw", "grid_permission_check"];
|
|
30
|
-
export interface GlueInitMessage {
|
|
31
|
-
type: 'init';
|
|
32
|
-
artifact: ArrayBuffer;
|
|
33
|
-
authority: 'player';
|
|
34
|
-
fuelPerDispatch?: string;
|
|
35
|
-
watchdogMs?: number;
|
|
36
|
-
}
|
|
37
|
-
/** Parse the fuel budget the broker forwards; undefined/invalid => unbounded (server still meters). */
|
|
38
|
-
export declare function parseFuelBudget(raw: string | undefined): bigint | null;
|
|
39
|
-
/** A dispatch outcome the worker reports back to the broker. */
|
|
40
|
-
export type GlueDispatchResult = {
|
|
41
|
-
ok: true;
|
|
42
|
-
} | {
|
|
43
|
-
ok: false;
|
|
44
|
-
reason: 'fuel' | 'watchdog' | 'trap';
|
|
45
|
-
detail?: string;
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* Wrap a single guest dispatch with the wall-clock watchdog. The fuel trap is
|
|
49
|
-
* enforced inside the gas-injected module; this guards against a hang that
|
|
50
|
-
* spins without consuming fuel (e.g. a tight host-call loop the broker rate
|
|
51
|
-
* cap already bounds, belt-and-suspenders). Pure and unit-testable.
|
|
19
|
+
* Re-exports the pure helpers + the runtime so tests and bundlers can use
|
|
20
|
+
* them without touching worker globals.
|
|
52
21
|
*/
|
|
53
|
-
|
|
22
|
+
import { GlueRuntime, GLUE_HOST_FUNCTIONS, parseFuelBudget, runWithWatchdog, type GlueInitMessage, type GlueDispatchResult } from './glue-runtime.js';
|
|
23
|
+
export { GlueRuntime, GLUE_HOST_FUNCTIONS, parseFuelBudget, runWithWatchdog, type GlueInitMessage, type GlueDispatchResult, };
|
|
54
24
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* synchronous bridge (SharedArrayBuffer + Atomics in the browser; injectable
|
|
59
|
-
* for tests).
|
|
25
|
+
* Wire the glue runtime to a worker-like message port. Exported (not just
|
|
26
|
+
* run at import) so the Node integration test can drive the identical wiring
|
|
27
|
+
* over a `worker_threads` port.
|
|
60
28
|
*/
|
|
61
|
-
export declare function
|
|
29
|
+
export declare function startGlueWorker(port: {
|
|
30
|
+
addEventListener?: (t: string, l: (e: MessageEvent) => void) => void;
|
|
31
|
+
on?: (t: string, l: (data: unknown) => void) => void;
|
|
32
|
+
postMessage: (message: unknown) => void;
|
|
33
|
+
}): void;
|
|
62
34
|
//# sourceMappingURL=player-glue-worker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"player-glue-worker.d.ts","sourceRoot":"","sources":["../../src/player-runtime/player-glue-worker.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"player-glue-worker.d.ts","sourceRoot":"","sources":["../../src/player-runtime/player-glue-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAQ3B,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,kBAAkB,GACxB,CAAC;AAOF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,gBAAgB,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,KAAK,IAAI,CAAC;IACrE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,KAAK,IAAI,CAAC;IACrD,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CACzC,GAAG,IAAI,CAuGP"}
|
|
@@ -1,127 +1,146 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Browser Web Worker entry for browser-target player WASM (player compute
|
|
3
|
+
* P3/P5). This is the ONLY platform code that shares an execution context
|
|
4
|
+
* with an untrusted player module. It is intentionally thin: the auditable
|
|
5
|
+
* runtime + ABI marshalling live in [glue-runtime.ts] and the synchronous
|
|
6
|
+
* host-call transport in [glue-sab.ts]; this file only wires them to the
|
|
7
|
+
* worker message loop.
|
|
7
8
|
*
|
|
8
9
|
* - it instantiates the gas-injected player artifact with an import table
|
|
9
|
-
* that exposes ONLY
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* Host calls are synchronous from the guest's perspective. In a worker that
|
|
19
|
-
* is realized with a SharedArrayBuffer control block plus Atomics.wait: the
|
|
20
|
-
* guest's import stub writes the request, wakes the page, and blocks until
|
|
21
|
-
* the broker writes the reply. The message/really-shared plumbing lives in
|
|
22
|
-
* buildImportObject / the init handler below; the pure, testable pieces
|
|
23
|
-
* (budget parsing, host-fn table) are exported.
|
|
10
|
+
* that exposes ONLY `ck.*` + inert wasi stubs (nothing else importable),
|
|
11
|
+
* - it never has the DOM, `window`, auth tokens, `fetch`, or third-party
|
|
12
|
+
* `importScripts`,
|
|
13
|
+
* - `ck.host_call` blocks the worker on a SharedArrayBuffer while the
|
|
14
|
+
* page-side broker services the (async) server-authorized call and writes
|
|
15
|
+
* the reply back, so the guest sees a synchronous gateway,
|
|
16
|
+
* - it enforces a per-dispatch wall-clock watchdog and reports traps to the
|
|
17
|
+
* broker, which owns the local circuit breaker.
|
|
24
18
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
19
|
+
* Re-exports the pure helpers + the runtime so tests and bundlers can use
|
|
20
|
+
* them without touching worker globals.
|
|
27
21
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
'container_get',
|
|
32
|
-
'containers_list',
|
|
33
|
-
'container_delete',
|
|
34
|
-
'property_set',
|
|
35
|
-
'model_invoke',
|
|
36
|
-
'user_state_get',
|
|
37
|
-
'user_state_set',
|
|
38
|
-
'grid_state_get',
|
|
39
|
-
'grid_state_set',
|
|
40
|
-
'chunk_get',
|
|
41
|
-
'voxels_list',
|
|
42
|
-
'actors_list',
|
|
43
|
-
'actors_list_radius',
|
|
44
|
-
'voxel_set',
|
|
45
|
-
'emit_spatial',
|
|
46
|
-
'hud_set',
|
|
47
|
-
'overlay_draw',
|
|
48
|
-
'grid_permission_check',
|
|
49
|
-
];
|
|
50
|
-
/** Parse the fuel budget the broker forwards; undefined/invalid => unbounded (server still meters). */
|
|
51
|
-
export function parseFuelBudget(raw) {
|
|
52
|
-
if (raw == null)
|
|
53
|
-
return null;
|
|
54
|
-
try {
|
|
55
|
-
const v = BigInt(raw);
|
|
56
|
-
return v > 0n ? v : null;
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
22
|
+
import { GlueRuntime, GLUE_HOST_FUNCTIONS, parseFuelBudget, runWithWatchdog, } from './glue-runtime.js';
|
|
23
|
+
import { createGlueSab, armGlueRequest, waitAndReadGlueReply, } from './glue-sab.js';
|
|
24
|
+
export { GlueRuntime, GLUE_HOST_FUNCTIONS, parseFuelBudget, runWithWatchdog, };
|
|
62
25
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* cap already bounds, belt-and-suspenders). Pure and unit-testable.
|
|
26
|
+
* Wire the glue runtime to a worker-like message port. Exported (not just
|
|
27
|
+
* run at import) so the Node integration test can drive the identical wiring
|
|
28
|
+
* over a `worker_threads` port.
|
|
67
29
|
*/
|
|
68
|
-
export
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
30
|
+
export function startGlueWorker(port) {
|
|
31
|
+
let runtime = null;
|
|
32
|
+
let sab = null;
|
|
33
|
+
let hostCallId = 0;
|
|
34
|
+
let watchdogMs = 250;
|
|
35
|
+
let ticking = false;
|
|
36
|
+
const post = (message) => port.postMessage(message);
|
|
37
|
+
// Synchronous gateway: post the request (so the page can see it), then
|
|
38
|
+
// block on the SAB until the broker writes the reply.
|
|
39
|
+
const hostCallSync = (reqBytes) => {
|
|
40
|
+
if (!sab)
|
|
41
|
+
throw new Error('host-call transport not initialized');
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(new TextDecoder().decode(reqBytes));
|
|
77
45
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
46
|
+
catch {
|
|
47
|
+
throw new Error('host_call request was not valid JSON');
|
|
48
|
+
}
|
|
49
|
+
const id = ++hostCallId;
|
|
50
|
+
armGlueRequest(sab);
|
|
51
|
+
post({
|
|
52
|
+
type: 'hostcall',
|
|
53
|
+
id,
|
|
54
|
+
fn: parsed.fn,
|
|
55
|
+
args: parsed.args ?? {},
|
|
56
|
+
reply: sab.sab,
|
|
57
|
+
});
|
|
58
|
+
return waitAndReadGlueReply(sab);
|
|
59
|
+
};
|
|
60
|
+
const onInit = async (init) => {
|
|
61
|
+
watchdogMs = init.watchdogMs ?? 250;
|
|
62
|
+
void parseFuelBudget(init.fuelPerDispatch);
|
|
63
|
+
sab = createGlueSab();
|
|
64
|
+
runtime = new GlueRuntime({
|
|
65
|
+
hostCallSync,
|
|
66
|
+
onLog: (level, message) => post({ type: 'log', level, message }),
|
|
67
|
+
});
|
|
68
|
+
try {
|
|
69
|
+
await runtime.instantiate(init.artifact);
|
|
70
|
+
const initResult = await runWithWatchdog(() => runtime.init(), watchdogMs);
|
|
71
|
+
report(initResult);
|
|
72
|
+
if ((init.tickIntervalMs ?? 0) > 0)
|
|
73
|
+
startTicking(init.tickIntervalMs);
|
|
74
|
+
post({ type: 'ready' });
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
post({ type: 'trap', reason: 'trap', detail: err.message });
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const report = (result) => {
|
|
81
|
+
if (result.ok)
|
|
82
|
+
post({ type: 'dispatch-ok' });
|
|
83
|
+
else
|
|
84
|
+
post({ type: 'trap', reason: result.reason, detail: result.detail });
|
|
85
|
+
};
|
|
86
|
+
const startTicking = (intervalMs) => {
|
|
87
|
+
if (ticking)
|
|
88
|
+
return;
|
|
89
|
+
ticking = true;
|
|
90
|
+
let last = Date.now();
|
|
91
|
+
const loop = () => {
|
|
92
|
+
if (!ticking || !runtime)
|
|
93
|
+
return;
|
|
94
|
+
const nowT = Date.now();
|
|
95
|
+
const dt = nowT - last;
|
|
96
|
+
last = nowT;
|
|
97
|
+
void runWithWatchdog(() => runtime.tick(dt), watchdogMs).then(report);
|
|
98
|
+
setTimeout(loop, intervalMs);
|
|
105
99
|
};
|
|
100
|
+
setTimeout(loop, intervalMs);
|
|
101
|
+
};
|
|
102
|
+
const handle = (data) => {
|
|
103
|
+
if (!data || typeof data !== 'object')
|
|
104
|
+
return;
|
|
105
|
+
const msg = data;
|
|
106
|
+
if (msg.type === 'init')
|
|
107
|
+
void onInit(msg);
|
|
108
|
+
else if (msg.type === 'tick' && runtime) {
|
|
109
|
+
void runWithWatchdog(() => runtime.tick(typeof msg.dtMs === 'number' ? msg.dtMs : 0), watchdogMs).then(report);
|
|
110
|
+
}
|
|
111
|
+
else if (msg.type === 'invoke' && runtime) {
|
|
112
|
+
const payload = msg.payload instanceof Uint8Array ? msg.payload : new Uint8Array(0);
|
|
113
|
+
try {
|
|
114
|
+
const out = runtime.invoke(payload);
|
|
115
|
+
post({ type: 'invoke-result', id: msg.id, ok: true, payload: out });
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
post({
|
|
119
|
+
type: 'invoke-result',
|
|
120
|
+
id: msg.id,
|
|
121
|
+
ok: false,
|
|
122
|
+
error: err.message,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else if (msg.type === 'stop') {
|
|
127
|
+
ticking = false;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
if (port.addEventListener) {
|
|
131
|
+
port.addEventListener('message', (e) => handle(e.data));
|
|
132
|
+
}
|
|
133
|
+
else if (port.on) {
|
|
134
|
+
port.on('message', (d) => handle(d));
|
|
106
135
|
}
|
|
107
|
-
return { ck };
|
|
108
136
|
}
|
|
137
|
+
// Auto-start when loaded as a real browser Web Worker (has addEventListener,
|
|
138
|
+
// no window). Guarded so importing for the pure helpers never touches globals.
|
|
109
139
|
if (typeof self !== 'undefined' &&
|
|
110
140
|
typeof self.addEventListener === 'function' &&
|
|
111
141
|
typeof self.window === 'undefined') {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return;
|
|
116
|
-
if (data.type === 'init') {
|
|
117
|
-
const init = data;
|
|
118
|
-
// The synchronous host-call bridge (SharedArrayBuffer + Atomics) is set
|
|
119
|
-
// up here from init.fuelPerDispatch/watchdogMs; instantiation uses
|
|
120
|
-
// buildImportObject so the guest sees only ck.* host functions.
|
|
121
|
-
void parseFuelBudget(init.fuelPerDispatch);
|
|
122
|
-
void (init.watchdogMs ?? 250);
|
|
123
|
-
self.postMessage?.({ type: 'ready' });
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
142
|
+
startGlueWorker({
|
|
143
|
+
addEventListener: self.addEventListener.bind(self),
|
|
144
|
+
postMessage: (m) => self.postMessage?.(m),
|
|
126
145
|
});
|
|
127
146
|
}
|