diff options
| author | Brian Cully <bjc@spork.org> | 2025-12-17 11:03:17 -0500 |
|---|---|---|
| committer | Brian Cully <bjc@spork.org> | 2025-12-17 11:03:17 -0500 |
| commit | 3d0d63ede8e267b320c672c248c9e731b0ed95c2 (patch) | |
| tree | c52d9881a852bb0adf5dc743202f7eb30d8f87b1 | |
| parent | 279c30f656cbf31ce1386d093a061a8c8ae0fe68 (diff) | |
| download | automathon-3d0d63ede8e267b320c672c248c9e731b0ed95c2.tar.gz automathon-3d0d63ede8e267b320c672c248c9e731b0ed95c2.zip | |
js: move robo state to worker
| -rw-r--r-- | site/bench.mjs | 13 | ||||
| -rw-r--r-- | site/index.html | 2 | ||||
| -rw-r--r-- | site/main.mjs | 161 | ||||
| -rw-r--r-- | site/robo.mjs | 73 |
4 files changed, 171 insertions, 78 deletions
diff --git a/site/bench.mjs b/site/bench.mjs new file mode 100644 index 0000000..ea88f6b --- /dev/null +++ b/site/bench.mjs @@ -0,0 +1,13 @@ +// todo: put this on a different page just for benching + +// document.querySelector(BENCH_BUTTON_SELECTOR).onclick = e => { +// console.debug('bench clicked', e); +// const start = performance.now(); +// let tickCount = 0; +// for (let i = 0; i < 1_000_000; i++) { +// tickCount += robo.vm.run(); +// } +// const end = performance.now(); +// console.info(`run took ${end-start} ms for ${tickCount} ticks (${(end-start)/tickCount * 1_000_000} ns/tick).`); +// console.info('result', robo.vm.stack()); +// }; diff --git a/site/index.html b/site/index.html index 635cc3c..dac9b4f 100644 --- a/site/index.html +++ b/site/index.html @@ -16,7 +16,7 @@ </canvas> <div class='controls'> - <button id='bench'>bench</button> + <!-- <button id='bench'>bench</button> --> <button id='tick'>tick</button> <button id='blinken'>blinken</button> </div> <!-- controls --> diff --git a/site/main.mjs b/site/main.mjs index b26fa4f..5358616 100644 --- a/site/main.mjs +++ b/site/main.mjs @@ -1,5 +1,3 @@ -import init, { make_vm } from './wasm/automathon.js'; - // simulation speed const TICKS_PER_SECOND = 12; const MS_PER_TICK = 1_000 / TICKS_PER_SECOND; @@ -28,12 +26,12 @@ function wordlistElts(wordlist) { return wordlist.map((bc, i) => { const bcElt = document.createElement('x-bytecode'); bcElt.setAttribute('x-index', i); - for (let i = 0; i < bc.len(); i++) { + bc.forEach((op, i) => { const opElt = document.createElement('x-op'); opElt.setAttribute('x-index', i); - opElt.textContent = bc.at(i); + opElt.textContent = op; bcElt.appendChild(opElt); - } + }) return bcElt; }) } @@ -45,12 +43,12 @@ function initWordlist() { }); } -function renderStack(vm) { +function renderStack(vmstack) { document.querySelectorAll(STACK_SELECTOR).forEach(e => { while (e.lastChild) { e.removeChild(e.lastChild); } - vm.stack().reverse() + vmstack.reverse() .forEach(datum => { const elt = document.createElement('li'); elt.textContent = datum; @@ -60,12 +58,12 @@ function renderStack(vm) { }); } -function renderCallStack(vm) { +function renderCallStack(vmcallstack) { document.querySelectorAll(CALLSTACK_SELECTOR).forEach(e => { while (e.lastChild) { e.removeChild(e.lastChild); } - vm.callstack().reverse() + vmcallstack.reverse() .forEach(datum => { const elt = document.createElement('li'); elt.textContent = `${datum.word}@${datum.offset}`; @@ -75,7 +73,7 @@ function renderCallStack(vm) { }); } -function renderVars(vm) { +function renderVars(vmvars) { document.querySelectorAll(VARS_SELECTOR).forEach(e => { while (e.lastChild) { e.removeChild(e.lastChild); @@ -87,7 +85,7 @@ function renderVars(vm) { e.appendChild(dt); const dd = document.createElement('dd'); - dd.textContent = vm[name](); + dd.textContent = vmvars[name]; e.appendChild(dd); }); }); @@ -110,16 +108,14 @@ function renderArena(robos, delta=0.0) { ctx.clearRect(0, 0, canvas.width, canvas.height); robos.forEach(robo => { - let heading = robo.vm.heading(); - let speed = robo.vm.speed(); - let [velx, vely] = [ - Math.cos(2 * Math.PI * heading / 360), - Math.sin(2 * Math.PI * heading / 360) - ].map(x => timeScale * speed * x); + const [velx, vely] = [ + robo.speedx, robo.speedy + ].map(x => timeScale * x); - renderRobo(ctx, robo.x, robo.y); - robo.x += velx; - robo.y += vely; + const x = robo.x + velx; + const y = robo.y + vely; + //console.debug('bjc render robo', robo, velx, vely, x, y) + renderRobo(ctx, x, y); }); } @@ -128,8 +124,8 @@ const highlight = new Highlight(highRange); CSS.highlights.set('exec', highlight); function renderTextHighlight(vm) { - const ip = vm.ip(); - const anno = vm.annotation_at(ip) + const ip = vm.ip; + const anno = vm.annos[ip.word][ip.offset]; const src = document.querySelector(SRC_SELECTOR); // this assumes the text node is the first child, maybe it isn't? @@ -137,23 +133,6 @@ function renderTextHighlight(vm) { highRange.setEnd(src.childNodes[0], anno.end); } -function tick(robo) { - if (!robo.vm.tick()) { - robo.vm.reset_ip(); - } - - const { word, offset } = robo.vm.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'); - }); - renderStack(robo.vm); - renderCallStack(robo.vm); - renderVars(robo.vm); - renderTextHighlight(robo.vm); -} - function loadForth(taintedPath) { // ascii only + ‘-’, ‘_’, ‘.’, and ‘/’, but no ‘../’ const path = @@ -176,13 +155,66 @@ function loadForth(taintedPath) { } async function loaded() { - console.debug('running init'); - const mod = await init(); - console.debug('init done', mod); + const roboWorker = new Worker('robo.mjs', { type: 'module' }); const robo = { + worker: roboWorker, + lastTick: undefined, x: 132, y: 25, - vm: make_vm() + heading: 0, + speed: 0, + speedx: 0, + speedy: 0, + }; + roboWorker.onmessage = e => { + const { kind, res, vm } = e.data; + switch (kind) { + case 'compile': + if (res) { + let wordlistContainer = document.querySelector(WORDLIST_SELECTOR); + console.debug('bjc new vm', vm); + const wordlist = wordlistElts(vm.wordlist); + wordlist.forEach(elt => wordlistContainer.appendChild(elt)); + initWordlist(); + renderStack(vm.stack); + renderCallStack(vm.callstack); + renderVars(vm.vars); + renderTextHighlight(vm); + + renderArena([robo]); + } + break; + case 'tick': + const { word, offset } = vm.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'); + }); + renderStack(vm.stack); + renderCallStack(vm.callstack); + renderVars(vm.vars); + renderTextHighlight(vm); + + robo.lastTick = document.timeline.currentTime; + robo.heading = vm.vars.heading; + robo.speed = vm.vars.speed; + const [speedx, speedy] = [ + Math.cos(2 * Math.PI * robo.heading / 360), + Math.sin(2 * Math.PI * robo.heading / 360) + ].map(x => robo.speed * x); + robo.speedx = speedx; + robo.speedy = speedy; + robo.x += speedx; + robo.y += speedy; + renderArena([robo]); + break; + default: + console.error('invalid message from robo worker', e.data); + } + }; + roboWorker.onerror = e => { + console.error('error in roboWorker', e); }; document.querySelectorAll(SRC_SELECT_SELECTOR).forEach(async sel => { @@ -202,45 +234,19 @@ async function loaded() { // always add a newline until i decide what to do with the parser. const text = document.querySelector(SRC_SELECTOR).textContent + '\n'; console.debug('compiling', text); - const start = performance.now(); - const res = robo.vm.compile(text); - const end = performance.now(); - console.info(`compile took ${end-start} ms`); - if (res) { - const wordlist = wordlistElts(robo.vm.wordlist()); - wordlist.forEach(elt => wordlistContainer.appendChild(elt)); - initWordlist(); - renderStack(robo.vm); - renderCallStack(robo.vm); - renderVars(robo.vm); - renderTextHighlight(robo.vm); - renderArena([robo]); - } - }; - - document.querySelector(BENCH_BUTTON_SELECTOR).onclick = e => { - console.debug('bench clicked', e); - const start = performance.now(); - let tickCount = 0; - for (let i = 0; i < 1_000_000; i++) { - tickCount += robo.vm.run(); - } - const end = performance.now(); - console.info(`run took ${end-start} ms for ${tickCount} ticks (${(end-start)/tickCount * 1_000_000} ns/tick).`); - console.info('result', robo.vm.stack()); + roboWorker.postMessage({ kind: 'compile', text }); }; document.querySelector(TICK_BUTTON_SELECTOR).onclick = e => { console.debug('tick clicked', e); - tick(robo); - renderArena([robo], MS_PER_TICK); + roboWorker.postMessage({ kind: 'tick' }); }; let blinkenRun = false; document.querySelector(BLINKEN_BUTTON_SELECTOR).onclick = e => { console.debug('blinken clicked', e); - let lastTime; + let lastTime = 0; function r(t, manual=false) { if (!blinkenRun) { return; @@ -249,9 +255,11 @@ async function loaded() { window.requestAnimationFrame(r); } - const delta = (lastTime === undefined) ? 0 : t - lastTime; - lastTime = t; - if (delta > 0) { + // we often get called with the same time stamp many times + // in a row due to fingerprint-reduction tech. + if (t > lastTime) { + lastTime = t; + const delta = robo.lastTick ? t - robo.lastTick : 0; renderArena([robo], delta); } } @@ -269,8 +277,7 @@ async function loaded() { } setTimeout(onTimeout, MS_PER_TICK); - tick(robo); - r(document.timeline.currentTime, true); + roboWorker.postMessage({ kind: 'tick' }); } setTimeout(onTimeout); } diff --git a/site/robo.mjs b/site/robo.mjs new file mode 100644 index 0000000..e6db244 --- /dev/null +++ b/site/robo.mjs @@ -0,0 +1,73 @@ +import init, { make_vm } from './wasm/automathon.js'; + +let vm; + +function vmData(vm) { + const wordlist = vm.wordlist().map(bc => { + let res = []; + for (let i = 0; i < bc.len(); i++) { + res.push(bc.at(i)) + } + return res; + }); + const callstack = vm.callstack().map(e => { return { word: e.word, offset: e.offset }; }); + const vars = Object.fromEntries(['out', 'heading', 'speed', 'doppler'].map(name => [name, vm[name]()])); + const ip = {word: vm.ip().word, offset: vm.ip().offset}; + const annos = vm.annotations().map(word_annos => { + let res = []; + for (let i = 0; i < word_annos.len(); i++) { + const anno = word_annos.at(i); + res.push({ start: anno.start, end: anno.end }) + } + return res; + }); + return { + wordlist, + stack: vm.stack(), + callstack, + vars, + ip, + annos, + } +} + +function compile(text) { + const start = performance.now(); + const res = vm.compile(text); + const end = performance.now(); + console.info(`compile took ${end-start} ms`); + postMessage({ kind: 'compile', res, vm: vmData(vm) }); +} + +function tick() { + if (!vm.tick()) { + vm.reset_ip(); + } + postMessage({ kind: 'tick', vm: vmData(vm) }); +} + +let mod; +async function messageHandler(e) { + if (mod === undefined) { + console.debug('worker running init'); + mod = await init(); + console.debug('worker init done'); + vm = make_vm(); + console.debug('worker vm made'); + } + const { kind } = e.data; + switch (kind) { + case 'compile': + compile(e.data.text); + break; + case 'tick': + tick(); + break; + default: + console.error('invalid message to robo worker', e.data); + postMessage({ kind: 'error', error: 'badmsg' }); + } +} + +console.debug('loading robo worker'); +addEventListener('message', messageHandler); |
