kinetic-slider
Version:
A WebGL-powered kinetic slider component using PIXI.js
288 lines (285 loc) • 8.7 kB
JavaScript
import { createUpdateId, getPriorityForUpdateType } from './UpdateTypes.js';
import { FrameThrottler, ThrottleStrategy } from './FrameThrottler.js';
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);
const _RenderScheduler = class _RenderScheduler {
/**
* Private constructor for singleton pattern.
* @private
*/
constructor() {
/** Map of task IDs to pending update tasks */
__publicField(this, "updateQueue", /* @__PURE__ */ new Map());
/** Whether the processing loop is active */
__publicField(this, "isProcessing", false);
/** Current requestAnimationFrame ID or null if not active */
__publicField(this, "rafId", null);
/** Timestamp of the last frame execution */
__publicField(this, "lastFrameTime", 0);
/** Minimum time between frames in milliseconds (~60fps default) */
__publicField(this, "frameThrottle", 16);
/** Frame throttler for advanced timing control */
__publicField(this, "frameThrottler");
this.lastFrameTime = performance.now();
this.frameThrottler = new FrameThrottler({
targetFps: 60,
strategy: ThrottleStrategy.PRIORITY,
enableMonitoring: true
});
}
/**
* Get the singleton instance of the RenderScheduler.
* @returns {RenderScheduler} The singleton instance
*/
static getInstance() {
if (!_RenderScheduler.instance) {
_RenderScheduler.instance = new _RenderScheduler();
}
return _RenderScheduler.instance;
}
/**
* Schedule a task for execution.
* If a task with the same ID already exists, it will be replaced.
*
* @param {string} id - Unique identifier for the task
* @param {() => void} callback - Function to execute
* @param {UpdatePriority} [priority=UpdatePriority.NORMAL] - Priority level
* @returns {RenderScheduler} The scheduler instance for chaining
*
* @example
* ```typescript
* scheduler.scheduleUpdate('update-text', () => {
* element.textContent = 'Updated text';
* }, UpdatePriority.NORMAL);
* ```
*/
scheduleUpdate(id, callback, priority = 1 /* NORMAL */) {
this.updateQueue.set(id, {
id,
callback,
priority,
timestamp: performance.now()
});
this.startProcessing();
return this;
}
/**
* Schedule an update using a standard update type.
* This provides a more semantic API for scheduling updates.
*
* @param {string} componentId - ID of the component requesting the update
* @param {UpdateType} updateType - Type of update
* @param {() => void} callback - Function to execute
* @param {string} [suffix] - Optional suffix for the update ID
* @returns {RenderScheduler} The scheduler instance for chaining
*
* @example
* ```typescript
* scheduler.scheduleTypedUpdate(
* 'slider',
* UpdateType.SLIDE_TRANSFORM,
* () => updateSlidePosition()
* );
* ```
*/
scheduleTypedUpdate(componentId, updateType, callback, suffix) {
const id = createUpdateId(componentId, updateType, suffix);
const priority = getPriorityForUpdateType(updateType);
return this.scheduleUpdate(id, callback, priority);
}
/**
* Cancel a scheduled update.
*
* @param {string} id - ID of the task to cancel
* @returns {boolean} True if a task was found and removed
*
* @example
* ```typescript
* const wasRemoved = scheduler.cancelUpdate('update-text');
* console.log(`Update was ${wasRemoved ? 'successfully' : 'not'} canceled`);
* ```
*/
cancelUpdate(id) {
return this.updateQueue.delete(id);
}
/**
* Cancel an update that was scheduled with a standard update type.
*
* @param {string} componentId - ID of the component that scheduled the update
* @param {UpdateType} updateType - Type of update to cancel
* @param {string} [suffix] - Optional suffix from the update ID
* @returns {boolean} True if a task was found and removed
*
* @example
* ```typescript
* scheduler.cancelTypedUpdate('slider', UpdateType.SLIDE_TRANSFORM);
* ```
*/
cancelTypedUpdate(componentId, updateType, suffix) {
const id = createUpdateId(componentId, updateType, suffix);
return this.cancelUpdate(id);
}
/**
* Immediately execute a task with CRITICAL priority.
* Bypasses the queue but still respects frame throttling.
*
* @param {() => void} callback - Function to execute immediately
*
* @example
* ```typescript
* scheduler.executeImmediate(() => {
* // Handle urgent user input
* processCriticalUserAction();
* });
* ```
*/
executeImmediate(callback) {
const id = `immediate-${performance.now()}`;
this.scheduleUpdate(id, callback, 3 /* CRITICAL */);
this.processQueue();
}
/**
* Start the processing loop if not already running.
* @private
*/
startProcessing() {
if (this.isProcessing || this.updateQueue.size === 0) return;
this.isProcessing = true;
this.processQueue();
}
/**
* Process the queue using requestAnimationFrame for timing.
* @private
*/
processQueue() {
if (this.rafId !== null) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
this.rafId = requestAnimationFrame(() => {
const now = performance.now();
let highestPriority = 0;
this.updateQueue.forEach((task) => {
if (task.priority > highestPriority) {
highestPriority = task.priority;
}
});
if (this.frameThrottler.shouldProcessFrame(highestPriority)) {
this.executeQueuedTasks();
this.frameThrottler.frameProcessed();
this.lastFrameTime = now;
}
if (this.updateQueue.size > 0) {
this.processQueue();
} else {
this.isProcessing = false;
this.rafId = null;
}
});
}
/**
* Execute all queued tasks in priority order.
* @private
*/
executeQueuedTasks() {
if (this.updateQueue.size === 0) return;
const tasks = Array.from(this.updateQueue.values()).sort((a, b) => {
if (a.priority !== b.priority) {
return b.priority - a.priority;
}
return a.timestamp - b.timestamp;
});
this.updateQueue.clear();
tasks.forEach((task) => {
try {
task.callback();
} catch (error) {
console.error(`Error executing task ${task.id}:`, error);
}
});
}
/**
* Set the frame throttle rate.
*
* @param {number} milliseconds - Minimum time between frames
*
* @example
* ```typescript
* // Set to 30fps (33.33ms between frames)
* scheduler.setFrameThrottle(33.33);
* ```
*/
setFrameThrottle(milliseconds) {
this.frameThrottle = Math.max(0, milliseconds);
}
/**
* Get current queue size for debugging.
*
* @returns {number} Number of tasks currently in the queue
*
* @example
* ```typescript
* const queueSize = scheduler.getQueueSize();
* console.log(`Current queue size: ${queueSize}`);
* ```
*/
getQueueSize() {
return this.updateQueue.size;
}
/**
* Clear all pending updates.
*
* @example
* ```typescript
* // Cancel all pending updates (e.g., when component unmounts)
* scheduler.clearQueue();
* ```
*/
clearQueue() {
this.updateQueue.clear();
}
/**
* Configure frame throttling behavior.
*
* @param {ThrottlerConfig} config - Throttling configuration options
*
* @example
* ```typescript
* scheduler.configureThrottling({
* targetFps: 30,
* strategy: ThrottleStrategy.ADAPTIVE
* });
* ```
*/
configureThrottling(config) {
if (config.strategy !== void 0) {
this.frameThrottler.setStrategy(config.strategy);
}
if (config.targetFps !== void 0) {
this.frameThrottler.setTargetFps(config.targetFps);
}
}
/**
* Get current performance metrics.
*
* @returns {Object} Performance metrics including queue size and frame rate
*
* @example
* ```typescript
* const metrics = scheduler.getPerformanceMetrics();
* console.log(`Current FPS: ${metrics.currentFps}`);
* ```
*/
getPerformanceMetrics() {
return {
queueSize: this.getQueueSize(),
...this.frameThrottler.getPerformanceMetrics()
};
}
};
/** Singleton instance of the scheduler */
__publicField(_RenderScheduler, "instance");
let RenderScheduler = _RenderScheduler;
export { RenderScheduler, RenderScheduler as default };
//# sourceMappingURL=RenderScheduler.js.map