Files
ai.interactive.fiction/public/js/animation-queue-module.js

323 lines
9.7 KiB
JavaScript

/**
* Animation Queue Module
* Handles scheduling and executing animations with proper resource management
* and synchronization with TTS
*/
import { BaseModule } from './base-module.js';
class AnimationQueueModule extends BaseModule {
constructor() {
super('animation-queue', 'Animation Queue');
// Module dependencies
this.dependencies = [];
// Queue of scheduled animations/functions
this.timeoutQueue = [];
// Animation timing properties - use parent's config system
this.updateConfig({
speed: 1.0, // Speed multiplier for delays (1.0 = no scaling, delays are pre-calculated)
fastForwardEnabled: false
});
this.delay = 0; // Current accumulated delay
// Bind methods using parent's bindMethods utility
this.bindMethods([
'schedule',
'fastForward',
'clearAll',
'setSpeed',
'beginFastForward',
'endFastForward',
'emitAnimationComplete',
'cleanupStaleTasks'
]);
}
async initialize() {
try {
this.reportProgress(20, "Initializing Animation Queue");
// Listen for speed changes from UI
document.addEventListener('animation:speed:change', (event) => {
if (event.detail && typeof event.detail.speed === 'number') {
// Speed from UI is a rate multiplier (0.5-2.0 typically)
this.config.speed = event.detail.speed;
console.log(`AnimationQueue: Speed updated to ${this.config.speed}`);
}
});
this.reportProgress(100, "Animation Queue ready");
return true;
} catch (error) {
console.error("Error initializing Animation Queue:", error);
return false;
}
}
/**
* Schedule a function to execute after a delay, with optional TTS synchronization
* @param {Function} func - Function to execute
* @param {number} delay - Delay in milliseconds
* @param {Object} options - Optional parameters including TTS text
* @returns {number} - Timeout ID for cancellation
*/
schedule(func, delay, options = {}) {
if (typeof func !== 'function') {
console.error('AnimationQueue: Invalid function provided to schedule');
return -1;
}
// Adjust delay based on fast-forward or speed settings
const actualDelay = this.config.fastForwardEnabled ? 0 : Math.max(0, delay * this.config.speed);
// Record the delay for tracking
this.delay = Math.max(this.delay, delay);
// Create a timeout object
const timeoutObject = {
func: func,
delay: actualDelay,
timeoutId: null,
executed: false,
startTime: Date.now(),
// Add an execute method that marks the timeout as executed
execute: function() {
if (!this.executed) {
this.func();
this.executed = true;
}
}
};
// Add to queue
this.timeoutQueue.push(timeoutObject);
// If we're fast forwarding, execute immediately
if (this.config.fastForwardEnabled) {
timeoutObject.execute();
return -1; // No timeout ID since it executed immediately
}
// Schedule the timeout
timeoutObject.timeoutId = setTimeout(() => {
// Execute the function
timeoutObject.execute();
// Remove from queue
const index = this.timeoutQueue.indexOf(timeoutObject);
if (index !== -1) {
this.timeoutQueue.splice(index, 1);
}
// If queue is empty and no TTS is speaking, emit animation complete
if (this.timeoutQueue.length === 0 && true) {
this.emitAnimationComplete();
}
}, actualDelay);
return timeoutObject.timeoutId;
}
/**
* Emit an animation complete event
*/
emitAnimationComplete() {
// Only emit if queue is empty and no TTS is speaking
if (this.timeoutQueue.length === 0 && true) {
// Use parent's dispatchEvent method
this.dispatchEvent('ui:animation:complete', {
timestamp: Date.now()
});
}
}
/**
* Clean up any animation tasks that might have been missed
* (e.g. due to browser tab being inactive)
*/
cleanupStaleTasks() {
const now = Date.now();
const staleTasks = [];
// Find stale tasks
this.timeoutQueue.forEach(task => {
// If task has been running for more than 10 seconds, consider it stale
if (now - task.startTime > 10000 && !task.executed) {
staleTasks.push(task);
}
});
// Execute and remove stale tasks
staleTasks.forEach(task => {
console.log('AnimationQueue: Cleaning up stale task');
// Clear the timeout
if (task.timeoutId !== null) {
clearTimeout(task.timeoutId);
}
// Execute the task
task.execute();
// Remove from queue
const index = this.timeoutQueue.indexOf(task);
if (index !== -1) {
this.timeoutQueue.splice(index, 1);
}
});
}
/**
* Fast forward all pending animations
*/
fastForward() {
if (this.timeoutQueue.length === 0) {
console.log('AnimationQueue: No animations to fast forward');
return;
}
console.log(`AnimationQueue: Fast forwarding ${this.timeoutQueue.length} pending items`);
// Execute all pending animations immediately
this.timeoutQueue.forEach(timeout => {
// Clear the timeout
if (timeout.timeoutId !== null) {
clearTimeout(timeout.timeoutId);
timeout.timeoutId = null;
}
// Clear TTS flag
timeout.ttsSpeaking = false;
// Execute the function immediately
timeout.execute();
});
// Clear the queue
this.timeoutQueue = [];
// Reset delay
this.delay = 0;
// Update config using parent's updateConfig method
this.updateConfig({ fastForwardEnabled: false });
// Emit animation complete event
this.emitAnimationComplete();
// Log the fastforward completion
console.log('AnimationQueue: Fast forward complete');
// Use parent's dispatchEvent method
this.dispatchEvent('ui:animation:fastforward', { state: false });
}
/**
* Begin fast forwarding mode
*/
beginFastForward() {
if (this.config.fastForwardEnabled) return;
// Update config using parent's updateConfig method
this.updateConfig({ fastForwardEnabled: true });
// Use parent's dispatchEvent method
this.dispatchEvent('ui:animation:fastforward', { state: true });
console.log('AnimationQueue: Fast forward mode activated');
}
/**
* End fast forwarding mode
*/
endFastForward() {
if (!this.config.fastForwardEnabled) return;
// Update config using parent's updateConfig method
this.updateConfig({ fastForwardEnabled: false });
// Use parent's dispatchEvent method
this.dispatchEvent('ui:animation:fastforward', { state: false });
console.log('AnimationQueue: Fast forward mode deactivated');
}
/**
* Clear all scheduled animations without executing them
*/
clearAll() {
console.log(`Animation Queue: Clearing ${this.timeoutQueue.length} pending items`);
// Clear all timeouts
this.timeoutQueue.forEach(timeoutObject => {
if (timeoutObject.timeoutId !== null) {
clearTimeout(timeoutObject.timeoutId);
}
});
// Clear queue
this.timeoutQueue = [];
// Reset delay
this.delay = 0;
}
/**
* Set the animation speed
* @param {number} speed - Animation speed factor (lower is faster)
*/
setSpeed(speed) {
if (typeof speed !== 'number' || speed <= 0) {
console.error('Animation Queue: Invalid speed value');
return;
}
// Update config using parent's updateConfig method
this.updateConfig({ speed });
console.log(`Animation Queue: Speed set to ${speed}`);
}
/**
* Get the current animation speed
* @returns {number} - Current animation speed factor
*/
getSpeed() {
return this.config.speed;
}
/**
* Check if fast forwarding is active
* @returns {boolean} - Whether fast forwarding is active
*/
isFastForwarding() {
return this.config.fastForwardEnabled;
}
/**
* Get current queue length
* @returns {number} - Number of items in the queue
*/
getQueueLength() {
return this.timeoutQueue.length;
}
/**
* Get current accumulated delay
* @returns {number} - Current delay in milliseconds
*/
getCurrentDelay() {
return this.delay;
}
}
// Create the singleton instance
const AnimationQueue = new AnimationQueueModule();
// Export the module
export { AnimationQueue };