Some checks failed
Deploy to Pages / build (pull_request) Failing after 1m56s
138 lines
3.9 KiB
TypeScript
138 lines
3.9 KiB
TypeScript
import { appConfig } from '../config';
|
|
|
|
interface TelemetrySnapshot {
|
|
frameCpuStartedAt: number;
|
|
encodeCpuMs: number;
|
|
activeAgentCount: number;
|
|
agentBudgetMax: number;
|
|
canvas: HTMLCanvasElement;
|
|
devicePixelRatio: number;
|
|
renderSpeed: number;
|
|
}
|
|
|
|
const COMMON_DISPLAY_REFRESH_RATES = [
|
|
50, 60, 72, 75, 90, 100, 120, 144, 165, 180, 240,
|
|
] as const;
|
|
const DISPLAY_REFRESH_CONFIRMATION_FRAMES = 8;
|
|
const DISPLAY_REFRESH_SNAP_TOLERANCE = 0.15;
|
|
|
|
export class FramePerformance {
|
|
public latestFps = 60;
|
|
public smoothedFps = 60;
|
|
public displayRefreshFps = 60;
|
|
public readonly refreshTargetFps = 60;
|
|
|
|
private lastTelemetryAt = 0;
|
|
private hasConfirmedDisplayRefreshFps = false;
|
|
private pendingDisplayRefreshFps = 0;
|
|
private pendingDisplayRefreshFrameCount = 0;
|
|
|
|
public markCpuStart(): number {
|
|
return appConfig.telemetry.enabled ? performance.now() : 0;
|
|
}
|
|
|
|
public measureSince(startedAt: number): number {
|
|
return appConfig.telemetry.enabled ? performance.now() - startedAt : 0;
|
|
}
|
|
|
|
public update(deltaTime: number): void {
|
|
const fps = 1 / Math.max(deltaTime, appConfig.deltaTime.minDeltaTimeSeconds);
|
|
this.latestFps = fps;
|
|
this.updateDisplayRefreshEstimate(fps);
|
|
this.smoothedFps =
|
|
this.smoothedFps * appConfig.simulation.budget.fpsSmoothingRetain +
|
|
fps * appConfig.simulation.budget.fpsSmoothingNew;
|
|
}
|
|
|
|
public renderTelemetry({
|
|
frameCpuStartedAt,
|
|
encodeCpuMs,
|
|
activeAgentCount,
|
|
agentBudgetMax,
|
|
canvas,
|
|
devicePixelRatio,
|
|
renderSpeed,
|
|
}: TelemetrySnapshot): void {
|
|
if (!appConfig.telemetry.enabled) {
|
|
return;
|
|
}
|
|
|
|
const now = performance.now();
|
|
if (now - this.lastTelemetryAt < appConfig.telemetry.intervalMs) {
|
|
return;
|
|
}
|
|
|
|
this.lastTelemetryAt = now;
|
|
console.debug('Fleeting Garden telemetry', {
|
|
fps: Math.round(this.latestFps),
|
|
smoothedFps: Math.round(this.smoothedFps),
|
|
refreshTargetFps: Math.round(this.refreshTargetFps),
|
|
displayRefreshFps: Math.round(this.displayRefreshFps),
|
|
activeAgentCount,
|
|
agentBudgetMax,
|
|
canvasWidth: canvas.width,
|
|
canvasHeight: canvas.height,
|
|
dpr: devicePixelRatio,
|
|
renderSpeed,
|
|
frameCpuMs: now - frameCpuStartedAt,
|
|
encodeCpuMs,
|
|
});
|
|
}
|
|
|
|
private updateDisplayRefreshEstimate(fps: number): void {
|
|
const displayRefreshFps = this.snapDisplayRefreshRate(fps);
|
|
if (displayRefreshFps === null) {
|
|
this.resetPendingDisplayRefreshEstimate();
|
|
return;
|
|
}
|
|
|
|
if (
|
|
this.hasConfirmedDisplayRefreshFps &&
|
|
displayRefreshFps < this.displayRefreshFps
|
|
) {
|
|
this.resetPendingDisplayRefreshEstimate();
|
|
return;
|
|
}
|
|
|
|
if (displayRefreshFps !== this.pendingDisplayRefreshFps) {
|
|
this.pendingDisplayRefreshFps = displayRefreshFps;
|
|
this.pendingDisplayRefreshFrameCount = 1;
|
|
} else {
|
|
this.pendingDisplayRefreshFrameCount += 1;
|
|
}
|
|
|
|
if (this.pendingDisplayRefreshFrameCount < DISPLAY_REFRESH_CONFIRMATION_FRAMES) {
|
|
return;
|
|
}
|
|
|
|
this.displayRefreshFps = displayRefreshFps;
|
|
this.hasConfirmedDisplayRefreshFps = true;
|
|
this.resetPendingDisplayRefreshEstimate();
|
|
}
|
|
|
|
private snapDisplayRefreshRate(fps: number): number | null {
|
|
if (!Number.isFinite(fps) || fps <= 0) {
|
|
return null;
|
|
}
|
|
|
|
let nearestRefreshRate: number = COMMON_DISPLAY_REFRESH_RATES[0];
|
|
let nearestDifference = Math.abs(fps - nearestRefreshRate);
|
|
|
|
COMMON_DISPLAY_REFRESH_RATES.forEach((refreshRate) => {
|
|
const difference = Math.abs(fps - refreshRate);
|
|
if (difference < nearestDifference) {
|
|
nearestRefreshRate = refreshRate;
|
|
nearestDifference = difference;
|
|
}
|
|
});
|
|
|
|
return nearestDifference / nearestRefreshRate <= DISPLAY_REFRESH_SNAP_TOLERANCE
|
|
? nearestRefreshRate
|
|
: null;
|
|
}
|
|
|
|
private resetPendingDisplayRefreshEstimate(): void {
|
|
this.pendingDisplayRefreshFps = 0;
|
|
this.pendingDisplayRefreshFrameCount = 0;
|
|
}
|
|
}
|