Compare commits

6 Commits

Author SHA1 Message Date
b30a241d09 ok no more debugging for now 2025-02-15 21:39:32 -07:00
7914eb444a debuggable and code stripping 2025-02-15 20:41:47 -07:00
6009818d93 More debug enhancement 2025-02-15 19:33:06 -07:00
8bd2c30108 debug overhaul, fixes stringifying over-recursion 2025-02-15 18:33:19 -07:00
ffa2ef97e0 train car occupied segment tracking 2025-02-15 16:04:30 -07:00
8d379461c3 Yay a devtools! 2025-02-15 13:11:38 -07:00
19 changed files with 579 additions and 344 deletions

10
.temp/deno.lock generated
View File

@@ -1,7 +1,6 @@
{ {
"version": "4", "version": "4",
"specifiers": { "specifiers": {
"jsr:@bearmetal/doodler@0.0.5-b": "0.0.5-b",
"jsr:@luca/esbuild-deno-loader@*": "0.11.0", "jsr:@luca/esbuild-deno-loader@*": "0.11.0",
"jsr:@luca/esbuild-deno-loader@0.11.1": "0.11.1", "jsr:@luca/esbuild-deno-loader@0.11.1": "0.11.1",
"jsr:@std/assert@*": "1.0.10", "jsr:@std/assert@*": "1.0.10",
@@ -25,9 +24,6 @@
"@bearmetal/doodler@0.0.4": { "@bearmetal/doodler@0.0.4": {
"integrity": "b631083cff84994c513f70d1f09e6a9256edabcb224112c93a9ca6a87c88a389" "integrity": "b631083cff84994c513f70d1f09e6a9256edabcb224112c93a9ca6a87c88a389"
}, },
"@bearmetal/doodler@0.0.5-b": {
"integrity": "94f265ea21162f943291526800de7f3f6560634a4fe762a38cd73892685b6742"
},
"@luca/esbuild-deno-loader@0.11.0": { "@luca/esbuild-deno-loader@0.11.0": {
"integrity": "c05a989aa7c4ee6992a27be5f15cfc5be12834cab7ff84cabb47313737c51a2c", "integrity": "c05a989aa7c4ee6992a27be5f15cfc5be12834cab7ff84cabb47313737c51a2c",
"dependencies": [ "dependencies": [
@@ -208,7 +204,11 @@
] ]
} }
}, },
"redirects": {
"https://deno.land/x/clipboard/mod.ts": "https://deno.land/x/clipboard@v0.0.3/mod.ts"
},
"remote": { "remote": {
"https://deno.land/x/clipboard@v0.0.3/mod.ts": "5b68fe3b710b852de5273c135fc3c11b2b4050577401ee994161380bd8cab219",
"https://git.cyborggrizzly.com/emma/doodler/raw/tag/0.0.7a/canvas.ts": "aadfb4b2e9acce34d4a5da3f9027be642c93229bbfc2641cb55301542cbb87bf", "https://git.cyborggrizzly.com/emma/doodler/raw/tag/0.0.7a/canvas.ts": "aadfb4b2e9acce34d4a5da3f9027be642c93229bbfc2641cb55301542cbb87bf",
"https://git.cyborggrizzly.com/emma/doodler/raw/tag/0.0.7a/geometry/constants.ts": "4f4cf7bf49ac871d984e9b43896783b0cc8ab0ea60d0fc4c8c582f7e00c3df5a", "https://git.cyborggrizzly.com/emma/doodler/raw/tag/0.0.7a/geometry/constants.ts": "4f4cf7bf49ac871d984e9b43896783b0cc8ab0ea60d0fc4c8c582f7e00c3df5a",
"https://git.cyborggrizzly.com/emma/doodler/raw/tag/0.0.7a/geometry/vector.ts": "a08ecff64c5436a28c6451a31c68fc912d25f941aabafb79418fa0a1aeffa9d2", "https://git.cyborggrizzly.com/emma/doodler/raw/tag/0.0.7a/geometry/vector.ts": "a08ecff64c5436a28c6451a31c68fc912d25f941aabafb79418fa0a1aeffa9d2",
@@ -232,7 +232,7 @@
}, },
"workspace": { "workspace": {
"dependencies": [ "dependencies": [
"jsr:@bearmetal/doodler@0.0.5-b" "jsr:@bearmetal/doodler@0.0.5"
] ]
} }
} }

1
.temp/temp.json Normal file
View File

