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 };