import { TrackSegment } from "../track/system.ts"; import { Train, TrainCar } from "../train/train.ts"; import { InputManager } from "./input.ts"; type ContextStore = Record; const defaultContext: ContextStore = {}; const contextStack: ContextStore[] = [defaultContext]; const debug = JSON.parse(localStorage.getItem("debug") || "false"); export function setDefaultContext(context: ContextStore) { Object.assign(defaultContext, context); } export function withContext(context: ContextStore, fn: () => T): T { contextStack.push(context); try { return fn(); } finally { contextStack.pop(); } } export const ctx = new Proxy( {}, { get(_, prop: string) { for (let i = contextStack.length - 1; i >= 0; i--) { if (prop in contextStack[i]) return contextStack[i][prop]; } if (prop in defaultContext) return defaultContext[prop]; throw new Error(`Context variable '${prop}' is not defined.`); }, }, ) as Record; export function getContext() { return ctx; } export function getContextItem(prop: string): T { return ctx[prop] as T; } export function getContextItemOrDefault(prop: string, defaultValue: T): T { try { return ctx[prop] as T; } catch { return defaultValue; } } export function setContextItem(prop: string, value: T) { Object.assign(contextStack[contextStack.length - 1] ?? defaultContext, { [prop]: value, }); } if (debug) { setInterval(() => { let ctxEl = document.getElementById("context"); if (!ctxEl) { ctxEl = document.createElement("div"); ctxEl.id = "context"; document.body.append(ctxEl); } ctxEl.innerHTML = ""; const div = document.createElement("div"); const pre = document.createElement("pre"); const h3 = document.createElement("h3"); h3.textContent = "Default"; div.append(h3); pre.textContent = safeStringify(defaultContext); div.append(pre); ctxEl.append(div); for (const [idx, ctx] of contextStack.entries()) { const div = document.createElement("div"); const pre = document.createElement("pre"); const h3 = document.createElement("h3"); h3.textContent = "CTX " + idx; div.append(h3); pre.textContent = safeStringify(ctx); div.append(pre); ctxEl.append(div); } }, 1000); } function safeStringify(obj: any) { const seen = new WeakSet(); return JSON.stringify(obj, (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return "[Circular]"; // Replace circular references } seen.add(value); } if (value instanceof Map) { const val: Record = {}; for (const [k, v] of value) { if (typeof k !== "string") continue; val[k] = v; } seen.add(value); return val; } if (value instanceof Set) { seen.add(value); value = Array.from(value); return value; } if (value instanceof TrackSegment) { seen.add(value); const val = { ...value }; (val as any).frontNeighbours = value.frontNeighbours.map((n) => n.id); (val as any).backNeighbours = value.backNeighbours.map((n) => n.id); return val; } // if (value instanceof Train) { // const val = { ...value }; // // val.segments = value.segments.map((s) => s.id); // delete (val as any).path; // delete (val as any).nodes; // delete (val as any).cars; // delete (val as any).t; // // val.t // // val. // return null; // } // if (value instanceof InputManager) { // return {}; // } // if (value instanceof TrainCar) { // const val = { ...value }; // return val; // } return value; }, 2); } const getContextStack = () => JSON.parse(safeStringify(contextStack)); const updateContextValue = ( key: string, value: unknown, depth = contextStack.length - 1, ) => { const keys = key.split("."); let context = contextStack[depth] ?? defaultContext; for (let i = 0; i < keys.length - 1; i++) { if (context instanceof Map) { context = context.get(keys[i]); } else { context = context[keys[i]]; } } if (context instanceof Map) { context.set(keys.at(-1)!, value); } else { context[keys.at(-1)!] = value; } }; if (location.hostname === "localhost") { globalThis.getContextStack = getContextStack; globalThis.updateContextValue = updateContextValue; globalThis.contextStack = contextStack; } declare global { var getContextStack: () => ContextStore[]; var updateContextValue: (key: string, value: unknown, depth?: number) => void; var contextStack: ContextStore[]; }