diff options
Diffstat (limited to 'site')
| -rw-r--r-- | site/arena.mjs | 79 | ||||
| -rw-r--r-- | site/index.html | 10 | ||||
| -rw-r--r-- | site/main.css | 2 | ||||
| -rw-r--r-- | site/main.mjs | 78 |
4 files changed, 97 insertions, 72 deletions
diff --git a/site/arena.mjs b/site/arena.mjs new file mode 100644 index 0000000..c76f7dc --- /dev/null +++ b/site/arena.mjs @@ -0,0 +1,79 @@ +const CANVAS_SELECTOR = 'canvas'; +const FPS_SELECTOR = '#fps'; + +export default class extends HTMLElement { + static name = 'x-arena'; + static register() { + console.debug('registering custom element', this.name, this); + self.customElements.define(this.name, this); + } + static radius = 25; + + #canvas; + #ctx; + #lastTime; + tickMS; + + connectedCallback() { + console.debug('connectedCallback', this); + this.#canvas = this.querySelector(CANVAS_SELECTOR); + this.#ctx = this.#canvas.getContext('2d'); + } + + invalidateFPS() { + document.querySelector(FPS_SELECTOR).textContent = 'n/a'; + } + + clamp(radius, x, y) { + const xx = Math.min(Math.max(radius, x), this.#canvas.width-radius); + const yy = Math.min(Math.max(radius, y), this.#canvas.height-radius); + return [xx, yy]; + } + + randStart(robo) { + const [x, y] = [this.#canvas.width, this.#canvas.height].map(len => + this.constructor.radius + Math.floor(Math.random() * (len - 2*this.constructor.radius))); + return {...robo, x, y}; + } + + renderFPS(now) { + const delta = now - (this.#lastTime ?? now); + if (delta > 0) { + document.querySelector(FPS_SELECTOR).textContent = Math.floor(1_000 / delta); + } + } + + render(robos, now=0) { + // we often get called with the same time stamp many times in + // a row due to fingerprint-reduction tech. + if (now === this.#lastTime) { + return; + } + this.renderFPS(now); + this.#lastTime = now; + + this.#ctx.clearRect(0, 0, this.#canvas.width, this.#canvas.height); + + robos.forEach(robo => { + // interpolation factor for smoother movement independent + // of tick rate, but never tween more than 1 tick. + const delta = robo.lastTick ? now - robo.lastTick : 0; + const timeScale = this.tickMS > 0 ? Math.min(delta / this.tickMS, 1.0) : 1; + let [x, y] = [robo.x, robo.y]; + if (delta > 0) { + const [velx, vely] = [ + robo.speedx, robo.speedy + ].map(x => timeScale * x); + [x, y] = this.clamp(this.constructor.radius, robo.x + velx, robo.y + vely); + } + this.#renderRobo(x, y); + }); + } + + #renderRobo(x, y) { + this.#ctx.fillStyle = 'rgb(200 0 0)'; + this.#ctx.beginPath(); + this.#ctx.arc(x, y, this.constructor.radius, 0, 2 * Math.PI); + this.#ctx.fill(); + } +} diff --git a/site/index.html b/site/index.html index 665411b..ace176c 100644 --- a/site/index.html +++ b/site/index.html @@ -13,9 +13,12 @@ <div class='flex-col'> <section> <h2>arena</h2> - <canvas id='arena' width='300' height='300'> - oh no! no canvas support! - </canvas> + <x-arena> + <p>fps: <span id='fps'>n/a</span></p> + <canvas width='300' height='300'> + oh no! no canvas support! + </canvas> + </x-arena> <div class='controls'> <button id='tick'> @@ -49,7 +52,6 @@ <option>60</option> </select> </div> <!-- controls --> - fps: <span id='fps'>n/a</span> </section> <!-- arena --> <section> diff --git a/site/main.css b/site/main.css index d31bec3..7e7ed18 100644 --- a/site/main.css +++ b/site/main.css @@ -14,7 +14,7 @@ html { flex: 1; } -#arena { +x-arena canvas { border: 1px solid orangered; background-color: rgb(200 200 200); } diff --git a/site/main.mjs b/site/main.mjs index fd9abb6..e2cdc3e 100644 --- a/site/main.mjs +++ b/site/main.mjs @@ -1,83 +1,38 @@ +import Arena from './arena.mjs'; import Inspector from './inspector.mjs'; -// simulation speed -let TICKS_PER_SECOND = 15; -let MS_PER_TICK = 1_000 / TICKS_PER_SECOND; - // one per page -const CANVAS_SELECTOR = '#arena'; const TICK_BUTTON_SELECTOR = '#tick'; const TICK_RATE_SELECTOR = '#tick-rate-select'; const RUN_BUTTON_SELECTOR = '#run'; -const FPS_SELECTOR = '#fps'; - -const ROBO_RADIUS = 25; - -function clamp(radius, x, y, width, height) { - const xx = Math.min(Math.max(radius, x), width-radius); - const yy = Math.min(Math.max(radius, y), height-radius); - return [xx, yy]; -} - -function renderRobo(ctx, x, y) { - ctx.fillStyle = 'rgb(200 0 0)'; - ctx.beginPath(); - //ctx.arc(Math.floor(x), Math.floor(y), 25, 0, 2 * Math.PI); - ctx.arc(x, y, ROBO_RADIUS, 0, 2 * Math.PI); - ctx.fill(); -} - -function renderArena(robos, now=0) { - // interpolation factor for smoother movement independent of tick - // rate, but never tween more than 1 tick. - const canvas = document.querySelector(CANVAS_SELECTOR); - const ctx = canvas.getContext('2d'); - - ctx.clearRect(0, 0, canvas.width, canvas.height); - - robos.forEach(robo => { - const delta = robo.lastTick ? now - robo.lastTick : 0; - const timeScale = Math.min(delta / MS_PER_TICK, 1.0); - let [x, y] = [robo.x, robo.y]; - if (delta > 0) { - const [velx, vely] = [ - robo.speedx, robo.speedy - ].map(x => timeScale * x); - [x, y] = clamp(ROBO_RADIUS, robo.x + velx, robo.y + vely, canvas.width, canvas.height); - } - renderRobo(ctx, x, y); - }); -} async function loaded() { + Arena.register(); Inspector.register(); - const canvas = document.querySelector(CANVAS_SELECTOR); const robos = [{ worker: new Worker('robo.mjs', { type: 'module' }), lastTick: undefined, - x: ROBO_RADIUS + Math.floor(Math.random() * (canvas.width - ROBO_RADIUS)), - y: ROBO_RADIUS + Math.floor(Math.random() * (canvas.height - ROBO_RADIUS)), heading: 0, speed: 0, speedx: 0, speedy: 0, - }]; + }].map(r => document.querySelector(Arena.name).randStart(r)); robos.forEach((robo, i) => { robo.worker.onmessage = e => { const { kind, res, trans } = e.data; switch (kind) { case 'compile': if (res) { - renderArena(robos); + document.querySelector(Arena.name).render(robos); document.querySelectorAll(Inspector.name)[i].render(trans, true); } break; case 'tick': robo.lastTick = document.timeline.currentTime; - [robo.x, robo.y] = clamp(ROBO_RADIUS, robo.x + robo.speedx, robo.y + robo.speedy, canvas.width, canvas.height); - renderArena(robos); + [robo.x, robo.y] = document.querySelector(Arena.name).clamp(Arena.radius, robo.x + robo.speedx, robo.y + robo.speedy); + document.querySelector(Arena.name).render(robos, robo.lastTick); robo.heading = trans.vars.heading; robo.speed = trans.vars.speed; @@ -111,35 +66,24 @@ async function loaded() { document.querySelector(TICK_RATE_SELECTOR).onchange = e => { console.debug('tick rate changed', e, Number(e.target.value)); - TICKS_PER_SECOND = e.target.value; - MS_PER_TICK = 1_000 / TICKS_PER_SECOND; + document.querySelector(Arena.name).tickMS = 1_000 / e.target.value; } - document.querySelector(TICK_RATE_SELECTOR).onchange({target: { value: TICKS_PER_SECOND }}); + document.querySelectorAll(TICK_RATE_SELECTOR).forEach(elt => elt.onchange({ target: elt })); let blinkenRun = false; - let lastTime = 0; function renderFrame(t) { if (!blinkenRun) { return; } self.requestAnimationFrame(renderFrame); - - // we often get called with the same time stamp many times - // in a row due to fingerprint-reduction tech. - const ms = t - lastTime; - if (ms > 0) { - lastTime = t; - renderArena(robos, t); - const fps = document.querySelector(FPS_SELECTOR); - fps.textContent = ms > 0 ? Math.floor(1_000 / ms) : '0'; - } + document.querySelector(Arena.name).render(robos, t); } function timeout() { if (!blinkenRun) { return } - self.setTimeout(timeout, MS_PER_TICK); + self.setTimeout(timeout, document.querySelector(Arena.name).tickMS); robos.forEach(robo => robo.worker.postMessage({ kind: 'tick' })); } @@ -158,7 +102,7 @@ async function loaded() { e.currentTarget.classList.remove('blinken'); e.currentTarget.classList.add('halten'); e.currentTarget.querySelector('title').textContent = 'blinken'; - document.querySelector(FPS_SELECTOR).textContent = 'n/a'; + document.querySelector(Arena.name).invalidateFPS(); } } } |