@@ -0,0 +1 @@
[{"p":[[200,24,0],[233,24,0],[264.87555226753926,32.541028488383176,0],[293.45439059242574,49.041028488383176,0]],"id":"11d7561a-3172-4ad7-9d53-6f65f49ce8c3","bNeighbors":["93e4d69d-10f2-4ecc-a4d0-560ee71708e8"],"fNeighbors":["e44c3a93-01f0-42f1-aee9-939cbde5903b"]},{"p":[[293.45439059242574,49.041028488383176,0],[322.0332289173122,65.54102848838318,0],[345.3677526964683,88.87555226753923,0],[361.8677526964683,117.45439059242571,0]],"id":"e44c3a93-01f0-42f1-aee9-939cbde5903b","bNeighbors":["11d7561a-3172-4ad7-9d53-6f65f49ce8c3"],"fNeighbors":["bf9833f2-fd66-45fb-924d-f40b7c863c26"]},{"p":[[361.8677526964683,117.45439059242571,0],[378.3677526964683,146.0332289173122,0],[386.9087811848515,177.90878118485145,0],[386.9087811848515,210.90878118485148,0]],"id":"bf9833f2-fd66-45fb-924d-f40b7c863c26","bNeighbors":["e44c3a93-01f0-42f1-aee9-939cbde5903b"],"fNeighbors":["081c81f3-8fe7-4c71-babd-1d02d52c3385"]},{"p":[[386.9087811848515,210.90878118485148,0],[386.9087811848515,243.90878118485148,0],[378.3677526964683,275.78433345239074,0],[361.8677526964683,304.3631717772772,0]],"id":"081c81f3-8fe7-4c71-babd-1d02d52c3385","bNeighbors":["bf9833f2-fd66-45fb-924d-f40b7c863c26"],"fNeighbors":["d1380635-1dba-4180-8038-931289a56d17"]},{"p":[[361.8677526964683,304.3631717772772,0],[345.3677526964683,332.9420101021637,0],[322.0332289173122,356.2765338813198,0],[293.45439059242574,372.7765338813198,0]],"id":"d1380635-1dba-4180-8038-931289a56d17","bNeighbors":["081c81f3-8fe7-4c71-babd-1d02d52c3385"],"fNeighbors":["8fe94c53-c0be-4f0d-986b-f37e143e62d7"]},{"p":[[293.45439059242574,372.7765338813198,0],[264.87555226753926,389.2765338813198,0],[233,397.81756236970296,0],[200,397.81756236970296,0]],"id":"8fe94c53-c0be-4f0d-986b-f37e143e62d7","bNeighbors":["d1380635-1dba-4180-8038-931289a56d17"],"fNeighbors":["1542c063-b548-4d27-9fd8-c7a8344b05cb"]},{"p":[[200,397.81756236970296,0],[167,397.81756236970296,0],[135.12444773246074,389.2765338813198,0],[106.54560940757426,372.7765338813198,0]],"id":"1542c063-b548-4d27-9fd8-c7a8344b05cb","bNeighbors":["8fe94c53-c0be-4f0d-986b-f37e143e62d7"],"fNeighbors":["9be20051-651f-4cca-b6f1-ca80d5db7635"]},{"p":[[106.54560940757426,372.7765338813198,0],[77.96677108268779,356.2765338813198,0],[54.6322473035317,332.94201010216375,0],[38.1322473035317,304.3631717772772,0]],"id":"9be20051-651f-4cca-b6f1-ca80d5db7635","bNeighbors":["1542c063-b548-4d27-9fd8-c7a8344b05cb"],"fNeighbors":["4b7ff960-9fe7-48b3-b64e-9e32f60b0d2f"]},{"p":[[38.1322473035317,304.3631717772772,0],[21.632247303531692,275.7843334523908,0],[13.091218815148487,243.9087811848515,0],[13.09121881514848,210.90878118485153,0]],"id":"4b7ff960-9fe7-48b3-b64e-9e32f60b0d2f","bNeighbors":["9be20051-651f-4cca-b6f1-ca80d5db7635"],"fNeighbors":["0c481aa2-92de-4517-8e76-6c3f163f3dde"]},{"p":[[13.09121881514848,210.90878118485153,0],[13.091218815148467,177.90878118485153,0],[21.632247303531646,146.03322891731227,0],[38.132247303531635,117.45439059242577,0]],"id":"0c481aa2-92de-4517-8e76-6c3f163f3dde","bNeighbors":["4b7ff960-9fe7-48b3-b64e-9e32f60b0d2f"],"fNeighbors":["c62384ad-3fec-4479-9596-1173a6e651bb"]},{"p":[[38.132247303531635,117.45439059242577,0],[54.63224730353162,88.87555226753928,0],[77.96677108268767,65.54102848838318,0],[106.54560940757415,49.041028488383176,0]],"id":"c62384ad-3fec-4479-9596-1173a6e651bb","bNeighbors":["0c481aa2-92de-4517-8e76-6c3f163f3dde"],"fNeighbors":["93e4d69d-10f2-4ecc-a4d0-560ee71708e8"]},{"p":[[106.54560940757415,49.041028488383176,0],[135.12444773246062,32.541028488383176,0],[166.9999999999999,23.99999999999997,0],[199.9999999999999,23.99999999999997,0]],"id":"93e4d69d-10f2-4ecc-a4d0-560ee71708e8","bNeighbors":["c62384ad-3fec-4479-9596-1173a6e651bb"],"fNeighbors":["11d7561a-3172-4ad7-9d53-6f65f49ce8c3"]}]

View File

@@ -1,24 +1,54 @@
console.log("background.js loaded"); console.log("background.js loaded");
// Listen for messages from the devtools panel
browser.runtime.onMessage.addListener((message, sender, sendResponse) => { browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
// You could add a check to see if the message is from the devtools panel.
if (message.type === "GET_CONTEXT_STACK") { if (message.type === "GET_CONTEXT_STACK") {
// Forward the message to the content script running in the active tab. browser.tabs.sendMessage(message.tabId, message)
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { .then((res) => {
if (tabs.length) { console.log("RESPONSE", res);
browser.tabs.sendMessage(tabs[0].id, message).then(sendResponse); sendResponse(res);
} })
}); .catch((err) => {
// Return true to indicate you wish to send a response asynchronously console.log("Error sending message to content script:", err);
sendResponse({ error: err });
});
// browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
// if (tabs.length) {
// }
// });
return true; return true;
} else if (message.type === "UPDATE_CONTEXT_VALUE") { } else if (message.type === "UPDATE_CONTEXT_VALUE") {
// Forward update messages similarly browser.tabs.sendMessage(message.tabId, message).then(sendResponse);
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { // browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
if (tabs.length) { // if (tabs.length) {
browser.tabs.sendMessage(tabs[0].id, message).then(sendResponse); // }
} // });
});
return true; return true;
} }
}); });
let devtoolsPort = null;
browser.runtime.onConnect.addListener((port) => {
if (port.name === "devtools") {
devtoolsPort = port;
console.log("Devtools panel connected.");
// port.onMessage.addListener((msg) => {
// console.log("Received message from devtools panel:", msg);
// });
port.onDisconnect.addListener(() => {
devtoolsPort = null;
});
}
});
// Relay messages from content scripts to the devtools panel.
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "PAGE_LOADED") {
console.log("Background received PAGE_LOADED message from content script.");
if (devtoolsPort) {
devtoolsPort.postMessage({ type: "PAGE_LOADED" });
}
}
});

View File

