Keep long G-code execution running past full planner queue
This commit is contained in:
@@ -2,18 +2,18 @@ const EVENT_SCHEMA = "event-log.v1";
|
||||
const SNAPSHOT_SCHEMA = "snapshot.v1";
|
||||
const REPLAY_SCHEMA = "replay-timeline.v1";
|
||||
|
||||
export function canonicalJson(value) {
|
||||
export function canonicalJson(value, path = "$") {
|
||||
if (value === null) return "null";
|
||||
if (typeof value === "number") {
|
||||
if (!Number.isFinite(value)) throw new Error("canonical_nonfinite");
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
if (typeof value !== "object") {
|
||||
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") throw new Error("canonical_unsupported");
|
||||
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") throw new Error(`canonical_unsupported:${path}`);
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
||||
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
|
||||
if (Array.isArray(value)) return `[${value.map((item, index) => canonicalJson(item, `${path}[${index}]`)).join(",")}]`;
|
||||
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key], `${path}.${key}`)}`).join(",")}}`;
|
||||
}
|
||||
|
||||
export function fnv1a64(value) {
|
||||
|
||||
@@ -151,7 +151,10 @@ export class SimSession {
|
||||
const lightweightXyzacStep = input.kind === "SIM_STEP" && Boolean(this.xyzacProgram);
|
||||
const currentMotion = this.wasmMotion.read();
|
||||
const completedMotion = program.status === 4 && currentMotion.state?.queue_depth === 0;
|
||||
const motionDelta = lightweightXyzacStep && input.payload?.response === "motion_delta" && !completedMotion;
|
||||
// Compact payloads are only valid for successful motion samples. Error
|
||||
// responses must retain their code/reason instead of being rewritten into
|
||||
// a delta with undefined fields.
|
||||
const motionDelta = kind === "COMMAND_RESULT" && lightweightXyzacStep && input.payload?.response === "motion_delta" && !completedMotion;
|
||||
const panelSnapshot = motionDelta ? null : this.panelSnapshot({ task, program, input, tick, motionTrace: statePayload.motion_trace, includeToolpath: !lightweightXyzacStep, includeConfiguration: !lightweightXyzacStep, includeStableRuntime: !lightweightXyzacStep });
|
||||
const compactProgram = motionDelta ? {
|
||||
schema: program.schema,
|
||||
@@ -1415,6 +1418,11 @@ export class SimSession {
|
||||
if (this.xyzacProgram) {
|
||||
const wasmState = this.xyzacProgram.snapshot();
|
||||
if (![2, 3].includes(wasmState.status)) return;
|
||||
// LinuxCNC's task/interpreter blocks on a full trajectory
|
||||
// planner queue. Let the motion phase consume one segment
|
||||
// before reading another source line instead of converting
|
||||
// normal planner backpressure into a program fault.
|
||||
if (this.wasmMotion.read().state.queue_depth >= this.wasmMotion.queueCapacity) return;
|
||||
if (this.pendingProgramControl !== 0) {
|
||||
if (this.wasmMotion.read().state.queue_depth !== 0) return;
|
||||
const current = this.wasmMotion.read().state.position;
|
||||
|
||||
@@ -44,13 +44,18 @@ assert.equal(send("POWER", { active: true }).kind, "COMMAND_RESULT");
|
||||
assert.equal(send("HOME", { all: true }).kind, "COMMAND_RESULT");
|
||||
const run = send("PROGRAM_RUN");
|
||||
assert.equal(run.kind, "COMMAND_RESULT");
|
||||
const stepped = send("SIM_STEP", { cycles: 16, response: "motion_delta" });
|
||||
let stepped = null;
|
||||
for (let cycle = 0; cycle < 300; cycle += 1) {
|
||||
stepped = send("SIM_STEP", { cycles: 1, response: "motion_delta" });
|
||||
assert.equal(stepped.kind, "COMMAND_RESULT");
|
||||
}
|
||||
assert.equal(stepped.kind, "COMMAND_RESULT");
|
||||
assert.equal(stepped.payload.program.program_status, "running");
|
||||
assert.ok(stepped.payload.program.current_line > 0);
|
||||
assert.ok(stepped.payload.program.current_line > 82);
|
||||
assert.ok(stepped.payload.execution_trace.length > 0);
|
||||
assert.ok(stepped.payload.program.execution_line_text.length > 0);
|
||||
assert.equal(stepped.payload.motion_authority, "wasm");
|
||||
assert.equal(Object.hasOwn(stepped.payload.xyzac_ledger.entries.at(-1), "response"), false);
|
||||
|
||||
console.log(JSON.stringify({
|
||||
schema: "xyzac-3d-chips-program.v1",
|
||||
|
||||
@@ -45,10 +45,11 @@ export class XyzacCommandLedger extends OperationLedger {
|
||||
return { status: "reconcile_required", operation_id: operationId, retry_allowed: false, reconciliation };
|
||||
}
|
||||
|
||||
audit() {
|
||||
audit({ includeResponses = true } = {}) {
|
||||
const exported = super.export();
|
||||
const pending = this.pending();
|
||||
const unsigned = { schema: XYZAC_COMMAND_LEDGER_SCHEMA, operation_schema: LEDGER_SCHEMA, phases: [...LEDGER_PHASES], scope: this.scope, entries: exported.entries, pending_count: pending.length };
|
||||
const entries = includeResponses ? exported.entries : exported.entries.map((entry) => { const { response, ...summary } = entry; return summary; });
|
||||
const unsigned = { schema: XYZAC_COMMAND_LEDGER_SCHEMA, operation_schema: LEDGER_SCHEMA, phases: [...LEDGER_PHASES], scope: this.scope, entries, pending_count: pending.length };
|
||||
return { ...unsigned, state_hash: fnv1a64(unsigned) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,15 @@ export class XyzacSession extends SimSession {
|
||||
|
||||
accept(input) {
|
||||
const response = super.accept(input);
|
||||
if (response?.payload && typeof response.payload === "object") {
|
||||
response.payload.xyzac_session = this.authority();
|
||||
response.payload.xyzac_ledger = this.ledger.audit();
|
||||
response.payload.xyzac_scheduler = this.scheduler.export();
|
||||
}
|
||||
return response;
|
||||
if (!response?.payload || typeof response.payload !== "object") return response;
|
||||
// super.accept stores the response in the operation ledger. Enrich a
|
||||
// shallow client copy so the next ledger audit cannot recursively embed
|
||||
// every previous response inside the stored response object.
|
||||
const payload = { ...response.payload };
|
||||
payload.xyzac_session = this.authority();
|
||||
payload.xyzac_ledger = this.ledger.audit({ includeResponses: input.kind !== "SIM_STEP" });
|
||||
payload.xyzac_scheduler = this.scheduler.export();
|
||||
return { ...response, payload };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user