按建议,继续完成后续工作
结论:已新增 INI WASM JS SDK 封装,把 UI 和 Node smoke test 改为通过 SDK 调用 vendored LinuxCNC inifile.cc 的导出 C ABI,并同步更新兼容性、复用和漂移文档。
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
4
wasm-port/runtime/sdk/package.json
Normal file
4
wasm-port/runtime/sdk/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"private": true,
|
||||
"type": "module"
|
||||
}
|
||||
67
wasm-port/runtime/sdk/src/linuxcnc-ini.js
Normal file
67
wasm-port/runtime/sdk/src/linuxcnc-ini.js
Normal 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),
|
||||
]),
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user