Feature: FPS counter and memory tracker for performance investigations

This commit is contained in:
Dejvino 2026-01-04 10:28:35 +00:00
parent 4cd869791f
commit 7c18a3db11
2 changed files with 206 additions and 0 deletions

View File

@ -0,0 +1,205 @@
import { SceneFeature } from './SceneFeature.js';
import sceneFeatureManager from './SceneFeatureManager.js';
export class FPSCounter extends SceneFeature {
constructor() {
super();
this.frames = 0;
this.timeAccumulator = 0;
this.fpsElement = null;
this.textElement = null;
this.canvas = null;
this.ctx = null;
this.history = [];
this.memTextElement = null;
this.memCanvas = null;
this.memCtx = null;
this.memHistory = [];
sceneFeatureManager.register(this);
}
init() {
// Create the FPS display container
this.fpsElement = document.createElement('div');
Object.assign(this.fpsElement.style, {
position: 'absolute',
top: '0',
right: '0',
zIndex: '10000',
padding: '5px',
background: 'rgba(0, 0, 0, 0.5)',
pointerEvents: 'none',
userSelect: 'none',
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end'
});
// Text Display
this.textElement = document.createElement('div');
Object.assign(this.textElement.style, {
color: '#00ff00',
fontFamily: 'monospace',
fontSize: '16px',
fontWeight: 'bold',
marginBottom: '2px'
});
this.textElement.innerText = 'FPS: --';
this.fpsElement.appendChild(this.textElement);
// Graph Canvas
this.canvas = document.createElement('canvas');
this.canvas.width = 100;
this.canvas.height = 40;
Object.assign(this.canvas.style, {
width: '100px',
height: '40px',
background: '#222',
border: '1px solid #444'
});
this.ctx = this.canvas.getContext('2d');
this.fpsElement.appendChild(this.canvas);
// Memory Text Display
this.memTextElement = document.createElement('div');
Object.assign(this.memTextElement.style, {
color: '#00ffff',
fontFamily: 'monospace',
fontSize: '16px',
fontWeight: 'bold',
marginBottom: '2px',
marginTop: '5px'
});
this.memTextElement.innerText = 'MEM: --';
this.fpsElement.appendChild(this.memTextElement);
// Memory Graph Canvas
this.memCanvas = document.createElement('canvas');
this.memCanvas.width = 100;
this.memCanvas.height = 40;
Object.assign(this.memCanvas.style, {
width: '100px',
height: '40px',
background: '#222',
border: '1px solid #444'
});
this.memCtx = this.memCanvas.getContext('2d');
this.fpsElement.appendChild(this.memCanvas);
document.body.appendChild(this.fpsElement);
this.history = new Array(this.canvas.width).fill(0);
this.memHistory = new Array(this.memCanvas.width).fill(0);
}
update(deltaTime) {
this.frames++;
this.timeAccumulator += deltaTime;
const segment = 0.25;
if (this.timeAccumulator >= segment) {
const fps = Math.round(this.frames / this.timeAccumulator);
if (this.textElement) {
this.textElement.innerText = `FPS: ${fps}`;
if (fps >= 58) {
this.textElement.style.color = '#00ff00';
} else if (fps >= 55) {
this.textElement.style.color = '#ffff00';
} else {
this.textElement.style.color = '#ff0000';
}
}
// Update Graph
this.history.push(fps);
if (this.history.length > this.canvas.width) {
this.history.shift();
}
this.drawGraph();
// Update Memory
if (performance && performance.memory) {
const mem = performance.memory.usedJSHeapSize / 1048576; // MB
if (this.memTextElement) this.memTextElement.innerText = `MEM: ${Math.round(mem)} MB`;
this.memHistory.push(mem);
if (this.memHistory.length > this.memCanvas.width) {
this.memHistory.shift();
}
this.drawMemGraph();
} else {
if (this.memTextElement) this.memTextElement.style.display = 'none';
if (this.memCanvas) this.memCanvas.style.display = 'none';
}
this.frames = 0;
this.timeAccumulator = 0;
}
}
drawGraph() {
if (!this.ctx) return;
const ctx = this.ctx;
const w = this.canvas.width;
const h = this.canvas.height;
const maxFps = 70;
ctx.clearRect(0, 0, w, h);
// Draw 60 FPS reference line
const y60 = h - (60 / maxFps) * h;
ctx.beginPath();
ctx.strokeStyle = '#555';
ctx.moveTo(0, y60);
ctx.lineTo(w, y60);
ctx.stroke();
// Draw Graph
ctx.lineWidth = 1.5;
for (let i = 0; i < this.history.length - 1; i++) {
const val = this.history[i + 1];
const x1 = i;
const y1 = h - (this.history[i] / maxFps) * h;
const x2 = i + 1;
const y2 = h - (val / maxFps) * h;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
if (val >= 58) ctx.strokeStyle = '#00ff00';
else if (val >= 55) ctx.strokeStyle = '#ffff00';
else ctx.strokeStyle = '#ff0000';
ctx.stroke();
}
}
drawMemGraph() {
if (!this.memCtx) return;
const ctx = this.memCtx;
const w = this.memCanvas.width;
const h = this.memCanvas.height;
// Auto-scale max memory
const maxMem = Math.max(...this.memHistory, 100) * 1.1;
ctx.clearRect(0, 0, w, h);
// Draw Graph
ctx.beginPath();
ctx.strokeStyle = '#00ffff';
ctx.lineWidth = 1.5;
for (let i = 0; i < this.memHistory.length; i++) {
const val = this.memHistory[i];
const x = i;
const y = h - (val / maxMem) * h;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
}
}
new FPSCounter();

View File

@ -20,6 +20,7 @@ import { ProjectionScreen } from './projection-screen.js';
import { StageLasers } from './stage-lasers.js';
import { ConfigUI } from './config-ui.js';
import { StageLightBars } from './stage-light-bars.js';
import { FPSCounter } from './fps-counter.js';
// Scene Features ^^^
// --- Scene Modeling Function ---