kinetic-slider
Version:
A WebGL-powered kinetic slider component using PIXI.js
1,059 lines (1,056 loc) • 37.6 kB
JavaScript
import { ShaderResourceManager } from './ShaderResourceManager.js';
/**
* Centralized Resource Manager for WebGL and Browser Resources
*
* Provides efficient batch tracking and lifecycle management for:
* - PIXI.js textures, filters, and display objects
* - GSAP animations
* - DOM event listeners
* - Timers and intervals
*/
class ResourceManager {
// Resource collections
textures = new Map();
filters = new Set();
displayObjects = new Set();
pixiApps = new Set();
animations = new Set();
// Event listener tracking with improved nesting
listeners = new Map();
// Timer tracking
timeouts = new Set();
intervals = new Set();
// Manager state
disposed = false;
unmounting = false;
componentId;
options;
// Performance metrics (optional)
metrics = null;
autoCleanupTimer = null;
// Shader resource manager
shaderManager = null;
/**
* Creates a new ResourceManager instance
*
* @param componentId - Unique identifier for this component instance
* @param options - Configuration options
*/
constructor(componentId, options = {}) {
this.componentId = componentId;
this.options = {
logLevel: options.logLevel || 'warn',
enableMetrics: options.enableMetrics || false,
autoCleanupInterval: options.autoCleanupInterval || null,
enableShaderPooling: options.enableShaderPooling !== false // Enable by default
};
// Initialize metrics if enabled
if (this.options.enableMetrics) {
this.metrics = {
operations: {},
batchStats: {
textures: this.createEmptyBatchStats(),
filters: this.createEmptyBatchStats(),
displayObjects: this.createEmptyBatchStats(),
animations: this.createEmptyBatchStats()
}
};
}
// Initialize shader manager if shader pooling is enabled
if (this.options.enableShaderPooling) {
this.shaderManager = ShaderResourceManager.getInstance({
debug: this.options.logLevel === 'debug',
enableMetrics: this.options.enableMetrics
});
this.log('info', 'Shader pooling enabled');
}
this.log('info', `ResourceManager initialized`);
// Set up auto cleanup if enabled
this.setupAutoCleanup();
}
/**
* Creates an empty batch statistics object
*/
createEmptyBatchStats() {
return {
totalBatches: 0,
totalItems: 0,
averageBatchSize: 0,
largestBatch: 0
};
}
/**
* Set up automatic cleanup interval
*/
setupAutoCleanup() {
if (this.options.autoCleanupInterval) {
this.autoCleanupTimer = setInterval(() => {
this.performAutoCleanup();
}, this.options.autoCleanupInterval);
}
}
/**
* Perform automatic cleanup of unused resources
*/
performAutoCleanup() {
if (this.disposed || this.unmounting)
return;
this.log('debug', 'Performing automatic resource cleanup...');
// Clean up 0-reference count textures
let texturesReleased = 0;
this.textures.forEach((entry, url) => {
if (entry.refCount <= 0) {
this.releaseTexture(url);
texturesReleased++;
}
});
// Clean up unused animations
let animationsReleased = 0;
this.animations.forEach(animation => {
if (animation.isActive() === false) {
animation.kill();
this.animations.delete(animation);
animationsReleased++;
}
});
this.log('debug', `Auto cleanup complete: ${texturesReleased} textures and ${animationsReleased} animations released`);
}
/**
* Log a message with the appropriate level
*
* @param level - Log level
* @param message - Log message
* @param data - Optional data to log
*/
log(level, message, data) {
const logLevels = {
'error': 0,
'warn': 1,
'info': 2,
'debug': 3
};
if (logLevels[level] <= logLevels[this.options.logLevel || 'warn']) {
const logMessage = `[ResourceManager:${this.componentId}] ${message}`;
switch (level) {
case 'error':
console.error(logMessage, data);
break;
case 'warn':
console.warn(logMessage, data);
break;
case 'info':
console.info(logMessage, data);
break;
case 'debug':
console.debug(logMessage, data);
break;
}
}
}
/**
* Records performance metric for an operation
*
* @param name - Operation name
* @param startTime - Start time of the operation
*/
recordMetric(name, startTime) {
if (!this.metrics)
return;
const endTime = performance.now();
const duration = endTime - startTime;
if (!this.metrics.operations[name]) {
this.metrics.operations[name] = {
count: 0,
totalTime: 0,
averageTime: 0
};
}
const op = this.metrics.operations[name];
op.count++;
op.totalTime += duration;
op.averageTime = op.totalTime / op.count;
}
/**
* Update batch statistics
*
* @param type - Resource type
* @param batchSize - Size of the batch
*/
updateBatchStats(type, batchSize) {
if (!this.metrics)
return;
const stats = this.metrics.batchStats[type];
stats.totalBatches++;
stats.totalItems += batchSize;
stats.averageBatchSize = stats.totalItems / stats.totalBatches;
stats.largestBatch = Math.max(stats.largestBatch, batchSize);
}
/**
* Mark component as unmounting to prevent new resource allocations
*/
markUnmounting() {
this.unmounting = true;
this.log('info', 'Component marked as unmounting');
}
/**
* Check if the resource manager is active and can allocate resources
*/
isActive() {
return !this.unmounting && !this.disposed;
}
// ===== FILTER CONTROL METHODS =====
/**
* Safely disable a filter to ensure it has no visible effect
*
* @param filter - Filter to disable
*/
disableFilter(filter) {
try {
// Try multiple approaches to disable the filter
if ('enabled' in filter && typeof filter.enabled === 'boolean') {
filter.enabled = false;
}
// Some filters use alpha property
if ('alpha' in filter && typeof filter.alpha === 'number') {
filter.alpha = 0;
}
// Some filters use strength
if ('strength' in filter && typeof filter.strength === 'number') {
filter.strength = 0;
}
// Some filters have a scale property (e.g. DisplacementFilter)
if ('scale' in filter) {
const scale = filter.scale;
if (scale && typeof scale.x === 'number' && typeof scale.y === 'number') {
scale.x = 0;
scale.y = 0;
}
else if (typeof scale === 'number') {
filter.scale = 0;
}
}
// Some filters use the blur property
if ('blur' in filter && typeof filter.blur === 'number') {
filter.blur = 0;
}
}
catch (error) {
this.log('debug', 'Error disabling filter', error);
}
}
/**
* Disable all filters on a display object
*
* @param displayObject - The display object whose filters should be disabled
*/
disableFiltersOnObject(displayObject) {
if (!displayObject.filters)
return;
try {
if (Array.isArray(displayObject.filters)) {
displayObject.filters.forEach(filter => {
if (filter)
this.disableFilter(filter);
});
}
else if (displayObject.filters) {
// Handle single filter case
this.disableFilter(displayObject.filters);
}
this.log('debug', 'Disabled filters on display object');
}
catch (error) {
this.log('warn', 'Error disabling filters on object', error);
}
}
/**
* Initialize a filter in a disabled state
* This ensures the filter has no effect when first created
*
* @param filter - Filter to initialize
* @returns The initialized filter
*/
initializeFilterDisabled(filter) {
this.disableFilter(filter);
return filter;
}
/**
* Batch initialize and disable filters
*
* @param filters - Array of filters to initialize disabled
* @returns The array of initialized filters
*/
initializeFilterBatchDisabled(filters) {
filters.forEach(filter => this.disableFilter(filter));
return filters;
}
/**
* Track a filter
*
* @param filter - Filter to track
* @returns The filter for chaining
*/
trackFilter(filter) {
if (!this.isActive())
return filter;
const startTime = this.metrics ? performance.now() : 0;
this.filters.add(filter);
// Register with shader manager if available
if (this.shaderManager) {
// The shader manager will keep track of shader programs used by this filter
// This is a simplified integration - in a real implementation we'd hook into
// the filter's shader creation process
this.log('debug', `Filter registered with shader manager`);
}
if (this.metrics) {
this.recordMetric('trackFilter', startTime);
this.updateBatchStats('filters', 1);
}
return filter;
}
/**
* Dispose of a single filter
*/
disposeFilter(filter) {
const startTime = this.metrics ? performance.now() : 0;
try {
// First disable the filter before destroying it
this.disableFilter(filter);
// Release any shaders associated with this filter
if (this.shaderManager) {
this.shaderManager.releaseShader(filter);
}
// Then destroy it
filter.destroy();
}
catch (error) {
// Fallback destruction for resilience
this.log('debug', `Using fallback disposal for filter`, error);
this.disableFilter(filter);
}
this.filters.delete(filter);
if (this.metrics) {
this.recordMetric('disposeFilter', startTime);
}
}
/**
* Get shader manager instance
*
* @returns The shader manager instance or null if not enabled
*/
getShaderManager() {
return this.shaderManager;
}
// ===== BATCH TRACKING METHODS =====
/**
* Track multiple textures at once
*
* @param textures - Map of URL to texture
* @returns The same map for chaining
*/
trackTextureBatch(textures) {
if (!this.isActive())
return textures;
const startTime = this.metrics ? performance.now() : 0;
textures.forEach((texture, url) => {
const entry = this.textures.get(url);
if (entry) {
entry.refCount++;
entry.lastUsed = Date.now();
}
else {
this.textures.set(url, {
resource: texture,
refCount: 1,
lastUsed: Date.now()
});
}
});
if (this.metrics) {
this.recordMetric('trackTextureBatch', startTime);
this.updateBatchStats('textures', textures.size);
}
this.log('debug', `Tracked ${textures.size} textures in batch`);
return textures;
}
/**
* Track multiple filters at once
*
* @param filters - Array of filters to track
* @returns The same array for chaining
*/
trackFilterBatch(filters) {
if (!this.isActive())
return filters;
const startTime = this.metrics ? performance.now() : 0;
filters.forEach(filter => this.filters.add(filter));
if (this.metrics) {
this.recordMetric('trackFilterBatch', startTime);
this.updateBatchStats('filters', filters.length);
}
this.log('debug', `Tracked ${filters.length} filters in batch`);
return filters;
}
/**
* Track multiple display objects at once
*
* @param objects - Array of display objects to track
* @returns The same array for chaining
*/
trackDisplayObjectBatch(objects) {
if (!this.isActive())
return objects;
const startTime = this.metrics ? performance.now() : 0;
objects.forEach(object => this.displayObjects.add(object));
if (this.metrics) {
this.recordMetric('trackDisplayObjectBatch', startTime);
this.updateBatchStats('displayObjects', objects.length);
}
this.log('debug', `Tracked ${objects.length} display objects in batch`);
return objects;
}
/**
* Track multiple animations at once
*
* @param animations - Array of animations to track
* @returns The same array for chaining
*/
trackAnimationBatch(animations) {
if (!this.isActive())
return animations;
const startTime = this.metrics ? performance.now() : 0;
animations.forEach(animation => this.animations.add(animation));
if (this.metrics) {
this.recordMetric('trackAnimationBatch', startTime);
this.updateBatchStats('animations', animations.length);
}
this.log('debug', `Tracked ${animations.length} animations in batch`);
return animations;
}
/**
* Track event listeners in batch
*
* @param element - DOM element
* @param listeners - Map of event types to callbacks
*/
addEventListenerBatch(element, listeners) {
if (!this.isActive())
return;
const startTime = this.metrics ? performance.now() : 0;
let count = 0;
// Ensure we have a map for this element
if (!this.listeners.has(element)) {
this.listeners.set(element, new Map());
}
const elementListeners = this.listeners.get(element);
// Add all the listeners
listeners.forEach((callbacks, eventType) => {
if (!elementListeners.has(eventType)) {
elementListeners.set(eventType, new Set());
}
const callbackSet = elementListeners.get(eventType);
callbacks.forEach(callback => {
callbackSet.add(callback);
element.addEventListener(eventType, callback);
count++;
});
});
if (this.metrics) {
this.recordMetric('addEventListenerBatch', startTime);
}
this.log('debug', `Added ${count} event listeners in batch`);
}
// ===== INDIVIDUAL TRACKING METHODS =====
/**
* Track a GSAP animation
*
* @param animation - Animation to track
* @returns The animation for chaining
*/
trackAnimation(animation) {
if (!this.isActive()) {
animation.kill();
return animation;
}
const startTime = this.metrics ? performance.now() : 0;
this.animations.add(animation);
if (this.metrics) {
this.recordMetric('trackAnimation', startTime);
this.updateBatchStats('animations', 1);
}
return animation;
}
/**
* Track a texture
*
* @param url - Texture URL
* @param texture - Texture to track
* @returns The texture for chaining
*/
trackTexture(url, texture) {
if (!this.isActive())
return texture;
const startTime = this.metrics ? performance.now() : 0;
const entry = this.textures.get(url);
if (entry) {
entry.refCount++;
entry.lastUsed = Date.now();
}
else {
this.textures.set(url, {
resource: texture,
refCount: 1,
lastUsed: Date.now()
});
}
if (this.metrics) {
this.recordMetric('trackTexture', startTime);
this.updateBatchStats('textures', 1);
}
return texture;
}
/**
* Release a texture, destroying it when no longer referenced
*/
releaseTexture(url) {
const entry = this.textures.get(url);
if (!entry)
return;
const startTime = this.metrics ? performance.now() : 0;
entry.refCount--;
if (entry.refCount <= 0) {
try {
entry.resource.destroy(true);
this.textures.delete(url);
this.log('debug', `Destroyed texture: ${url}`);
}
catch (error) {
this.log('warn', `Failed to destroy texture: ${url}`, error);
}
}
if (this.metrics) {
this.recordMetric('releaseTexture', startTime);
}
}
/**
* Track a PIXI Application
*
* @param app - Application to track
* @returns The application for chaining
*/
trackPixiApp(app) {
if (!this.isActive()) {
this.disposePixiApp(app);
return app;
}
const startTime = this.metrics ? performance.now() : 0;
this.pixiApps.add(app);
if (this.metrics) {
this.recordMetric('trackPixiApp', startTime);
}
this.log('info', 'Tracking PIXI application');
return app;
}
/**
* Dispose of a PIXI Application
*/
disposePixiApp(app) {
const startTime = this.metrics ? performance.now() : 0;
try {
app.stop();
// Remove canvas from DOM
if (app.canvas instanceof HTMLCanvasElement) {
app.canvas.remove();
}
app.destroy(true, { children: true });
this.log('info', 'PIXI application destroyed');
}
catch (error) {
this.log('warn', 'Error disposing PIXI application', error);
}
this.pixiApps.delete(app);
if (this.metrics) {
this.recordMetric('disposePixiApp', startTime);
}
}
/**
* Track a display object
*
* @param displayObject - Display object to track
* @returns The display object for chaining
*/
trackDisplayObject(displayObject) {
if (!this.isActive())
return displayObject;
const startTime = this.metrics ? performance.now() : 0;
this.displayObjects.add(displayObject);
if (this.metrics) {
this.recordMetric('trackDisplayObject', startTime);
this.updateBatchStats('displayObjects', 1);
}
return displayObject;
}
/**
* Dispose of a display object
*/
disposeDisplayObject(displayObject) {
const startTime = this.metrics ? performance.now() : 0;
try {
// Remove from parent if possible
if (displayObject.parent) {
displayObject.parent.removeChild(displayObject);
}
// Disable filters first, then dispose them
this.disableFiltersOnObject(displayObject);
// Dispose filters if any
if (displayObject.filters) {
if (Array.isArray(displayObject.filters)) {
displayObject.filters.forEach((filter) => {
this.disposeFilter(filter);
});
}
else {
// Handle single filter case
this.disposeFilter(displayObject.filters);
}
// Set to empty array instead of null
displayObject.filters = [];
}
// Destroy the object with appropriate options
displayObject.destroy({
children: true,
texture: false
});
}
catch (error) {
this.log('warn', 'Error disposing display object', error);
}
this.displayObjects.delete(displayObject);
if (this.metrics) {
this.recordMetric('disposeDisplayObject', startTime);
}
}
/**
* Add an event listener with tracking
*/
addEventListener(element, eventType, callback) {
if (!this.isActive())
return;
const startTime = this.metrics ? performance.now() : 0;
if (!this.listeners.has(element)) {
this.listeners.set(element, new Map());
}
const elementListeners = this.listeners.get(element);
if (!elementListeners.has(eventType)) {
elementListeners.set(eventType, new Set());
}
const callbacks = elementListeners.get(eventType);
callbacks.add(callback);
element.addEventListener(eventType, callback);
if (this.metrics) {
this.recordMetric('addEventListener', startTime);
}
}
/**
* Remove all event listeners
*/
removeAllEventListeners() {
const startTime = this.metrics ? performance.now() : 0;
let count = 0;
this.listeners.forEach((eventMap, element) => {
eventMap.forEach((callbacks, eventType) => {
callbacks.forEach(callback => {
element.removeEventListener(eventType, callback);
count++;
});
});
});
this.listeners.clear();
if (this.metrics) {
this.recordMetric('removeAllEventListeners', startTime);
}
this.log('debug', `Removed ${count} event listeners`);
}
/**
* Create a setTimeout with tracking
*/
setTimeout(callback, delay) {
if (!this.isActive())
return setTimeout(() => { }, 0);
const startTime = this.metrics ? performance.now() : 0;
const timeout = setTimeout(() => {
this.timeouts.delete(timeout);
callback();
}, delay);
this.timeouts.add(timeout);
if (this.metrics) {
this.recordMetric('setTimeout', startTime);
}
return timeout;
}
/**
* Create a setInterval with tracking
*/
setInterval(callback, delay) {
if (!this.isActive())
return setInterval(() => { }, 0);
const startTime = this.metrics ? performance.now() : 0;
const interval = setInterval(callback, delay);
this.intervals.add(interval);
if (this.metrics) {
this.recordMetric('setInterval', startTime);
}
return interval;
}
/**
* Clear a tracked timeout
*/
clearTimeout(id) {
const startTime = this.metrics ? performance.now() : 0;
globalThis.clearTimeout(id);
this.timeouts.delete(id);
if (this.metrics) {
this.recordMetric('clearTimeout', startTime);
}
}
/**
* Clear a tracked interval
*/
clearInterval(id) {
const startTime = this.metrics ? performance.now() : 0;
globalThis.clearInterval(id);
this.intervals.delete(id);
if (this.metrics) {
this.recordMetric('clearInterval', startTime);
}
}
/**
* Clear all tracked timeouts
*/
clearAllTimeouts() {
const startTime = this.metrics ? performance.now() : 0;
let count = 0;
this.timeouts.forEach(id => {
globalThis.clearTimeout(id);
count++;
});
this.timeouts.clear();
if (this.metrics) {
this.recordMetric('clearAllTimeouts', startTime);
}
this.log('debug', `Cleared ${count} timeouts`);
}
/**
* Clear all tracked intervals
*/
clearAllIntervals() {
const startTime = this.metrics ? performance.now() : 0;
let count = 0;
this.intervals.forEach(id => {
globalThis.clearInterval(id);
count++;
});
this.intervals.clear();
if (this.metrics) {
this.recordMetric('clearAllIntervals', startTime);
}
this.log('debug', `Cleared ${count} intervals`);
}
/**
* Get current resource statistics
*
* @returns An object containing counts of various tracked resources
*/
getStats() {
const result = {
textures: this.textures.size,
filters: this.filters.size,
displayObjects: this.displayObjects.size,
animations: this.animations.size,
eventTargets: this.listeners.size,
timeouts: this.timeouts.size,
intervals: this.intervals.size,
pixiApps: this.pixiApps.size,
shaderPoolingEnabled: !!this.shaderManager
};
// Add performance metrics if enabled
if (this.metrics) {
result.metrics = this.metrics;
}
// Add shader manager stats if available
if (this.shaderManager) {
result.shaderManager = this.shaderManager.getStats();
}
return result;
}
/**
* Clear all tracked resources
*/
dispose() {
if (this.disposed)
return;
const startTime = this.metrics ? performance.now() : 0;
this.log('info', 'Disposing all resources...');
this.disposed = true;
this.markUnmounting();
// Stop auto cleanup if running
if (this.autoCleanupTimer) {
clearInterval(this.autoCleanupTimer);
this.autoCleanupTimer = null;
}
// Clear animations first to stop visual changes
this.animations.forEach(animation => animation.kill());
this.animations.clear();
// Remove all event listeners
this.removeAllEventListeners();
// Dispose PIXI applications (which will handle most resources)
this.pixiApps.forEach(app => this.disposePixiApp(app));
this.pixiApps.clear();
// Disable all filters on display objects first
this.displayObjects.forEach(obj => this.disableFiltersOnObject(obj));
// Dispose remaining display objects
this.displayObjects.forEach(obj => this.disposeDisplayObject(obj));
this.displayObjects.clear();
// Dispose filters
this.filters.forEach(filter => this.disposeFilter(filter));
this.filters.clear();
// Release textures
this.textures.forEach((entry, url) => {
try {
entry.resource.destroy(true);
}
catch (error) {
this.log('warn', `Error disposing texture: ${url}`, error);
}
});
this.textures.clear();
// Clear timers
this.clearAllTimeouts();
this.clearAllIntervals();
// Clear shader manager if present
if (this.shaderManager) {
// Release all filters from the shader manager by iterating through our filters
this.filters.forEach(filter => {
this.shaderManager?.releaseShader(filter);
});
// Set to null without calling the nonexistent clear method
this.shaderManager = null;
}
if (this.metrics) {
this.recordMetric('dispose', startTime);
// Log performance summary
this.log('info', 'Performance metrics summary:', this.metrics);
}
this.log('info', 'All resources disposed');
}
/**
* Clears any pending updates in the queue
*/
clearPendingUpdates() {
// Clear any pending timeouts or intervals
this.timeouts.forEach(clearTimeout);
this.timeouts.clear();
// Clear any pending animation frames
this.intervals.forEach(clearInterval);
this.intervals.clear();
}
/**
* Monitor filter performance and adjust quality if necessary
* Part of the shader optimization implementation
*
* @param filter - Filter to monitor
* @param performanceThreshold - Performance threshold in ms
* @param qualityReduceFactor - How much to reduce quality (0-1)
* @returns Whether the filter was optimized
*/
monitorFilterPerformance(filter, performanceThreshold = 16, qualityReduceFactor = 0.5) {
if (!filter || !this.isActive())
return false;
try {
// Measure filter rendering time
const startTime = performance.now();
// Simulate a render cycle - in practice this would be done
// during actual rendering with proper timing
if (filter.enabled) {
// Force filter to update its internal state
if ('apply' in filter && typeof filter.apply === 'function') {
// This is a simplification - in a real implementation,
// we would measure during actual rendering
this.log('debug', 'Monitoring filter performance');
}
}
const endTime = performance.now();
const renderTime = endTime - startTime;
// If rendering takes too long, reduce quality
if (renderTime > performanceThreshold) {
this.optimizeFilterQuality(filter, qualityReduceFactor);
this.log('info', `Filter performance optimized: ${renderTime.toFixed(2)}ms -> threshold: ${performanceThreshold}ms`);
return true;
}
return false;
}
catch (error) {
this.log('warn', 'Error monitoring filter performance', error);
return false;
}
}
/**
* Optimize a filter's quality settings to improve performance
*
* @param filter - Filter to optimize
* @param factor - Factor to reduce quality by (0-1)
* @returns Whether optimization was applied
*/
optimizeFilterQuality(filter, factor = 0.5) {
if (!filter || !this.isActive())
return false;
try {
let optimized = false;
// Adjust quality parameter if available
if ('quality' in filter && typeof filter.quality === 'number') {
const newQuality = Math.max(1, Math.floor(filter.quality * factor));
if (newQuality < filter.quality) {
filter.quality = newQuality;
optimized = true;
this.log('debug', `Reduced filter quality to ${newQuality}`);
}
}
// Adjust resolution if available
if ('resolution' in filter && typeof filter.resolution === 'number') {
const newResolution = Math.max(0.1, filter.resolution * factor);
if (newResolution < filter.resolution) {
filter.resolution = newResolution;
optimized = true;
this.log('debug', `Reduced filter resolution to ${newResolution.toFixed(2)}`);
}
}
// Many filters have a specific quality parameter
const specificParams = [
'kernelSize', 'blur', 'steps', 'passes', 'iterations',
'sampleSize', 'pixelSize', 'blurX', 'blurY'
];
// Try to adjust known quality parameters
for (const param of specificParams) {
if (param in filter && typeof filter[param] === 'number') {
const currentValue = filter[param];
// For parameters where higher = better quality, reduce
const newValue = Math.max(1, Math.floor(currentValue * factor));
if (newValue < currentValue) {
filter[param] = newValue;
optimized = true;
this.log('debug', `Reduced filter ${param} to ${newValue}`);
}
}
}
return optimized;
}
catch (error) {
this.log('warn', 'Error optimizing filter quality', error);
return false;
}
}
/**
* Run diagnostics on all active filters
* Provides insights into filter performance
*
* @returns Diagnostic information about filters
*/
runFilterDiagnostics() {
const results = {
totalFilters: this.filters.size,
filtersByType: {},
potentialOptimizations: 0
};
try {
// Group filters by type
this.filters.forEach(filter => {
const filterType = filter.constructor.name;
if (!results.filtersByType[filterType]) {
results.filtersByType[filterType] = 0;
}
results.filtersByType[filterType]++;
// Check for potential optimizations
let canOptimize = false;
// Check common quality parameters
if ('quality' in filter && typeof filter.quality === 'number' && filter.quality > 1) {
canOptimize = true;
}
if ('resolution' in filter && typeof filter.resolution === 'number' && filter.resolution > 0.5) {
canOptimize = true;
}
// Count potential optimizations
if (canOptimize) {
results.potentialOptimizations++;
}
});
// Add shader manager diagnostics if available
if (this.shaderManager) {
results.shaderPoolStats = this.shaderManager.getStats();
}
return results;
}
catch (error) {
this.log('warn', 'Error running filter diagnostics', error);
return { error: 'Failed to run diagnostics', totalFilters: this.filters.size };
}
}
/**
* Automatically optimize all filters based on FPS
* This method should be called periodically during animation
*
* @param currentFPS - Current FPS of the application
* @param targetFPS - Target FPS to maintain
* @param optimizationStep - How aggressive to optimize (0-1)
* @returns Number of filters optimized
*/
autoOptimizeFilters(currentFPS, targetFPS = 55, optimizationStep = 0.8) {
if (!this.isActive() || this.filters.size === 0)
return 0;
// Don't optimize if FPS is already good
if (currentFPS >= targetFPS)
return 0;
try {
let optimizedCount = 0;
const fpsDifference = targetFPS - currentFPS;
// More aggressive optimization for lower FPS
const optimizationFactor = Math.min(0.9, Math.max(0.5, optimizationStep * (1 - (currentFPS / targetFPS))));
this.log('info', `Auto-optimizing filters: FPS ${currentFPS.toFixed(1)}/${targetFPS}, factor: ${optimizationFactor.toFixed(2)}`);
// Sort filters by complexity/cost (simplified approach)
// In a real implementation, you'd track actual rendering cost
const filterEntries = Array.from(this.filters.entries())
.map(([id, filter]) => {
// Estimate filter cost based on its properties
let estimatedCost = 1;
// Quality-based cost estimation
if ('quality' in filter && typeof filter.quality === 'number') {
estimatedCost *= filter.quality;
}
// Resolution-based cost estimation
if ('resolution' in filter && typeof filter.resolution === 'number') {
estimatedCost *= filter.resolution;
}
return { id, filter, cost: estimatedCost };
})
.sort((a, b) => b.cost - a.cost); // Sort by highest cost first
// Optimize the most expensive filters first
for (const { filter } of filterEntries) {
if (optimizedCount >= 3)
break; // Limit optimizations per frame
const wasOptimized = this.optimizeFilterQuality(filter, optimizationFactor);
if (wasOptimized) {
optimizedCount++;
}
}
if (optimizedCount > 0) {
this.log('info', `Optimized ${optimizedCount} filters to improve performance`);
}
return optimizedCount;
}
catch (error) {
this.log('warn', 'Error auto-optimizing filters', error);
return 0;
}
}
}
export { ResourceManager as default };
//# sourceMappingURL=ResourceManager.js.map