@@ -1,20 +1,12 @@
console.log("content.js loaded", window.wrappedJSObject);
// setTimeout(() => {
const getContextStack = () => window.wrappedJSObject.getContextStack();
const updateContextValue = (key, value, depth) =>
window.wrappedJSObject.updateContextValue(key, value, depth);
console.log(getContextStack());
browser.runtime.onMessage.addListener((message, sender, sendResponse) => { browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "GET_CONTEXT_STACK") { if (message.type === "GET_CONTEXT_STACK") {
const contextStack = getContextStack(); const contextStack = window.wrappedJSObject.getContextStack();
sendResponse({ contextStack }); sendResponse({ contextStack });
} else if (message.type === "UPDATE_CONTEXT_VALUE") { } else if (message.type === "UPDATE_CONTEXT_VALUE") {
const { key, value, depth } = message; const { key, value, depth } = message;
if (glowindow.updateContextValue) { if (window.wrappedJSObject.updateContextValue) {
updateContextValue(key, value, depth); window.wrappedJSObject.updateContextValue(key, value, depth);
sendResponse({ success: true }); sendResponse({ success: true });
} else { } else {
sendResponse({ sendResponse({
@@ -24,4 +16,5 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
} }
} }
}); });
// }, 0);
browser.runtime.sendMessage({ type: "PAGE_LOADED" });

View File

@@ -1,6 +1,6 @@
// Create a new devtools panel named "Context Stack" // Create a new devtools panel named "Context Stack"
browser.devtools.panels.create( browser.devtools.panels.create(
"Context Stack", // Tab title "Smoke and Rails", // Tab title
"train icon.png", // Icon for your panel (optional) "train icon.png", // Icon for your panel (optional)
"panel.html", // HTML page for your panel content "panel.html", // HTML page for your panel content
); );

View File

@@ -16,7 +16,8 @@
], ],
"js": [ "js": [
"content.js" "content.js"
] ],
"all_frames": true
} }
], ],
"permissions": [ "permissions": [

View File

@@ -1,39 +1,48 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<meta charset="utf-8">
<title>Smoke and Rails</title>
<style>
body {
font-family: sans-serif;
/* margin: 10px; */
/* width: 100%;
height: 100%; */
background-color: #302040;
color: #fff;
/* text-align: right; */
}
<head> .context-row {
<meta charset="utf-8"> display: flex;
<title>Context Stack</title> margin-bottom: 8px;
<style> }
body {
font-family: sans-serif;
/* margin: 10px; */
width: 100%;
height: 100%;
background-color: #302040;
}
.context-row { .context-key {
display: flex; width: 150px;
margin-bottom: 8px; font-weight: bold;
} }
.context-key { .context-value input {
width: 150px; width: 100%;
font-weight: bold; }
}
.context-value input { form {
width: 100%; margin-left: 1rem;
} margin-right: 1rem;
</style> }
</head> label {
display: inline-block;
margin-right: 1rem;
}
</style>
</head>
<body> <body>
<h1>Context Stack</h1> <h2>Smoke and Rails Debugger</h2>
<div id="contextContainer"></div> <button id="refresh">Refresh</button>
<button id="refresh">Refresh</button> <div id="contextContainer"></div>
<script src="panel.js"></script> <script src="panel.js"></script>
</body> </body>
</html>
</html>

View File

@@ -1,52 +1,127 @@
const port = browser.runtime.connect({ name: "devtools" });
port.onMessage.addListener((message) => {
if (message.type === "PAGE_LOADED") {
loadContextStack(); // Refresh your context stack here.
}
});
const tabId = browser.devtools.inspectedWindow.tabId;
document.getElementById("refresh").addEventListener("click", loadContextStack); document.getElementById("refresh").addEventListener("click", loadContextStack);
function loadContextStack() { function loadContextStack() {
const container = document.getElementById("contextContainer"); const container = document.getElementById("contextContainer");
container.innerHTML = ""; container.innerHTML = "";
browser.runtime.sendMessage({ type: "GET_CONTEXT_STACK" }).then( browser.runtime.sendMessage({ type: "GET_CONTEXT_STACK", tabId }).then(
(response) => { (response) => {
if (response.error) {
container.innerHTML = `<p>Error: ${response.error}</p>`;
return;
}
if (!response || !response.contextStack) { if (!response || !response.contextStack) {
container.innerHTML = "<p>No context stack found.</p>"; container.innerHTML = "<p>No context stack found.</p>";
return; return;
} }
const contextStack = response.contextStack; const contextStack = coalesceContextStack(response.contextStack);
for (const key in contextStack) { const form = generateObjectForm(contextStack);
const row = document.createElement("div"); container.appendChild(form);
row.classList.add("context-row");
const keyDiv = document.createElement("div");
keyDiv.classList.add("context-key");
keyDiv.textContent = key;
const valueDiv = document.createElement("div");
valueDiv.classList.add("context-value");
const input = document.createElement("input");
input.value = contextStack[key];
input.addEventListener("change", () => {
browser.runtime.sendMessage({
type: "UPDATE_CONTEXT_VALUE",
key,
value: input.value,
}).then((updateResponse) => {
if (updateResponse && updateResponse.success) {
console.log(`Updated ${key} to ${input.value}`);
} else {
console.error(`Failed to update ${key}:`, updateResponse.error);
}
});
});
valueDiv.appendChild(input);
row.appendChild(keyDiv);
row.appendChild(valueDiv);
container.appendChild(row);
}
}, },
); );
} }
// Initial load when the panel opens. function generateObjectForm(obj, path = "") {
loadContextStack(); const detail = document.createElement("details");
detail.open = path === "";
const summary = document.createElement("summary");
summary.textContent = path.split(".").at(-1);
detail.appendChild(summary);
const form = document.createElement("form");
let count = 0;
for (const [key, value] of Object.entries(obj)) {
if (value == undefined || value === "[Circular]") continue;
const isObject = value.constructor === Object;
const isArray = Array.isArray(value);
if (isObject || isArray) {
const nestedForm = generateObjectForm(
value,
path ? path + "." + key : key,
);
if (!nestedForm) {
continue;
}
const div = document.createElement("div");
div.appendChild(nestedForm);
form.appendChild(div);
count++;
continue;
}
const div = document.createElement("div");
const label = document.createElement("label");
label.textContent = key;
div.appendChild(label);
const input = document.createElement("input");
input.name = key;
const isBoolean = typeof value === "boolean";
if (isBoolean) {
input.type = "checkbox";
input.checked = value;
input.addEventListener("change", () => {
browser.runtime.sendMessage({
type: "UPDATE_CONTEXT_VALUE",
key: path ? path + "." + key : key,
value: input.checked,
tabId,
}).then((updateResponse) => {
if (updateResponse && updateResponse.success) {
console.log(`Updated ${key} to ${input.checked}`);
} else {
console.error(`Failed to update ${key}:`, updateResponse.error);
}
});
});
} else {
input.type = "text";
input.value = value;
input.addEventListener("change", () => {
browser.runtime.sendMessage({
type: "UPDATE_CONTEXT_VALUE",
key: path ? path + "." + key : key,
value: input.value,
tabId,
}).then((updateResponse) => {
if (updateResponse && updateResponse.success) {
console.log(`Updated ${key} to ${input.value}`);
} else {
console.error(`Failed to update ${key}:`, updateResponse.error);
}
});
});
}
div.appendChild(input);
form.appendChild(div);
count++;
}
if (count === 0) {
const pre = document.createElement("pre");
pre.textContent = JSON.stringify(obj, null, 2);
detail.appendChild(pre);
} else {
detail.appendChild(form);
}
return detail;
}
function coalesceContextStack(contextStack) {
const obj = {};
for (const ctx of contextStack) {
Object.assign(obj, ctx);
}
return obj;
}
document.addEventListener("DOMContentLoaded", () => {
loadContextStack();
});

View File

@@ -1,115 +0,0 @@
type ContextStore = Record<string, any>;
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<T>(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<string, unknown>;
export function getContext() {
return ctx;
}
export function getContextItem<T>(prop: string): T {
return ctx[prop] as T;
}
export function getContextItemOrDefault<T>(prop: string, defaultValue: T): T {
try {
return ctx[prop] as T;
} catch {
return defaultValue;
}
}
export function setContextItem<T>(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);
}
return value;
}, 2);
}
const getContextStack = () => contextStack.slice();
const updateContextValue = (
key: string,
value: unknown,
depth = contextStack.length - 1,
) => {
const context = contextStack[depth] ?? defaultContext;
context[key] = value;
};
if (location.hostname === "localhost") {
globalThis.getContextStack = getContextStack;
globalThis.updateContextValue = updateContextValue;
}
console.log("globalThis.getContextStack", globalThis.getContextStack);
declare global {
var getContextStack: () => ContextStore[];
var updateContextValue: (key: string, value: unknown, depth?: number) => void;
}

