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 { 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 { 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', ]; } }