summaryrefslogtreecommitdiff
path: root/2026/games_submissions/liquidcake1/interpreter.mjs
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2026-08-18 00:08:28 -0400
committerLLLL Colonq <llll@colonq>2026-08-18 00:08:28 -0400
commit32a62cfcb925225359a5c90761b795f2f737ebd2 (patch)
tree95c2261964803b19f2d39cb35d8d514ed6e5a6f4 /2026/games_submissions/liquidcake1/interpreter.mjs
parentde99c5f7636a952025ea13de02bafd25bcb6bdec (diff)
Re-populate games
Diffstat (limited to '2026/games_submissions/liquidcake1/interpreter.mjs')
-rw-r--r--2026/games_submissions/liquidcake1/interpreter.mjs449
1 files changed, 0 insertions, 449 deletions
diff --git a/2026/games_submissions/liquidcake1/interpreter.mjs b/2026/games_submissions/liquidcake1/interpreter.mjs
deleted file mode 100644
index 703b2d7..0000000
--- a/2026/games_submissions/liquidcake1/interpreter.mjs
+++ /dev/null
@@ -1,449 +0,0 @@
-// JIT TODO:
-// * OK, it's probably about 6x faster with JIT than without, but we can go another 4-5x faster by loop unrolling.
-// * We don't bounds-check the stack before executing a JITted routine.
-// * We can probably do a lot better in speed by unrolling and aliasing the stack. Name top N stack vars a1 to aN,
-// then unpack the top N into those, then run code, then pack aN to a?.
-// * We don't have support for branches, even just the dumb "primary only, bail otherwise" variety.
-// * We don't ever remove JIT. We should destroy JIT when a cell is modified.
-// * If a cell is modified _DURING_ a JITted sequence, what do? If external, we can just claim it doesn't matter.
-// If the thread self-modified, we'll need a means to abort the JITted sequence early.
-// * There is no JIT visualisation.
-
-import { instructions, instructions_raw } from "./instructions.mjs";
-import { Queue } from "./queue.mjs";
-import { Jit } from "./jit.mjs";
-
-function gen_fingerprint(s) {
- let x = 0;
- for(let c of Array.from(s).map(x => x.charCodeAt(0))) {
- x = x * 256 + c;
- }
- return x;
-}
-
-function deep_copy(obj) {
- return JSON.parse(JSON.stringify(obj));
-}
-
-function shallow_copy(obj) {
- return Object.fromEntries(Object.entries(obj));
-}
-
-function shallowish_copy(obj, i) {
- if (i > 0) {
- return shallowish_copy(obj, i - 1);
- } else {
- return shallow_copy(obj);
- }
-}
-
-class Thread {
- running = true;
- mode = "normal";
- jit = null;
- tick_count = 0;
-
- constructor(interpreter, col, row, cold, rowd, stack, overlays) {
- this.interpreter = interpreter;
- this.col = col;
- this.row = row;
- this.cold = cold;
- this.rowd = rowd;
- this.stack = stack;
- this.overlays = overlays || {};
- }
-
- split_thread() {
- let thread = new Thread(
- this.interpreter,
- this.col,
- this.row,
- -this.cold,
- -this.rowd,
- Array.from(this.stack),
- shallow_copy(this.overlays),
- );
- thread.tick_forwards();
- thread.tick_count = this.tick_count; // Ensure new thread has equal priority to old thread.
- return thread;
- }
-
- tick_forwards() {
- this.col += this.cold;
- this.row += this.rowd;
- if (this.row == this.interpreter.field.length && this.rowd > 0) this.row = 0;
- else if (this.row == -1 && this.rowd < 0) this.row = this.interpreter.field.length - 1;
- if (this.col == this.interpreter.field[this.row].length && this.cold > 0) this.col = 0;
- else if (this.col == -1 && this.cold < 0) this.col = this.interpreter.field[this.row].length - 1;
- }
-
- pop() {
- if (this.stack.length > 0) {
- return this.stack.pop();
- } else {
- this.interpreter.error(this, "Stack underflow");
- return 0;
- }
- }
-}
-
-export class Interpreter {
- max_loops = 1000000000000;
- ticks = 0;
- paused_wake = null;
- paused_event = null;
- slice_loops = 1;
- slice_sleep = 20;
- running = false;
- threads = [];
- events = {};
- awake_sleep = null;
- stats = [];
- field = [];
- instructions = shallow_copy(instructions);
- instructions_raw = shallow_copy(instructions_raw);
- overlays = {};
- input_queue = new Queue(); // Queue things like: change speed, input, etc.
- stdin_queue = new Queue(); // Chars from stdin.
- stdin_waiters = [];
- handlers = {};
-
- constructor() {
- this.jit = new Jit(this);
- }
-
- load_overlay(fingerprint, overlay) {
- this.overlays[gen_fingerprint(fingerprint)] = overlay;
- }
-
- add_handler(event_name, f) {
- if (this.handlers[event_name] === undefined) {
- this.handlers[event_name] = [];
- }
- this.handlers[event_name].push(f);
- }
-
- trigger_event(event_name, ...args) {
- //console.log(`Event: ${event_name} -- ${args}`);
- let handlers = this.handlers[event_name];
- if (handlers === undefined) {
- //console.log(`Unhandled event: ${event_name} -- ${args}`);
- } else {
- handlers.forEach(f => f(...args));
- }
- }
-
- set_speed(raw_speed) {
- if (raw_speed > 0) {
- this.slice_sleep = 1;
- this.slice_loops = Math.floor(1.1 ** raw_speed);
- } else {
- this.slice_sleep = Math.floor(1.1 ** -raw_speed);
- this.slice_loops = 1;
- }
- this.trigger_event("speed_changed", raw_speed, this.slice_sleep, this.slice_loops);
- }
-
- toggle_pause(e) {
- if (this.paused_wake === null) {
- this.pause();
- } else {
- this.unpause();
- }
- }
-
- pause() {
- let thisthis = this;
- this.paused_event = new Promise(function (r) {
- thisthis.paused_wake = r;
- // We shouldn't be in the sleep loop if paused.
- if (thisthis.awake_sleep != null) thisthis.awake_sleep();
- });
- // These really need outputting from the main loop, not here!
- this.trigger_event("paused", true);
- this.threads.forEach(function (thread) {
- thisthis.trigger_event("thread_paused", thread, true);
- });
- }
-
- unpause() {
- let old_wake = this.paused_wake;
- let thisthis = this;
- this.paused_wake = null;
- this.trigger_event("paused", false);
- this.threads.forEach(function (thread) {
- thisthis.trigger_event("thread_paused", thread, false)
- });
- if (old_wake)
- old_wake();
- }
-
- async sleep_until_unpaused(all_blocked) { // TODO???
- if (!this.paused_event)
- await this.sleep(all_blocked ? 50 : this.slice_sleep);
- if (this.paused_event) {
- let paused_event = this.paused_event;
- this.paused_event = null;
- let unpause_data = await paused_event;
- return unpause_data;
- }
- }
-
- step(e) {
- let old_wake = this.paused_wake;
- this.pause();
- if (old_wake !== null) {
- old_wake({count: 1});
- }
- }
-
- step_thread(thread) {
- let old_wake = this.paused_wake;
- this.pause();
- if (old_wake !== null) {
- old_wake({threads: [thread], count: 1});
- }
- }
-
- async go() {
- //console.log(this.threads);
- this.stop();
- if (this.running_promise != null) {
- console.log("Waiting for last thread to exit");
- await this.running_promise;
- console.log("Last thread has exited");
- }
- let thread = new Thread(this, 0, 0, 1, 0, []);
- this.new_thread(thread);
- this.trigger_event("started");
- this.main_loop();
- }
-
- new_thread(thread) {
- this.threads.push(thread);
- this.trigger_event("thread_created", thread);
- }
-
- stop() {
- if (this.threads.length > 0) {
- this.trigger_event("stopped");
- }
- this.threads.forEach(function (thread) { thread.running = false; });
- this.unpause();
- if (this.awake_sleep != null) this.awake_sleep();
- }
-
- check(state, col, row) {
- if (row < 0 || row >= this.field.length) this.error(state, `Row ${row} is out of bounds`);
- //else if (col < 0 || col >= this.field[row].length) this.error(state, `Col ${col} is out of bounds`);
- else return true;
- }
-
- set_field(state, col, row, val) {
- if (this.check(state, col, row)) {
- this.set_cell(col, row, val);
- } else {
- console.log(`out of bounds access ${state} ${col} ${row} ${val}`);
- }
- }
-
- set_cell(col, row, val) {
- let val_str = String.fromCharCode(val);
- this.field[row][col] = val;
- let title = `(${col},${row})=${val} (${val_str})`;
- if (this.instructions_raw[val_str] !== undefined)
- title += ": " + this.instructions_raw[val_str].desc;
- this.jit.cell_changed(row, col);
- this.trigger_event("cell_changed", col, row, val);
- }
-
- get_field(state, col, row, val) {
- if (this.check(state, col, row)) {
- let val = this.field[row][col];
- return val === undefined ? 0 : val;
- }
- }
-
- out(s) {
- this.trigger_event("char_out", s);
- //console.log("Out: " + s);
- }
- oute(s) {
- this.trigger_event("error_occurred", s);
- //console.log("Err: " + s);
- }
-
- error(thread, message) {
- thread.running = false;
- this.output_error(thread, message);
- }
-
- output_error(thread, message) {
- this.oute(`ERROR: ${message}`);
- this.oute(`State was: row=${thread.row} col=${thread.col} stack=${thread.stack}`);
- this.output_field();
- }
-
- output_field() {
- let lines = [];
- for(let row=0; row<this.field.length; row++) {
- let s = "";
- for(let col=0; col<this.field[row].length; col++) {
- let c = this.field[row][col];
- s += (c > 31 && c < 127 ? String.fromCharCode(this.field[row][col]) : "?");
- }
- lines.push(s);
- }
- this.trigger_event("output_field", lines);
- }
-
- async sleep(ms) {
- await new Promise(r => { this.awake_sleep = r; setTimeout(r, ms) });
- this.awake_sleep = null;
- }
-
- process_events() {
- while (this.input_queue.length > 0) {
- let item = this.input_queue.pop();
- if (item[0] == "set_speed") {
- this.set_speed(item[1]);
- } else if (item[0] == "pause") {
- this.pause();
- } else if (item[0] == "unpause") {
- this.unpause();
- } else if (item[0] == "stdin") {
- if (this.stdin_waiters.length > 0) {
- this.stdin_waiters.shift()(item[1]);
- } else {
- this.stdin_queue.push(item[1]);
- }
- }
- }
- }
-
- async main_loop() {
- let ticks = 0;
- this.running = true;
- let running_wake = null;
- this.running_promise = new Promise(r => { running_wake = r; });
- let highlighted_cells = [];
- let start_time = new Date().getTime();
- let all_blocked = false;
- while(this.threads.length > 0) {
- this.process_events();
- let slice_loops = this.slice_loops;
- let limit_threads = null;
- let need_sleep = true;
-
- this.threads.forEach(thread => this.trigger_event("thread_state_updated", thread));
- this.trigger_event("thread_state_synced", this.threads);
-
- if (ticks > this.max_loops) {
- this.ticks += ticks;
- ticks = 0;
- let end_time = new Date().getTime();
- this.oute(`Ran out of ticks! (${ticks} > ${this.max_loops})`);
- console.log(`${ticks} in ${end_time - start_time} is ${ticks / (end_time - start_time) / 1000} MHz`);
- this.pause();
- this.output_field();
- }
-
- let unpause_data = await this.sleep_until_unpaused(all_blocked);
- if (unpause_data) {
- console.log(`Unpaused; resetting ticks to 0 from ${ticks}`);
- start_time = new Date().getTime();
- if (unpause_data.count !== undefined) {
- need_sleep = false;
- slice_loops = unpause_data.count;
- }
- if (unpause_data.threads !== undefined)
- limit_threads = unpause_data.threads;
- }
-
- let count = 0;
- let dead_threads = [];
- while(count < slice_loops && this.threads.length > 0) {
- // This will _not_ iterate over newly-added threads.
- all_blocked = true;
- let to_iter = limit_threads !== null ? limit_threads : this.threads;
- to_iter = to_iter.filter(thread => !thread.blocked);
- if (to_iter.length == 0) {
- break;
- }
- to_iter.forEach(function (thread) { if (!thread.waiter) { all_blocked = false; } });
- let min_count = Math.min(...to_iter.map(x => x.tick_count));
- to_iter.forEach((thread, i) => {
- if (thread.tick_count == min_count) {
- let tick_count = this.tick(thread, slice_loops - count, i);
- count += tick_count;
- thread.tick_count += tick_count;
- }
- });
- let new_dead_threads = this.threads.filter(t => !t.running);
- if (new_dead_threads) {
- dead_threads.push(...new_dead_threads);
- this.threads = this.threads.filter(t => t.running);
- }
- }
-
- dead_threads.forEach(
- dead_thread => this.trigger_event("thread_dead", dead_thread));
-
- ticks += count;
- }
- running_wake();
- //console.log(`exited main_loop with ticks ${ticks}`);
- this.ticks += ticks;
- let end_time = new Date().getTime();
- //console.log(`${ticks} in ${end_time - start_time} is ${ticks / (end_time - start_time) / 1000} MHz`);
- this.output_field();
- this.trigger_event("terminate");
- }
-
- tick(thread, target_ticks, thread_num) {
- if (thread.waiter) {
- try {
- if (thread.waiter(thread)) {
- thread.waiter = null;
- }
- } catch (err) {
- this.error(thread, "Exception caught during waiter: " + err.message);
- //console.log(err.stack);
- }
- return 1;
- }
- if (thread.mode == "normal") {
- let symbol = this.field[thread.row][thread.col] || 32;
- let instruction = thread.overlays[symbol] || this.instructions[symbol];
- //console.log(`Executing ${String.fromCharCode(symbol)} at ${thread.col},${thread.row} stack_length=${thread.stack.length} stack_tail=${thread.stack.slice(-10)}`);
- //console.log("Executing " + String.fromCharCode(symbol));
- let jit_return = this.jit.step_jit(thread, target_ticks);
- if (jit_return > 0) {
- return jit_return;
- }
- if (instruction == null) {
- this.error(thread, "Invalid instruction: " + symbol);
- } else {
- try {
- let ret = instruction(thread, thread_num);
- if (ret && ret.constructor === Promise) {
- thread.blocked = true;
- }
- } catch (err) {
- this.error(thread, "Exception caught during " + symbol + ": " + err.message);
- //console.log(err.stack);
- }
- }
- } else if (thread.mode == "string") {
- let symbol = this.field[thread.row][thread.col];
- if (symbol == '"'.charCodeAt(0)) {
- thread.mode = "normal";
- } else {
- thread.stack.push(symbol);
- }
- }
- thread.tick_count += 1;
- thread.tick_forwards();
- return 1;
- }
-}