kinetic-slider
Version:
A WebGL-powered kinetic slider component using PIXI.js
343 lines (340 loc) • 11.3 kB
JavaScript
import { getPriorityForUpdateType, createUpdateId } from './UpdateTypes.js';
import { FrameThrottler, ThrottleStrategy } from './FrameThrottler.js';
/**
* @file RenderScheduler.ts
* @description Provides centralized render scheduling and batching for optimized UI updates.
* Coordinates the timing of visual effects to minimize rendering overhead and improve performance.
*/
/**
* Priority levels for rendering updates.
* Higher numbers indicate higher priority.
* @enum {number}
*/
var UpdatePriority;
(function (UpdatePriority) {
/** Background, non-visual updates */
UpdatePriority[UpdatePriority["LOW"] = 0] = "LOW";
/** Standard visual updates */
UpdatePriority[UpdatePriority["NORMAL"] = 1] = "NORMAL";
/** Important visual feedback */
UpdatePriority[UpdatePriority["HIGH"] = 2] = "HIGH";
/** Must-run-immediately updates */
UpdatePriority[UpdatePriority["CRITICAL"] = 3] = "CRITICAL";
})(UpdatePriority || (UpdatePriority = {}));
/**
* Coordinates and batches render updates for KineticSlider.
* This helps reduce unnecessary render cycles by grouping related updates
* and executing them at the optimal time.
*
* @example
* ```typescript
* // Get the scheduler instance
* const scheduler = RenderScheduler.getInstance();
*
* // Schedule a normal priority update
* scheduler.scheduleUpdate('my-component-update', () => {
* // Update logic here
* });
*
* // Schedule a high priority update with a specific update type
* scheduler.scheduleTypedUpdate('my-component', UpdateType.MOUSE_RESPONSE, () => {
* // Mouse response update logic
* });
* ```
*/
class RenderScheduler {
/** Singleton instance of the scheduler */
static instance;
/** Map of task IDs to pending update tasks */
updateQueue = new Map();
/** Whether the processing loop is active */
isProcessing = false;
/** Current requestAnimationFrame ID or null if not active */
rafId = null;
/** Timestamp of the last frame execution */
lastFrameTime = 0;
/** Minimum time between frames in milliseconds (~60fps default) */
frameThrottle = 16;
/** Frame throttler for advanced timing control */
frameThrottler;
/**
* Get the singleton instance of the RenderScheduler.
* @returns {RenderScheduler} The singleton instance
*/
static getInstance() {
if (!RenderScheduler.instance) {
RenderScheduler.instance = new RenderScheduler();
}
return RenderScheduler.instance;
}
/**
* Private constructor for singleton pattern.
* @private
*/
constructor() {
// Initialize
this.lastFrameTime = performance.now();
// Initialize frame throttler with default settings
this.frameThrottler = new FrameThrottler({
targetFps: 60,
strategy: ThrottleStrategy.PRIORITY,
enableMonitoring: true
});
}
/**
* 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 = UpdatePriority.NORMAL) {
this.updateQueue.set(id, {
id,
callback,
priority,
timestamp: performance.now()
});
// Start processing if not already running
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) {
// For truly immediate execution, add to queue with CRITICAL priority
const id = `immediate-${performance.now()}`;
this.scheduleUpdate(id, callback, UpdatePriority.CRITICAL);
// Force processing on next available frame
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() {
// Cancel any existing frame request
if (this.rafId !== null) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
// Schedule next frame
this.rafId = requestAnimationFrame(() => {
const now = performance.now();
// Get highest priority in the queue
let highestPriority = 0;
this.updateQueue.forEach(task => {
if (task.priority > highestPriority) {
highestPriority = task.priority;
}
});
// Check if we should process this frame based on throttling settings
if (this.frameThrottler.shouldProcessFrame(highestPriority)) {
this.executeQueuedTasks();
this.frameThrottler.frameProcessed();
this.lastFrameTime = now;
}
// Continue processing if queue is not empty
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;
// Get all tasks and sort by priority (highest first) and then by timestamp
const tasks = Array.from(this.updateQueue.values()).sort((a, b) => {
if (a.priority !== b.priority) {
return b.priority - a.priority; // Higher priority first
}
return a.timestamp - b.timestamp; // Older tasks first within same priority
});
// Clear the queue before executing to avoid potential infinite loops
this.updateQueue.clear();
// Execute all tasks
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 !== undefined) {
this.frameThrottler.setStrategy(config.strategy);
}
if (config.targetFps !== undefined) {
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()
};
}
}
export { RenderScheduler, UpdatePriority, RenderScheduler as default };
//# sourceMappingURL=RenderScheduler.js.map