import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import createModule from "../../build-wasm/linuxcnc-sim-core.js"; import { SimScheduler, SCHEDULER_PHASES } from "./sim-scheduler.mjs"; import { SimSession, CONTEXT_ID } from "./sim-session.mjs"; const wasmBinary = await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)); const core = await createModule({ wasmBinary }); const fast = new SimScheduler(core, { budget: 100 }); const stepped = new SimScheduler(core, { budget: 100 }); const seen = []; fast.step(10, { reconcile: () => seen.push("reconcile"), task: () => seen.push("task"), tp: () => seen.push("tp"), kinematics: () => seen.push("kinematics"), motion: () => seen.push("motion"), hal_io: () => seen.push("hal_io"), callbacks: () => seen.push("callbacks"), publish: () => seen.push("publish") }); for (let index = 0; index < 10; index += 1) stepped.step(1); assert.equal(fast.tick, 10); assert.equal(fast.hash(), stepped.hash()); assert.deepEqual(seen.slice(0, 8), SCHEDULER_PHASES); assert.throws(() => fast.step(101), /scheduler_budget_exceeded/); assert.equal(fast.tick, 10); const restored = new SimScheduler(core, { budget: 100 }); assert.equal(restored.restore(fast.export()).state_hash, fast.hash()); const interrupted = new SimScheduler(core, { budget: 100 }); assert.throws(() => interrupted.step(10, { motion: (tick) => { if (tick === 3) throw new Error("injected_motion_fault"); } }), /injected_motion_fault/); assert.equal(interrupted.tick, 3); assert.equal(interrupted.last_cycles, 3); const afterFault = []; interrupted.step(2, { motion: (tick) => afterFault.push(tick) }); assert.deepEqual(afterFault, [4, 5]); assert.equal(interrupted.tick, 5); let lastSeq = 0; const session = new SimSession({ _sim_protocol_reset() {}, _sim_protocol_accept(...args) { if (args[1] !== lastSeq + 1) return -101; lastSeq = args[1]; return 0; }, _sim_create() { return 0; }, _sim_load_workspace() { return 0; }, _sim_step() { return 0; }, _sim_protocol_set_tick() { return 0; }, }, { sessionId: "scheduler-session" }); const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: "scheduler-session" }; session.accept({ ...base, kind: "HELLO", seq: 1, payload: {} }); session.accept({ ...base, kind: "SESSION_ATTACH", seq: 2, operation_id: "attach", payload: {} }); session.accept({ ...base, kind: "LOAD_WORKSPACE", seq: 3, operation_id: "load", payload: { root_path: "/workspace" } }); const steppedResponse = session.accept({ ...base, kind: "SIM_STEP", seq: 4, operation_id: "step", payload: { cycles: 4 } }); assert.equal(steppedResponse.payload.scheduler.tick, 4); assert.deepEqual(steppedResponse.payload.scheduler.phase_trace, [...SCHEDULER_PHASES]); assert.equal(steppedResponse.payload.task.simulation_tick, 4); console.log(`sim_scheduler=passed tick=${fast.tick} state_hash=${fast.hash()} worker_hash=${steppedResponse.payload.scheduler.state_hash}`);