diff options
| author | Brian Cully <bjc@spork.org> | 2025-12-16 18:59:39 -0500 |
|---|---|---|
| committer | Brian Cully <bjc@spork.org> | 2025-12-16 19:29:25 -0500 |
| commit | 3014a14b3a86681be15288f2495de6ae01196b42 (patch) | |
| tree | 880b1eb185a42e58438f799a05788ab6699e2773 | |
| parent | 45a2cfca666e5698462090399ff2db13be57a38c (diff) | |
| download | automathon-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.
| -rw-r--r-- | site/index.html | 68 | ||||
| -rw-r--r-- | site/main.css | 52 | ||||
| -rw-r--r-- | site/main.mjs | 97 |
3 files changed, 132 insertions, 85 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 @@ <body> <h1>automathon</h1> - <canvas id='arena' width='300px' height='300px'> - oh no! no canvas support! - </canvas> + <div id='ff'> + <section id='arena'> + <h2>arena</h2> + <canvas width='300px' height='300px'> + oh no! no canvas support! + </canvas> - <div id='editor'> - <div id='code'> - <select id='src-select'> - <option>rob.fs</option> - <option>fac.fs</option> - <option>slo-fac.fs</option> - </select> - <button id='compile'>compile</button> - <br> - <div id='src' contenteditable='plaintext-only'></div> - </div> + <div class='controls'> + <button id='bench'>bench</button> + <button id='tick'>tick</button> + <button id='blinken'>blinken</button> + </div> <!-- controls --> + </section> <!-- arena --> - <div id='state-container'> - <div id='state'> - <div class='controls'> - <button id='tick'>tick</button> - <button id='bench'>bench</button> - <button id='blinken'>blinken</button> - </div> + <section id='robview'> + <h2>code</h2> + <details id='editor' open> + <summary>code</summary> + <div id='code'> + <select id='src-select'> + <option>rob.fs</option> + <option>fac.fs</option> + <option>slo-fac.fs</option> + </select> + <button id='compile'>compile</button> + <br> + <div id='src' contenteditable='plaintext-only'></div> + </div> <!-- code --> + </details> + <details id='scope' open> + <summary>scope</summary> <fieldset class='wordlist'> <legend>word list</legend> <div id='wordlist'></div> </fieldset> + <fieldset class='vars'> + <legend>vars</legend> + <dl id='vars'></dl> + </fieldset> + <fieldset class='stack'> <legend>stack</legend> <ol id='stack'></ol> @@ -46,15 +59,14 @@ <legend>call stack</legend> <ol id='callstack'></ol> </fieldset> - - <fieldset class='vars'> - <legend>vars</legend> - <dl id='vars'></dl> - </fieldset> - </div> - </div> + </details> <!-- scope --> + </section> <!-- robview --> </div> <script src='./main.mjs' type='module'></script> + + <footer> + <address><a href='https://git.spork.org/automathon.git'>src</a></address> + </footer> </body> </html> 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); } |
