summaryrefslogtreecommitdiffstats
path: root/site
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
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')
-rw-r--r--site/index.html68
-rw-r--r--site/main.css52
-rw-r--r--site/main.mjs97
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);
}