Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
| import { TraceData } from './types'; | |
| export type PlaybackMode = 'live' | 'paused' | 'slow' | 'fast' | 'catchup'; | |
| export interface BufferState { | |
| mode: PlaybackMode; | |
| speed: number; // Multiplier: 0.25, 0.5, 1, 2, 4 | |
| bufferSize: number; | |
| currentSize: number; | |
| writePosition: number; | |
| readPosition: number; | |
| droppedCount: number; | |
| isPaused: boolean; | |
| } | |
| export interface TraceBufferConfig { | |
| maxSize?: number; | |
| updateInterval?: number; | |
| batchSize?: number; | |
| } | |
| type BufferListener = (traces: TraceData[], state: BufferState) => void; | |
| export class TraceBuffer { | |
| private buffer: (TraceData | null)[]; | |
| private maxSize: number; | |
| private writePos: number = 0; | |
| private readPos: number = 0; | |
| private currentSize: number = 0; | |
| private droppedCount: number = 0; | |
| private mode: PlaybackMode = 'live'; | |
| private speed: number = 1; | |
| private isPaused: boolean = false; | |
| private listeners: Set<BufferListener> = new Set(); | |
| private updateTimer: NodeJS.Timeout | null = null; | |
| private updateInterval: number; | |
| private batchSize: number; | |
| private pendingTraces: TraceData[] = []; | |
| private lastUpdateTime: number = 0; | |
| private targetFPS: number = 60; | |
| constructor(config: TraceBufferConfig = {}) { | |
| this.maxSize = config.maxSize || 10000; | |
| this.updateInterval = config.updateInterval || 16; // ~60fps | |
| this.batchSize = config.batchSize || 10; | |
| this.buffer = new Array(this.maxSize).fill(null); | |
| this.startUpdateLoop(); | |
| } | |
| private startUpdateLoop(): void { | |
| const update = () => { | |
| const now = Date.now(); | |
| const deltaTime = now - this.lastUpdateTime; | |
| if (!this.isPaused && this.pendingTraces.length > 0) { | |
| const effectiveInterval = this.updateInterval / this.speed; | |
| if (deltaTime >= effectiveInterval) { | |
| this.processPendingTraces(); | |
| this.lastUpdateTime = now; | |
| } | |
| } | |
| this.updateTimer = setTimeout(update, Math.max(1, this.updateInterval)); | |
| }; | |
| update(); | |
| } | |
| private processPendingTraces(): void { | |
| if (this.pendingTraces.length === 0) return; | |
| let tracesToProcess: TraceData[]; | |
| switch (this.mode) { | |
| case 'live': | |
| // Process in real-time batches | |
| tracesToProcess = this.pendingTraces.splice(0, this.batchSize); | |
| break; | |
| case 'slow': | |
| // Process one at a time for slow motion | |
| tracesToProcess = this.pendingTraces.splice(0, 1); | |
| break; | |
| case 'fast': | |
| // Process larger batches for fast forward | |
| tracesToProcess = this.pendingTraces.splice(0, this.batchSize * 4); | |
| break; | |
| case 'catchup': | |
| // Process all pending to catch up | |
| tracesToProcess = this.pendingTraces.splice(0, this.pendingTraces.length); | |
| this.mode = 'live'; // Return to live after catching up | |
| break; | |
| default: | |
| tracesToProcess = []; | |
| } | |
| if (tracesToProcess.length > 0) { | |
| this.notifyListeners(tracesToProcess); | |
| } | |
| } | |
| public push(trace: TraceData): void { | |
| // Add to ring buffer | |
| if (this.currentSize >= this.maxSize) { | |
| // Buffer is full, overwrite oldest | |
| this.droppedCount++; | |
| } else { | |
| this.currentSize++; | |
| } | |
| this.buffer[this.writePos] = trace; | |
| this.writePos = (this.writePos + 1) % this.maxSize; | |
| // Add to pending traces for processing | |
| if (!this.isPaused) { | |
| this.pendingTraces.push(trace); | |
| } | |
| } | |
| public setMode(mode: PlaybackMode): void { | |
| this.mode = mode; | |
| switch (mode) { | |
| case 'paused': | |
| this.isPaused = true; | |
| break; | |
| case 'catchup': | |
| this.isPaused = false; | |
| // Will process all pending traces | |
| break; | |
| default: | |
| this.isPaused = false; | |
| } | |
| } | |
| public setSpeed(speed: number): void { | |
| this.speed = Math.max(0.25, Math.min(4, speed)); | |
| } | |
| public pause(): void { | |
| this.isPaused = true; | |
| this.mode = 'paused'; | |
| } | |
| public resume(): void { | |
| this.isPaused = false; | |
| this.mode = 'live'; | |
| } | |
| public clear(): void { | |
| this.buffer.fill(null); | |
| this.writePos = 0; | |
| this.readPos = 0; | |
| this.currentSize = 0; | |
| this.droppedCount = 0; | |
| this.pendingTraces = []; | |
| this.notifyListeners([]); | |
| } | |
| public getState(): BufferState { | |
| return { | |
| mode: this.mode, | |
| speed: this.speed, | |
| bufferSize: this.maxSize, | |
| currentSize: this.currentSize, | |
| writePosition: this.writePos, | |
| readPosition: this.readPos, | |
| droppedCount: this.droppedCount, | |
| isPaused: this.isPaused, | |
| }; | |
| } | |
| public getRecentTraces(count: number = 100): TraceData[] { | |
| const traces: TraceData[] = []; | |
| let pos = (this.writePos - 1 + this.maxSize) % this.maxSize; | |
| let collected = 0; | |
| while (collected < count && collected < this.currentSize) { | |
| const trace = this.buffer[pos]; | |
| if (trace) { | |
| traces.unshift(trace); | |
| collected++; | |
| } | |
| pos = (pos - 1 + this.maxSize) % this.maxSize; | |
| } | |
| return traces; | |
| } | |
| public subscribe(listener: BufferListener): () => void { | |
| this.listeners.add(listener); | |
| // Send current state immediately | |
| listener(this.getRecentTraces(), this.getState()); | |
| // Return unsubscribe function | |
| return () => { | |
| this.listeners.delete(listener); | |
| }; | |
| } | |
| private notifyListeners(newTraces: TraceData[]): void { | |
| const state = this.getState(); | |
| this.listeners.forEach(listener => { | |
| try { | |
| listener(newTraces, state); | |
| } catch (error) { | |
| console.error('Error in buffer listener:', error); | |
| } | |
| }); | |
| } | |
| public destroy(): void { | |
| if (this.updateTimer) { | |
| clearTimeout(this.updateTimer); | |
| this.updateTimer = null; | |
| } | |
| this.listeners.clear(); | |
| this.buffer = []; | |
| this.pendingTraces = []; | |
| } | |
| } | |
| // Singleton instance | |
| let bufferInstance: TraceBuffer | null = null; | |
| export function getTraceBuffer(config?: TraceBufferConfig): TraceBuffer { | |
| if (!bufferInstance) { | |
| bufferInstance = new TraceBuffer(config); | |
| } | |
| return bufferInstance; | |
| } | |
| export function destroyTraceBuffer(): void { | |
| if (bufferInstance) { | |
| bufferInstance.destroy(); | |
| bufferInstance = null; | |
| } | |
| } |