kinetic-slider
Version:
A WebGL-powered kinetic slider component using PIXI.js
270 lines (268 loc) • 9.48 kB
JavaScript
// src/components/KineticSlider/managers/FrameThrottler.ts
/**
* @file FrameThrottler.ts
* @description Provides advanced frame timing controls to optimize rendering performance.
* Offers various throttling strategies to consolidate updates and reduce GPU load.
*/
/**
* Frame throttling strategies that determine how updates are scheduled.
* @enum {string}
*/
var ThrottleStrategy;
(function (ThrottleStrategy) {
/**
* Fixed frames per second - aims for consistent frame timing
* by enforcing a fixed interval between frames.
*/
ThrottleStrategy["FIXED_FPS"] = "fixed_fps";
/**
* Adaptive - adjusts timing based on device performance
* by monitoring actual frame rates and adjusting accordingly.
*/
ThrottleStrategy["ADAPTIVE"] = "adaptive";
/**
* Priority-based - only throttles lower priority updates
* while allowing high-priority updates to bypass throttling.
*/
ThrottleStrategy["PRIORITY"] = "priority";
/**
* None - no throttling applied (use with caution)
* as it may lead to performance issues on low-end devices.
*/
ThrottleStrategy["NONE"] = "none";
})(ThrottleStrategy || (ThrottleStrategy = {}));
/**
* Default configuration values for the throttler.
* @type {ThrottlerConfig}
*/
const DEFAULT_CONFIG = {
targetFps: 60,
minFps: 30,
maxFps: 120,
strategy: ThrottleStrategy.FIXED_FPS,
enableMonitoring: true
};
/**
* Manages frame timing and throttling for optimal performance.
* This class helps reduce GPU load by controlling when frames are processed.
*/
class FrameThrottler {
/** Active configuration */
config;
/** Performance monitoring data */
performance;
/** Timestamp of the last processed frame */
lastFrameTime = 0;
/**
* Create a new FrameThrottler with the specified configuration.
*
* @param {ThrottlerConfig} [config] - Configuration options for the throttler
*
* @example
* // Create a throttler with default settings (60fps, FIXED_FPS strategy)
* const throttler = new FrameThrottler();
*
* @example
* // Create a throttler with custom settings
* const throttler = new FrameThrottler({
* targetFps: 30,
* strategy: ThrottleStrategy.ADAPTIVE,
* minFps: 15,
* maxFps: 60
* });
*/
constructor(config) {
this.config = { ...DEFAULT_CONFIG, ...config };
// Initialize performance data
this.performance = {
frameTimes: [],
currentFps: this.config.targetFps,
frameCount: 0,
lastAdjustment: performance.now(),
currentInterval: this.calculateInterval(this.config.targetFps)
};
this.lastFrameTime = performance.now();
}
/**
* Calculate the frame interval in milliseconds from FPS.
*
* @param {number} fps - Frames per second
* @returns {number} Interval in milliseconds between frames
* @private
*/
calculateInterval(fps) {
return 1000 / fps;
}
/**
* Check if enough time has passed to process the next frame.
*
* @param {number} [priority] - Optional priority level to consider (higher values bypass more throttling)
* @returns {boolean} True if the frame should be processed, false if it should be skipped
*
* @example
* // Basic usage in animation loop
* if (throttler.shouldProcessFrame()) {
* // Process frame
* render();
* throttler.frameProcessed();
* }
*
* @example
* // Usage with priority (3 is highest priority)
* if (throttler.shouldProcessFrame(2)) {
* // Process high-priority frame
* renderImportantElements();
* throttler.frameProcessed();
* }
*/
shouldProcessFrame(priority) {
const now = performance.now();
const elapsed = now - this.lastFrameTime;
// Handle different strategies
switch (this.config.strategy) {
case ThrottleStrategy.NONE:
return true;
case ThrottleStrategy.PRIORITY:
// High priority updates bypass throttling
if (priority !== undefined && priority >= 2) {
return true;
}
// Otherwise use fixed fps throttling
return elapsed >= this.performance.currentInterval;
case ThrottleStrategy.ADAPTIVE:
// Adaptively adjust the interval based on recent performance
this.updateAdaptivePerformance(now);
return elapsed >= this.performance.currentInterval;
case ThrottleStrategy.FIXED_FPS:
default:
// Simple fixed interval throttling
return elapsed >= this.performance.currentInterval;
}
}
/**
* Mark the current frame as processed and update timing metrics.
* Should be called after processing a frame that passed the shouldProcessFrame check.
*
* @example
* if (throttler.shouldProcessFrame()) {
* // Process frame
* render();
* throttler.frameProcessed();
* }
*/
frameProcessed() {
const now = performance.now();
const frameDuration = now - this.lastFrameTime;
this.lastFrameTime = now;
// Update performance metrics
if (this.config.enableMonitoring) {
this.updatePerformanceMetrics(frameDuration);
}
}
/**
* Update performance metrics with the latest frame duration.
*
* @param {number} frameDuration - Duration of the last frame in milliseconds
* @private
*/
updatePerformanceMetrics(frameDuration) {
// Add to the frame times array (keep last 60 frames)
this.performance.frameTimes.push(frameDuration);
if (this.performance.frameTimes.length > 60) {
this.performance.frameTimes.shift();
}
// Calculate current FPS
const avgFrameDuration = this.performance.frameTimes.reduce((sum, time) => sum + time, 0) /
this.performance.frameTimes.length;
this.performance.currentFps = 1000 / avgFrameDuration;
// Increment frame count
this.performance.frameCount++;
}
/**
* Update adaptive performance settings based on recent metrics.
* This method adjusts the frame rate based on the device's capabilities.
*
* @param {number} now - Current timestamp in milliseconds
* @private
*/
updateAdaptivePerformance(now) {
// Only adjust every 1 second
if (now - this.performance.lastAdjustment < 1000 ||
this.performance.frameTimes.length < 10) {
return;
}
// Calculate average FPS
const avgFps = this.performance.currentFps;
// Adjust based on performance
let targetFps = this.config.targetFps;
if (avgFps < this.config.minFps) {
// Performance is too low, reduce target FPS
targetFps = Math.max(this.config.minFps, targetFps * 0.8);
}
else if (avgFps > this.config.maxFps) {
// Performance is very good, increase target FPS up to max
targetFps = Math.min(this.config.maxFps, targetFps * 1.2);
}
else if (avgFps > targetFps * 1.2) {
// We have headroom, gradually increase target FPS
targetFps = Math.min(this.config.maxFps, targetFps * 1.1);
}
// Update the interval
this.performance.currentInterval = this.calculateInterval(targetFps);
this.performance.lastAdjustment = now;
}
/**
* Set a specific throttling strategy.
*
* @param {ThrottleStrategy} strategy - The throttling strategy to use
*
* @example
* // Switch to adaptive strategy based on device capability
* if (isLowEndDevice) {
* throttler.setStrategy(ThrottleStrategy.ADAPTIVE);
* }
*/
setStrategy(strategy) {
this.config.strategy = strategy;
}
/**
* Set a specific target FPS.
*
* @param {number} fps - Target frames per second (must be > 0)
*
* @example
* // Reduce target FPS to save battery
* if (isBatteryLow) {
* throttler.setTargetFps(30);
* } else {
* throttler.setTargetFps(60);
* }
*/
setTargetFps(fps) {
this.config.targetFps = Math.max(1, fps);
// Update the interval if using fixed FPS
if (this.config.strategy === ThrottleStrategy.FIXED_FPS) {
this.performance.currentInterval = this.calculateInterval(this.config.targetFps);
}
}
/**
* Get current performance metrics for monitoring and debugging.
*
* @returns {Object} Performance information including current FPS, interval, frame count, and strategy
*
* @example
* // Log performance metrics
* console.log(throttler.getPerformanceMetrics());
* // Example output: { currentFps: 58.2, targetInterval: 16.67, frameCount: 1242, strategy: 'fixed_fps' }
*/
getPerformanceMetrics() {
return {
currentFps: this.performance.currentFps,
targetInterval: this.performance.currentInterval,
frameCount: this.performance.frameCount,
strategy: this.config.strategy
};
}
}
export { FrameThrottler, ThrottleStrategy };
//# sourceMappingURL=FrameThrottler.js.map