按规划继续工作
结论:OPFS 参数文件已通过桥接进入 LinuxCNC-backed WASM restore/save 路径,native 与 host/WASM/browser 验证全部通过。
This commit is contained in:
@@ -99,7 +99,10 @@ paths, unavailable OPFS storage, and the host-side OPFS path model for INI,
|
||||
tool table, parameter, G-code, preview-cache, and session-snapshot storage
|
||||
targets. It also validates the host-side session snapshot JSON envelope and
|
||||
round-trip store plus pure-text machine file and G-code stores without
|
||||
defining CNC machine-state or file-format semantics.
|
||||
defining CNC machine-state or file-format semantics. It now also validates the
|
||||
OPFS-to-WASM parameter-file bridge with a mock interpreter SDK to ensure the
|
||||
host boundary copies text into and out of the WASM filesystem without defining
|
||||
parameter semantics.
|
||||
|
||||
The browser INI/OPFS smoke script serves `wasm-port/` over localhost and runs
|
||||
Chromium headless against a test page that imports the JS SDK, loads the INI
|
||||
@@ -114,7 +117,9 @@ behavior in JavaScript, and verifies existing canonical fixtures through the
|
||||
exported C ABI backed by vendored LinuxCNC `Interp::execute()` and
|
||||
`Interp::open()`/`read()`/`execute()` paths, including the INI-aware
|
||||
named-parameter file path and negative interpreter fixtures with expected
|
||||
error text plus absent canonical motion output.
|
||||
error text plus absent canonical motion output. It also uses real browser OPFS
|
||||
storage plus the interpreter SDK to restore and save a LinuxCNC parameter file
|
||||
through vendored `Interp::restore_parameters()` and `Interp::save_parameters()`.
|
||||
|
||||
The aggregate host smoke script builds the INI and interpreter-core WASM
|
||||
artifacts once, then runs the Node WASM smokes, the Node OPFS mock smoke, and
|
||||
@@ -168,9 +173,9 @@ The validation fails if:
|
||||
| --- | --- |
|
||||
| `tests/wasm/node/verify_ini_wasm.sh` | Validates the browser-facing INI WASM module can be built from vendored LinuxCNC `inifile.cc`, loaded through the JS SDK in Node, and queried through the exported C ABI. |
|
||||
| `tests/wasm/node/verify_interp_wasm.sh` | Validates the initial interpreter-core WASM module can be built from vendored LinuxCNC interpreter source, loaded through the interpreter JS SDK, run the first fixture group through `Interp::execute()` and selected file fixtures through `Interp::open()`/`read()`/`execute()`, match the native canonical event plus required state readback fixtures, and run parameter-file restore/save through vendored LinuxCNC `Interp::restore_parameters()` and `Interp::save_parameters()`. |
|
||||
| `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store, machine file store, and G-code text store used by the browser INI panel without moving file persistence into the WASM core. |
|
||||
| `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store, machine file store, G-code text store, and OPFS-to-WASM parameter-file bridge without moving file persistence or parameter semantics into the WASM core. |
|
||||
| `tests/browser/verify_ini_panel_browser.sh` | Validates the INI SDK, WASM module loading, OPFS text-file round trip, generic session snapshot round trip, machine file text round trip, and G-code text round trip in a real browser runtime. |
|
||||
| `tests/browser/verify_interp_browser.sh` | Validates the interpreter-core WASM module loads through the interpreter JS SDK in a real browser runtime and runs selected positive and negative canonical fixtures through vendored LinuxCNC `Interp::execute()` plus `Interp::open()`/`read()`/`execute()` via the exported C ABI. |
|
||||
| `tests/browser/verify_interp_browser.sh` | Validates the interpreter-core WASM module loads through the interpreter JS SDK in a real browser runtime and runs selected positive and negative canonical fixtures through vendored LinuxCNC `Interp::execute()` plus `Interp::open()`/`read()`/`execute()` via the exported C ABI, including OPFS-backed parameter-file restore/save through vendored LinuxCNC source. |
|
||||
| `tests/host/verify_host_smokes.sh` | Runs the current host-side Node, WASM interpreter-core, OPFS, and browser smoke validation with shared WASM builds. |
|
||||
|
||||
## Fixture Coverage
|
||||
@@ -238,8 +243,10 @@ restore/save behavior through the exported C ABI, including out-of-order file
|
||||
rejection, missing-file success, required numeric parameter writeback, removal
|
||||
of named-parameter-only lines from saved output, and the `.bak` backup produced
|
||||
by vendored `save_parameters()`.
|
||||
OPFS validation is limited to the JavaScript host-boundary adapter plus the INI
|
||||
browser smoke harness. Full browser coverage, full SDK coverage, and full
|
||||
OPFS validation covers the JavaScript host-boundary adapter, the INI browser
|
||||
smoke harness, and a browser interpreter smoke that copies OPFS-backed
|
||||
parameter text through the SDK into vendored LinuxCNC parameter-file
|
||||
restore/save. Full browser coverage, full SDK coverage, and full
|
||||
machine-session validation remain future work.
|
||||
|
||||
## WASM/Browser Fixture Matrix
|
||||
|
||||
@@ -43,7 +43,7 @@ semantic rewrites:
|
||||
| Kinematics component lifecycle | Kinematics modules are initialized through LinuxCNC module entry points where native runtime probes exist, while HAL component init/ready/exit, HAL pin allocation, and RTAPI module metadata are handled by standalone shims. |
|
||||
| Go math C/C++ linkage | `genserkins` runtime probing compiles vendored `gomath.c` through a narrow C++ wrapper so LinuxCNC `genserfuncs.c` can link to the upstream Go math symbols without editing vendored source. |
|
||||
| Switchkins iterative forward | `genhexkins` runtime probing follows LinuxCNC switchkins iterative-forward behavior, including the first-call warmup path before asserting roundtrip convergence. |
|
||||
| Browser storage | OPFS remains outside the native core; `runtime/opfs/file-service.js` owns browser text-file persistence, `runtime/opfs/path-model.js` owns host-side storage paths for INI, tool table, parameter, G-code, preview-cache, and session-snapshot content, `runtime/opfs/snapshot-store.js` owns generic JSON session snapshot persistence, and `runtime/opfs/machine-file-store.js` owns pure-text machine-file and G-code persistence. |
|
||||
| Browser storage | OPFS remains outside the native core; `runtime/opfs/file-service.js` owns browser text-file persistence, `runtime/opfs/path-model.js` owns host-side storage paths for INI, tool table, parameter, G-code, preview-cache, and session-snapshot content, `runtime/opfs/snapshot-store.js` owns generic JSON session snapshot persistence, `runtime/opfs/machine-file-store.js` owns pure-text machine-file and G-code persistence, and `runtime/opfs/linuxcnc-parameter-bridge.js` only copies parameter files between OPFS text storage and the LinuxCNC-backed WASM parameter-file ABI. |
|
||||
|
||||
## Enforced Non-Drift Rules
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ Current validation is intentionally mechanical:
|
||||
|
||||
| Dependency | LinuxCNC files that expose it | Standalone treatment |
|
||||
| --- | --- | --- |
|
||||
| Native file IO | `inifile.cc`, `rs274ngc_pre.cc`, parameter file paths | Allowed in native probes; the interpreter WASM C ABI now validates parameter-file restore/save by calling vendored `Interp::restore_parameters()` and `Interp::save_parameters()` against Emscripten filesystem paths; browser OPFS remains a host-side adapter under `runtime/opfs/`, with path ownership in `runtime/opfs/path-model.js`, generic snapshot persistence in `runtime/opfs/snapshot-store.js`, and pure-text machine-file persistence in `runtime/opfs/machine-file-store.js` |
|
||||
| Native file IO | `inifile.cc`, `rs274ngc_pre.cc`, parameter file paths | Allowed in native probes; the interpreter WASM C ABI validates parameter-file restore/save by calling vendored `Interp::restore_parameters()` and `Interp::save_parameters()` against Emscripten filesystem paths; browser OPFS remains a host-side adapter under `runtime/opfs/`, with path ownership in `runtime/opfs/path-model.js`, generic snapshot persistence in `runtime/opfs/snapshot-store.js`, pure-text machine-file persistence in `runtime/opfs/machine-file-store.js`, and OPFS-to-WASM parameter-file copying in `runtime/opfs/linuxcnc-parameter-bridge.js` |
|
||||
| RTAPI | `rtapi_*.h`, TP, posemath, motion headers | Minimal standalone shim in `runtime/core/shims/rtapi.h` |
|
||||
| NML transport | `emc.hh`, motion/NML type headers | Transport is not ported; only the status/type edges needed by vendored compute code are exposed through standalone shims and probes |
|
||||
| HAL runtime | named parameter lookup, kinematics component lifecycle, and runtime status edges | Standalone HAL adapter under `runtime/core/linuxcnc_wrap/` |
|
||||
@@ -82,8 +82,9 @@ Current validation is intentionally mechanical:
|
||||
preview-cache, and session-snapshot targets. `runtime/opfs/snapshot-store.js`
|
||||
adds a generic JSON session snapshot envelope.
|
||||
`runtime/opfs/machine-file-store.js` adds pure-text storage for INI, tool
|
||||
table, parameter file, and G-code program content; browser session plumbing
|
||||
that feeds OPFS-backed parameter files into the existing WASM
|
||||
`restore_parameters()`/`save_parameters()` ABI, plus semantic loading for
|
||||
tool tables and machine-state restoration, remains future work.
|
||||
table, parameter file, and G-code program content.
|
||||
`runtime/opfs/linuxcnc-parameter-bridge.js` copies OPFS-backed parameter
|
||||
files into the interpreter SDK filesystem and writes back the LinuxCNC-saved
|
||||
parameter file plus backup; UI-level session plumbing, semantic loading for
|
||||
tool tables, and machine-state restoration remain future work.
|
||||
- Native LinuxCNC GUI code remains out of scope for implementation.
|
||||
|
||||
63
wasm-port/runtime/opfs/linuxcnc-parameter-bridge.js
Normal file
63
wasm-port/runtime/opfs/linuxcnc-parameter-bridge.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { loadTextFile, saveTextFile } from "./file-service.js";
|
||||
import { parameterFilePath } from "./path-model.js";
|
||||
|
||||
const DEFAULT_WASM_PARAMETER_PATH = "/work/linuxcnc.var";
|
||||
|
||||
function requireParameterSdk(interp) {
|
||||
for (const method of ["writeTextFile", "readTextFile", "restoreParameters", "saveParameters"]) {
|
||||
if (typeof interp?.[method] !== "function") {
|
||||
throw new Error(`interpreter SDK is missing ${method}().`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePaths(machineId, options = {}) {
|
||||
const opfsPath = options.opfsPath ?? parameterFilePath(machineId, options.filename);
|
||||
const wasmPath = options.wasmPath ?? DEFAULT_WASM_PARAMETER_PATH;
|
||||
return { opfsPath, wasmPath };
|
||||
}
|
||||
|
||||
function readOptionalWasmTextFile(interp, path) {
|
||||
try {
|
||||
return interp.readTextFile(path);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function restoreMachineParametersFromOpfs(interp, machineId, options = {}) {
|
||||
requireParameterSdk(interp);
|
||||
const { opfsPath, wasmPath } = resolvePaths(machineId, options);
|
||||
const text = await loadTextFile(opfsPath, options.storage);
|
||||
interp.writeTextFile(wasmPath, text);
|
||||
return {
|
||||
opfsPath,
|
||||
wasmPath,
|
||||
result: interp.restoreParameters(wasmPath),
|
||||
};
|
||||
}
|
||||
|
||||
export async function saveMachineParametersToOpfs(interp, machineId, values, options = {}) {
|
||||
requireParameterSdk(interp);
|
||||
const { opfsPath, wasmPath } = resolvePaths(machineId, options);
|
||||
const existingText = await loadTextFile(opfsPath, options.storage);
|
||||
interp.writeTextFile(wasmPath, existingText);
|
||||
|
||||
const result = interp.saveParameters(wasmPath, values);
|
||||
const savedText = interp.readTextFile(wasmPath);
|
||||
const backupText = readOptionalWasmTextFile(interp, `${wasmPath}.bak`);
|
||||
|
||||
await saveTextFile(opfsPath, savedText, options.storage);
|
||||
if (backupText !== null) {
|
||||
await saveTextFile(`${opfsPath}.bak`, backupText, options.storage);
|
||||
}
|
||||
|
||||
return {
|
||||
opfsPath,
|
||||
backupOpfsPath: backupText === null ? null : `${opfsPath}.bak`,
|
||||
wasmPath,
|
||||
result,
|
||||
savedText,
|
||||
backupText,
|
||||
};
|
||||
}
|
||||
@@ -8,6 +8,14 @@
|
||||
<pre id="status">running</pre>
|
||||
<script type="module">
|
||||
import { createLinuxCncInterpSdk } from "../../runtime/sdk/src/index.js";
|
||||
import {
|
||||
restoreMachineParametersFromOpfs,
|
||||
saveMachineParametersToOpfs,
|
||||
} from "../../runtime/opfs/linuxcnc-parameter-bridge.js";
|
||||
import {
|
||||
loadMachineTextFiles,
|
||||
saveMachineTextFiles,
|
||||
} from "../../runtime/opfs/machine-file-store.js";
|
||||
import {
|
||||
INTERP_BROWSER_FILE_FIXTURES,
|
||||
INTERP_BROWSER_MDI_FIXTURES,
|
||||
@@ -108,6 +116,71 @@
|
||||
(await fetchText(`../fixtures/canon/${INTERP_INI_FIXTURE}.events`)).trimEnd(),
|
||||
);
|
||||
|
||||
await saveMachineTextFiles("browser-interp", {
|
||||
ini: "[EMC]\nMACHINE = browser-interp\n",
|
||||
toolTable: "T0 P0 ; no tool\n",
|
||||
parameters: [
|
||||
"5161 10.5",
|
||||
"5162 20.25",
|
||||
"5220 1",
|
||||
"5221 2.25",
|
||||
"5399 44",
|
||||
"<_named_param> 123",
|
||||
"",
|
||||
].join("\n"),
|
||||
});
|
||||
const restoredParameters = await restoreMachineParametersFromOpfs(
|
||||
interp,
|
||||
"browser-interp",
|
||||
{ wasmPath: "/work/browser-linuxcnc.var" },
|
||||
);
|
||||
verifyExpectedOutput(
|
||||
"opfs_restore_parameters",
|
||||
restoredParameters.result,
|
||||
[
|
||||
"restore_parameters=0",
|
||||
"parameter_5161=10.5",
|
||||
"parameter_5162=20.25",
|
||||
"parameter_5221=2.25",
|
||||
"parameter_5399=44",
|
||||
].join("\n"),
|
||||
);
|
||||
const savedParameters = await saveMachineParametersToOpfs(
|
||||
interp,
|
||||
"browser-interp",
|
||||
{
|
||||
5161: 12.34,
|
||||
5162: 56.78,
|
||||
5220: 1.0,
|
||||
5221: 9.87,
|
||||
5399: 66.6,
|
||||
},
|
||||
{ wasmPath: "/work/browser-linuxcnc.var" },
|
||||
);
|
||||
verifyExpectedOutput(
|
||||
"opfs_save_parameters",
|
||||
savedParameters.result,
|
||||
[
|
||||
"save_parameters=0",
|
||||
"parameter_5161=12.34",
|
||||
"parameter_5162=56.78",
|
||||
"parameter_5221=9.87",
|
||||
"parameter_5399=66.6",
|
||||
].join("\n"),
|
||||
);
|
||||
const machineFiles = await loadMachineTextFiles("browser-interp");
|
||||
verifyExpectedOutput(
|
||||
"opfs_saved_parameter_text",
|
||||
machineFiles.parameters,
|
||||
[
|
||||
"5161\t12.340000",
|
||||
"5162\t56.780000",
|
||||
"5221\t9.870000",
|
||||
"5399\t66.600000",
|
||||
"absent=<_named_param>",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
status.textContent = "browser_interp_smoke=ok";
|
||||
} catch (error) {
|
||||
status.textContent = `browser_interp_smoke=fail ${error.stack || error.message}`;
|
||||
|
||||
@@ -28,6 +28,10 @@ import {
|
||||
saveGcodeProgram,
|
||||
saveMachineTextFiles,
|
||||
} from "../../../runtime/opfs/machine-file-store.js";
|
||||
import {
|
||||
restoreMachineParametersFromOpfs,
|
||||
saveMachineParametersToOpfs,
|
||||
} from "../../../runtime/opfs/linuxcnc-parameter-bridge.js";
|
||||
|
||||
class MockFileHandle {
|
||||
constructor(name) {
|
||||
@@ -153,6 +157,57 @@ assert.equal(
|
||||
);
|
||||
assert.equal(await loadGcodeProgram("fixture.ngc", { storage }), "G0 X0 Y0\nM2\n");
|
||||
|
||||
const bridgeFiles = new Map();
|
||||
const bridgeInterp = {
|
||||
writeTextFile(path, text) {
|
||||
bridgeFiles.set(path, text);
|
||||
},
|
||||
readTextFile(path) {
|
||||
if (!bridgeFiles.has(path)) {
|
||||
throw new Error(`missing wasm file: ${path}`);
|
||||
}
|
||||
return bridgeFiles.get(path);
|
||||
},
|
||||
restoreParameters(path) {
|
||||
return `restore_parameters=0\nrestore_path=${path}\n`;
|
||||
},
|
||||
saveParameters(path, values) {
|
||||
bridgeFiles.set(path, "5161\t12.340000\n5162\t56.780000\n");
|
||||
bridgeFiles.set(`${path}.bak`, "5161 0.0\n5162 0.0\n");
|
||||
return `save_parameters=0\nsave_path=${path}\nvalues=${Object.keys(values).join(",")}\n`;
|
||||
},
|
||||
};
|
||||
|
||||
const restoredParameters = await restoreMachineParametersFromOpfs(
|
||||
bridgeInterp,
|
||||
"xyzab-tdr",
|
||||
{ storage, wasmPath: "/work/bridge.var" },
|
||||
);
|
||||
assert.deepEqual(restoredParameters, {
|
||||
opfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
wasmPath: "/work/bridge.var",
|
||||
result: "restore_parameters=0\nrestore_path=/work/bridge.var\n",
|
||||
});
|
||||
assert.equal(bridgeFiles.get("/work/bridge.var"), "5161 0.0\n5162 0.0\n");
|
||||
|
||||
const savedParameters = await saveMachineParametersToOpfs(
|
||||
bridgeInterp,
|
||||
"xyzab-tdr",
|
||||
{ 5161: 12.34, 5162: 56.78 },
|
||||
{ storage, wasmPath: "/work/bridge.var" },
|
||||
);
|
||||
assert.equal(savedParameters.opfsPath, "linuxcnc/machines/xyzab-tdr/linuxcnc.var");
|
||||
assert.equal(savedParameters.backupOpfsPath, "linuxcnc/machines/xyzab-tdr/linuxcnc.var.bak");
|
||||
assert.equal(savedParameters.result.includes("save_parameters=0"), true);
|
||||
assert.equal(
|
||||
await loadTextFile("linuxcnc/machines/xyzab-tdr/linuxcnc.var", storage),
|
||||
"5161\t12.340000\n5162\t56.780000\n",
|
||||
);
|
||||
assert.equal(
|
||||
await loadTextFile("linuxcnc/machines/xyzab-tdr/linuxcnc.var.bak", storage),
|
||||
"5161 0.0\n5162 0.0\n",
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
() => loadTextFile("linuxcnc/machines/missing.ini", storage),
|
||||
/missing file/,
|
||||
|
||||
Reference in New Issue
Block a user