Align AXIS control flow and fix pause and program rendering
Document source UI gates, explicit native live-plot clearing, and remaining parity boundaries. Fix pause/resume transport continuity, repeated stepping, coordinate previews, and spindle override state. Optimize large program snapshots and rendering, and cover the control flows with native Tcl, WASM, and browser checks. Validation: 22 native tests and source/browser checks pass. Worker/App regression: 113 of 123 pass; 10 pre-existing failures remain documented with no new failures.
This commit is contained in:
@@ -72,6 +72,8 @@ export class WasmProgramRuntime {
|
||||
this.samplePointer = 0;
|
||||
this.samplePosePointer = 0;
|
||||
this.source = "";
|
||||
this.hashedSource = null;
|
||||
this.cachedSourceHash = null;
|
||||
this.path = null;
|
||||
this.subroutines = [];
|
||||
this.executionSource = "";
|
||||
@@ -95,6 +97,16 @@ export class WasmProgramRuntime {
|
||||
return result === 0 ? { status: "verified", command: name } : { status: "error", code: `wasm_${errorPrefix}_${result}`, result, reason: PROGRAM_ERROR_REASONS[result] ?? `wasm_${errorPrefix}_${result}`, message_key: PROGRAM_ERROR_REASONS[result] ?? `wasm_${errorPrefix}_${result}`, source: "linuxcnc-wasm-sim_program" };
|
||||
}
|
||||
|
||||
get sourceHash() {
|
||||
// Source text is immutable between file replacements. Only its identity
|
||||
// needs checking on a tick; the canonical WASM state is still read fresh.
|
||||
if (this.hashedSource !== this.source) {
|
||||
this.hashedSource = this.source;
|
||||
this.cachedSourceHash = this.source ? fnv1a64(this.source) : null;
|
||||
}
|
||||
return this.cachedSourceHash;
|
||||
}
|
||||
|
||||
load(source, path = null) {
|
||||
if (typeof source !== "string" || source.length === 0) return { status: "error", code: "program_source_invalid" };
|
||||
const bytes = new TextEncoder().encode(source);
|
||||
@@ -140,6 +152,9 @@ export class WasmProgramRuntime {
|
||||
if (loaded.status !== "verified") return loaded;
|
||||
const synchronized = temporary.synchronizePosition(this.synchronizedPosition);
|
||||
if (synchronized.status !== "verified") return synchronized;
|
||||
const state = this.snapshot();
|
||||
const initialized = temporary.initializeCoordinates(state.coordinate_context, state);
|
||||
if (initialized.status !== "verified") return initialized;
|
||||
temporary.setRapidVelocity(this.rapidVelocity);
|
||||
for (const subroutine of this.subroutines) {
|
||||
const added = temporary.addSubroutine(subroutine.name, subroutine.source, subroutine.path);
|
||||
@@ -156,11 +171,26 @@ export class WasmProgramRuntime {
|
||||
if (step.motion) segments.push({ id: segments.length + 1, kinematics_mode: kinematicsMode, ...step.motion });
|
||||
if ([4, 5, 6].includes(step.program.status)) break;
|
||||
}
|
||||
return { status: "verified", schema: "wasm-canonical-preview.v1", source: "linuxcnc-wasm-sim_program", path: this.path, source_hash: fnv1a64(this.source), line_count: this.snapshot().line_count, segments, preview_hash: fnv1a64(segments) };
|
||||
return { status: "verified", schema: "wasm-canonical-preview.v1", source: "linuxcnc-wasm-sim_program", path: this.path, source_hash: this.sourceHash, line_count: this.snapshot().line_count, segments, preview_hash: fnv1a64(segments) };
|
||||
} finally { temporary.destroy(); }
|
||||
}
|
||||
|
||||
start() { return this.invoke("sim_program_start", [], "program_start"); }
|
||||
initializeCoordinates(context, state = {}) {
|
||||
if (!context) return { status: "verified" };
|
||||
const previous = this.snapshot().coordinate_context;
|
||||
const blocks = ["G21"];
|
||||
for (const [index, offsets] of context.offsets.entries()) {
|
||||
if (offsets.some((value, axis) => value !== (previous?.offsets?.[index]?.[axis] ?? 0))) blocks.push(`G10 L2 P${index + 1} ${AXES.map((axis, a) => `${axis.toUpperCase()}${offsets[a]}`).join(" ")}`);
|
||||
}
|
||||
const system = context.system <= 6 ? `G${53 + context.system}` : `G59.${context.system - 6}`;
|
||||
blocks.push(`${system} G${state.units === 20 ? 20 : 21} G${state.distance_mode === 91 ? 91 : 90}`);
|
||||
for (const block of blocks) {
|
||||
const result = this.executeBlock(block);
|
||||
if (result.status !== "verified") return result;
|
||||
}
|
||||
return { status: "verified" };
|
||||
}
|
||||
synchronizePosition(value) {
|
||||
const position = Object.fromEntries(AXES.map((axis) => [axis, Number(value?.[axis] ?? 0)]));
|
||||
if (Object.values(position).some((coordinate) => !Number.isFinite(coordinate))) return { status: "error", code: "program_position_nonfinite" };
|
||||
@@ -208,10 +238,12 @@ export class WasmProgramRuntime {
|
||||
|
||||
restoreState(value) {
|
||||
if (!value || ![SCHEMA, "sim_program.v1"].includes(value.schema) || !Number.isSafeInteger(value.current_line) || value.current_line < 0 || !this.source) throw new Error("wasm_program_restore_schema");
|
||||
if (value.source_hash && value.source_hash !== fnv1a64(this.source)) throw new Error("wasm_program_restore_source_hash");
|
||||
if (value.source_hash && value.source_hash !== this.sourceHash) throw new Error("wasm_program_restore_source_hash");
|
||||
const subroutines = [...this.subroutines];
|
||||
const loaded = this.load(this.source, this.path);
|
||||
if (loaded.status !== "verified") throw new Error(loaded.code);
|
||||
const initialized = this.initializeCoordinates(value.coordinate_context, value);
|
||||
if (initialized.status !== "verified") throw new Error(initialized.code);
|
||||
const synchronized = this.synchronizePosition(Object.fromEntries(AXES.map((axis) => [axis, Number(value[axis] ?? 0)])));
|
||||
if (synchronized.status !== "verified") throw new Error(synchronized.code);
|
||||
for (const subroutine of subroutines) {
|
||||
@@ -232,6 +264,14 @@ export class WasmProgramRuntime {
|
||||
} else if (status === "completed") {
|
||||
this.start();
|
||||
for (let index = 0; index < this.snapshot().line_count + 1 && this.snapshot().status === 2; index += 1) this.step();
|
||||
} else if (status === "paused") {
|
||||
const from = Number(value.pause_from ?? 2);
|
||||
if (from !== 1) {
|
||||
this.start();
|
||||
if (from === 4) for (let index = 0; index < this.snapshot().line_count + 1 && this.snapshot().status === 2; index += 1) this.step();
|
||||
}
|
||||
const paused = this.pause();
|
||||
if (paused.status !== "verified") throw new Error(paused.code);
|
||||
} else if (status === "aborted") {
|
||||
const aborted = this.abort();
|
||||
if (aborted.status !== "verified") throw new Error(aborted.code);
|
||||
@@ -305,7 +345,7 @@ export class WasmProgramRuntime {
|
||||
const motionControl = { 0: "continuous", 1: "exact_path", 2: "exact_stop" }[state.motion_control_mode] ?? "continuous";
|
||||
const executionLineText = state.current_line > 0 ? (this.executionLines[state.current_line - 1] ?? "").trim() : "";
|
||||
const executionMeta = state.current_line > 0 ? (this.executionLineMetadata[state.current_line - 1] ?? { path: this.path, line: state.current_line }) : { path: this.path, line: 0 };
|
||||
return { schema: SCHEMA, source: "linuxcnc-wasm-sim_program", ...state, motion_control: { mode: motionControl, path_tolerance: Number(state.path_tolerance ?? 0), naive_cam_tolerance: Number(state.naive_cam_tolerance ?? 0) }, active_gcodes: activeGcodes(state), loaded: state.status !== 0, line: state.current_line, currentLine: state.current_line, execution_line: state.current_line, execution_line_text: executionLineText, execution_source_file: executionMeta.path, execution_source_line: executionMeta.line, program_status: programStatus, path: this.path, subroutines: this.subroutines.map((subroutine) => subroutine.name), state_hash: `fnv1a64:${BigInt.asUintN(64, BigInt(hash >>> 0)).toString(16).padStart(16, "0")}`, source_hash: this.source ? fnv1a64(this.source) : null };
|
||||
return { schema: SCHEMA, source: "linuxcnc-wasm-sim_program", ...state, motion_control: { mode: motionControl, path_tolerance: Number(state.path_tolerance ?? 0), naive_cam_tolerance: Number(state.naive_cam_tolerance ?? 0) }, active_gcodes: activeGcodes(state), loaded: state.status !== 0, line: state.current_line, currentLine: state.current_line, execution_line: state.current_line, execution_line_text: executionLineText, execution_source_file: executionMeta.path, execution_source_line: executionMeta.line, program_status: programStatus, path: this.path, subroutines: this.subroutines.map((subroutine) => subroutine.name), state_hash: `fnv1a64:${BigInt.asUintN(64, BigInt(hash >>> 0)).toString(16).padStart(16, "0")}`, source_hash: this.sourceHash };
|
||||
} finally { this.core._free(buffer); }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user