UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

408 lines 14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RenderOptimizer = void 0; const events_1 = require("events"); const crypto_1 = require("crypto"); class RenderOptimizer extends events_1.EventEmitter { constructor(config = {}) { super(); this.componentStates = new Map(); this.virtualNodes = new Map(); this.pendingOperations = new Map(); this.lastFrameTime = 0; this.frameCount = 0; this.isRunning = false; this.config = { minRenderInterval: 16, maxRenderInterval: 1000, enableIncrementalRendering: true, enableRenderBatching: true, batchWindow: 16, enableVirtualDom: true, targetFrameRate: 60, enableProfiling: true, maxPendingRenders: 100, ...config, }; this.metrics = { totalRenders: 0, averageRenderTime: 0, currentFps: 0, skippedRenders: 0, batchedRenders: 0, incrementalRenders: 0, virtualDomMemory: 0, activeComponents: 0, dirtyComponents: 0, }; this.setupRenderLoop(); } start() { if (this.isRunning) { return; } this.isRunning = true; this.lastFrameTime = performance.now(); this.emit('optimizerStarted', { timestamp: Date.now(), config: this.config, }); } stop() { if (!this.isRunning) { return; } this.isRunning = false; if (this.batchTimer) { clearTimeout(this.batchTimer); this.batchTimer = undefined; } if (this.renderTimer) { clearInterval(this.renderTimer); this.renderTimer = undefined; } this.emit('optimizerStopped', { timestamp: Date.now(), metrics: this.getMetrics(), }); } registerComponent(componentId, componentType, options = {}) { const state = { componentId, componentType, lastRenderTime: 0, contentHash: '', renderCount: 0, averageRenderTime: 0, isDirty: true, priority: options.priority || 'medium', isVisible: options.isVisible !== false, dimensions: options.dimensions || { width: 0, height: 0, x: 0, y: 0 }, }; this.componentStates.set(componentId, state); this.updateActiveComponentsCount(); this.emit('componentRegistered', { componentId, componentType, state, timestamp: Date.now(), }); } unregisterComponent(componentId) { this.componentStates.delete(componentId); this.virtualNodes.delete(componentId); this.pendingOperations.delete(componentId); this.updateActiveComponentsCount(); this.emit('componentUnregistered', { componentId, timestamp: Date.now(), }); } markDirty(componentId, force = false) { const state = this.componentStates.get(componentId); if (!state) { return; } state.isDirty = true; this.updateDirtyComponentsCount(); this.emit('componentMarkedDirty', { componentId, force, timestamp: Date.now(), }); if (force || this.shouldRenderImmediately(state)) { this.scheduleRender(componentId, 'high'); } } scheduleRender(componentId, priority = 'medium', renderFn, force = false) { if (!this.isRunning) { return; } const state = this.componentStates.get(componentId); if (!state && !force) { return; } if (this.pendingOperations.size >= this.config.maxPendingRenders) { this.emit('renderQueueFull', { componentId, queueSize: this.pendingOperations.size, timestamp: Date.now(), }); return; } const operation = { componentId, type: 'full', priority, renderFn: renderFn || (() => this.performDefaultRender(componentId)), queuedAt: Date.now(), force, }; const existing = this.pendingOperations.get(componentId); if (!existing || this.getPriorityValue(priority) > this.getPriorityValue(existing.priority)) { this.pendingOperations.set(componentId, operation); } if (this.config.enableRenderBatching) { this.scheduleBatchRender(); } else { this.processRenderOperation(operation); } } updateContent(componentId, content) { const state = this.componentStates.get(componentId); if (!state) { return false; } const newHash = this.generateContentHash(content); if (newHash !== state.contentHash) { state.contentHash = newHash; state.isDirty = true; this.updateDirtyComponentsCount(); if (this.config.enableVirtualDom) { this.updateVirtualNode(componentId, content); } this.emit('contentUpdated', { componentId, contentHash: newHash, timestamp: Date.now(), }); return true; } return false; } updateVisibility(componentId, isVisible) { const state = this.componentStates.get(componentId); if (!state) { return; } if (state.isVisible !== isVisible) { state.isVisible = isVisible; if (isVisible && state.isDirty) { this.scheduleRender(componentId, 'medium'); } this.emit('visibilityChanged', { componentId, isVisible, timestamp: Date.now(), }); } } updateDimensions(componentId, dimensions) { const state = this.componentStates.get(componentId); if (!state) { return; } if (dimensions) { state.dimensions = dimensions; } state.isDirty = true; this.updateDirtyComponentsCount(); this.emit('dimensionsChanged', { componentId, dimensions, timestamp: Date.now(), }); } getMetrics() { this.updateMetrics(); return { ...this.metrics }; } getComponentStates() { return new Map(this.componentStates); } async flushRenders() { const dirtyComponents = Array.from(this.componentStates.entries()) .filter(([_, state]) => state.isDirty) .map(([componentId]) => componentId); const renderPromises = dirtyComponents.map(componentId => this.performDefaultRender(componentId)); await Promise.all(renderPromises); this.emit('rendersFlused', { componentCount: dirtyComponents.length, timestamp: Date.now(), }); } clearPendingRenders() { this.pendingOperations.clear(); this.emit('pendingRendersCleared', { timestamp: Date.now(), }); } updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; this.emit('configUpdated', { config: this.config, timestamp: Date.now(), }); } generateContentHash(content) { const serialized = JSON.stringify(content); return (0, crypto_1.createHash)('md5').update(serialized).digest('hex'); } shouldRenderImmediately(state) { const now = Date.now(); const timeSinceLastRender = now - state.lastRenderTime; return (state.priority === 'high' || timeSinceLastRender > this.config.maxRenderInterval || !state.isVisible); } getPriorityValue(priority) { switch (priority) { case 'low': return 1; case 'medium': return 2; case 'high': return 3; default: return 2; } } scheduleBatchRender() { if (this.batchTimer) { return; } this.batchTimer = setTimeout(() => { this.processBatchRenders(); this.batchTimer = undefined; }, this.config.batchWindow); } async processBatchRenders() { if (this.pendingOperations.size === 0) { return; } const operations = Array.from(this.pendingOperations.values()) .sort((a, b) => this.getPriorityValue(b.priority) - this.getPriorityValue(a.priority)); this.pendingOperations.clear(); const batchStartTime = performance.now(); this.emit('batchRenderStarted', { operationCount: operations.length, timestamp: Date.now(), }); const highPriorityOps = operations.filter(op => op.priority === 'high'); const otherOps = operations.filter(op => op.priority !== 'high'); for (const operation of highPriorityOps) { await this.processRenderOperation(operation); } if (otherOps.length > 0) { await this.processBatchOperations(otherOps); } const batchTime = performance.now() - batchStartTime; this.metrics.batchedRenders += operations.length; this.emit('batchRenderCompleted', { operationCount: operations.length, batchTime, timestamp: Date.now(), }); } async processBatchOperations(operations) { const frameTimeout = 1000 / this.config.targetFrameRate; let frameStartTime = performance.now(); for (const operation of operations) { const now = performance.now(); if (now - frameStartTime > frameTimeout * 0.8) { await new Promise(resolve => setTimeout(resolve, 0)); frameStartTime = performance.now(); } await this.processRenderOperation(operation); } } async processRenderOperation(operation) { const state = this.componentStates.get(operation.componentId); if (!state && !operation.force) { return; } const now = Date.now(); const renderStartTime = performance.now(); try { if (state && !operation.force) { const timeSinceLastRender = now - state.lastRenderTime; if (timeSinceLastRender < this.config.minRenderInterval) { this.metrics.skippedRenders++; return; } if (!state.isVisible && state.priority !== 'high') { this.metrics.skippedRenders++; return; } } await operation.renderFn(); const renderTime = performance.now() - renderStartTime; if (state) { state.lastRenderTime = now; state.renderCount++; state.isDirty = false; state.averageRenderTime = ((state.averageRenderTime * (state.renderCount - 1)) + renderTime) / state.renderCount; } this.metrics.totalRenders++; this.updateAverageRenderTime(renderTime); this.updateDirtyComponentsCount(); this.emit('renderCompleted', { componentId: operation.componentId, renderTime, operationType: operation.type, timestamp: now, }); } catch (error) { this.emit('renderError', { componentId: operation.componentId, error, timestamp: now, }); } } async performDefaultRender(componentId) { await new Promise(resolve => setTimeout(resolve, 1 + Math.random() * 5)); this.emit('defaultRenderPerformed', { componentId, timestamp: Date.now(), }); } updateVirtualNode(componentId, content) { const node = { type: 'component', props: content.props || {}, children: content.children || [], key: componentId, hash: this.generateContentHash(content), }; this.virtualNodes.set(componentId, node); this.updateVirtualDomMemory(); } setupRenderLoop() { this.renderTimer = setInterval(() => { if (this.isRunning) { this.updateFpsMetrics(); } }, 1000); } updateFpsMetrics() { const now = performance.now(); const deltaTime = now - this.lastFrameTime; if (deltaTime >= 1000) { this.metrics.currentFps = this.frameCount; this.frameCount = 0; this.lastFrameTime = now; } else { this.frameCount++; } } updateAverageRenderTime(renderTime) { this.metrics.averageRenderTime = ((this.metrics.averageRenderTime * (this.metrics.totalRenders - 1)) + renderTime) / this.metrics.totalRenders; } updateActiveComponentsCount() { this.metrics.activeComponents = this.componentStates.size; } updateDirtyComponentsCount() { this.metrics.dirtyComponents = Array.from(this.componentStates.values()) .filter(state => state.isDirty).length; } updateVirtualDomMemory() { this.metrics.virtualDomMemory = this.virtualNodes.size * 1024; } updateMetrics() { this.updateActiveComponentsCount(); this.updateDirtyComponentsCount(); this.updateVirtualDomMemory(); } } exports.RenderOptimizer = RenderOptimizer; //# sourceMappingURL=render-optimizer.js.map