From cd0286a3d678544ed75f5f9810b669f9b1abf016 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Wed, 2 Sep 2026 11:40:28 +0800 Subject: [PATCH] Fix paused SIM_STEP resume sequencing --- .../xyzac-panel-button-parity.report.json | 18 +++++++++++---- .../xyzac-panel-button-parity-test-report.md | 21 +++++++++++++++++ wasm-core/src/sim_protocol.c | 1 + wasm-core/tests/sim_protocol_test.c | 19 +++++++-------- web/worker/sim-session.mjs | 23 ++++++++++++++++++- web/worker/test_xyzac_workflow.mjs | 3 +++ 6 files changed, 71 insertions(+), 14 deletions(-) diff --git a/coverage/xyzac-panel-button-parity.report.json b/coverage/xyzac-panel-button-parity.report.json index f3a1118..ff256e6 100644 --- a/coverage/xyzac-panel-button-parity.report.json +++ b/coverage/xyzac-panel-button-parity.report.json @@ -57,7 +57,7 @@ }, "published_browser_smoke": { "url": "https://49.232.250.35:58010/", - "release": "20260902-linuxcnc-wasm-continuous-r10", + "release": "20260902-linuxcnc-wasm-pause-resume-r11", "status": "verified", "startup_pause_resume_stop": "verified", "pause_tick": 140, @@ -73,15 +73,25 @@ "screenshot_sha256": "c93fd23e648e49bfa9359af245372f03f3fccee123497909f3dcc005f5a59749", "browser_errors": 0 }, + "pause_resume_runtime": { + "before_pause": {"line": 62, "tick": 140, "queue_depth": 1, "velocity": 28.2842712475}, + "paused": {"line": 62, "tick": 145, "queue_depth": 1, "velocity": 28.2842712475}, + "paused_after_1_5s": {"tick": 145}, + "resumed": {"line": 62, "tick": 160, "queue_depth": 1, "velocity": 4.96408252063}, + "after_resume": {"line": 68, "tick": 360, "queue_depth": 2, "velocity": 32.829526006}, + "status": "verified", + "browser_errors": 0, + "screenshot_sha256": "9d1660fd043dd0628ff2989a3e139a3239acd6386d141657a5834c9d2f1bc5a7" + }, "screenshot_sha256": { "authority": "e58205228a04b386c7b7514bf03c71df7b53f66f2d9ebe2292931b36ef0edb55", "running": "7fc474d0cb31a0fdd4afb198061e9812c753684e3d2963cc004ec4483c5c4990", "switchkins": "920b85ad7fd6451dfb134255ffdcc4f03a7aa615ebf486c63cde8582e17af195" }, "resource_sha256": { - "index-Byst0JhC.js": "26d3f21f496c8223218761ba641dab8b4f36fd18892f260c7bf5b5cfa83cafec", - "sim-worker-entry-BTVfbu6g.js": "adf2ac5621ba388408f31ceab4bbcbfa6ba2923577d8eb69aa79ea54f3be0134", - "linuxcnc-sim-core-B2786u4k.wasm": "3163f5ff63c5f3e1d2c07d1d948fce328bf3ad919eee188e432f7ba57468fc71" + "index-EXbw4yiL.js": "6759738df285bc5e2f5c3fb84043dc09fb82292bbc1c3c1011a558c8654c96ab", + "sim-worker-entry-DjXIMZ0D.js": "190515af943d70e93037e88a5cf63b3b8e9cda675a8338cfc1fb4bb954bfd685", + "linuxcnc-sim-core-D3Qw0e1g.wasm": "f1b1961279cf8ff7ef2e3fcd0068b3ba79de57abe1e56d5c32b16d17568b9536" }, "authority_dom": { "data-axis-authority": "linuxcnc-wasm-worker", diff --git a/doc-rtcp/xyzac-panel-button-parity-test-report.md b/doc-rtcp/xyzac-panel-button-parity-test-report.md index db6317a..014d6cb 100644 --- a/doc-rtcp/xyzac-panel-button-parity-test-report.md +++ b/doc-rtcp/xyzac-panel-button-parity-test-report.md @@ -172,6 +172,26 @@ bash -n run-5axis-rtcp.sh 本轮连续性修复后,`3D_Chips.ngc` 3000 tick 执行到第 337 行,`running_queue_gaps=0`;这证明长距离运行中的中间停顿已不再由 Worker 队列饥饿制造。 +暂停/恢复修复:暂停期间若有一个 `SIM_STEP` 已在浏览器与 Worker 之间传输,WASM protocol 现在接受该帧为稳定无动作轮询,不会消耗错误序号;随后 `PROGRAM_RESUME` 正常恢复 source TP。 + +发布站点 r11 暂停/恢复验证: + +```text +url: https://49.232.250.35:58010/ +release: 20260902-linuxcnc-wasm-pause-resume-r11 +resource_sha256: index-EXbw4yiL.js 6759738df285bc5e2f5c3fb84043dc09fb82292bbc1c3c1011a558c8654c96ab + sim-worker-entry-DjXIMZ0D.js 190515af943d70e93037e88a5cf63b3b8e9cda675a8338cfc1fb4bb954bfd685 + linuxcnc-sim-core-D3Qw0e1g.wasm f1b1961279cf8ff7ef2e3fcd0068b3ba79de57abe1e56d5c32b16d17568b9536 +before_pause: line=62, tick=140, queue_depth=1, velocity=28.2842712475 +paused: line=62, tick=145, queue_depth=1, velocity=28.2842712475 +paused_after_1.5s: tick=145 (stable) +resumed: line=62, tick=160, queue_depth=1, velocity=4.96408252063 +after_resume: line=68, tick=360, queue_depth=2, velocity=32.8295260060 +PROGRAM_RESUME: acknowledged; browser_errors: 0 +screenshot: /tmp/published-pause-resume-final-r11c.png +screenshot_sha256: 9d1660fd043dd0628ff2989a3e139a3239acd6386d141657a5834c9d2f1bc5a7 +``` + 发布站点 r10 连续性验证: ```text @@ -249,6 +269,7 @@ wasm_io_c_source 09bce1430d6f71702ca0b3bf29ac50db377a6f232ce0d1a9b2c1eaa panel_button_test_source 80782fc1f85f88beb48ec3dfaf638a93f2edb912e694840199dbff504477fe74 switchkins_direct_test_output 24e6cb8ec1195a97f71294b49217db0cbc0d7c3ecfa4bc8aa244ba3921c816a7 panel_r10_output 9bddcd8b0219925e00598e081dc7a4ed0f4868e8822a21f0ccd08d8e9d355d45 +pause_resume_browser_screenshot 9d1660fd043dd0628ff2989a3e139a3239acd6386d141657a5834c9d2f1bc5a7 ``` 报告对应提交前工作树保留了既有 coverage 大文件和 LinuxCNC 子模块改动;本轮提交包含 WASM TP/IO/HOME authority、无 JS JOG 运动计算、前端 pending 投影、长程序快照性能修正、聚合测试和本报告。 diff --git a/wasm-core/src/sim_protocol.c b/wasm-core/src/sim_protocol.c index 4d471e0..0368a35 100644 --- a/wasm-core/src/sim_protocol.c +++ b/wasm-core/src/sim_protocol.c @@ -49,6 +49,7 @@ static int state_accepts_kind(uint32_t current, const char *kind) { return is_kind(kind, "PROGRAM_RESUME") || is_kind(kind, "RESUME") || is_kind(kind, "PROGRAM_ABORT") || is_kind(kind, "ESTOP") || is_kind(kind, "POWER") || is_kind(kind, "SET_OVERRIDE") || + is_kind(kind, "SIM_STEP") || is_kind(kind, "PING") || is_kind(kind, "SNAPSHOT_REQUEST") || is_kind(kind, "DESTROY"); } diff --git a/wasm-core/tests/sim_protocol_test.c b/wasm-core/tests/sim_protocol_test.c index e11093d..c168e28 100644 --- a/wasm-core/tests/sim_protocol_test.c +++ b/wasm-core/tests/sim_protocol_test.c @@ -14,16 +14,17 @@ int main(void) { assert(sim_protocol_accept(1, 4, "RESET", "reset-machine", 2) == SIM_PROTOCOL_OK); assert(sim_protocol_last_seq() == 4); assert(sim_protocol_accept(1, 5, "PROGRAM_PAUSE", "op-4", 2) == SIM_PROTOCOL_OK); - assert(sim_protocol_accept(1, 6, "MDI_EXECUTE", "op-5", 2) == SIM_PROTOCOL_ERR_STATE); - assert(sim_protocol_accept(1, 6, "PROGRAM_ABORT", "op-5", 2) == SIM_PROTOCOL_OK); + assert(sim_protocol_accept(1, 6, "SIM_STEP", "op-5-step", 2) == SIM_PROTOCOL_OK); + assert(sim_protocol_accept(1, 7, "MDI_EXECUTE", "op-5", 2) == SIM_PROTOCOL_ERR_STATE); + assert(sim_protocol_accept(1, 7, "PROGRAM_ABORT", "op-5", 2) == SIM_PROTOCOL_OK); assert(sim_protocol_state_value() == SIM_PROTOCOL_READY); - assert(sim_protocol_accept(1, 7, "PROGRAM_PAUSE", "op-6", 2) == SIM_PROTOCOL_OK); - assert(sim_protocol_accept(1, 8, "PROGRAM_RESUME", "op-7", 2) == SIM_PROTOCOL_OK); - assert(sim_protocol_accept(2, 9, "PING", NULL, 2) == SIM_PROTOCOL_ERR_SCHEMA); - assert(sim_protocol_accept(1, 9, "PING", NULL, 1) == SIM_PROTOCOL_ERR_TICK); - assert(sim_protocol_accept(1, 9, "DESTROY", NULL, 2) == SIM_PROTOCOL_OK); - assert(sim_protocol_accept(1, 10, "PING", NULL, 2) == SIM_PROTOCOL_ERR_STATE); - assert(sim_protocol_accept(1, 11, "RESET", "reset-1", 0) == SIM_PROTOCOL_OK); + assert(sim_protocol_accept(1, 8, "PROGRAM_PAUSE", "op-6", 2) == SIM_PROTOCOL_OK); + assert(sim_protocol_accept(1, 9, "PROGRAM_RESUME", "op-7", 2) == SIM_PROTOCOL_OK); + assert(sim_protocol_accept(2, 10, "PING", NULL, 2) == SIM_PROTOCOL_ERR_SCHEMA); + assert(sim_protocol_accept(1, 10, "PING", NULL, 1) == SIM_PROTOCOL_ERR_TICK); + assert(sim_protocol_accept(1, 10, "DESTROY", NULL, 2) == SIM_PROTOCOL_OK); + assert(sim_protocol_accept(1, 11, "PING", NULL, 2) == SIM_PROTOCOL_ERR_STATE); + assert(sim_protocol_accept(1, 12, "RESET", "reset-1", 0) == SIM_PROTOCOL_OK); assert(sim_protocol_state_value() == SIM_PROTOCOL_NEW); assert(sim_protocol_set_tick(4) == SIM_PROTOCOL_ERR_TICK); return 0; diff --git a/web/worker/sim-session.mjs b/web/worker/sim-session.mjs index 5fdeb79..fede802 100644 --- a/web/worker/sim-session.mjs +++ b/web/worker/sim-session.mjs @@ -1188,7 +1188,8 @@ export class SimSession { }); } if (Number.isSafeInteger(input.simulation_tick) && input.simulation_tick >= this.tick) this.tick = input.simulation_tick; - const eventTick = input.kind === "SIM_STEP" && Number.isSafeInteger(input.payload?.cycles) + const pausedStep = input.kind === "SIM_STEP" && this.xyzacProgram && (this.task.state.state === "PAUSED" || this.xyzacProgram.snapshot().status === 3); + const eventTick = input.kind === "SIM_STEP" && Number.isSafeInteger(input.payload?.cycles) && !pausedStep ? this.tick + input.payload.cycles : this.tick; try { this.timeline.appendEvent(input.seq, eventTick, input.kind, input.payload ?? {}); @@ -1535,6 +1536,26 @@ export class SimSession { const cycles = input.payload?.cycles; const limit = validateLimit("max_tick_step", cycles); if (limit.status !== "accepted") return finish(this.envelope("ERROR", input, { ...limit, operation_id: input.operation_id ?? null, simulation_tick: this.tick, retryable: false }), "failed"); + // A poll may already be in flight when PROGRAM_PAUSE reaches the + // worker. LinuxCNC keeps the paused task/TP state stable; accept that + // transport frame as a no-op instead of rejecting it at the protocol + // layer and leaving the next PROGRAM_RESUME with a sequence gap. + if (this.xyzacProgram && (this.task.state.state === "PAUSED" || this.xyzacProgram.snapshot().status === 3)) { + const pausedProgram = this.xyzacProgram.snapshot(); + return finish(this.envelope("COMMAND_RESULT", input, { + status: "verified", + cycles, + program_steps: [], + execution_trace: [], + snapshot_mode: input.payload?.response === "motion_delta" ? "motion_delta" : "full", + program: pausedProgram, + task: this.task.export(), + wasm_motion: this.wasmMotion.read(), + motion: this.wasmMotionProjection(), + motion_authority: "wasm", + operation_id: input.operation_id ?? null, + }), "verified"); + } let program_steps = []; const motion_trace = []; let sourceLookaheadReady = false; diff --git a/web/worker/test_xyzac_workflow.mjs b/web/worker/test_xyzac_workflow.mjs index 11c69c0..a1aef5d 100644 --- a/web/worker/test_xyzac_workflow.mjs +++ b/web/worker/test_xyzac_workflow.mjs @@ -30,6 +30,9 @@ const cursorPaused = paused.payload.program.current_line; const pausedSnapshot = send("SNAPSHOT_REQUEST", "paused-snapshot"); assert.equal(pausedSnapshot.payload.task.state, "PAUSED"); assert.equal(pausedSnapshot.payload.panel_snapshot.program.current_line, cursorPaused); +const pausedPoll = send("SIM_STEP", "paused-poll", { cycles: 5, response: "motion_delta" }); +assert.equal(pausedPoll.payload.task.state, "PAUSED"); +assert.equal(pausedPoll.payload.program.program_status, "paused"); const resumed = send("PROGRAM_RESUME", "resume"); assert.equal(resumed.payload.task.state, "RUNNING"); const second = send("SIM_STEP", "step-2", { cycles: 1 });