From 3014a14b3a86681be15288f2495de6ae01196b42 Mon Sep 17 00:00:00 2001 From: Brian Cully Date: Tue, 16 Dec 2025 18:59:39 -0500 Subject: 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. --- site/index.html | 72 ++++++++++++++++++++++++------------------ site/main.css | 52 +++++++++++++++---------------- site/main.mjs | 97 +++++++++++++++++++++++++++++++++++++++------------------ 3 files changed, 134 insertions(+), 87 deletions(-) diff --git a/site/index.html b/site/index.html index a0775ae..635cc3c 100644 --- a/site/index.html +++ b/site/index.html @@ -8,35 +8,48 @@

automathon

- - oh no! no canvas support! - - -
-
- - -
-
-
- -
-
-
- - - -
+
+
+

arena

+ + oh no! no canvas support! + +
+ + + +
+
+ +
+

code

+
+ code +
+ + +
+
+
+
+ +
+ scope
word list
+
+ vars +
+
+
stack
    @@ -46,15 +59,14 @@ call stack
      - -
      - vars -
      -
      -
      -
      + +
      + + diff --git a/site/main.css b/site/main.css index 52b3063..519ae31 100644 --- a/site/main.css +++ b/site/main.css @@ -5,19 +5,28 @@ html { box-sizing: inherit; } -#editor { - display: grid; - grid-template-columns: repeat(2, 1fr); - grid-gap: 1em; +#ff { + display: flex; + flex-direction: row; } -#code { - position: relative; - grid-row: 1; +body h1 { + flex: 2; +} + +body footer { + flex: 2; +} + +body #arena { + flex: 1; +} + +body #robview { + flex: 1; } #code #src { - width: 100%; min-height: 25ex; margin-top: 1ex; padding: 1em; @@ -29,36 +38,24 @@ html { background-color: yellowgreen; } -#compile { - position: absolute; - right: 0; -} - -#state-container { - grid-row: 1; -} - -#state { - display: grid; - grid-template-columns: repeat(2, 1fr); +#scope { + /* display: grid; */ + /* grid-template-columns: repeat(2, 1fr); */ } -#state .controls { - grid-row: 1; +#scope summary { grid-column: 1 / span 2; } -#state .wordlist { - grid-row: 2; +#scope .wordlist { grid-column: 1 / span 2; } -#state .vars { - grid-row: 3; +#scope .vars { grid-column: 1 / span 2; } -#wordlist .ip { +#scope .wordlist .ip { background-color: yellow; } @@ -82,4 +79,5 @@ x-op { canvas { border: 1px solid orangered; + background-color: rgb(200 200 200); } 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); } -- cgit v1.3