UNPKG

kinetic-slider

Version:

A WebGL-powered kinetic slider component using PIXI.js

435 lines (431 loc) 15.4 kB
'use strict'; var ShaderResourceManager = require('../managers/ShaderResourceManager.cjs'); var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); class FilterFactory { /** * Initialize the FilterFactory with a ShaderResourceManager * * @param options - Configuration options */ static initialize(options = {}) { this.shaderManager = ShaderResourceManager.ShaderResourceManager.getInstance({ debug: options.enableDebug, maxPoolSize: 100 }); this.debug = options.enableDebug ?? false; this.shaderPoolingEnabled = options.enableShaderPooling ?? true; if (options.lazyLoadConfig) { this.lazyLoadConfig = { ...this.lazyLoadConfig, ...options.lazyLoadConfig }; } if (this.debug) { console.log(`[FilterFactory] Initialized with shader pooling ${this.shaderPoolingEnabled ? "enabled" : "disabled"}`); console.log(`[FilterFactory] Lazy loading configuration:`, this.lazyLoadConfig); } this.startCleanupTimer(); if (this.shaderPoolingEnabled && this.shaderManager) { if (this.debug) { console.log("[FilterFactory] Shader pooling enabled - shader programs will be shared between filter instances"); } } Object.keys(this.MODULE_PATHS).forEach((type) => { const filterType = type; if (!this.moduleRegistry.has(filterType)) { this.moduleRegistry.set(filterType, { state: "unloaded" /* UNLOADED */, lastUsed: 0, useCount: 0 }); } }); } /** * Start the cleanup timer for unused filter modules */ static startCleanupTimer() { if (this.cleanupTimer !== null) { window.clearInterval(this.cleanupTimer); } this.cleanupTimer = window.setInterval(() => { this.cleanupUnusedModules(); }, 3e4); } /** * Cleanup unused filter modules */ static cleanupUnusedModules() { if (this.debug) { console.log("[FilterFactory] Running cleanup for unused filter modules"); } const now = Date.now(); let unloadCount = 0; for (const [type, entry] of this.moduleRegistry.entries()) { if (entry.state !== "loaded" /* LOADED */) continue; const timeSinceLastUse = now - entry.lastUsed; if (timeSinceLastUse > this.lazyLoadConfig.unloadTimeoutMs) { entry.creator = void 0; entry.state = "unloaded" /* UNLOADED */; unloadCount++; if (this.debug) { console.log(`[FilterFactory] Unloaded unused filter module: ${type} (${timeSinceLastUse}ms since last use)`); } } } if (this.debug) { console.log(`[FilterFactory] Cleanup complete: unloaded ${unloadCount} unused filter modules`); } } /** * Prefetch filter modules based on likely usage * * @param filterTypes - Array of filter types to prefetch * @param priority - Priority level (high, medium, low) */ static prefetchFilterModules(filterTypes, priority = "medium") { if (!this.lazyLoadConfig.enablePrefetching) return; const priorityDelay = { high: 0, medium: 100, low: 1e3 }; setTimeout(() => { filterTypes.forEach((type) => { const entry = this.moduleRegistry.get(type); if (!entry || entry.state === "loaded" /* LOADED */ || entry.state === "loading" /* LOADING */) { return; } if (this.debug) { console.log(`[FilterFactory] Prefetching filter module: ${type} (${priority} priority)`); } this.loadFilterModule(type).catch((error) => { if (this.debug) { console.error(`[FilterFactory] Error prefetching filter module ${type}:`, error); } }); }); }, priorityDelay[priority]); } /** * Check if a filter module is available (loaded) * * @param type - Filter type to check * @returns True if the filter module is loaded and ready to use */ static isFilterModuleLoaded(type) { const entry = this.moduleRegistry.get(type); return entry?.state === "loaded" /* LOADED */ && !!entry.creator; } /** * Get the loading state of a filter module * * @param type - Filter type to check * @returns The current loading state of the filter module */ static getFilterModuleState(type) { return this.moduleRegistry.get(type)?.state || "unloaded" /* UNLOADED */; } /** * Load a filter module if not already loaded * * @param type - Type of filter to load * @returns Promise resolving when the module is loaded */ static async loadFilterModule(type, retryCount = 0) { if (!this.moduleRegistry.has(type)) { this.moduleRegistry.set(type, { state: "unloaded" /* UNLOADED */, lastUsed: Date.now(), useCount: 0 }); } const entry = this.moduleRegistry.get(type); if (entry.state === "loading" /* LOADING */ && entry.loadPromise) { return entry.loadPromise; } if (entry.state === "loaded" /* LOADED */ && entry.creator) { entry.lastUsed = Date.now(); entry.useCount++; return Promise.resolve(entry.creator); } entry.state = "loading" /* LOADING */; const startTime = performance.now(); if (this.debug) { console.log(`[FilterFactory] Loading filter module: ${type}`); } try { entry.loadPromise = this.importAllFilters().then((module) => { const creatorFnName = `create${this.capitalizeFilterType(type)}Filter`; if (this.debug) { console.log(`[FilterFactory] Looking for creator function: ${creatorFnName}`); } const creator = module[creatorFnName]; if (!creator || typeof creator !== "function") { throw new Error(`Filter creator function ${creatorFnName} not found in filters module`); } entry.state = "loaded" /* LOADED */; entry.creator = creator; entry.loadTime = performance.now() - startTime; entry.lastUsed = Date.now(); entry.useCount = 1; entry.error = void 0; if (this.debug) { console.log(`[FilterFactory] Loaded filter module: ${type} in ${entry.loadTime.toFixed(2)}ms`); } return creator; }); return entry.loadPromise; } catch (error) { entry.state = "error" /* ERROR */; entry.error = error; if (this.debug) { console.error(`[FilterFactory] Error loading filter module ${type}:`, error); } if (this.lazyLoadConfig.retryFailedLoads && retryCount < this.lazyLoadConfig.maxRetries) { if (this.debug) { console.log(`[FilterFactory] Retrying load for filter module ${type} (attempt ${retryCount + 1}/${this.lazyLoadConfig.maxRetries})`); } const retryDelay = Math.min(1e3 * Math.pow(2, retryCount), 5e3); await new Promise((resolve) => setTimeout(resolve, retryDelay)); entry.state = "unloaded" /* UNLOADED */; entry.error = void 0; return this.loadFilterModule(type, retryCount + 1); } return Promise.reject(error); } } /** * Import all filters from the consolidated module * This is more reliable than individual dynamic imports * * @returns Promise resolving to the module with all filter creators */ static async importAllFilters() { if (this.debug) { console.log("[FilterFactory] Importing all filters from consolidated module"); } try { return Promise.resolve().then(function () { return require('./index.cjs'); }); } catch (error) { if (this.debug) { console.error("[FilterFactory] Error importing filter index:", error); } return Promise.resolve().then(function () { return require('./index.cjs'); }); } } /** * Capitalize the first letter of a filter type */ static capitalizeFilterType(type) { if (type === "rgbSplit") return "RGBSplit"; if (type === "hsl") return "HslAdjustment"; return type.charAt(0).toUpperCase() + type.slice(1); } /** * Generate a cache key for a filter configuration * * @param config - Filter configuration * @returns A string key for cache lookups */ static generateCacheKey(config) { return `${config.type}_${JSON.stringify(config)}`; } /** * Log a message if debug is enabled * * @param message - Message to log */ static log(message) { if (this.debug) { console.log(`[FilterFactory] ${message}`); } } /** * Create a filter based on the provided configuration * * @param config - Configuration for the filter * @returns Promise resolving to an object containing the filter instance and control functions */ static async createFilterAsync(config) { if (!this.shaderManager) { this.initialize(); } const cacheKey = this.generateCacheKey(config); if (this.filterCache.has(cacheKey)) { const cachedResult = this.filterCache.get(cacheKey); this.log(`Reusing cached filter for ${config.type}`); const entry = this.moduleRegistry.get(config.type); if (entry) { entry.lastUsed = Date.now(); entry.useCount++; } return cachedResult; } try { const creator = await this.loadFilterModule(config.type); const result = creator(config); this.filterCache.set(cacheKey, result); return result; } catch (error) { this.log(`Error creating filter ${config.type}: ${error}`); throw error; } } /** * Create a filter based on the provided configuration (synchronous version) * Falls back to async loading if the module is not already loaded * * @param config - Configuration for the filter * @returns Object containing the filter instance and control functions, or a promise resolving to it */ static createFilter(config) { if (!this.shaderManager) { this.initialize(); } const cacheKey = this.generateCacheKey(config); if (this.filterCache.has(cacheKey)) { const cachedResult = this.filterCache.get(cacheKey); this.log(`Reusing cached filter for ${config.type}`); const entry2 = this.moduleRegistry.get(config.type); if (entry2) { entry2.lastUsed = Date.now(); entry2.useCount++; } return cachedResult; } const entry = this.moduleRegistry.get(config.type); if (entry?.state === "loaded" /* LOADED */ && entry.creator) { try { const result = entry.creator(config); this.filterCache.set(cacheKey, result); entry.lastUsed = Date.now(); entry.useCount++; return result; } catch (error) { this.log(`Error creating filter ${config.type}: ${error}`); throw error; } } else { this.log(`Filter module ${config.type} not loaded, loading asynchronously`); return this.createFilterAsync(config); } } /** * Get stats about loaded filter modules * * @returns Object with statistics about filter module loading */ static getFilterModuleStats() { const stats = { totalModules: this.moduleRegistry.size, loadedModules: 0, loadingModules: 0, errorModules: 0, unloadedModules: 0, moduleDetails: {} }; this.moduleRegistry.forEach((entry, type) => { if (entry.state === "loaded" /* LOADED */) stats.loadedModules++; else if (entry.state === "loading" /* LOADING */) stats.loadingModules++; else if (entry.state === "error" /* ERROR */) stats.errorModules++; else stats.unloadedModules++; stats.moduleDetails[type] = { state: entry.state, useCount: entry.useCount, lastUsed: entry.lastUsed, loadTime: entry.loadTime, hasError: !!entry.error }; }); return stats; } /** * Clear the filter cache */ static clearCache() { this.filterCache.clear(); this.log("Filter cache cleared"); } /** * Dispose all resources managed by the FilterFactory */ static dispose() { this.clearCache(); if (this.cleanupTimer !== null) { window.clearInterval(this.cleanupTimer); this.cleanupTimer = null; } this.moduleRegistry.clear(); this.log("FilterFactory disposed"); } } /** Shader resource manager instance */ __publicField(FilterFactory, "shaderManager", null); /** Filter cache for reusing instances with identical configurations */ __publicField(FilterFactory, "filterCache", /* @__PURE__ */ new Map()); /** Debug mode flag */ __publicField(FilterFactory, "debug", false); /** Is shader pooling enabled */ __publicField(FilterFactory, "shaderPoolingEnabled", false); /** Registry of filter modules and their loading states */ __publicField(FilterFactory, "moduleRegistry", /* @__PURE__ */ new Map()); /** Lazy loading configuration */ __publicField(FilterFactory, "lazyLoadConfig", { unloadTimeoutMs: 6e4, // 1 minute maxCachedModules: 20, enablePrefetching: true, retryFailedLoads: true, maxRetries: 3 }); /** Timer for cleanup of unused modules */ __publicField(FilterFactory, "cleanupTimer", null); /** Map of filter types to their module paths for dynamic imports */ __publicField(FilterFactory, "MODULE_PATHS", { "adjustment": "./filters/adjustmentFilter.js", "advancedBloom": "./filters/advancedBloomFilter.js", "alpha": "./filters/alphaFilter.js", "ascii": "./filters/asciiFilter.js", "backdropBlur": "./filters/backdropBlurFilter.js", "bevel": "./filters/bevelFilter.js", "bloom": "./filters/bloomFilter.js", "blur": "./filters/blurFilter.js", "bulgePinch": "./filters/bulgePinchFilter.js", "colorGradient": "./filters/colorGradientFilter.js", "colorMap": "./filters/colorMapFilter.js", "colorMatrix": "./filters/colorMatrixFilter.js", "colorOverlay": "./filters/colorOverlayFilter.js", "colorReplace": "./filters/colorReplaceFilter.js", "convolution": "./filters/convolutionFilter.js", "crossHatch": "./filters/crossHatchFilter.js", "crt": "./filters/crtFilter.js", "dot": "./filters/dotFilter.js", "dropShadow": "./filters/dropShadowFilter.js", "emboss": "./filters/embossFilter.js", "glitch": "./filters/glitchFilter.js", "glow": "./filters/glowFilter.js", "godray": "./filters/godrayFilter.js", "grayscale": "./filters/grayscaleFilter.js", "hsl": "./filters/hslAdjustmentFilter.js", "kawaseBlur": "./filters/kawaseBlurFilter.js", "motionBlur": "./filters/motionBlurFilter.js", "multiColorReplace": "./filters/multiColorReplaceFilter.js", "noise": "./filters/noiseFilter.js", "oldFilm": "./filters/oldFilmFilter.js", "outline": "./filters/outlineFilter.js", "pixelate": "./filters/pixelateFilter.js", "radialBlur": "./filters/radialBlurFilter.js", "reflection": "./filters/reflectionFilter.js", "rgbSplit": "./filters/rgbSplitFilter.js", "shockwave": "./filters/shockwaveFilter.js", "simpleLightmap": "./filters/simpleLightmapFilter.js", "simplexNoise": "./filters/simplexNoiseFilter.js", "tiltShift": "./filters/tiltShiftFilter.js", "twist": "./filters/twistFilter.js", "zoomBlur": "./filters/zoomBlurFilter.js" }); exports.FilterFactory = FilterFactory; //# sourceMappingURL=FilterFactory.cjs.map