optimisations
This commit is contained in:
281
src/RefactoredShader.ts
Normal file
281
src/RefactoredShader.ts
Normal file
@ -0,0 +1,281 @@
|
||||
import { WorkerMessage } from './shader/types';
|
||||
import { InputManager } from './shader/core/InputManager';
|
||||
import { WorkerPool } from './shader/core/WorkerPool';
|
||||
import { RenderController } from './shader/core/RenderController';
|
||||
|
||||
/**
|
||||
* Refactored shader renderer with separated concerns
|
||||
* Demonstrates the benefits of extracting responsibilities from the God class
|
||||
*/
|
||||
export class RefactoredShader {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
private code: string;
|
||||
|
||||
// Extracted components with single responsibilities
|
||||
private inputManager: InputManager;
|
||||
private workerPool: WorkerPool;
|
||||
private renderController: RenderController;
|
||||
|
||||
// Render state
|
||||
private compiled: boolean = false;
|
||||
private renderMode: string = 'classic';
|
||||
private valueMode: string = 'integer';
|
||||
private hueShift: number = 0;
|
||||
|
||||
// Reusable message object for performance
|
||||
private reusableMessage: WorkerMessage = {
|
||||
id: '',
|
||||
type: 'render',
|
||||
width: 0,
|
||||
height: 0,
|
||||
fullWidth: 0,
|
||||
fullHeight: 0,
|
||||
time: 0,
|
||||
renderMode: 'classic',
|
||||
valueMode: 'integer',
|
||||
hueShift: 0,
|
||||
mouseX: 0,
|
||||
mouseY: 0,
|
||||
mousePressed: false,
|
||||
mouseVX: 0,
|
||||
mouseVY: 0,
|
||||
mouseClickTime: 0,
|
||||
touchCount: 0,
|
||||
touch0X: 0,
|
||||
touch0Y: 0,
|
||||
touch1X: 0,
|
||||
touch1Y: 0,
|
||||
pinchScale: 1,
|
||||
pinchRotation: 0,
|
||||
accelX: 0,
|
||||
accelY: 0,
|
||||
accelZ: 0,
|
||||
gyroX: 0,
|
||||
gyroY: 0,
|
||||
gyroZ: 0,
|
||||
audioLevel: 0,
|
||||
bassLevel: 0,
|
||||
midLevel: 0,
|
||||
trebleLevel: 0,
|
||||
bpm: 120,
|
||||
startY: 0,
|
||||
};
|
||||
|
||||
constructor(canvas: HTMLCanvasElement, code: string = 'x^y') {
|
||||
this.canvas = canvas;
|
||||
this.ctx = canvas.getContext('2d')!;
|
||||
this.code = code;
|
||||
|
||||
// Initialize separated components
|
||||
this.inputManager = new InputManager();
|
||||
this.workerPool = new WorkerPool();
|
||||
this.renderController = new RenderController();
|
||||
|
||||
this.setupEventHandlers();
|
||||
this.compile();
|
||||
}
|
||||
|
||||
private setupEventHandlers(): void {
|
||||
// Set up render controller callback
|
||||
this.renderController.setRenderFrameHandler((time, renderId) => {
|
||||
this.render(renderId, time);
|
||||
});
|
||||
|
||||
// Set up worker pool callbacks
|
||||
this.workerPool.setRenderCompleteHandler((imageData) => {
|
||||
this.ctx.putImageData(imageData, 0, 0);
|
||||
this.renderController.setRenderingState(false);
|
||||
});
|
||||
|
||||
this.workerPool.setErrorHandler((error) => {
|
||||
console.error('Rendering error:', error);
|
||||
this.renderController.setRenderingState(false);
|
||||
});
|
||||
}
|
||||
|
||||
async compile(): Promise<void> {
|
||||
try {
|
||||
await this.workerPool.compile(this.code);
|
||||
this.compiled = true;
|
||||
console.log('Shader compiled successfully');
|
||||
} catch (error) {
|
||||
console.error('Compilation failed:', error);
|
||||
this.compiled = false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private render(id: string, currentTime: number): void {
|
||||
if (!this.compiled || this.renderController.isCurrentlyRendering()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.renderController.setRenderingState(true);
|
||||
this.renderController.addPendingRender(id);
|
||||
|
||||
// Update reusable message to avoid allocations
|
||||
this.reusableMessage.id = id;
|
||||
this.reusableMessage.width = this.canvas.width;
|
||||
this.reusableMessage.height = this.canvas.height;
|
||||
this.reusableMessage.fullWidth = this.canvas.width;
|
||||
this.reusableMessage.fullHeight = this.canvas.height;
|
||||
this.reusableMessage.time = currentTime;
|
||||
this.reusableMessage.renderMode = this.renderMode;
|
||||
this.reusableMessage.valueMode = this.valueMode;
|
||||
this.reusableMessage.hueShift = this.hueShift;
|
||||
|
||||
// Populate input data from InputManager
|
||||
this.inputManager.populateWorkerMessage(this.reusableMessage);
|
||||
|
||||
// Choose rendering strategy based on worker count
|
||||
if (this.workerPool.getWorkerCount() > 1) {
|
||||
this.workerPool.renderMultiWorker(
|
||||
this.reusableMessage,
|
||||
this.canvas.width,
|
||||
this.canvas.height
|
||||
);
|
||||
} else {
|
||||
this.workerPool.renderSingleWorker(this.reusableMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Public API methods
|
||||
start(): void {
|
||||
if (!this.compiled) {
|
||||
console.warn('Cannot start rendering: shader not compiled');
|
||||
return;
|
||||
}
|
||||
this.renderController.start();
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
this.renderController.stop();
|
||||
}
|
||||
|
||||
setCode(code: string): Promise<void> {
|
||||
this.code = code;
|
||||
this.compiled = false;
|
||||
return this.compile();
|
||||
}
|
||||
|
||||
setRenderMode(mode: string): void {
|
||||
this.renderMode = mode;
|
||||
}
|
||||
|
||||
setValueMode(mode: string): void {
|
||||
this.valueMode = mode;
|
||||
}
|
||||
|
||||
setHueShift(shift: number): void {
|
||||
this.hueShift = shift;
|
||||
}
|
||||
|
||||
setTargetFPS(fps: number): void {
|
||||
this.renderController.setTargetFPS(fps);
|
||||
}
|
||||
|
||||
setTimeSpeed(speed: number): void {
|
||||
this.renderController.setTimeSpeed(speed);
|
||||
}
|
||||
|
||||
// Input methods - delegated to InputManager
|
||||
setMousePosition(x: number, y: number): void {
|
||||
this.inputManager.setMousePosition(x, y);
|
||||
}
|
||||
|
||||
setMousePressed(pressed: boolean): void {
|
||||
this.inputManager.setMousePressed(pressed);
|
||||
}
|
||||
|
||||
setMouseVelocity(vx: number, vy: number): void {
|
||||
this.inputManager.setMouseVelocity(vx, vy);
|
||||
}
|
||||
|
||||
setTouchData(
|
||||
count: number,
|
||||
x0: number = 0,
|
||||
y0: number = 0,
|
||||
x1: number = 0,
|
||||
y1: number = 0,
|
||||
scale: number = 1,
|
||||
rotation: number = 0
|
||||
): void {
|
||||
this.inputManager.setTouchData(count, x0, y0, x1, y1, scale, rotation);
|
||||
}
|
||||
|
||||
setAccelerometer(x: number, y: number, z: number): void {
|
||||
this.inputManager.setAccelerometer(x, y, z);
|
||||
}
|
||||
|
||||
setGyroscope(x: number, y: number, z: number): void {
|
||||
this.inputManager.setGyroscope(x, y, z);
|
||||
}
|
||||
|
||||
setAudioLevels(
|
||||
level: number,
|
||||
bass: number,
|
||||
mid: number,
|
||||
treble: number,
|
||||
bpm: number
|
||||
): void {
|
||||
this.inputManager.setAudioLevels(level, bass, mid, treble, bpm);
|
||||
}
|
||||
|
||||
// Getters
|
||||
isCompiled(): boolean {
|
||||
return this.compiled;
|
||||
}
|
||||
|
||||
isAnimating(): boolean {
|
||||
return this.renderController.isAnimating();
|
||||
}
|
||||
|
||||
getCurrentTime(): number {
|
||||
return this.renderController.getCurrentTime();
|
||||
}
|
||||
|
||||
getFrameRate(): number {
|
||||
return this.renderController.getFrameRate();
|
||||
}
|
||||
|
||||
getWorkerCount(): number {
|
||||
return this.workerPool.getWorkerCount();
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
destroy(): void {
|
||||
this.renderController.stop();
|
||||
this.workerPool.destroy();
|
||||
}
|
||||
|
||||
// Helper methods for generating example shader code
|
||||
static getExamples(): string[] {
|
||||
return [
|
||||
'x^y',
|
||||
'x|y',
|
||||
'(x+y+t*10)%256',
|
||||
'((x>>4)^(y>>4))<<4',
|
||||
'(x^y^(x*y))%256',
|
||||
'd * t / 2.0',
|
||||
'((x&y)|(x^y))%256',
|
||||
'(x+y)&255',
|
||||
'x%y',
|
||||
'((x*t)^y)%256',
|
||||
'(x&(y|t*8))%256',
|
||||
'a+d*t',
|
||||
'n*t*400',
|
||||
'((x>>2)|(y<<2))%88',
|
||||
'(x*y*t)%256',
|
||||
'(x+y*t)%256',
|
||||
'(x^y^(t*16))%256',
|
||||
'((x*t)&(y*t))%256',
|
||||
'(x+(y<<(t%4)))%256',
|
||||
'((x*t%128)^y)%256',
|
||||
'(x^(y*t*2))%256',
|
||||
'((x+t)*(y+t))%256',
|
||||
'(x&y&(t*8))%256',
|
||||
'((x|t)^(y|t))%256',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user