Created
March 4, 2025 08:09
-
-
Save composite/c785ec18aecf4303a86098c068d41e6b to your computer and use it in GitHub Desktop.
AI Generated utility functions - Typescript friendly, tested.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Definition of the animation frame callback function type | |
*/ | |
type AnimationFrameCallback = (time: number) => void; | |
/** | |
* Definition of the cancel function type | |
*/ | |
type CancelFunction = () => void; | |
/** | |
* Interface for the animation frame wrapper function | |
*/ | |
interface AnimationFrameWrapper { | |
(callback: AnimationFrameCallback): CancelFunction; | |
} | |
/** | |
* Factory function in singleton style for requestAnimationFrame | |
* | |
* - Ensures callbacks are executed sequentially | |
* - Provides cancellation functionality through a subscription pattern | |
* - Maintains core functionality even if an error occurs during callback execution | |
* - Manages a callback queue to prevent concurrent execution | |
* | |
* @returns A function that wraps requestAnimationFrame | |
*/ | |
function createAnimationFrameManager(): AnimationFrameWrapper { | |
// Animation frame state management | |
let isRunning = false; | |
let requestId: number | null = null; | |
let queue: Array<{ callback: AnimationFrameCallback; id: number }> = []; | |
let nextId = 0; | |
/** | |
* Function to process callbacks in the queue sequentially | |
*/ | |
const processQueue = (timestamp: number) => { | |
isRunning = true; | |
if (queue.length === 0) { | |
isRunning = false; | |
requestId = null; | |
return; | |
} | |
// Get the next callback from the queue | |
const { callback, id } = queue.shift()!; | |
let error: any = null; | |
try { | |
// Execute the callback | |
callback(timestamp); | |
} catch (err) { | |
// Store the error for later handling | |
error = err; | |
} | |
// Schedule the next frame | |
if (queue.length > 0) { | |
requestId = requestAnimationFrame(processQueue); | |
} else { | |
isRunning = false; | |
requestId = null; | |
} | |
// If an error occurred, throw it after the next frame is scheduled | |
if (error) { | |
console.error('Animation frame callback error:', error); | |
throw error; | |
} | |
}; | |
/** | |
* requestAnimationFrame wrapper function | |
* Implements a subscription pattern by registering a callback and returning a cancel function | |
*/ | |
const requestAnimationFrameWrapper = (callback: AnimationFrameCallback): CancelFunction => { | |
const id = nextId++; | |
// Add the callback to the queue | |
queue.push({ callback, id }); | |
// Start processing the queue if it’s not already running | |
if (!isRunning && requestId === null) { | |
requestId = requestAnimationFrame(processQueue); | |
} | |
// Return a function to cancel this specific request | |
return () => { | |
// Remove this callback from the queue | |
queue = queue.filter(item => item.id !== id); | |
// If the queue is empty and not running, cancel the pending animation frame | |
if (queue.length === 0 && requestId !== null && !isRunning) { | |
cancelAnimationFrame(requestId); | |
requestId = null; | |
} | |
}; | |
}; | |
return requestAnimationFrameWrapper; | |
} | |
// Usage example: | |
/* | |
const animationFrame = createAnimationFrameManager(); | |
// Subscribe to an animation frame | |
const cancel = animationFrame((timestamp) => { | |
console.log('Animation frame time:', timestamp); | |
// Perform animation tasks here | |
}); | |
// Cancel the subscription later | |
cancel(); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment