Make large G-code loading and execution robust

This commit is contained in:
2026-08-30 17:42:54 +08:00
parent 1a92d3b5b8
commit c015709d26
4 changed files with 19 additions and 6 deletions

View File

@@ -239,6 +239,9 @@ export function App() {
.then(() => dispatch("PROGRAM_RUN", {}, false)) .then(() => dispatch("PROGRAM_RUN", {}, false))
.catch(() => undefined); .catch(() => undefined);
}; };
const clearLoadedPreview = async () => {
await operationController.execute("VISUAL_CLEAR", {}, { state: axisState, gate: false });
};
const loadCase = async () => { const loadCase = async () => {
if (programLoading || programRunning) return; if (programLoading || programRunning) return;
setProgramLoading(true); setProgramLoading(true);
@@ -256,6 +259,7 @@ export function App() {
const operation = await operationController.execute("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }, { state: axisState, gate: false }); const operation = await operationController.execute("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }, { state: axisState, gate: false });
const resultPayload = (operation.response as { payload?: { configuration?: { state?: string } } }).payload; const resultPayload = (operation.response as { payload?: { configuration?: { state?: string } } }).payload;
setProgramText(normalizedSource); setProgramPath(PROGRAM_PATH); setPlotCleared(false); setProgramText(normalizedSource); setProgramPath(PROGRAM_PATH); setPlotCleared(false);
await clearLoadedPreview();
await operationController.execute("PROGRAM_LOAD", { source: normalizedSource, path: PROGRAM_PATH }, { state: axisState, gate: false }); await operationController.execute("PROGRAM_LOAD", { source: normalizedSource, path: PROGRAM_PATH }, { state: axisState, gate: false });
setOperationTrace(operationController.exportTrace()); setOperationTrace(operationController.exportTrace());
setCommandStatus("PROGRAM_LOAD acknowledged"); setCommandStatus("PROGRAM_LOAD acknowledged");
@@ -277,6 +281,7 @@ export function App() {
const source = normalizeProgramSource(await response.text()); const source = normalizeProgramSource(await response.text());
if (!source.trim()) throw new Error("program_asset_empty"); if (!source.trim()) throw new Error("program_asset_empty");
setProgramText(source); setProgramPath(path); setPlotCleared(false); setProgramText(source); setProgramPath(path); setPlotCleared(false);
await clearLoadedPreview();
await operationController.execute("PROGRAM_LOAD", { source, path }, { state: axisState, gate: false }); await operationController.execute("PROGRAM_LOAD", { source, path }, { state: axisState, gate: false });
setOperationTrace(operationController.exportTrace()); setOperationTrace(operationController.exportTrace());
setCommandStatus("PROGRAM_LOAD acknowledged"); setCommandStatus("PROGRAM_LOAD acknowledged");
@@ -291,7 +296,7 @@ export function App() {
setProgramLoading(true); setProgramLoading(true);
setCommandStatus("PROGRAM_LOAD pending: waiting for LinuxCNC WASM preview"); setCommandStatus("PROGRAM_LOAD pending: waiting for LinuxCNC WASM preview");
setPlotCleared(false); setPlotCleared(false);
void dispatch("PROGRAM_LOAD", { source: programText, path: programPath }) void clearLoadedPreview().then(() => dispatch("PROGRAM_LOAD", { source: programText, path: programPath }))
.then(() => setCaseStatus(`${programPath} loaded through linuxcnc-wasm`)) .then(() => setCaseStatus(`${programPath} loaded through linuxcnc-wasm`))
.catch(() => undefined) .catch(() => undefined)
.finally(() => setProgramLoading(false)); .finally(() => setProgramLoading(false));

View File

@@ -56,7 +56,13 @@ export class OperationLedger {
entry.phase = "verified"; entry.phase = "verified";
entry.status = response?.payload?.status === "error" || response?.kind === "ERROR" ? "failed" : "verified"; entry.status = response?.payload?.status === "error" || response?.kind === "ERROR" ? "failed" : "verified";
entry.result_hash = fnv1a64(response?.payload ?? {}); entry.result_hash = fnv1a64(response?.payload ?? {});
entry.response = clone(response); const payload = response?.payload;
// High-frequency fixed-tick responses are deltas. Keep only the compact
// operation evidence in the ledger; the full delta is already delivered
// to the client and retained by the frontend trace window.
entry.response = payload?.snapshot_mode === "motion_delta"
? { ...response, payload: { status: payload.status, snapshot_mode: payload.snapshot_mode, simulation_tick: response.simulation_tick ?? null, program: payload.program ? { current_line: payload.program.current_line, program_status: payload.program.program_status, state_hash: payload.program.state_hash } : null } }
: clone(response);
return clone(entry); return clone(entry);
} }

View File

@@ -45,11 +45,13 @@ export class XyzacCommandLedger extends OperationLedger {
return { status: "reconcile_required", operation_id: operationId, retry_allowed: false, reconciliation }; return { status: "reconcile_required", operation_id: operationId, retry_allowed: false, reconciliation };
} }
audit({ includeResponses = true } = {}) { audit({ includeResponses = true, maxEntries = Number.POSITIVE_INFINITY } = {}) {
const exported = super.export(); const exported = super.export();
const pending = this.pending(); const pending = this.pending();
const entries = includeResponses ? exported.entries : exported.entries.map((entry) => { const { response, ...summary } = entry; return summary; }); const totalEntries = exported.entries.length;
const unsigned = { schema: XYZAC_COMMAND_LEDGER_SCHEMA, operation_schema: LEDGER_SCHEMA, phases: [...LEDGER_PHASES], scope: this.scope, entries, pending_count: pending.length }; const windowed = Number.isFinite(maxEntries) && maxEntries >= 0 ? exported.entries.slice(-Math.floor(maxEntries)) : exported.entries;
const entries = includeResponses ? windowed : windowed.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, entry_count: totalEntries, truncated: entries.length < totalEntries, pending_count: pending.length };
return { ...unsigned, state_hash: fnv1a64(unsigned) }; return { ...unsigned, state_hash: fnv1a64(unsigned) };
} }
} }

View File

@@ -34,7 +34,7 @@ export class XyzacSession extends SimSession {
// every previous response inside the stored response object. // every previous response inside the stored response object.
const payload = { ...response.payload }; const payload = { ...response.payload };
payload.xyzac_session = this.authority(); payload.xyzac_session = this.authority();
payload.xyzac_ledger = this.ledger.audit({ includeResponses: input.kind !== "SIM_STEP" }); payload.xyzac_ledger = this.ledger.audit({ includeResponses: input.kind !== "SIM_STEP", maxEntries: input.kind === "SIM_STEP" ? 32 : 2048 });
payload.xyzac_scheduler = this.scheduler.export(); payload.xyzac_scheduler = this.scheduler.export();
return { ...response, payload }; return { ...response, payload };
} }