View File

@@ -1,3 +1,7 @@
import { TrackSegment } from "../track/system.ts";
import { Train, TrainCar } from "../train/train.ts";
import { InputManager } from "./input.ts";
type ContextStore = Record<string, any>; type ContextStore = Record<string, any>;
const defaultContext: ContextStore = {}; const defaultContext: ContextStore = {};
@@ -50,36 +54,6 @@ export function setContextItem<T>(prop: string, value: T) {
}); });
} }
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) { function safeStringify(obj: any) {
const seen = new WeakSet(); const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => { return JSON.stringify(obj, (key, value) => {
@@ -89,6 +63,85 @@ function safeStringify(obj: any) {
} }
seen.add(value); seen.add(value);
} }
if (value instanceof Map) {
const val: Record<string, unknown> = {};
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; return value;
}, 2); }, 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[];
}

35
src/lib/debuggable.ts Normal file
View File

@@ -0,0 +1,35 @@
import { getContextItem } from "./context.ts";
export abstract class Drawable {
abstract draw(...args: unknown[]): void;
}
export abstract class Debuggable extends Drawable {
constructor(...debugKeys: [boolean | keyof Debug, ...(keyof Debug)[]]) {
super();
if (import.meta.env.DEV) {
let drawFirst = false;
if (typeof debugKeys[0] === "boolean") {
drawFirst = debugKeys.shift() as boolean;
}
const draw = this.draw.bind(this);
this.draw = drawFirst
? (...args: unknown[]) => {
draw(...args);
const debug = getContextItem<Debug>("debug");
if (debugKeys.some((k) => debug[k as keyof Debug])) {
this.debugDraw();
}
}
: (...args: unknown[]) => {
const debug = getContextItem<Debug>("debug");
if (debugKeys.some((k) => debug[k as keyof Debug])) {
this.debugDraw();
}
draw(...args);
};
}
}
abstract debugDraw(...args: unknown[]): void;
}

View File

