kinetic-slider
Version:
A WebGL-powered kinetic slider component using PIXI.js
435 lines (431 loc) • 15.4 kB
JavaScript
'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