first commit
This commit is contained in:
237
src/FakeShader.ts
Normal file
237
src/FakeShader.ts
Normal file
@ -0,0 +1,237 @@
|
||||
interface WorkerMessage {
|
||||
id: string;
|
||||
type: 'compile' | 'render';
|
||||
code?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
time?: number;
|
||||
}
|
||||
|
||||
interface WorkerResponse {
|
||||
id: string;
|
||||
type: 'compiled' | 'rendered' | 'error';
|
||||
success: boolean;
|
||||
imageData?: ImageData;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export class FakeShader {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
private code: string;
|
||||
private worker: Worker;
|
||||
private animationId: number | null = null;
|
||||
private startTime: number = Date.now();
|
||||
private isCompiled: boolean = false;
|
||||
private isRendering: boolean = false;
|
||||
private pendingRenders: string[] = [];
|
||||
|
||||
// Frame rate limiting
|
||||
private targetFPS: number = 30;
|
||||
private frameInterval: number = 1000 / this.targetFPS;
|
||||
private lastFrameTime: number = 0;
|
||||
|
||||
constructor(canvas: HTMLCanvasElement, code: string = 'x^y') {
|
||||
this.canvas = canvas;
|
||||
this.ctx = canvas.getContext('2d')!;
|
||||
this.code = code;
|
||||
|
||||
// Initialize worker
|
||||
this.worker = new Worker(new URL('./ShaderWorker.ts', import.meta.url), { type: 'module' });
|
||||
this.worker.onmessage = (e: MessageEvent<WorkerResponse>) => this.handleWorkerMessage(e.data);
|
||||
this.worker.onerror = (error) => console.error('Worker error:', error);
|
||||
|
||||
this.compile();
|
||||
}
|
||||
|
||||
private handleWorkerMessage(response: WorkerResponse): void {
|
||||
switch (response.type) {
|
||||
case 'compiled':
|
||||
this.isCompiled = response.success;
|
||||
if (!response.success) {
|
||||
console.error('Compilation failed:', response.error);
|
||||
this.fillBlack();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'rendered':
|
||||
this.isRendering = false;
|
||||
if (response.success && response.imageData) {
|
||||
this.ctx.putImageData(response.imageData, 0, 0);
|
||||
} else {
|
||||
console.error('Render failed:', response.error);
|
||||
this.fillBlack();
|
||||
}
|
||||
|
||||
// Process pending renders
|
||||
if (this.pendingRenders.length > 0) {
|
||||
this.pendingRenders.shift(); // Remove completed render
|
||||
if (this.pendingRenders.length > 0) {
|
||||
// Skip to latest render request
|
||||
const latestId = this.pendingRenders[this.pendingRenders.length - 1];
|
||||
this.pendingRenders = [latestId];
|
||||
this.executeRender(latestId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
this.isRendering = false;
|
||||
console.error('Worker error:', response.error);
|
||||
this.fillBlack();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private fillBlack(): void {
|
||||
this.ctx.fillStyle = '#000';
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
private compile(): void {
|
||||
this.isCompiled = false;
|
||||
const id = `compile_${Date.now()}`;
|
||||
this.worker.postMessage({
|
||||
id,
|
||||
type: 'compile',
|
||||
code: this.code
|
||||
} as WorkerMessage);
|
||||
}
|
||||
|
||||
private executeRender(id: string): void {
|
||||
if (!this.isCompiled || this.isRendering) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isRendering = true;
|
||||
const currentTime = (Date.now() - this.startTime) / 1000;
|
||||
|
||||
this.worker.postMessage({
|
||||
id,
|
||||
type: 'render',
|
||||
width: this.canvas.width,
|
||||
height: this.canvas.height,
|
||||
time: currentTime
|
||||
} as WorkerMessage);
|
||||
}
|
||||
|
||||
setCode(code: string): void {
|
||||
this.code = code;
|
||||
this.compile();
|
||||
}
|
||||
|
||||
render(animate: boolean = false): void {
|
||||
const currentTime = performance.now();
|
||||
|
||||
// Frame rate limiting
|
||||
if (animate && currentTime - this.lastFrameTime < this.frameInterval) {
|
||||
if (animate) {
|
||||
this.animationId = requestAnimationFrame(() => this.render(true));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastFrameTime = currentTime;
|
||||
|
||||
if (!this.isCompiled) {
|
||||
this.fillBlack();
|
||||
if (animate) {
|
||||
this.animationId = requestAnimationFrame(() => this.render(true));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const renderId = `render_${Date.now()}_${Math.random()}`;
|
||||
|
||||
// Add to pending renders queue
|
||||
this.pendingRenders.push(renderId);
|
||||
|
||||
// If not currently rendering, start immediately
|
||||
if (!this.isRendering) {
|
||||
this.executeRender(renderId);
|
||||
}
|
||||
|
||||
// Continue animation
|
||||
if (animate) {
|
||||
this.animationId = requestAnimationFrame(() => this.render(true));
|
||||
}
|
||||
}
|
||||
|
||||
startAnimation(): void {
|
||||
this.stopAnimation();
|
||||
this.startTime = Date.now();
|
||||
this.lastFrameTime = 0; // Reset frame timing
|
||||
this.render(true);
|
||||
}
|
||||
|
||||
stopAnimation(): void {
|
||||
if (this.animationId !== null) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
this.animationId = null;
|
||||
}
|
||||
|
||||
// Clear pending renders
|
||||
this.pendingRenders = [];
|
||||
}
|
||||
|
||||
setTargetFPS(fps: number): void {
|
||||
this.targetFPS = Math.max(1, Math.min(120, fps)); // Clamp between 1-120 FPS
|
||||
this.frameInterval = 1000 / this.targetFPS;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.stopAnimation();
|
||||
this.worker.terminate();
|
||||
}
|
||||
|
||||
static generateRandomCode(): string {
|
||||
const presets = [
|
||||
'x^y',
|
||||
'x&y',
|
||||
'x|y',
|
||||
'(x*y)%256',
|
||||
'(x+y+t*10)%256',
|
||||
'((x>>4)^(y>>4))<<4',
|
||||
'(x^y^(x*y))%256',
|
||||
'((x&y)|(x^y))%256',
|
||||
'(x+y)&255',
|
||||
'x%y',
|
||||
'(x^(y<<2))%256',
|
||||
'((x*t)^y)%256',
|
||||
'(x&(y|t*8))%256',
|
||||
'((x>>2)|(y<<2))%256',
|
||||
'(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'
|
||||
];
|
||||
|
||||
const vars = ['x', 'y', 't', 'i'];
|
||||
const ops = ['^', '&', '|', '+', '-', '*', '%'];
|
||||
const shifts = ['<<', '>>'];
|
||||
const numbers = ['2', '4', '8', '16', '32', '64', '128', '256'];
|
||||
|
||||
const randomChoice = <T>(arr: T[]): T => arr[Math.floor(Math.random() * arr.length)];
|
||||
|
||||
const dynamicExpressions = [
|
||||
() => `${randomChoice(vars)}${randomChoice(ops)}${randomChoice(vars)}`,
|
||||
() => `(${randomChoice(vars)}${randomChoice(ops)}${randomChoice(vars)})%${randomChoice(numbers)}`,
|
||||
() => `${randomChoice(vars)}${randomChoice(shifts)}${Math.floor(Math.random() * 8)}`,
|
||||
() => `(${randomChoice(vars)}*${randomChoice(vars)})%${randomChoice(numbers)}`,
|
||||
() => `${randomChoice(vars)}^${randomChoice(vars)}^${randomChoice(vars)}`,
|
||||
];
|
||||
|
||||
// 70% chance to pick from presets, 30% chance to generate dynamic
|
||||
if (Math.random() < 0.7) {
|
||||
return randomChoice(presets);
|
||||
} else {
|
||||
return randomChoice(dynamicExpressions)();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user