按建议,继续完成后续工作

结论:已新增 INI WASM JS SDK 封装,把 UI 和 Node smoke test 改为通过 SDK 调用 vendored LinuxCNC inifile.cc 的导出 C ABI,并同步更新兼容性、复用和漂移文档。
This commit is contained in:
2026-06-08 00:23:48 +08:00
parent 9a9fb5d4c1
commit 0d78849d80
7 changed files with 121 additions and 103 deletions

View File

@@ -49,8 +49,8 @@ The native validation script runs these checks in order:
The WASM INI smoke script builds `runtime/ui/ini-panel/linuxcnc_ini.js` and
`linuxcnc_ini.wasm` from vendored LinuxCNC `inifile.cc`, then loads that
module in Node and verifies `lcini_get_string()` against an in-memory INI file
written to the Emscripten filesystem.
module through `runtime/sdk/src/linuxcnc-ini.js` in Node and verifies INI
queries against a file written to the Emscripten filesystem.
The OPFS host-boundary script validates the JavaScript file-service adapter
with a Node mock of the browser File System Access handles. It covers nested
@@ -103,7 +103,7 @@ The validation fails if:
| Harness | Purpose |
| --- | --- |
| `tests/wasm/node/verify_ini_wasm.sh` | Validates the browser-facing INI WASM module can be built from vendored LinuxCNC `inifile.cc`, loaded in Node, and queried through the exported C ABI. |
| `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/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter used by the browser INI panel without moving file persistence into the WASM core. |
## Fixture Coverage
@@ -147,10 +147,10 @@ Negative fixtures currently cover:
## Validation Boundaries
Current full-core validation is native-only. WASM validation is limited to the
INI parser smoke harness. OPFS validation is limited to the JavaScript
host-boundary adapter. Browser, SDK, and full machine-session validation
remain future work.
Current full-core validation is native-only. WASM/SDK validation is limited to
the INI parser smoke harness. OPFS validation is limited to the JavaScript
host-boundary adapter. Browser-level validation, full SDK coverage, and full
machine-session validation remain future work.
The current fixture expectations validate standalone behavior against both the
vendored LinuxCNC source path and an upstream `rs274` side-by-side baseline for

View File

@@ -58,7 +58,8 @@ semantic rewrites:
- No browser/full-core WASM parity tests yet. The INI parser now has a Node
WASM smoke harness against vendored LinuxCNC `inifile.cc`.
- No JS SDK validation yet.
- JS SDK validation is currently limited to the INI WASM wrapper around
vendored LinuxCNC `inifile.cc`.
- OPFS validation is currently limited to a Node mock of the browser
file-service adapter; browser-level OPFS validation is not yet established.
- Identity/trivial, `5axiskins`, TRT `xyzac`/`xyzbc`, delta, SCARA, PUMA,

View File

@@ -31,7 +31,7 @@ Current validation is intentionally mechanical:
| Capability | LinuxCNC source files | Port classification | Standalone boundary | Current validation |
| --- | --- | --- | --- | --- |
| INI parsing | `src/emc/ini/inifile.cc`, `inifile.h`, `inifile.hh` | Copy unchanged | Native file IO remains LinuxCNC-style in the vendored parser; browser OPFS integration remains outside this layer | Vendor byte sync, `linuxcnc_ini_probe`, `linuxcnc_inifile_source_probe`, `tests/wasm/node/verify_ini_wasm.sh` |
| INI parsing | `src/emc/ini/inifile.cc`, `inifile.h`, `inifile.hh` | Copy unchanged | Native file IO remains LinuxCNC-style in the vendored parser; browser OPFS integration remains outside this layer; `runtime/sdk/src/linuxcnc-ini.js` only wraps the exported WASM C ABI | Vendor byte sync, `linuxcnc_ini_probe`, `linuxcnc_inifile_source_probe`, `tests/wasm/node/verify_ini_wasm.sh` |
| RTAPI compatibility headers | `src/rtapi/rtapi_*.h` in the manifest | Copy unchanged plus standalone shim include path | `runtime/core/shims/rtapi.h` supplies the minimal standalone RTAPI surface needed by vendored code | Vendor byte sync, compile coverage through dependent source probes |
| Canon/NML-facing interpreter types | `src/emc/nml_intf/canon*.hh`, `emctool.h`, `interp_return.hh`, `motion_types.h`, `emcpose.*`, `emcpos.h`, `debugflags.h`, `src/emc/linuxcnc.h` | Copy unchanged plus narrow standalone status shim | NML transport is not ported; `runtime/core/shims/nml_intf/emc.hh` exposes only the `emcStatus` machine-units status edge currently needed by vendored interpreter conversion and initialization code | Vendor byte sync, dependent source probes, `linuxcnc_emc_status_probe`, `linuxcnc_tp_api_probe`, interpreter harnesses |
| Motion state headers | `src/emc/motion/state_tag.h`, `emcmotcfg.h`, `simple_tp.h`, `motion.h`, `mot_priv.h`, `axis.h` | Copy unchanged | Realtime motion process is not ported; standalone probes seed the small motion status/config state required by TP calls | Vendor byte sync, `linuxcnc_tp_api_probe` |
@@ -69,7 +69,8 @@ Current validation is intentionally mechanical:
- Cutter compensation positive motion and negative interpreter paths are
fixture-covered through vendored `interp_convert.cc` and `interp_queue.cc`.
- Browser/WASM C ABI and JS SDK layers are not yet built for the full
interpreter/planner core. The INI parser has a Node WASM smoke harness.
interpreter/planner core. The INI parser has a minimal JS SDK wrapper and
Node WASM smoke harness.
- OPFS persistence is connected to the INI panel through the host-side
`runtime/opfs/file-service.js` adapter; tool table, parameter file, G-code
program loading, and browser-level OPFS validation remain future work.

View File

@@ -0,0 +1,4 @@
{
"private": true,
"type": "module"
}

View File

@@ -0,0 +1,67 @@
import createLinuxCncIniModule from "../../ui/ini-panel/linuxcnc_ini.js";
function allocCString(mod, value) {
const bytes = mod.lengthBytesUTF8(value) + 1;
const ptr = mod._malloc(bytes);
mod.stringToUTF8(value, ptr, bytes);
return ptr;
}
function ensureParentPath(mod, path) {
const parts = path.split("/").filter(Boolean);
let current = "";
for (const part of parts.slice(0, -1)) {
current += `/${part}`;
try {
mod.FS.mkdir(current);
} catch {
// Directory already exists.
}
}
}
export async function createLinuxCncIniSdk(moduleOptions = {}) {
const mod = await createLinuxCncIniModule(moduleOptions);
return {
module: mod,
writeTextFile(path, text) {
ensureParentPath(mod, path);
mod.FS.writeFile(path, text, { encoding: "utf8" });
},
getString(path, section, tag) {
const pathPtr = allocCString(mod, path);
const sectionPtr = allocCString(mod, section);
const tagPtr = allocCString(mod, tag);
const outSize = 2048;
const outPtr = mod._malloc(outSize);
try {
const rc = mod._lcini_get_string(
pathPtr,
sectionPtr,
tagPtr,
outPtr,
outSize,
);
return rc === 0 ? mod.UTF8ToString(outPtr) : null;
} finally {
mod._free(pathPtr);
mod._free(sectionPtr);
mod._free(tagPtr);
mod._free(outPtr);
}
},
getFields(path, fields) {
return Object.fromEntries(
Object.entries(fields).map(([name, query]) => [
name,
this.getString(path, query.section, query.tag),
]),
);
},
};
}

View File

@@ -1,4 +1,4 @@
import createLinuxCncIniModule from "./linuxcnc_ini.js";
import { createLinuxCncIniSdk } from "../../sdk/src/linuxcnc-ini.js";
import {
getOpfsRoot,
loadTextFile,
@@ -24,7 +24,7 @@ const fields = {
parameterFile: document.getElementById("field-parameter-file"),
};
let moduleInstance = null;
let iniSdk = null;
function setBadge(node, text, className = "badge") {
node.className = className;
@@ -40,49 +40,15 @@ function setField(name, value) {
fields[name].textContent = value ?? "-";
}
function requireModule() {
if (!moduleInstance) {
function requireIniSdk() {
if (!iniSdk) {
throw new Error("WASM module is not ready yet.");
}
return moduleInstance;
}
function allocCString(mod, value) {
const bytes = mod.lengthBytesUTF8(value) + 1;
const ptr = mod._malloc(bytes);
mod.stringToUTF8(value, ptr, bytes);
return ptr;
}
function iniQuery(mod, wasmPath, section, tag) {
const pathPtr = allocCString(mod, wasmPath);
const sectionPtr = allocCString(mod, section);
const tagPtr = allocCString(mod, tag);
const outSize = 2048;
const outPtr = mod._malloc(outSize);
try {
const rc = mod._lcini_get_string(pathPtr, sectionPtr, tagPtr, outPtr, outSize);
if (rc !== 0) {
return null;
}
return mod.UTF8ToString(outPtr);
} finally {
mod._free(pathPtr);
mod._free(sectionPtr);
mod._free(tagPtr);
mod._free(outPtr);
}
return iniSdk;
}
function syncEditorToWasmFs() {
const mod = requireModule();
try {
mod.FS.mkdir("/work");
} catch {
// already exists
}
mod.FS.writeFile(WASM_FILE, editor.value, { encoding: "utf8" });
requireIniSdk().writeTextFile(WASM_FILE, editor.value);
}
async function loadSample() {
@@ -97,7 +63,7 @@ async function loadSample() {
async function boot() {
try {
moduleInstance = await createLinuxCncIniModule();
iniSdk = await createLinuxCncIniSdk();
setBadge(wasmBadge, "WASM: ready");
} catch (error) {
setBadge(wasmBadge, "WASM: failed", "badge danger");
@@ -152,17 +118,18 @@ document.getElementById("sync-wasm").addEventListener("click", () => {
document.getElementById("query").addEventListener("click", async () => {
try {
syncEditorToWasmFs();
const mod = requireModule();
const values = requireIniSdk().getFields(WASM_FILE, {
machine: { section: "EMC", tag: "MACHINE" },
display: { section: "DISPLAY", tag: "DISPLAY" },
kinematics: { section: "KINS", tag: "KINEMATICS" },
joints: { section: "KINS", tag: "JOINTS" },
coordinates: { section: "TRAJ", tag: "COORDINATES" },
parameterFile: { section: "RS274NGC", tag: "PARAMETER_FILE" },
});
setField("machine", iniQuery(mod, WASM_FILE, "EMC", "MACHINE"));
setField("display", iniQuery(mod, WASM_FILE, "DISPLAY", "DISPLAY"));
setField("kinematics", iniQuery(mod, WASM_FILE, "KINS", "KINEMATICS"));
setField("joints", iniQuery(mod, WASM_FILE, "KINS", "JOINTS"));
setField("coordinates", iniQuery(mod, WASM_FILE, "TRAJ", "COORDINATES"));
setField(
"parameterFile",
iniQuery(mod, WASM_FILE, "RS274NGC", "PARAMETER_FILE"),
);
for (const [name, value] of Object.entries(values)) {
setField(name, value);
}
setLog("Queried LinuxCNC INI fields through the standalone WASM parser.");
} catch (error) {

View File

@@ -3,14 +3,14 @@ import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import assert from "node:assert/strict";
import createLinuxCncIniModule from "../../../runtime/ui/ini-panel/linuxcnc_ini.js";
import { createLinuxCncIniSdk } from "../../../runtime/sdk/src/linuxcnc-ini.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const rootDir = resolve(__dirname, "../../..");
const wasmPath = resolve(rootDir, "runtime/ui/ini-panel/linuxcnc_ini.wasm");
const mod = await createLinuxCncIniModule({
const ini = await createLinuxCncIniSdk({
wasmBinary: readFileSync(wasmPath),
print() {},
printErr(message) {
@@ -18,33 +18,7 @@ const mod = await createLinuxCncIniModule({
},
});
function allocCString(value) {
const bytes = mod.lengthBytesUTF8(value) + 1;
const ptr = mod._malloc(bytes);
mod.stringToUTF8(value, ptr, bytes);
return ptr;
}
function queryIni(path, section, tag) {
const pathPtr = allocCString(path);
const sectionPtr = allocCString(section);
const tagPtr = allocCString(tag);
const outSize = 256;
const outPtr = mod._malloc(outSize);
try {
const rc = mod._lcini_get_string(pathPtr, sectionPtr, tagPtr, outPtr, outSize);
return { rc, value: rc === 0 ? mod.UTF8ToString(outPtr) : null };
} finally {
mod._free(pathPtr);
mod._free(sectionPtr);
mod._free(tagPtr);
mod._free(outPtr);
}
}
mod.FS.mkdir("/work");
mod.FS.writeFile(
ini.writeTextFile(
"/work/node-smoke.ini",
`[EMC]
MACHINE = wasm-node-smoke
@@ -57,21 +31,25 @@ COORDINATES = X Y Z A B
KINEMATICS = xyzbc-trt-kins
JOINTS = 5
`,
{ encoding: "utf8" },
);
assert.deepEqual(queryIni("/work/node-smoke.ini", "EMC", "MACHINE"), {
rc: 0,
value: "wasm-node-smoke",
assert.equal(
ini.getString("/work/node-smoke.ini", "EMC", "MACHINE"),
"wasm-node-smoke",
);
assert.equal(ini.getString("/work/node-smoke.ini", "TRAJ", "LINEAR_UNITS"), "mm");
assert.equal(
ini.getString("/work/node-smoke.ini", "KINS", "KINEMATICS"),
"xyzbc-trt-kins",
);
assert.equal(ini.getString("/work/node-smoke.ini", "TRAJ", "MISSING"), null);
assert.deepEqual(ini.getFields("/work/node-smoke.ini", {
machine: { section: "EMC", tag: "MACHINE" },
joints: { section: "KINS", tag: "JOINTS" },
}), {
machine: "wasm-node-smoke",
joints: "5",
});
assert.deepEqual(queryIni("/work/node-smoke.ini", "TRAJ", "LINEAR_UNITS"), {
rc: 0,
value: "mm",
});
assert.deepEqual(queryIni("/work/node-smoke.ini", "KINS", "KINEMATICS"), {
rc: 0,
value: "xyzbc-trt-kins",
});
assert.notEqual(queryIni("/work/node-smoke.ini", "TRAJ", "MISSING").rc, 0);
console.log("ini_wasm_node_smoke=ok");