Evolved crates/wasm/ from hardcoded PoC to a generic browser runtime: - WasmRuntime: wraps Runtime, provides tick/send/stop/stats/inbox creation - WasmAddr: opaque actor address handle for JS (replaces fragile indices) - WasmInboxU32, WasmInboxBytes: typed inboxes for receiving actor results - Free-standing spawn_counter/spawn_relay demonstrate the actor pattern - 10 Node.js tests pass (accumulator, relay, multi-counter, stop, bytes) Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
158 lines
4.4 KiB
JavaScript
158 lines
4.4 KiB
JavaScript
import {
|
|
WasmRuntime,
|
|
WasmAddr,
|
|
spawn_counter,
|
|
spawn_relay,
|
|
} from "./pkg/wasm.js";
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
function assert(cond, msg) {
|
|
if (!cond) {
|
|
console.error(` FAIL: ${msg}`);
|
|
failed++;
|
|
} else {
|
|
passed++;
|
|
}
|
|
}
|
|
|
|
function assertEq(a, b, msg) {
|
|
const aj = JSON.stringify(a);
|
|
const bj = JSON.stringify(b);
|
|
if (aj !== bj) {
|
|
console.error(` FAIL: ${msg} (got ${aj}, expected ${bj})`);
|
|
failed++;
|
|
} else {
|
|
passed++;
|
|
}
|
|
}
|
|
|
|
function drainInbox(inbox) {
|
|
const results = [];
|
|
let v;
|
|
while ((v = inbox.try_recv()) !== undefined) results.push(v);
|
|
return results;
|
|
}
|
|
|
|
// ---- accumulator ----------------------------------------------------------
|
|
{
|
|
console.log("test: accumulator processes messages via WasmAddr");
|
|
const rt = new WasmRuntime();
|
|
const inbox = rt.new_inbox_u32();
|
|
const c = spawn_counter(rt, inbox.addr());
|
|
rt.send_u32(c, 1);
|
|
rt.send_u32(c, 2);
|
|
rt.send_u32(c, 10);
|
|
rt.tick();
|
|
assertEq(drainInbox(inbox), [1, 3, 13], "running totals");
|
|
inbox.free();
|
|
rt.free();
|
|
}
|
|
|
|
// ---- relay ----------------------------------------------------------------
|
|
{
|
|
console.log("test: relay forwards to counter");
|
|
const rt = new WasmRuntime();
|
|
const inbox = rt.new_inbox_u32();
|
|
const c = spawn_counter(rt, inbox.addr());
|
|
const r = spawn_relay(rt, c);
|
|
rt.send_u32(r, 5);
|
|
rt.send_u32(r, 7);
|
|
// tick 1: relay receives and forwards (same worker → pending_local)
|
|
// tick 2: counter receives forwarded messages
|
|
rt.tick();
|
|
rt.tick();
|
|
assertEq(drainInbox(inbox), [5, 12], "relayed totals");
|
|
inbox.free();
|
|
rt.free();
|
|
}
|
|
|
|
// ---- multiple counters ----------------------------------------------------
|
|
{
|
|
console.log("test: multiple independent counters");
|
|
const rt = new WasmRuntime();
|
|
const inbox = rt.new_inbox_u32();
|
|
const a = spawn_counter(rt, inbox.addr());
|
|
const b = spawn_counter(rt, inbox.addr());
|
|
rt.send_u32(a, 10);
|
|
rt.send_u32(b, 100);
|
|
rt.tick();
|
|
const results = drainInbox(inbox);
|
|
assert(
|
|
results.includes(10) && results.includes(100) && results.length === 2,
|
|
"both counters report"
|
|
);
|
|
inbox.free();
|
|
rt.free();
|
|
}
|
|
|
|
// ---- actor_count ----------------------------------------------------------
|
|
{
|
|
console.log("test: actor_count tracks spawns");
|
|
const rt = new WasmRuntime();
|
|
const inbox = rt.new_inbox_u32();
|
|
spawn_counter(rt, inbox.addr());
|
|
spawn_counter(rt, inbox.addr());
|
|
spawn_counter(rt, inbox.addr());
|
|
rt.tick(); // drain spawn queue
|
|
assertEq(rt.actor_count(), 3, "three actors");
|
|
inbox.free();
|
|
rt.free();
|
|
}
|
|
|
|
// ---- WasmAddr toString ----------------------------------------------------
|
|
{
|
|
console.log("test: WasmAddr has string representation");
|
|
const rt = new WasmRuntime();
|
|
const inbox = rt.new_inbox_u32();
|
|
const addr = spawn_counter(rt, inbox.addr());
|
|
const s = addr.toString();
|
|
// no_random generates deterministic addresses — just check it's a non-empty hex string
|
|
assert(typeof s === "string" && s.length > 0, "addr toString is non-empty string");
|
|
inbox.free();
|
|
rt.free();
|
|
}
|
|
|
|
// ---- stop_actor -----------------------------------------------------------
|
|
{
|
|
console.log("test: stop_actor removes actor");
|
|
const rt = new WasmRuntime();
|
|
const inbox = rt.new_inbox_u32();
|
|
const c = spawn_counter(rt, inbox.addr());
|
|
rt.tick(); // drain spawn
|
|
assertEq(rt.actor_count(), 1, "one actor before stop");
|
|
rt.stop_actor(c);
|
|
rt.tick(); // process stop + cleanup
|
|
assertEq(rt.actor_count(), 0, "zero actors after stop");
|
|
inbox.free();
|
|
rt.free();
|
|
}
|
|
|
|
// ---- bytes inbox ----------------------------------------------------------
|
|
{
|
|
console.log("test: byte inbox receives Uint8Array");
|
|
const rt = new WasmRuntime();
|
|
const inbox = rt.new_inbox_bytes();
|
|
// Send bytes directly (no actor — just to the inbox address)
|
|
rt.send_bytes(inbox.addr(), new Uint8Array([1, 2, 3]));
|
|
rt.tick();
|
|
const result = inbox.try_recv();
|
|
assert(result instanceof Uint8Array, "result is Uint8Array");
|
|
assertEq(Array.from(result), [1, 2, 3], "bytes match");
|
|
inbox.free();
|
|
rt.free();
|
|
}
|
|
|
|
// ---- uptime ---------------------------------------------------------------
|
|
{
|
|
console.log("test: uptime_ms returns a number");
|
|
const rt = new WasmRuntime();
|
|
const uptime = rt.uptime_ms();
|
|
assert(typeof uptime === "number" && uptime >= 0, "uptime is non-negative number");
|
|
rt.free();
|
|
}
|
|
|
|
// ---- results --------------------------------------------------------------
|
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
if (failed > 0) process.exit(1);
|