@@ -24,7 +24,7 @@ const doodler = new ZoomableDoodler({
(doodler as any as { ctx: CanvasRenderingContext2D }).ctx (doodler as any as { ctx: CanvasRenderingContext2D }).ctx
.imageSmoothingEnabled = false; .imageSmoothingEnabled = false;
// doodler.minScale = 0.1; // doodler.minScale = 0.1;
// (doodler as any).scale = doodler.maxScale; (doodler as any).scale = 3.14;
const colors = [ const colors = [
"red", "red",
@@ -37,12 +37,32 @@ const colors = [
"violet", "violet",
]; ];
const _debug: Debug = JSON.parse(localStorage.getItem("debug") || "0") || {
track: false,
train: false,
path: false,
car: false,
};
const debug = new Proxy(_debug, {
get: (_, prop: string) => {
// if (prop !in _debug) {
// (_debug as any)[prop] = false;
// }
return prop in _debug ? (_debug as any)[prop] : false;
},
set: (_, prop: string, value: boolean) => {
(_debug as any)[prop] = value;
localStorage.setItem("debug", JSON.stringify(_debug));
return true;
},
});
setDefaultContext({ setDefaultContext({
inputManager, inputManager,
doodler, doodler,
resources, resources,
debug: true, debug,
showEnds: true,
colors, colors,
}); });
@@ -83,3 +103,7 @@ setInterval(() => {
const gameLoop = new GameLoop(); const gameLoop = new GameLoop();
gameLoop.start(state); gameLoop.start(state);
if (import.meta.env.DEV) {
console.log("Running in development mode");
}

View File

@@ -42,12 +42,12 @@ export class LoadState extends State<States> {
}); });
const doodler = getContextItem<Doodler>("doodler"); const doodler = getContextItem<Doodler>("doodler");
this.layers.push(doodler.createLayer((_, __, dTime) => { // this.layers.push(doodler.createLayer((_, __, dTime) => {
doodler.clearRect(new Vector(0, 0), doodler.width, doodler.height); // doodler.clearRect(new Vector(0, 0), doodler.width, doodler.height);
doodler.fillRect(new Vector(0, 0), doodler.width, doodler.height, { // doodler.fillRect(new Vector(0, 0), doodler.width, doodler.height, {
color: "#302040", // color: "#302040",
}); // });
})); // }));
} }
override stop(): void { override stop(): void {
// noop // noop

View File

@@ -61,7 +61,7 @@ export class RunningState extends State<States> {
const train = new Train(track.path, [new RedEngine(), new Tender()]); const train = new Train(track.path, [new RedEngine(), new Tender()]);
ctx.trains.push(train); ctx.trains.push(train);
}); });
// const trainCount = 2000; // const trainCount = 1000;
// for (let i = 0; i < trainCount; i++) { // for (let i = 0; i < trainCount; i++) {
// const train = new Train(track.path, [new RedEngine(), new Tender()]); // const train = new Train(track.path, [new RedEngine(), new Tender()]);
// ctx.trains.push(train); // ctx.trains.push(train);
@@ -72,6 +72,9 @@ export class RunningState extends State<States> {
for (const train of trains) { for (const train of trains) {
train.speed += 1; train.speed += 1;
} }
// for (const [i, train] of trains.entries()) {
// train.speed += .01 * i;
// }
}); });
inputManager.onKey("ArrowDown", () => { inputManager.onKey("ArrowDown", () => {
const trains = getContextItem<Train[]>("trains"); const trains = getContextItem<Train[]>("trains");

View File

@@ -4,33 +4,37 @@ import { getContextItem, setDefaultContext } from "../lib/context.ts";
import { clamp } from "../math/clamp.ts"; import { clamp } from "../math/clamp.ts";
export class TrackSystem { export class TrackSystem {
private segments: Map<string, TrackSegment> = new Map(); private _segments: Map<string, TrackSegment> = new Map();
private doodler: Doodler; private doodler: Doodler;
constructor(segments: TrackSegment[]) { constructor(segments: TrackSegment[]) {
this.doodler = getContextItem<Doodler>("doodler"); this.doodler = getContextItem<Doodler>("doodler");
for (const segment of segments) { for (const segment of segments) {
this.segments.set(segment.id, segment); this._segments.set(segment.id, segment);
} }
} }
getSegment(id: string) {
return this._segments.get(id);
}
get firstSegment() { get firstSegment() {
return this.segments.values().next().value; return this._segments.values().next().value;
} }
get lastSegment() { get lastSegment() {
return this.segments.values().toArray().pop(); return this._segments.values().toArray().pop();
} }
optimize(percent: number) { optimize(percent: number) {
console.log("Optimizing track", percent * 100 / 4); console.log("Optimizing track", percent * 100 / 4);
for (const segment of this.segments.values()) { for (const segment of this._segments.values()) {
segment.recalculateRailPoints(Math.round(percent * 100 / 4)); segment.recalculateRailPoints(Math.round(percent * 100 / 4));
} }
} }
recalculateAll() { recalculateAll() {
for (const segment of this.segments.values()) { for (const segment of this._segments.values()) {
segment.recalculateRailPoints(); segment.recalculateRailPoints();
segment.length = segment.calculateApproxLength(); segment.length = segment.calculateApproxLength();
} }
@@ -38,11 +42,11 @@ export class TrackSystem {
registerSegment(segment: TrackSegment) { registerSegment(segment: TrackSegment) {
segment.setTrack(this); segment.setTrack(this);
this.segments.set(segment.id, segment); this._segments.set(segment.id, segment);
} }
unregisterSegment(segment: TrackSegment) { unregisterSegment(segment: TrackSegment) {
this.segments.delete(segment.id); this._segments.delete(segment.id);
for (const s of this.segments.values()) { for (const s of this._segments.values()) {
s.backNeighbours = s.backNeighbours.filter((n) => n !== segment); s.backNeighbours = s.backNeighbours.filter((n) => n !== segment);
s.frontNeighbours = s.frontNeighbours.filter((n) => n !== segment); s.frontNeighbours = s.frontNeighbours.filter((n) => n !== segment);
} }
@@ -52,7 +56,7 @@ export class TrackSystem {
} }
draw(showControls = false) { draw(showControls = false) {
for (const [i, segment] of this.segments.entries()) { for (const [i, segment] of this._segments.entries()) {
segment.draw(showControls); segment.draw(showControls);
} }
@@ -85,7 +89,7 @@ export class TrackSystem {
endArray: End[] = []; endArray: End[] = [];
findEnds() { findEnds() {
for (const segment of this.segments.values()) { for (const segment of this._segments.values()) {
if (this.ends.has(segment)) continue; if (this.ends.has(segment)) continue;
const ends: [End, End] = [ const ends: [End, End] = [
{ {
@@ -109,14 +113,14 @@ export class TrackSystem {
serialize() { serialize() {
return JSON.stringify( return JSON.stringify(
this.segments.values().map((s) => s.serialize()).toArray(), this._segments.values().map((s) => s.serialize()).toArray(),
); );
} }
copy() { copy() {
const track = new TrackSystem([]); const track = new TrackSystem([]);
for (const segment of this.segments.values()) { for (const segment of this._segments.values()) {
track.segments.set(segment.id, segment.copy()); track._segments.set(segment.id, segment.copy());
} }
return track; return track;
} }
@@ -127,19 +131,19 @@ export class TrackSystem {
const neighborMap = new Map<string, [string[], string[]]>(); const neighborMap = new Map<string, [string[], string[]]>();
for (const segment of data) { for (const segment of data) {
track.segments.set(segment.id, TrackSegment.deserialize(segment)); track._segments.set(segment.id, TrackSegment.deserialize(segment));
neighborMap.set(segment.id, [segment.fNeighbors, segment.bNeighbors]); neighborMap.set(segment.id, [segment.fNeighbors, segment.bNeighbors]);
} }
for (const segment of track.segments.values()) { for (const segment of track._segments.values()) {
segment.setTrack(track); segment.setTrack(track);
const neighbors = neighborMap.get(segment.id); const neighbors = neighborMap.get(segment.id);
if (neighbors) { if (neighbors) {
segment.backNeighbours = neighbors[1].map((id) => segment.backNeighbours = neighbors[1].map((id) =>
track.segments.get(id) track._segments.get(id)
).filter((s) => s) as TrackSegment[]; ).filter((s) => s) as TrackSegment[];
segment.frontNeighbours = neighbors[0].map((id) => segment.frontNeighbours = neighbors[0].map((id) =>
track.segments.get(id) track._segments.get(id)
).filter((s) => s) as TrackSegment[]; ).filter((s) => s) as TrackSegment[];
} }
} }
@@ -148,7 +152,7 @@ export class TrackSystem {
} }
translate(v: Vector) { translate(v: Vector) {
for (const segment of this.segments.values()) { for (const segment of this._segments.values()) {
segment.translate(v); segment.translate(v);
} }
} }
@@ -164,7 +168,7 @@ export class TrackSystem {
generatePath() { generatePath() {
if (!this.firstSegment) throw new Error("No first segment"); if (!this.firstSegment) throw new Error("No first segment");
const flags = { looping: true }; const flags = { looping: false };
const rightOnlyPath = [ const rightOnlyPath = [
this.firstSegment.copy(), this.firstSegment.copy(),
...this.findRightPath( ...this.findRightPath(
@@ -276,15 +280,19 @@ export class TrackSegment extends PathSegment {
doodler: Doodler; doodler: Doodler;
normalPoints: Vector[] = []; normalPoints: Vector[] = [];
antiNormalPoints: Vector[] = []; antiNormalPoints: Vector[] = [];
evenPoints: [Vector, number][] = [];
constructor(p: VectorSet, id?: string) { constructor(p: VectorSet, id?: string) {
super(p); super(p);
this.doodler = getContextItem<Doodler>("doodler"); this.doodler = getContextItem<Doodler>("doodler");
this.id = id ?? crypto.randomUUID(); this.id = id ?? crypto.randomUUID();
this.recalculateRailPoints(); this.recalculateRailPoints();
const spacing = Math.ceil(this.length / 10);
this.evenPoints = this.calculateEvenlySpacedPoints(this.length / spacing);
} }
recalculateRailPoints(resolution = 100) { recalculateRailPoints(resolution = 60) {
this.normalPoints = []; this.normalPoints = [];
this.antiNormalPoints = []; this.antiNormalPoints = [];
for (let i = 0; i <= resolution; i++) { for (let i = 0; i <= resolution; i++) {
@@ -330,12 +338,12 @@ export class TrackSegment extends PathSegment {
}); });
} }
const spacing = Math.ceil(this.length / 10); // const spacing = Math.ceil(this.length / 10);
const points = this.calculateEvenlySpacedPoints(this.length / spacing); // const points = this.calculateEvenlySpacedPoints(this.length / spacing);
for (let i = 0; i < points.length - 1; i++) { for (let i = 0; i < this.evenPoints.length - 1; i++) {
// const t = i / ties; // const t = i / ties;
// const p = this.getPointAtT(t); // const p = this.getPointAtT(t);
const [p, t] = points[i]; const [p, t] = this.evenPoints[i];
// this.doodler.drawCircle(p, 2, { // this.doodler.drawCircle(p, 2, {
// color: "red", // color: "red",
// weight: 3, // weight: 3,
@@ -480,11 +488,17 @@ export class TrackSegment extends PathSegment {
} }
} }
interface PathPoint {
p: Vector;
segmentId: string;
tangent: Vector;
}
export class Spline<T extends PathSegment = PathSegment> { export class Spline<T extends PathSegment = PathSegment> {
segments: T[] = []; segments: T[] = [];
ctx?: CanvasRenderingContext2D; ctx?: CanvasRenderingContext2D;
evenPoints: Vector[]; evenPoints: PathPoint[];
pointSpacing: number; pointSpacing: number;
get points() { get points() {
@@ -534,13 +548,17 @@ export class Spline<T extends PathSegment = PathSegment> {
} }
} }
calculateEvenlySpacedPoints(spacing: number, resolution = 1) { calculateEvenlySpacedPoints(spacing: number, resolution = 1): PathPoint[] {
this.pointSpacing = 1; this.pointSpacing = 1;
// return this.segments.flatMap(s => s.calculateEvenlySpacedPoints(spacing, resolution)); // return this.segments.flatMap(s => s.calculateEvenlySpacedPoints(spacing, resolution));
const points: Vector[] = []; const points: PathPoint[] = [];
points.push(this.segments[0].points[0]); points.push({
let prev = points[0]; p: this.segments[0].points[0],
segmentId: this.segments[0].id,
tangent: this.segments[0].tangent(0),
});
let prev = points[0].p;
let distSinceLastEvenPoint = 0; let distSinceLastEvenPoint = 0;
for (const seg of this.segments) { for (const seg of this.segments) {
let t = 0; let t = 0;
@@ -558,8 +576,13 @@ export class Spline<T extends PathSegment = PathSegment> {
Vector.sub(point, prev).normalize().mult(overshoot), Vector.sub(point, prev).normalize().mult(overshoot),
); );
distSinceLastEvenPoint = overshoot; distSinceLastEvenPoint = overshoot;
points.push(evenPoint); points.push({
p: evenPoint,
segmentId: seg.id,
tangent: seg.tangent(t),
});
prev = evenPoint; prev = evenPoint;
continue;
} }
prev = point; prev = point;
@@ -571,13 +594,17 @@ export class Spline<T extends PathSegment = PathSegment> {
return points; return points;
} }
followEvenPoints(t: number) { followEvenPoints(t: number): PathPoint {
if (this.looped) { if (this.looped) {
if (t < 0) t += this.evenPoints.length; if (t < 0) t += this.evenPoints.length;
const i = Math.floor(t) % this.evenPoints.length; const i = Math.floor(t) % this.evenPoints.length;
const a = this.evenPoints[i]; const a = this.evenPoints[i];
const b = this.evenPoints[(i + 1) % this.evenPoints.length]; const b = this.evenPoints[(i + 1) % this.evenPoints.length];
return Vector.lerp(a, b, t % 1); return {
p: Vector.lerp(a.p, b.p, t % 1),
segmentId: b.segmentId,
tangent: b.tangent,
};
} }
t = clamp(t, 0, this.evenPoints.length - 1); t = clamp(t, 0, this.evenPoints.length - 1);
const i = clamp(Math.floor(t), 0, this.evenPoints.length - 1); const i = clamp(Math.floor(t), 0, this.evenPoints.length - 1);
@@ -586,7 +613,11 @@ export class Spline<T extends PathSegment = PathSegment> {
.evenPoints[ .evenPoints[
clamp((i + 1) % this.evenPoints.length, 0, this.evenPoints.length - 1) clamp((i + 1) % this.evenPoints.length, 0, this.evenPoints.length - 1)
]; ];
return Vector.lerp(a, b, t % 1); return {
p: Vector.lerp(a.p, b.p, t % 1),
segmentId: b.segmentId,
tangent: b.tangent,
};
} }
calculateApproxLength() { calculateApproxLength() {

View File

@@ -1,12 +1,10 @@
import { ComplexPath, PathSegment } from "../math/path.ts"; import { getContextItem } from "../lib/context.ts";
import { Follower } from "../physics/follower.ts";
import { Mover } from "../physics/mover.ts";
import { getContext, getContextItem } from "../lib/context.ts";
import { Doodler, Vector } from "@bearmetal/doodler"; import { Doodler, Vector } from "@bearmetal/doodler";
import { Spline, TrackSegment } from "../track/system.ts"; import { Spline, TrackSegment, TrackSystem } from "../track/system.ts";
import { ResourceManager } from "../lib/resources.ts"; import { Debuggable } from "../lib/debuggable.ts";
import { map } from "../math/lerp.ts";
export class Train { export class Train extends Debuggable {
nodes: Vector[] = []; nodes: Vector[] = [];
cars: TrainCar[] = []; cars: TrainCar[] = [];
@@ -14,32 +12,20 @@ export class Train {
path: Spline<TrackSegment>; path: Spline<TrackSegment>;
t: number; t: number;
engineLength = 40; spacing = 20;
spacing = 30;
speed = 10; speed = 10;
get segments() {
return Array.from(new Set(this.cars.flatMap((c) => c.segments)));
}
constructor(track: Spline<TrackSegment>, cars: TrainCar[]) { constructor(track: Spline<TrackSegment>, cars: TrainCar[]) {
super("train", "path");
this.path = track; this.path = track;
this.t = 0; this.t = 0;
const resources = getContextItem<ResourceManager>("resources");
this.cars = cars; this.cars = cars;
// this.cars.push(
// new TrainCar(
// 55,
// engineSprites,
// 80,
// 20,
// { at: new Vector(0, 60), width: 80, height: 20 },
// ),
// new TrainCar(
// 25,
// engineSprites,
// 40,
// 20,
// { at: new Vector(80, 0), width: 40, height: 20 },
// ),
// );
let currentOffset = 0; let currentOffset = 0;
try { try {
for (const car of this.cars) { for (const car of this.cars) {
@@ -47,8 +33,9 @@ export class Train {
const a = this.path.followEvenPoints(this.t - currentOffset); const a = this.path.followEvenPoints(this.t - currentOffset);
currentOffset += car.length; currentOffset += car.length;
const b = this.path.followEvenPoints(this.t - currentOffset); const b = this.path.followEvenPoints(this.t - currentOffset);
car.points = [a, b]; car.points = [a.p, b.p];
this.nodes.push(a, b); this.nodes.push(a.p, b.p);
car.segments = [a.segmentId, b.segmentId];
} }
} catch { } catch {
currentOffset = 0; currentOffset = 0;
@@ -57,24 +44,29 @@ export class Train {
const a = this.path.followEvenPoints(this.t - currentOffset); const a = this.path.followEvenPoints(this.t - currentOffset);
currentOffset += car.length; currentOffset += car.length;
const b = this.path.followEvenPoints(this.t - currentOffset); const b = this.path.followEvenPoints(this.t - currentOffset);
car.points = [a, b]; car.points = [a.p, b.p];
this.nodes.push(a, b); this.nodes.push(a.p, b.p);
car.segments = [a.segmentId, b.segmentId];
} }
} }
} }
move(dTime: number) { move(dTime: number) {
if (!this.speed) return;
this.t = this.t + this.speed * dTime * 10; this.t = this.t + this.speed * dTime * 10;
// % this.path.evenPoints.length; // This should probably be on the track system // % this.path.evenPoints.length; // This should probably be on the track system
// console.log(this.t);
let currentOffset = 0; let currentOffset = 0;
for (const car of this.cars) { for (const car of this.cars) {
// This needs to be moved to the car itself
if (!car.points) return; if (!car.points) return;
const [a, b] = car.points; const [a, b] = car.points;
a.set(this.path.followEvenPoints(this.t - currentOffset)); const nA = this.path.followEvenPoints(this.t - currentOffset);
a.set(nA.p);
currentOffset += car.length; currentOffset += car.length;
b.set(this.path.followEvenPoints(this.t - currentOffset)); const nB = this.path.followEvenPoints(this.t - currentOffset);
b.set(nB.p);
currentOffset += this.spacing; currentOffset += this.spacing;
car.segments = [nA.segmentId, nB.segmentId];
// car.draw(); // car.draw();
} }
// this.draw(); // this.draw();
@@ -96,19 +88,44 @@ export class Train {
// } // }
draw() { draw() {
const ctx = getContext(); for (const car of this.cars) {
if (ctx.debug) { car.draw();
const doodler = getContextItem<Doodler>("doodler"); }
}
override debugDraw(): void {
const debug = getContextItem<Debug>("debug");
const doodler = getContextItem<Doodler>("doodler");
if (debug.path) {
// doodler.drawLine(this.path.points, { // doodler.drawLine(this.path.points, {
// color: "red", // color: "red",
// weight: 3, // weight: 3,
// }); // });
for (const p of this.path.evenPoints) { const colors = getContextItem<string[]>("colors");
doodler.drawCircle(p, 2, { color: "red", weight: .5 }); for (const [i, p] of this.path.evenPoints.entries()) {
const color = colors[
Math.floor(
map(i, 0, this.path.evenPoints.length, 0, colors.length),
)
];
doodler.drawCircle(p.p, 2, { color, weight: .5 });
} }
} }
for (const car of this.cars) {
car.draw(); if (debug.train) {
const track = getContextItem<TrackSystem>("track");
const colors = getContextItem<string[]>("colors").slice();
colors.push(colors.shift()!);
colors.push(colors.shift()!);
colors.push(colors.shift()!);
for (const [i, segmentId] of this.segments.entries()) {
const segment = track.getSegment(segmentId);
segment &&
doodler.drawBezier(...segment.points, {
color: colors[i % colors.length],
weight: 3,
});
}
} }
} }
@@ -117,7 +134,7 @@ export class Train {
} }
} }
export class TrainCar { export class TrainCar extends Debuggable {
img: HTMLImageElement; img: HTMLImageElement;
imgWidth: number; imgWidth: number;
imgHeight: number; imgHeight: number;
@@ -126,6 +143,8 @@ export class TrainCar {
points?: [Vector, Vector, ...Vector[]]; points?: [Vector, Vector, ...Vector[]];
length: number; length: number;
segments: string[] = [];
constructor( constructor(
length: number, length: number,
img: HTMLImageElement, img: HTMLImageElement,
@@ -133,6 +152,7 @@ export class TrainCar {
h: number, h: number,
sprite?: ISprite, sprite?: ISprite,
) { ) {
super(true, "car");
this.img = img; this.img = img;
this.sprite = sprite; this.sprite = sprite;
this.imgWidth = w; this.imgWidth = w;
@@ -165,14 +185,14 @@ export class TrainCar {
origin.copy().sub(this.imgWidth / 2, this.imgHeight / 2), origin.copy().sub(this.imgWidth / 2, this.imgHeight / 2),
); );
}); });
}
const ctx = getContext(); override debugDraw(...args: unknown[]): void {
if (ctx.debug) { if (!this.points) return;
doodler.drawLine(this.points, { const doodler = getContextItem<Doodler>("doodler");
color: "blue", doodler.drawLine(this.points, {
weight: 3, color: "blue",
}); weight: 3,
} });
} }
} }

View File

@@ -15,4 +15,11 @@ declare global {
bNeighbors: string[]; bNeighbors: string[];
fNeighbors: string[]; fNeighbors: string[];
}; };
type Debug = {
track: boolean;
train: boolean;
car: boolean;
path: boolean;
};
} }

View File

@@ -1,7 +1,8 @@
import { defineConfig } from 'vite' import { defineConfig } from "vite";
import deno from '@deno/vite-plugin' import deno from "@deno/vite-plugin";
import { strip } from "./vite/plugins/strip.ts";
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [deno()], plugins: [deno(), strip()],
}) });

67
vite/plugins/strip.ts Normal file
View File

@@ -0,0 +1,67 @@
import { Plugin } from "vite";
export function strip(): Plugin {
const p: Plugin = {
name: "debug-strip",
enforce: "pre",
apply: "build",
transform(code: string, id: string) {
if (!id.endsWith(".ts") || import.meta.env.DEV) {
return code;
}
const keyword = "override debugDraw";
const results = [];
let currentIndex = 0;
while (true) {
// Find the next occurrence of the keyword starting from currentIndex.
const startIndex = code.indexOf(keyword, currentIndex);
if (startIndex === -1) {
break; // No more occurrences.
}
// Find the first opening brace '{' after the keyword.
const braceStart = code.indexOf("{", startIndex);
if (braceStart === -1) {
// No opening brace found; skip this occurrence.
currentIndex = startIndex + keyword.length;
continue;
}
// Use a counter to find the matching closing brace.
let openBraces = 0;
let endIndex = -1;
for (let i = braceStart; i < code.length; i++) {
if (code[i] === "{") {
openBraces++;
} else if (code[i] === "}") {
openBraces--;
}
// When openBraces returns to 0, we found the matching closing brace.
if (openBraces === 0) {
endIndex = i;
break;
}
}
// If a matching closing brace was found, extract the substring.
if (endIndex !== -1) {
results.push(code.substring(startIndex, endIndex + 1));
// Move the currentIndex past the extracted block.
currentIndex = endIndex + 1;
} else {
// If no matching closing brace is found, skip this occurrence.
currentIndex = startIndex + keyword.length;
}
}
for (const result of results) {
code = code.replace(result, "");
}
return code;
},
};
return p;
}