This commit is contained in:
2025-11-01 10:01:59 -07:00
parent eeab08af75
commit 53fdd24093
7 changed files with 421 additions and 171 deletions

View File

@@ -46,7 +46,6 @@ if (typeof window !== "undefined") {
interface EditorProps {
initialValue?: string;
onChange?: (editor: any, changes: any) => void;
onRequestRun?: () => void;
onReady?: (editor: any) => void;
}

View File

@@ -5,7 +5,12 @@ import { Button } from "@/components/ui/Button";
import { Dropdown } from "@/components/ui/Dropdown";
import { Editor } from "@/components/Editor";
import { Terminal, TerminalRef } from "@/components/Terminal";
import { sendDataToScript, startScript, stopScript } from "@/lib/runner";
import {
sendDataToScript,
startScript,
startScriptBulk,
stopScript,
} from "@/lib/runner";
import styles from "@/styles/Playground.module.css";
const initialCode = `
@@ -49,7 +54,7 @@ export default function Playground() {
const runDisabled = isScriptRunning || !isEditorReady;
const stopDisabled = !isScriptRunning;
const requestRun = useCallback(async () => {
const runHuman = useCallback(async () => {
if (resultRef.current) {
resultRef.current.value = "";
}
@@ -94,6 +99,51 @@ export default function Playground() {
setIsScriptRunning(false);
}, [runDisabled]);
const runBulk = useCallback(async () => {
if (resultRef.current) {
resultRef.current.value = "";
}
if (runDisabled || !editorRef.current) return;
setIsScriptRunning(true);
try {
terminalRef.current?.clear();
terminalRef.current?.focus();
await startScriptBulk(
editorRef.current.getValue(),
(line: string) => {
if (resultRef.current) {
let v = resultRef.current.value + line + "\n";
if (v.length > 10000) {
v = v.substring(v.length - 10000);
}
resultRef.current.value = v;
resultRef.current.scrollTop =
resultRef.current.scrollHeight -
resultRef.current.clientHeight;
}
},
(line: string) => {
terminalRef.current?.write(line);
}
);
} catch (ex) {
const errorMsg = `\nEXCEPTION: "${ex}"\n`;
if (resultRef.current) {
resultRef.current.value += errorMsg;
}
terminalRef.current?.write(
"\r\n\x1B[1;31mEXCEPTION:\x1B[0m " + String(ex) + "\r\n"
);
}
setIsScriptRunning(false);
}, [runDisabled]);
const stopScriptHandler = useCallback(() => {
stopScript();
}, []);
@@ -106,9 +156,7 @@ export default function Playground() {
<Button
variant="success"
iconLeft="play"
onClick={() => {
requestRun();
}}
onClick={runHuman}
loading={isScriptRunning}
disabled={runDisabled}
>
@@ -118,9 +166,7 @@ export default function Playground() {
<Button
variant="success"
iconLeft="play"
onClick={() => {
requestRun();
}}
onClick={runBulk}
loading={isScriptRunning}
disabled={runDisabled}
>
@@ -201,12 +247,6 @@ export default function Playground() {
ref={editorRef}
initialValue={initialCode}
onChange={() => {}}
onRequestRun={() => {
if (resultRef.current) {
resultRef.current.value = "";
}
requestRun();
}}
onReady={() => setIsEditorReady(true)}
/>
</div>

View File

@@ -18,7 +18,51 @@ export async function startScript(
worker.terminate();
}
worker = new Worker(new URL("./worker.ts", import.meta.url));
worker = new Worker(new URL("./worker_human.ts", import.meta.url));
worker.onmessage = (event) => {
const { type, line, error } = event.data;
if (type === "output") {
appendOutput(line);
} else if (type === "terminal") {
appendTerminal(line);
} else if (type === "complete") {
worker?.terminate();
worker = null;
resolve();
} else if (type === "error") {
worker?.terminate();
worker = null;
reject(new Error(error));
} else if (type === "stopped") {
worker?.terminate();
worker = null;
resolve();
}
};
worker.onerror = (error) => {
worker?.terminate();
worker = null;
reject(error);
};
worker.postMessage({ type: "run", script });
});
}
export async function startScriptBulk(
script: string,
appendOutput: (line: string) => void,
appendTerminal: (line: string) => void
): Promise<void> {
return new Promise((resolve, reject) => {
if (worker) {
worker.terminate();
}
worker = new Worker(new URL("./worker_bulk.ts", import.meta.url));
worker.onmessage = (event) => {
const { type, line, error } = event.data;

View File

@@ -0,0 +1,106 @@
import init, { GameState } from "../wasm/runner";
let wasmReady = false;
let wasmInitPromise: Promise<void> | null = null;
let currentGame: GameState | null = null;
async function initWasm(): Promise<void> {
if (wasmReady) return;
if (wasmInitPromise) {
return wasmInitPromise;
}
wasmInitPromise = (async () => {
await init();
wasmReady = true;
})();
return wasmInitPromise;
}
self.onmessage = async (event) => {
const { type, ...event_data } = event.data;
if (type === "init") {
try {
await initWasm();
self.postMessage({ type: "ready" });
} catch (error) {
self.postMessage({ type: "error", error: String(error) });
}
} else if (type === "run") {
try {
await initWasm();
self.postMessage({
type: "output",
line: "Output is disabled during bulk runs.",
});
const appendTerminal = (line: string) => {
self.postMessage({ type: "terminal", line });
};
let red_wins = 0;
let blue_wins = 0;
let ties = 0;
for (var i = 0; i < 50; i++) {
appendTerminal(`\n\r`);
appendTerminal(`============\n\r`);
appendTerminal(`= Round ${i + 1}\n\r`);
appendTerminal(`============\n\n\r`);
currentGame = new GameState(
event_data.script,
"max",
() => {},
() => {},
// TODO: pick opponent
event_data.script,
"min",
() => {},
() => {},
appendTerminal
);
currentGame.print_start();
while (currentGame && !currentGame.is_done()) {
currentGame.step();
}
appendTerminal("\r\n");
if (currentGame.is_error()) {
break;
}
if (currentGame.red_won() === true) {
red_wins += 1;
} else if (currentGame.red_won() === false) {
blue_wins += 1;
} else {
ties += 1;
}
}
appendTerminal("\r\n");
appendTerminal(`Red wins: ${red_wins}\r\n`);
appendTerminal(`Blue wins: ${blue_wins}\r\n`);
appendTerminal(`Draw games: ${ties}\r\n`);
if (currentGame) {
self.postMessage({ type: "complete" });
}
} catch (error) {
self.postMessage({ type: "error", error: String(error) });
}
} else if (type === "stop") {
currentGame = null;
self.postMessage({ type: "stopped" });
}
};

View File

@@ -63,35 +63,6 @@ self.onmessage = async (event) => {
);
currentGame.print_start();
/*
currentGame = new GameState(
event_data.script,
"max",
appendOutput,
appendOutput,
// TODO: pick opponent
event_data.script,
"min",
appendOutput,
appendOutput,
appendTerminal
);
while (currentGame && !currentGame.is_done()) {
const res = currentGame.step();
if (res === undefined) {
self.postMessage({ type: "complete" });
return;
}
}
if (currentGame) {
self.postMessage({ type: "complete" });
}
*/
} catch (error) {
self.postMessage({ type: "error", error: String(error) });
}

View File

@@ -54,7 +54,9 @@ export const loadAllWasm = async (): Promise<void> => {
await loadRhaiWasm();
// Load Script Runner WASM by creating and immediately terminating a worker
const worker = new Worker(new URL("../lib/worker.ts", import.meta.url));
const worker = new Worker(
new URL("../lib/worker_bulk.ts", import.meta.url)
);
await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
worker.terminate();