128 lines
6.3 KiB
JavaScript
128 lines
6.3 KiB
JavaScript
import { fnv1a64 } from "../worker/replay.mjs";
|
|
import { WorkspaceRuntime } from "./runtime.mjs";
|
|
import { parseIni } from "./ini-parser.mjs";
|
|
import { ConfigurationStartupStateMachine } from "./configuration-startup.mjs";
|
|
import { loadSimulationCase, toWorkerConfiguration } from "./sim-case-loader.mjs";
|
|
|
|
export const XYZAC_TRT_CASE_LOADER_SCHEMA = "xyzac-trt-case-loader.v1";
|
|
export const XYZAC_TRT_CASE = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini";
|
|
export const XYZAC_TRT_STARTUP_STATES = Object.freeze(["TASK_NEW", "CONFIG_RESOLVED", "HAL_READY", "RUNTIME_READY", "TASK_READY"]);
|
|
|
|
const CASE_DIR = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting";
|
|
const REQUIRED_RESOURCES = Object.freeze([
|
|
[XYZAC_TRT_CASE, "case_entrypoint"],
|
|
[`${CASE_DIR}/xyzac-trt.xml`, "pyvcp"],
|
|
[`${CASE_DIR}/switchkins_postgui.hal`, "postgui_hal"],
|
|
[`${CASE_DIR}/xyzac-trt.tbl`, "tool_table"],
|
|
[`${CASE_DIR}/xyzac.var`, "parameter_file"],
|
|
[`${CASE_DIR}/demos/xyzac_switchkins.ngc`, "open_file"],
|
|
[`${CASE_DIR}/remap_subs/428remap.ngc`, "remap_m428"],
|
|
[`${CASE_DIR}/remap_subs/429remap.ngc`, "remap_m429"],
|
|
[`${CASE_DIR}/remap_subs/430remap.ngc`, "remap_m430"],
|
|
[`${CASE_DIR}/remap_subs/xyzac_switchkins_sub.ngc`, "ngcgui_subfile"],
|
|
[`${CASE_DIR}/remap_subs/centering.ngc`, "ngcgui_subfile"],
|
|
[`${CASE_DIR}/remap_subs/helix_ac.ngc`, "rtcp_subroutine"],
|
|
["linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py", "vismach_source"],
|
|
["linuxcnc/lib/hallib/basic_sim.tcl", "hal_library"],
|
|
["linuxcnc/linuxcnc.gif", "intro_graphic"],
|
|
]);
|
|
const REQUIRED_SECTIONS = Object.freeze(["DISPLAY", "TASK", "HAL", "RS274NGC", "KINS", "TRAJ", "EMCIO"]);
|
|
|
|
function clone(value) { return structuredClone(value); }
|
|
|
|
async function readExact(fetchImpl, baseUrl, path) {
|
|
try {
|
|
const response = await fetchImpl(`${String(baseUrl ?? "").replace(/\/$/, "")}/${path}`.replace(/^\//, "/"));
|
|
if (!response?.ok) return null;
|
|
return await response.text();
|
|
} catch { return null; }
|
|
}
|
|
|
|
function resourceRows(files) {
|
|
return REQUIRED_RESOURCES.map(([path, role]) => {
|
|
const content = files[path];
|
|
return {
|
|
path,
|
|
role,
|
|
status: typeof content === "string" ? "resolved" : "BLOCKED_RESOURCE",
|
|
bytes: typeof content === "string" ? new TextEncoder().encode(content).byteLength : null,
|
|
data_hash: typeof content === "string" ? fnv1a64(content) : null,
|
|
};
|
|
});
|
|
}
|
|
|
|
function xyzacSections(parsed) {
|
|
const sections = Object.keys(parsed.sections ?? {}).map((name) => name.toUpperCase());
|
|
return Object.fromEntries(REQUIRED_SECTIONS.map((name) => [name, sections.includes(name)]));
|
|
}
|
|
|
|
function startupProjection(startup, exactResources, sections) {
|
|
const generic = startup;
|
|
const resolved = exactResources.every((row) => row.status === "resolved");
|
|
const requiredSections = Object.values(sections).every(Boolean);
|
|
const state = generic.state === "TASK_READY" && resolved && requiredSections ? "TASK_READY" : generic.state === "BLOCKED" ? "BLOCKED" : "RUNTIME_READY";
|
|
const transitions = [{ from: null, to: "TASK_NEW" }];
|
|
if (generic.state !== "BLOCKED") {
|
|
transitions.push({ from: "TASK_NEW", to: "CONFIG_RESOLVED" });
|
|
transitions.push({ from: "CONFIG_RESOLVED", to: "HAL_READY" });
|
|
transitions.push({ from: "HAL_READY", to: "RUNTIME_READY" });
|
|
if (state === "TASK_READY") transitions.push({ from: "RUNTIME_READY", to: "TASK_READY" });
|
|
} else transitions.push({ from: "TASK_NEW", to: "BLOCKED" });
|
|
return {
|
|
schema: "xyzac-trt-startup.v1",
|
|
state,
|
|
status: state === "TASK_READY" ? "ready" : "blocked",
|
|
transitions,
|
|
generic_state: generic.state,
|
|
generic_status: generic.status,
|
|
diagnostics: generic.diagnostics,
|
|
hal: generic.hal,
|
|
exact_resources: exactResources,
|
|
required_sections: sections,
|
|
state_hash: fnv1a64({ state, transitions, diagnostics: generic.diagnostics, exact_resources: exactResources, required_sections: sections }),
|
|
};
|
|
}
|
|
|
|
export async function loadXyzacTrtCase({ fetchImpl = globalThis.fetch, baseUrl = "", core = null, task = null } = {}) {
|
|
if (typeof fetchImpl !== "function") throw new Error("xyzac_case_fetch_required");
|
|
const bundle = await loadSimulationCase({ path: XYZAC_TRT_CASE, fetchImpl, baseUrl });
|
|
const files = { ...bundle.files };
|
|
for (const [path] of REQUIRED_RESOURCES) if (!(path in files)) {
|
|
const content = await readExact(fetchImpl, baseUrl, path);
|
|
if (content !== null) files[path] = content;
|
|
}
|
|
const exact = resourceRows(files);
|
|
const caseText = files[XYZAC_TRT_CASE];
|
|
const workspace = new WorkspaceRuntime({ files });
|
|
let parsed = null;
|
|
let startup = null;
|
|
let parsedError = null;
|
|
try {
|
|
parsed = parseIni({ workspace, entrypoint: XYZAC_TRT_CASE });
|
|
const machine = new ConfigurationStartupStateMachine({ workspace, entrypoint: XYZAC_TRT_CASE, core, task, halManifest: bundle.halManifest ?? [] });
|
|
startup = startupProjection(machine.run(), exact, xyzacSections(parsed));
|
|
} catch (error) {
|
|
parsedError = { code: error?.code ?? "xyzac_startup_exception", message: String(error?.message ?? error) };
|
|
}
|
|
const sections = parsed ? xyzacSections(parsed) : Object.fromEntries(REQUIRED_SECTIONS.map((name) => [name, false]));
|
|
const missing = exact.filter((row) => row.status !== "resolved").map((row) => row.path);
|
|
const unsigned = {
|
|
schema: XYZAC_TRT_CASE_LOADER_SCHEMA,
|
|
case: XYZAC_TRT_CASE,
|
|
case_hash: typeof caseText === "string" ? fnv1a64(caseText) : null,
|
|
bundle_status: bundle.status,
|
|
bundle_hash: bundle.state_hash,
|
|
fetched: [...new Set(bundle.fetched ?? [])].sort(),
|
|
missing: [...new Set([...(bundle.missing ?? []), ...missing])].sort(),
|
|
exact_resources: exact,
|
|
required_sections: sections,
|
|
ini: parsed ? { state_hash: parsed.state_hash, graph: parsed.graph, includes: parsed.includes } : null,
|
|
startup,
|
|
parsed_error: parsedError,
|
|
machine: { coordinates: "XYZAC", joint_count: 5, rotary_units: "degrees", kinematics: "xyzac-trt", switchkins: { 0: "identity", 1: "xyzac", 2: "userk" } },
|
|
};
|
|
return { ...clone(unsigned), status: startup?.status === "ready" && missing.length === 0 ? "ready" : "blocked", state_hash: fnv1a64(unsigned), worker_configuration: toWorkerConfiguration({ ...bundle, files, missing: unsigned.missing, status: unsigned.missing.length ? "blocked" : "ready" }) };
|
|
}
|
|
|
|
export { REQUIRED_RESOURCES, REQUIRED_SECTIONS };
|