summaryrefslogtreecommitdiffstats
path: root/site/main.mjs
diff options
context:
space:
mode:
authorBrian Cully <bjc@spork.org>2025-12-16 18:59:39 -0500
committerBrian Cully <bjc@spork.org>2025-12-16 19:29:25 -0500
commit3014a14b3a86681be15288f2495de6ae01196b42 (patch)
tree880b1eb185a42e58438f799a05788ab6699e2773 /site/main.mjs
parent45a2cfca666e5698462090399ff2db13be57a38c (diff)
downloadautomathon-3014a14b3a86681be15288f2495de6ae01196b42.tar.gz
automathon-3014a14b3a86681be15288f2495de6ae01196b42.zip
html: animate independently of tick rate, reorg a lot of html
i should have done this in more commits, but i'm not going to go back and fix it now.
Diffstat (limited to 'site/main.mjs')
-rw-r--r--site/main.mjs97
1 files changed, 67 insertions, 30 deletions
diff --git a/site/main.mjs b/site/main.mjs
index e049dea..f4e1989 100644
--- a/site/main.mjs
+++ b/site/main.mjs
@@ -1,5 +1,29 @@
import init, { make_vm } from './wasm/automathon.js';
+// simulation speed
+const TICKS_PER_SECOND = 12;
+const MS_PER_TICK = 1_000 / TICKS_PER_SECOND;
+
+// one per page
+const CANVAS_SELECTOR = '#arena canvas';
+const TICK_BUTTON_SELECTOR = '#tick';
+const BENCH_BUTTON_SELECTOR = '#bench';
+const BLINKEN_BUTTON_SELECTOR = '#blinken';
+
+// one per bot
+const SRC_SELECT_SELECTOR = '#src-select';
+const COMPILE_BUTTON_SELECTOR = '#compile';
+const WORDLIST_SELECTOR = '#wordlist';
+const STACK_SELECTOR = '#stack';
+const CALLSTACK_SELECTOR = '#callstack';
+const VARS_SELECTOR = '#vars';
+const SRC_SELECTOR = '#src';
+const IP_SELECTOR = '#wordlist .ip';
+
+function selectorForIP(word, offset) {
+ return `#wordlist x-bytecode[x-index='${word}'] x-op[x-index='${offset}']`;
+}
+
function wordlistElts(wordlist) {
return wordlist.map((bc, i) => {
const bcElt = document.createElement('x-bytecode');
@@ -14,10 +38,6 @@ function wordlistElts(wordlist) {
})
}
-function selectorForIP(word, offset) {
- return `#wordlist x-bytecode[x-index='${word}'] x-op[x-index='${offset}']`;
-}
-
function initWordlist() {
const sel = selectorForIP(0, 0);
document.querySelectorAll(sel).forEach(e => {
@@ -26,7 +46,7 @@ function initWordlist() {
}
function renderStack(vm) {
- document.querySelectorAll('#stack').forEach(e => {
+ document.querySelectorAll(STACK_SELECTOR).forEach(e => {
while (e.lastChild) {
e.removeChild(e.lastChild);
}
@@ -41,7 +61,7 @@ function renderStack(vm) {
}
function renderCallStack(vm) {
- document.querySelectorAll('#callstack').forEach(e => {
+ document.querySelectorAll(CALLSTACK_SELECTOR).forEach(e => {
while (e.lastChild) {
e.removeChild(e.lastChild);
}
@@ -56,7 +76,7 @@ function renderCallStack(vm) {
}
function renderVars(vm) {
- document.querySelectorAll('#vars').forEach(e => {
+ document.querySelectorAll(VARS_SELECTOR).forEach(e => {
while (e.lastChild) {
e.removeChild(e.lastChild);
}
@@ -80,13 +100,14 @@ function renderRobo(ctx, x, y) {
ctx.fill();
}
-function renderArena(robos) {
- const canvas = document.querySelector('#arena');
+function renderArena(robos, delta=0.0) {
+ // interpolation factor for smoother movement independent of tick
+ // rate, but never tween more than 1 tick.
+ const timeScale = Math.min(delta / MS_PER_TICK, 1.0);
+ const canvas = document.querySelector(CANVAS_SELECTOR);
const ctx = canvas.getContext('2d');
- //ctx.clearRect(0, 0, canvas.width, canvas.height);
- ctx.fillStyle = 'rgb(200 200 200 / 5%)';
- ctx.fillRect(0, 0, canvas.width, canvas.height);
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
robos.forEach(robo => {
let heading = robo.vm.heading();
@@ -94,7 +115,7 @@ function renderArena(robos) {
let [velx, vely] = [
Math.cos(2 * Math.PI * heading / 360),
Math.sin(2 * Math.PI * heading / 360)
- ].map(x => speed * x);
+ ].map(x => timeScale * speed * x);
renderRobo(ctx, robo.x, robo.y);
robo.x += velx;
@@ -109,7 +130,7 @@ CSS.highlights.set('exec', highlight);
function renderTextHighlight(vm) {
const ip = vm.ip();
const anno = vm.annotation_at(ip)
- const src = document.querySelector('#src');
+ const src = document.querySelector(SRC_SELECTOR);
// this assumes the text node is the first child, maybe it isn't?
highRange.setStart(src.childNodes[0], anno.start);
@@ -122,7 +143,7 @@ function tick(robo) {
}
const { word, offset } = robo.vm.ip();
- document.querySelectorAll('#wordlist .ip').forEach(e => e.classList.remove('ip'));
+ document.querySelectorAll(IP_SELECTOR).forEach(e => e.classList.remove('ip'));
const sel = selectorForIP(word, offset);
document.querySelectorAll(sel).forEach(e => {
e.classList.add('ip');
@@ -131,7 +152,6 @@ function tick(robo) {
renderCallStack(robo.vm);
renderVars(robo.vm);
renderTextHighlight(robo.vm);
- renderArena([robo]);
}
function loadForth(taintedPath) {
@@ -148,7 +168,7 @@ function loadForth(taintedPath) {
return resp.text()
})
.then(text => {
- document.querySelector('#src').textContent = text;
+ document.querySelector(SRC_SELECTOR).textContent = text;
})
.catch(e => {
console.error(`couldn't fetch ‘${path}’`, e);
@@ -165,22 +185,22 @@ async function loaded() {
vm: make_vm()
};
- document.querySelectorAll('#src-select').forEach(async sel => {
+ document.querySelectorAll(SRC_SELECT_SELECTOR).forEach(async sel => {
sel.onchange = _ => loadForth(sel.value);
loadForth(sel.value);
});
- document.querySelector('#compile').onclick = e => {
+ document.querySelector(COMPILE_BUTTON_SELECTOR).onclick = e => {
console.debug('compile clicked', e);
- let wordlistContainer = document.querySelector('#wordlist');
+ let wordlistContainer = document.querySelector(WORDLIST_SELECTOR);
while (wordlistContainer.lastChild) {
console.debug('removing child', wordlistContainer.lastChild)
wordlistContainer.removeChild(wordlistContainer.lastChild);
}
// always add a newline until i decide what to do with the parser.
- const text = document.querySelector('#src').textContent + '\n';
+ const text = document.querySelector(SRC_SELECTOR).textContent + '\n';
console.debug('compiling', text);
const start = performance.now();
const res = robo.vm.compile(text);
@@ -197,11 +217,8 @@ async function loaded() {
renderArena([robo]);
}
};
- document.querySelector('#tick').onclick = e => {
- console.debug('tick clicked', e);
- tick(robo);
- };
- document.querySelector('#bench').onclick = e => {
+
+ document.querySelector(BENCH_BUTTON_SELECTOR).onclick = e => {
console.debug('bench clicked', e);
const start = performance.now();
let tickCount = 0;
@@ -213,21 +230,41 @@ async function loaded() {
console.info('result', robo.vm.stack());
};
+ document.querySelector(TICK_BUTTON_SELECTOR).onclick = e => {
+ console.debug('tick clicked', e);
+ tick(robo);
+ renderArena([robo], MS_PER_TICK);
+ };
+
let blinkenRun = false;
- document.querySelector('#blinken').onclick = e => {
+ document.querySelector(BLINKEN_BUTTON_SELECTOR).onclick = e => {
console.debug('blinken clicked', e);
+
+ let lastTime;
+ function r(t, manual=false) {
+ if (blinkenRun && !manual) {
+ window.requestAnimationFrame(r);
+ }
+ const delta = (lastTime === undefined) ? 0 : t - lastTime;
+ lastTime = t;
+ if (delta > 0) {
+ renderArena([robo], delta);
+ }
+ }
+
blinkenRun = !blinkenRun;
if (blinkenRun) {
e.target.textContent = 'haltenblinken';
+ r(document.timeline.currentTime);
} else {
e.target.textContent = 'blinken';
}
- const frameRate = 30;
const onTimeout = _ => {
if (blinkenRun) {
- tick(robo);
- setTimeout(onTimeout, 1_000 / frameRate);
+ setTimeout(onTimeout, MS_PER_TICK);
}
+ tick(robo);
+ r(document.timeline.currentTime, true);
}
setTimeout(onTimeout);
}