Optimize XYZAC motion updates and persist power-off recovery

This commit is contained in:
2026-08-29 04:52:44 +08:00
parent a97971ea19
commit f6e545018e
17 changed files with 479 additions and 74 deletions

View File

@@ -1,11 +1,43 @@
export const CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1";
export const PROTOCOL_SCHEMA = 1;
const POWER_OFF_CHECKPOINT_KEY = "linuxcnc-web-wasm:xyzac-power-off-checkpoint";
let CLIENT_INSTANCE = 0;
function isObject(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function persistPowerOffCheckpoint(message) {
const payload = isObject(message?.payload) ? message.payload : {};
if (message.kind !== "COMMAND_RESULT" || payload.status !== "verified" || payload.command !== "POWER" || payload.active !== false || !isObject(payload.recovery_checkpoint)) return;
try { globalThis.localStorage?.setItem(POWER_OFF_CHECKPOINT_KEY, JSON.stringify(payload.recovery_checkpoint)); } catch { /* checkpoint remains visible in the response */ }
}
function attachPowerOffCheckpoint(payload) {
if (!isObject(payload)) return payload;
try {
const raw = globalThis.localStorage?.getItem(POWER_OFF_CHECKPOINT_KEY);
return raw ? { ...payload, recovery_checkpoint: JSON.parse(raw) } : payload;
} catch { return payload; }
}
function clearPowerOffCheckpoint(message) {
const payload = isObject(message?.payload) ? message.payload : {};
if (message.kind !== "COMMAND_RESULT" || payload.status !== "verified" || payload.command !== "POWER" || payload.active !== true) return;
try { globalThis.localStorage?.removeItem(POWER_OFF_CHECKPOINT_KEY); } catch { /* cleanup is best effort */ }
}
function commandError(message) {
const payload = isObject(message?.payload) ? message.payload : {};
const reason = payload.reason ?? payload.gate_reason ?? payload.message_key ?? payload.code ?? "worker_error";
return Object.assign(new Error(String(reason)), {
code: payload.code ?? "worker_error",
reason: payload.reason ?? payload.gate_reason ?? null,
message_key: payload.message_key ?? null,
response: message,
});
}
export class WorkerClient {
constructor({ worker = null, workerFactory = null, contextId = CONTEXT_ID, sessionId = null, timeoutMs = 10000, maxPending = 64 } = {}) {
this.contextId = contextId;
@@ -32,6 +64,9 @@ export class WorkerClient {
}
#receive(message) {
const persistence = persistPowerOffCheckpoint(message);
if (persistence && isObject(message?.payload)) message.payload.recovery_persistence = persistence;
clearPowerOffCheckpoint(message);
this.#emit(message);
if (!isObject(message)) return;
const operationId = message.operation_id ?? message.payload?.operation_id;
@@ -41,9 +76,7 @@ export class WorkerClient {
this.pending.delete(operationId ?? `seq:${message.seq}`);
if (operationId) this.completed.set(operationId, message);
if (message.kind === "ERROR" || message.payload?.status === "error" || message.payload?.status === "blocked") {
const error = new Error(message.payload?.message_key ?? message.payload?.code ?? "worker_error");
error.response = message;
pending.reject(error);
pending.reject(commandError(message));
} else pending.resolve(message);
}
@@ -68,7 +101,7 @@ export class WorkerClient {
seq,
kind,
...(operationId ? { operation_id: operationId } : {}),
payload,
payload: kind === "LOAD_WORKSPACE" ? attachPowerOffCheckpoint(payload) : payload,
};
let resolvePromise;
let rejectPromise;