Files
bitfielder/src/shader/core/RenderController.ts
2025-07-15 00:52:00 +02:00

114 lines
3.0 KiB
TypeScript

import { TIMING } from '../../utils/constants';
/**
* Manages animation timing and frame rate control
* Extracted from FakeShader for better separation of concerns
*/
export class RenderController {
private animationId: number | null = null;
private startTime: number = Date.now();
private targetFPS: number = TIMING.DEFAULT_FPS;
private frameInterval: number = TIMING.MILLISECONDS_PER_SECOND / this.targetFPS;
private lastFrameTime: number = 0;
private timeSpeed: number = 1.0;
private isRendering: boolean = false;
private pendingRenders: string[] = [];
private idCounter: number = 0;
private onRenderFrame?: (time: number, renderId: string) => void;
setRenderFrameHandler(handler: (time: number, renderId: string) => void): void {
this.onRenderFrame = handler;
}
start(): void {
if (this.animationId !== null) return;
const animate = (timestamp: number) => {
if (timestamp - this.lastFrameTime >= this.frameInterval) {
const currentTime = (Date.now() - this.startTime) / TIMING.MILLISECONDS_PER_SECOND * this.timeSpeed;
const renderId = this.generateId();
this.onRenderFrame?.(currentTime, renderId);
this.lastFrameTime = timestamp;
}
this.animationId = requestAnimationFrame(animate);
};
this.animationId = requestAnimationFrame(animate);
}
stop(): void {
if (this.animationId !== null) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
}
setTargetFPS(fps: number): void {
this.targetFPS = Math.max(TIMING.MIN_FPS, Math.min(TIMING.MAX_FPS, fps));
this.frameInterval = TIMING.MILLISECONDS_PER_SECOND / this.targetFPS;
}
setTimeSpeed(speed: number): void {
this.timeSpeed = speed;
}
getTimeSpeed(): number {
return this.timeSpeed;
}
getCurrentTime(): number {
return (Date.now() - this.startTime) / TIMING.MILLISECONDS_PER_SECOND * this.timeSpeed;
}
isAnimating(): boolean {
return this.animationId !== null;
}
generateId(): string {
return `render_${this.idCounter++}_${Date.now()}`;
}
setRenderingState(isRendering: boolean): void {
this.isRendering = isRendering;
}
isCurrentlyRendering(): boolean {
return this.isRendering;
}
addPendingRender(renderId: string): void {
this.pendingRenders.push(renderId);
// Keep only the latest render to avoid backlog
if (this.pendingRenders.length > 3) {
const latestId = this.pendingRenders[this.pendingRenders.length - 1];
this.pendingRenders = [latestId];
}
}
removePendingRender(renderId: string): void {
const index = this.pendingRenders.indexOf(renderId);
if (index !== -1) {
this.pendingRenders.splice(index, 1);
}
}
getPendingRenders(): string[] {
return [...this.pendingRenders];
}
clearPendingRenders(): void {
this.pendingRenders = [];
}
getFrameRate(): number {
return this.targetFPS;
}
getFrameInterval(): number {
return this.frameInterval;
}
}