diff --git a/index.html b/index.html index 4d4f02a..0b58ea1 100644 --- a/index.html +++ b/index.html @@ -45,7 +45,26 @@ width: 65px; } - .reset-btn { + #stats { + font-family: monospace; + color: #777; + padding: 8px 12px; + font-size: 11px; + line-height: 1.8; + border-bottom: 1px solid #1a1a1a; + } + + #stats .label { + display: inline-block; + width: 65px; + color: #555; + } + + #stats .value { + color: #c43c3c; + } + + .reset-btn { width: calc(100% - 16px); margin: 12px 8px; padding: 6px; diff --git a/src/App.ts b/src/App.ts index ec7c290..6aa39ec 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,6 +1,7 @@ import Config from "./Config"; import GUI from "./GUI"; import Renderer from "./Renderer"; +import StatsOverlay from "./StatsOverlay"; import AntsComputeScene from "./scenes/AntsComputeScene"; import AntsDiscretizeScene from "./scenes/AntsDiscretizeScene"; import DrawScene from "./scenes/DrawScene"; @@ -25,6 +26,7 @@ export default new (class App { ); private scenes!: SceneCollection; private gui: GUI = new GUI(); + private statsOverlay: StatsOverlay = new StatsOverlay(); private renderLoop = (time: number): void => this.render(time); private lastTime: number = 0; private queuedSimSteps: number = 0; @@ -82,6 +84,7 @@ export default new (class App { } this.renderer.renderSimulation(this.scenes); + this.statsOverlay.recordTick(); } private render(time: number) { @@ -105,6 +108,11 @@ export default new (class App { this.renderer.renderToScreen(this.scenes); + this.statsOverlay.update( + this.scenes.screen.pointerPosition, + this.renderer.colonyStatsData, + ); + this.lastTime = time; } })(); diff --git a/src/Renderer.ts b/src/Renderer.ts index 8e09a51..54dbce2 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -1,7 +1,7 @@ import type { WebGLRenderTarget } from "three"; import * as THREE from "three"; import type { SceneCollection } from "./App"; -import ColonyStats from "./ColonyStats"; +import ColonyStats, { type ColonyStatsData } from "./ColonyStats"; import Config from "./Config"; import { MAT_AIR, @@ -406,6 +406,10 @@ export default class Renderer { } } + public get colonyStatsData(): ColonyStatsData { + return this.colonyStats.data; + } + static convertNumberToFloatString(n: number): string { return n.toFixed(8); } diff --git a/src/StatsOverlay.ts b/src/StatsOverlay.ts new file mode 100644 index 0000000..0de2898 --- /dev/null +++ b/src/StatsOverlay.ts @@ -0,0 +1,69 @@ +import type { ColonyStatsData } from "./ColonyStats"; +import Config from "./Config"; + +export default class StatsOverlay { + private el: HTMLElement; + private cursorEl: HTMLElement; + private tpsEl: HTMLElement; + private antsEl: HTMLElement; + private carryingEl: HTMLElement; + + private tickTimestamps: number[] = []; + + constructor() { + this.el = document.createElement("div"); + this.el.id = "stats"; + + // insert after #info + // biome-ignore lint/style/noNonNullAssertion: #info exists in index.html + const info = document.getElementById("info")!; + info.after(this.el); + + this.cursorEl = this.addLine("cursor"); + this.tpsEl = this.addLine("tps"); + this.antsEl = this.addLine("ants"); + this.carryingEl = this.addLine("carrying"); + } + + private addLine(label: string): HTMLElement { + const line = document.createElement("div"); + line.innerHTML = `${label}`; + this.el.appendChild(line); + // biome-ignore lint/style/noNonNullAssertion: .value span created by innerHTML above + return line.querySelector(".value")!; + } + + public recordTick(): void { + const now = performance.now(); + this.tickTimestamps.push(now); + // keep last 60 timestamps for averaging + if (this.tickTimestamps.length > 60) { + this.tickTimestamps.shift(); + } + } + + public update( + pointerPosition: { x: number; y: number }, + colonyStats: ColonyStatsData, + ): void { + const gridX = Math.floor(pointerPosition.x * Config.worldSize); + const gridY = Math.floor(pointerPosition.y * Config.worldSize); + this.cursorEl.textContent = `${gridX}, ${gridY}`; + + // tps from tick timestamps + if (this.tickTimestamps.length >= 2) { + const span = + this.tickTimestamps[this.tickTimestamps.length - 1] - + this.tickTimestamps[0]; + const tps = + span > 0 ? ((this.tickTimestamps.length - 1) / span) * 1000 : 0; + this.tpsEl.textContent = `${Math.round(tps)}`; + } + + this.antsEl.textContent = `${colonyStats.totalAnts}`; + const carrying = Math.round( + colonyStats.foragerRatio * colonyStats.totalAnts, + ); + this.carryingEl.textContent = `${carrying}`; + } +}