Refactored modules and updated loader.
This commit is contained in:
+503
-88
@@ -17,6 +17,7 @@ console.log('Module registry initialized and assigned to window.moduleRegistry')
|
||||
const ModuleState = {
|
||||
PENDING: 'PENDING',
|
||||
LOADING: 'LOADING',
|
||||
FETCHING: 'FETCHING', // Added new state for fetching resources
|
||||
WAITING: 'WAITING',
|
||||
INITIALIZING: 'INITIALIZING',
|
||||
FINISHED: 'FINISHED',
|
||||
@@ -37,6 +38,7 @@ const ModuleLoader = (function() {
|
||||
let moduleWeights = {};
|
||||
let createdModules = new Set(); // Track which modules we've created UI elements for
|
||||
let gameLoopModule = null; // Add variable to hold game loop instance
|
||||
let moduleTimings = {}; // Track timing data for modules
|
||||
|
||||
/**
|
||||
* Initialize the loader
|
||||
@@ -80,14 +82,15 @@ const ModuleLoader = (function() {
|
||||
* Setup event listeners for module communication
|
||||
*/
|
||||
function setupEventListeners() {
|
||||
// Listen for module progress events
|
||||
document.addEventListener('module:progress', handleModuleProgress);
|
||||
|
||||
// Listen for module state change events
|
||||
document.addEventListener('module:stateChange', handleModuleStateChange);
|
||||
|
||||
// Listen for module status message events
|
||||
document.addEventListener('module:message', handleModuleMessage);
|
||||
|
||||
// Listen for module progress events
|
||||
document.addEventListener('module:progress', handleModuleProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,35 +106,35 @@ const ModuleLoader = (function() {
|
||||
];
|
||||
|
||||
// Define modules with their weights
|
||||
const modulesToLoad = [
|
||||
let modulesToLoad = [
|
||||
// Core functionality modules
|
||||
{ id: 'persistence-manager', script: '/js/persistence-manager.js', weight: 40 },
|
||||
{ id: 'localization', script: '/js/localization.js', weight: 40 },
|
||||
{ id: 'text-processor', script: '/js/text-processor.js', weight: 40 },
|
||||
{ id: 'paragraph-layout', script: '/js/paragraph-layout.js', weight: 40 },
|
||||
{ id: 'layout-renderer', script: '/js/layout-renderer.js', weight: 45 }, // Add Layout Renderer module
|
||||
{ id: 'animation-queue', script: '/js/animation-queue.js', weight: 50 },
|
||||
{ id: 'persistence-manager', script: '/js/persistence-manager-module.js', weight: 12 },
|
||||
{ id: 'localization', script: '/js/localization-module.js', weight: 12 },
|
||||
{ id: 'text-processor', script: '/js/text-processor-module.js', weight: 15 },
|
||||
{ id: 'paragraph-layout', script: '/js/paragraph-layout-module.js', weight: 17 },
|
||||
{ id: 'layout-renderer', script: '/js/layout-renderer-module.js', weight: 13 }, // Add Layout Renderer module
|
||||
{ id: 'animation-queue', script: '/js/animation-queue-module.js', weight: 12 },
|
||||
|
||||
// Audio and TTS modules
|
||||
{ id: 'audio-manager', script: '/js/audio-manager.js', weight: 60 },
|
||||
{ id: 'kokoro', script: '/js/kokoro-tts-module.js', weight: 65 },
|
||||
{ id: 'browser', script: '/js/browser-tts-module.js', weight: 65 },
|
||||
{ id: 'elevenlabs', script: '/js/elevenlabs-tts-module.js', weight: 65 },
|
||||
{ id: 'openai', script: '/js/openai-tts-module.js', weight: 65 },
|
||||
{ id: 'tts-factory', script: '/js/tts-factory.js', weight: 70 }, // TTSFactory must be loaded before TTSPlayer
|
||||
{ id: 'tts', script: '/js/tts-player.js', weight: 75 },
|
||||
{ id: 'audio-manager', script: '/js/audio-manager-module.js', weight: 12 },
|
||||
{ id: 'kokoro', script: '/js/kokoro-tts-module.js', weight: 50 },
|
||||
{ id: 'browser', script: '/js/browser-tts-module.js', weight: 12 },
|
||||
{ id: 'elevenlabs', script: '/js/elevenlabs-tts-module.js', weight: 12 },
|
||||
{ id: 'openai', script: '/js/openai-tts-module.js', weight: 12 },
|
||||
{ id: 'tts-factory', script: '/js/tts-factory-module.js', weight: 13 }, // TTSFactory must be loaded before TTSPlayer
|
||||
{ id: 'tts-player', script: '/js/tts-player-module.js', weight: 13 },
|
||||
|
||||
// UI and interaction modules
|
||||
{ id: 'text-buffer', script: '/js/text-buffer.js', weight: 50 },
|
||||
{ id: 'ui-effects', script: '/js/ui-effects.js', weight: 50 }, // Add UI Effects module
|
||||
{ id: 'ui-input-handler', script: '/js/ui-input-handler.js', weight: 50 }, // Add UI Input Handler module
|
||||
{ id: 'ui-display-handler', script: '/js/ui-display-handler.js', weight: 60 }, // Add UI Display Handler module
|
||||
{ id: 'ui-controller', script: '/js/ui-controller.js', weight: 100 },
|
||||
{ id: 'options-ui', script: '/js/options-ui.js', weight: 40 },
|
||||
{ id: 'socket-client', script: '/js/socket-client.js', weight: 60 },
|
||||
{ id: 'text-buffer', script: '/js/text-buffer-module.js', weight: 12 },
|
||||
{ id: 'ui-effects', script: '/js/ui-effects-module.js', weight: 12 }, // Add UI Effects module
|
||||
{ id: 'ui-input-handler', script: '/js/ui-input-handler-module.js', weight: 27 }, // Add UI Input Handler module
|
||||
{ id: 'ui-display-handler', script: '/js/ui-display-handler-module.js', weight: 27 }, // Add UI Display Handler module
|
||||
{ id: 'ui-controller', script: '/js/ui-controller-module.js', weight: 27 },
|
||||
{ id: 'options-ui', script: '/js/options-ui-module.js', weight: 13 },
|
||||
{ id: 'socket-client', script: '/js/socket-client-module.js', weight: 17 },
|
||||
|
||||
// Main game module - should be last to load
|
||||
{ id: 'game-loop', script: '/js/game-loop.js', weight: 25 }
|
||||
{ id: 'game-loop', script: '/js/game-loop-module.js', weight: 27 }
|
||||
];
|
||||
|
||||
// Store module weights for progress calculation
|
||||
@@ -141,7 +144,7 @@ const ModuleLoader = (function() {
|
||||
|
||||
// Create a module list entry for each module
|
||||
modulesToLoad.forEach(module => {
|
||||
createModuleListItem(module.id, getModuleNameFromId(module.id));
|
||||
createModuleItem(module.id, getModuleNameFromId(module.id));
|
||||
});
|
||||
|
||||
// Load dependencies first
|
||||
@@ -150,7 +153,271 @@ const ModuleLoader = (function() {
|
||||
|
||||
// Load each module script
|
||||
const loadPromises = modulesToLoad.map(module => loadScript(module.script));
|
||||
return Promise.all(loadPromises);
|
||||
const loadResult = await Promise.all(loadPromises);
|
||||
|
||||
// Wait briefly for modules to register
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Analyze dependencies and detect circular references
|
||||
analyzeModuleDependencies();
|
||||
|
||||
return loadResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze module dependencies to detect circular references and print detailed diagnostics
|
||||
*/
|
||||
function analyzeModuleDependencies() {
|
||||
const registry = window.moduleRegistry;
|
||||
if (!registry || !registry.modules) {
|
||||
console.error("Module Registry not available for dependency analysis");
|
||||
return;
|
||||
}
|
||||
|
||||
// Build dependency graph
|
||||
const graph = {};
|
||||
|
||||
// Initialize the graph with all modules
|
||||
Object.keys(registry.modules).forEach(moduleId => {
|
||||
graph[moduleId] = [];
|
||||
});
|
||||
|
||||
// Add dependencies to graph
|
||||
Object.entries(registry.modules).forEach(([moduleId, module]) => {
|
||||
if (module.dependencies && Array.isArray(module.dependencies)) {
|
||||
module.dependencies.forEach(depId => {
|
||||
// Check if dependency exists
|
||||
if (!registry.modules[depId]) {
|
||||
console.warn(`Module ${moduleId} depends on missing module ${depId}`);
|
||||
} else {
|
||||
graph[moduleId].push(depId);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Detect circular dependencies using DFS
|
||||
const detectCycles = () => {
|
||||
const visited = {};
|
||||
const recStack = {};
|
||||
const cycles = [];
|
||||
const pathStack = [];
|
||||
|
||||
const dfs = (node, path = []) => {
|
||||
// Node is already in recursion stack - we found a cycle
|
||||
if (recStack[node]) {
|
||||
const cycleStart = path.indexOf(node);
|
||||
if (cycleStart !== -1) {
|
||||
const cycle = path.slice(cycleStart).concat(node);
|
||||
cycles.push(cycle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If already visited and not in recursion, no cycle through this node
|
||||
if (visited[node]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark node as visited and add to recursion stack
|
||||
visited[node] = true;
|
||||
recStack[node] = true;
|
||||
pathStack.push(node);
|
||||
|
||||
// Visit all neighbors
|
||||
const hasCycle = graph[node].some(neighbor => {
|
||||
return dfs(neighbor, [...pathStack]);
|
||||
});
|
||||
|
||||
// Remove from recursion stack and path
|
||||
recStack[node] = false;
|
||||
pathStack.pop();
|
||||
|
||||
return hasCycle;
|
||||
};
|
||||
|
||||
// Start DFS from each node
|
||||
Object.keys(graph).forEach(node => {
|
||||
if (!visited[node]) {
|
||||
dfs(node);
|
||||
}
|
||||
});
|
||||
|
||||
return cycles;
|
||||
};
|
||||
|
||||
// Find all circular dependencies
|
||||
const cycles = detectCycles();
|
||||
|
||||
// Display detailed information about circular dependencies
|
||||
if (cycles.length > 0) {
|
||||
console.group("%cCircular Dependencies Detected", "color: red; font-weight: bold");
|
||||
cycles.forEach((cycle, index) => {
|
||||
console.log(`%cCircular Dependency Chain ${index + 1}:`, "font-weight: bold");
|
||||
|
||||
// Print the cycle with dependency details
|
||||
cycle.forEach((moduleId, i) => {
|
||||
const module = registry.modules[moduleId] || { name: 'Unknown' };
|
||||
const nextModuleId = cycle[(i + 1) % cycle.length];
|
||||
const nextModule = registry.modules[nextModuleId] || { name: 'Unknown' };
|
||||
console.log(
|
||||
`%c${moduleId}%c (${module.name}) depends on %c${nextModuleId}%c (${nextModule.name})`,
|
||||
"color: blue; font-weight: bold",
|
||||
"color: black",
|
||||
"color: blue; font-weight: bold",
|
||||
"color: black"
|
||||
);
|
||||
|
||||
// Print the actual dependencies declared by this module
|
||||
if (module.dependencies && Array.isArray(module.dependencies)) {
|
||||
console.log(` Dependencies declared by ${moduleId}:`, module.dependencies);
|
||||
}
|
||||
});
|
||||
|
||||
// Suggest potential solutions
|
||||
console.log('%cPossible solutions:', 'font-weight: bold');
|
||||
console.log('1. Remove one of the dependencies from the chain');
|
||||
console.log('2. Use dynamic dependency resolution instead of static dependencies');
|
||||
console.log('3. Create an interface module that both modules depend on');
|
||||
console.log('4. Refactor module responsibilities to eliminate circular needs');
|
||||
});
|
||||
console.groupEnd();
|
||||
} else {
|
||||
console.log("%cNo circular dependencies detected", "color: green; font-weight: bold");
|
||||
}
|
||||
|
||||
// Calculate an optimized loading order using topological sort
|
||||
const calculateOptimalLoadOrder = () => {
|
||||
const result = [];
|
||||
const visited = {};
|
||||
const temp = {}; // Temporary marks for detecting cycles
|
||||
|
||||
const visit = (node) => {
|
||||
// Node is already in result, skip
|
||||
if (visited[node]) return;
|
||||
|
||||
// If temp is true, we have a cycle
|
||||
if (temp[node]) {
|
||||
console.warn(`Skipping cycle involving ${node} during topological sort`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark node as being processed
|
||||
temp[node] = true;
|
||||
|
||||
// Process all dependencies first
|
||||
if (graph[node]) {
|
||||
graph[node].forEach(dep => visit(dep));
|
||||
}
|
||||
|
||||
// Mark as visited and add to result
|
||||
temp[node] = false;
|
||||
visited[node] = true;
|
||||
result.push(node);
|
||||
};
|
||||
|
||||
// Visit all nodes
|
||||
Object.keys(graph).forEach(node => {
|
||||
if (!visited[node]) {
|
||||
visit(node);
|
||||
}
|
||||
});
|
||||
|
||||
return result.reverse(); // Reverse to get correct order
|
||||
};
|
||||
|
||||
const optimalOrder = calculateOptimalLoadOrder();
|
||||
|
||||
// Print the optimal loading order
|
||||
console.group("%cOptimal Module Loading Order", "color: green; font-weight: bold");
|
||||
optimalOrder.forEach((moduleId, index) => {
|
||||
const module = registry.modules[moduleId] || { name: 'Unknown' };
|
||||
console.log(`${index + 1}. %c${moduleId}%c (${module.name})`,
|
||||
"font-weight: bold", "font-weight: normal");
|
||||
});
|
||||
console.groupEnd();
|
||||
|
||||
// Compare with actual loading order and suggest improvements
|
||||
console.log("%cRecommended changes to module loading order in loader.js:", "font-weight: bold");
|
||||
if (optimalOrder.length > 0) {
|
||||
console.log("const modulesToLoad = [");
|
||||
optimalOrder.forEach((moduleId, index) => {
|
||||
// Generate a reasonable path based on the moduleId
|
||||
const scriptPath = `/js/${moduleId}.js`;
|
||||
console.log(` { id: '${moduleId}', script: '${scriptPath}', weight: ${index * 5 + 5} },`);
|
||||
});
|
||||
console.log("];");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort modules by their dependencies to create an optimal loading order
|
||||
* This function can be used before initialization to ensure modules are loaded in the correct order
|
||||
* @param {Array} modules - Array of module objects with id and dependencies
|
||||
* @returns {Array} - Sorted array of modules
|
||||
*/
|
||||
function sortModulesByDependencies(modules) {
|
||||
// Build a dependency graph
|
||||
const graph = {};
|
||||
|
||||
// Initialize the graph with all modules
|
||||
modules.forEach(module => {
|
||||
graph[module.id] = { module, dependencies: [] };
|
||||
});
|
||||
|
||||
// Add dependencies to the graph
|
||||
// We need to do this in a second pass because some modules might reference others that come later in the array
|
||||
modules.forEach(module => {
|
||||
if (module.dependencies && Array.isArray(module.dependencies)) {
|
||||
module.dependencies.forEach(depId => {
|
||||
if (graph[depId]) {
|
||||
graph[module.id].dependencies.push(depId);
|
||||
} else {
|
||||
console.warn(`Module ${module.id} depends on unknown module ${depId}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Perform a topological sort
|
||||
const result = [];
|
||||
const visited = {};
|
||||
const temp = {}; // For cycle detection
|
||||
|
||||
function visit(nodeId) {
|
||||
// Node is already in result, skip
|
||||
if (visited[nodeId]) return;
|
||||
|
||||
// If temp is true, we have a cycle
|
||||
if (temp[nodeId]) {
|
||||
console.warn(`Circular dependency detected involving ${nodeId}. Skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark node as being processed
|
||||
temp[nodeId] = true;
|
||||
|
||||
// Process all dependencies first
|
||||
if (graph[nodeId] && graph[nodeId].dependencies) {
|
||||
graph[nodeId].dependencies.forEach(depId => {
|
||||
visit(depId);
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as visited and add to result
|
||||
temp[nodeId] = false;
|
||||
visited[nodeId] = true;
|
||||
result.push(graph[nodeId].module);
|
||||
}
|
||||
|
||||
// Visit all nodes
|
||||
Object.keys(graph).forEach(nodeId => {
|
||||
if (!visited[nodeId]) {
|
||||
visit(nodeId);
|
||||
}
|
||||
});
|
||||
|
||||
return result.reverse(); // Reverse for correct dependency order
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,12 +426,78 @@ const ModuleLoader = (function() {
|
||||
* @returns {Promise} - Resolves when script is loaded
|
||||
*/
|
||||
function loadScript(src) {
|
||||
// Extract module ID from src path
|
||||
const moduleId = src.split('/').pop().replace('.js', '');
|
||||
|
||||
// Update state to LOADING if this is a module
|
||||
if (moduleId && moduleWeights[moduleId]) {
|
||||
// Ensure module item exists in UI
|
||||
const moduleItem = document.getElementById(`module-${moduleId}`);
|
||||
if (!moduleItem) {
|
||||
createModuleItem(moduleId, getModuleNameFromId(moduleId));
|
||||
}
|
||||
|
||||
// Set initial progress to 0%
|
||||
handleModuleProgress({
|
||||
detail: { moduleId, progress: 0 }
|
||||
});
|
||||
|
||||
// Set state to loading
|
||||
updateModuleState(moduleId, ModuleState.LOADING);
|
||||
|
||||
// Record start time for this module (for timing data)
|
||||
if (!moduleTimings[moduleId]) {
|
||||
moduleTimings[moduleId] = {};
|
||||
}
|
||||
moduleTimings[moduleId].startTime = performance.now();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.type = 'module';
|
||||
script.src = src;
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
|
||||
|
||||
// Monitor loading progress using a fake progress indicator (0-10%)
|
||||
if (moduleId && moduleWeights[moduleId]) {
|
||||
let loadProgress = 0;
|
||||
const progressInterval = setInterval(() => {
|
||||
loadProgress = Math.min(loadProgress + 1, 9); // Max 9% until actual load completes
|
||||
handleModuleProgress({
|
||||
detail: { moduleId, progress: loadProgress }
|
||||
});
|
||||
}, 100);
|
||||
|
||||
script.onload = () => {
|
||||
clearInterval(progressInterval);
|
||||
// Final progress at 10% when script is loaded
|
||||
handleModuleProgress({
|
||||
detail: { moduleId, progress: 10 }
|
||||
});
|
||||
|
||||
// Record script load complete time
|
||||
if (moduleTimings[moduleId]) {
|
||||
moduleTimings[moduleId].scriptLoadTime = performance.now();
|
||||
}
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
script.onerror = (error) => {
|
||||
clearInterval(progressInterval);
|
||||
updateModuleState(moduleId, ModuleState.ERROR);
|
||||
|
||||
// Record error time
|
||||
if (moduleTimings[moduleId]) {
|
||||
moduleTimings[moduleId].errorTime = performance.now();
|
||||
}
|
||||
|
||||
reject(new Error(`Failed to load script: ${src}`));
|
||||
};
|
||||
} else {
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
|
||||
}
|
||||
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
@@ -292,27 +625,50 @@ const ModuleLoader = (function() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a module list item in the UI
|
||||
* Create a module list item
|
||||
* @param {string} id - Module ID
|
||||
* @param {string} name - Module display name
|
||||
* @param {string} name - Module name
|
||||
* @returns {HTMLLIElement} List item element
|
||||
*/
|
||||
function createModuleListItem(id, name) {
|
||||
if (!modulesList) return;
|
||||
|
||||
// Check if we've already created this module item
|
||||
if (createdModules.has(id)) return;
|
||||
|
||||
// Mark this module as created
|
||||
function createModuleItem(id, name) {
|
||||
if (!modulesList || createdModules.has(id)) return null;
|
||||
createdModules.add(id);
|
||||
|
||||
const moduleItem = document.createElement('li');
|
||||
moduleItem.className = 'module-item';
|
||||
moduleItem.id = `module-${id}`;
|
||||
moduleItem.innerHTML = `
|
||||
<span class="module-name">${name}</span>
|
||||
<span class="module-status status-pending">Pending</span>
|
||||
`;
|
||||
modulesList.appendChild(moduleItem);
|
||||
// Create elements dynamically
|
||||
const li = document.createElement('li');
|
||||
li.id = `module-${id}`;
|
||||
li.className = 'module-item';
|
||||
|
||||
// Set initial progress to 0 using CSS variable
|
||||
li.style.setProperty('--progress-width', '0%');
|
||||
|
||||
// Create module name element
|
||||
const moduleName = document.createElement('div');
|
||||
moduleName.className = 'module-name';
|
||||
moduleName.textContent = name;
|
||||
|
||||
// Create module status element
|
||||
const moduleStatus = document.createElement('div');
|
||||
moduleStatus.className = 'module-status status-pending';
|
||||
moduleStatus.textContent = 'Pending';
|
||||
|
||||
// Create module status details element
|
||||
const moduleDetails = document.createElement('div');
|
||||
moduleDetails.className = 'module-status-detail';
|
||||
moduleDetails.textContent = '';
|
||||
|
||||
// Append all elements to the list item
|
||||
li.appendChild(moduleName);
|
||||
li.appendChild(moduleStatus);
|
||||
li.appendChild(moduleDetails);
|
||||
|
||||
// Force a reflow to ensure animation works
|
||||
void li.offsetWidth;
|
||||
|
||||
// Add to modules list
|
||||
modulesList.appendChild(li);
|
||||
|
||||
return li;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,7 +676,17 @@ const ModuleLoader = (function() {
|
||||
*/
|
||||
function handleModuleProgress(event) {
|
||||
const { moduleId, progress } = event.detail;
|
||||
updateModuleProgress(moduleId, progress);
|
||||
|
||||
// Get the module element
|
||||
const moduleItem = document.querySelector(`#module-${moduleId}`);
|
||||
if (moduleItem) {
|
||||
// Update module item's before pseudo-element width using CSS variable
|
||||
moduleItem.style.setProperty('--progress-width', `${progress}%`);
|
||||
|
||||
// Also set a data attribute for browsers that don't support CSS variables
|
||||
moduleItem.setAttribute('data-progress', progress);
|
||||
}
|
||||
|
||||
updateOverallProgress();
|
||||
}
|
||||
|
||||
@@ -329,9 +695,37 @@ const ModuleLoader = (function() {
|
||||
*/
|
||||
function handleModuleStateChange(event) {
|
||||
const { moduleId, state } = event.detail;
|
||||
// Update UI with the new state
|
||||
updateModuleState(moduleId, state);
|
||||
|
||||
// If module is finished, update overall completion
|
||||
if (state === ModuleState.FINISHED) {
|
||||
// This triggers only when ALL modules are complete, so modules would be removed too quickly
|
||||
// if (areAllModulesComplete()) {
|
||||
// hideLoadingOverlay();
|
||||
// }
|
||||
const moduleItem = document.getElementById(`module-${moduleId}`);
|
||||
if (moduleItem) {
|
||||
// Ensure module-finished class is added with a small delay to avoid race conditions
|
||||
setTimeout(() => {
|
||||
moduleItem.classList.add('module-finished');
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
updateOverallProgress();
|
||||
|
||||
// Record timing data
|
||||
if (moduleTimings[moduleId]) {
|
||||
moduleTimings[moduleId][state] = performance.now();
|
||||
|
||||
// If the module is finished or has an error, calculate total time
|
||||
if (state === ModuleState.FINISHED || state === ModuleState.ERROR) {
|
||||
const startTime = moduleTimings[moduleId].startTime || 0;
|
||||
moduleTimings[moduleId].totalTime = performance.now() - startTime;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all modules are finished after each state change
|
||||
checkAllFinished();
|
||||
}
|
||||
@@ -349,33 +743,45 @@ const ModuleLoader = (function() {
|
||||
*/
|
||||
function checkAllFinished() {
|
||||
const modules = moduleRegistry.getAllModules();
|
||||
const allFinished = Object.values(modules).every(module => {
|
||||
|
||||
// Add detailed logging of all module states
|
||||
console.log('Module states:', Object.entries(modules).map(([id, module]) => {
|
||||
return `${id}: ${module.getState()}`;
|
||||
}));
|
||||
|
||||
// First determine which modules are pending
|
||||
const pendingModules = Object.values(modules).filter(module => {
|
||||
const state = module.getState();
|
||||
return state === ModuleState.FINISHED || state === ModuleState.ERROR;
|
||||
return state !== ModuleState.FINISHED && state !== ModuleState.ERROR;
|
||||
});
|
||||
|
||||
// Log pending modules (if any)
|
||||
if (pendingModules.length > 0) {
|
||||
console.log('Modules still pending:', pendingModules.map(m => `${m.id} (${m.getState()})`));
|
||||
} else {
|
||||
console.log('No modules pending - all modules are in FINISHED or ERROR state');
|
||||
}
|
||||
|
||||
// Determine if all modules are finished based on pendingModules
|
||||
const allFinished = pendingModules.length === 0;
|
||||
|
||||
if (allFinished && !isLoadingComplete) {
|
||||
console.log('All modules finished loading. Proceeding to finalization...');
|
||||
finalizeLoading();
|
||||
} else if (!allFinished) {
|
||||
// Log which modules are not finished yet
|
||||
const pendingModules = Object.values(modules).filter(module => {
|
||||
const state = module.getState();
|
||||
return state !== ModuleState.FINISHED && state !== ModuleState.ERROR;
|
||||
});
|
||||
|
||||
if (pendingModules.length > 0) {
|
||||
console.log('Modules still pending:', pendingModules.map(m => `${m.id} (${m.getState()})`))
|
||||
}
|
||||
} else if (allFinished && isLoadingComplete) {
|
||||
console.log('All modules are finished but isLoadingComplete is already true');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finalize the loading process
|
||||
*/
|
||||
function finalizeLoading() {
|
||||
console.log('Loading completed. Finalizing...');
|
||||
try {
|
||||
// Display timing data
|
||||
displayModuleTimings();
|
||||
|
||||
completeFinalization();
|
||||
} catch (error) {
|
||||
console.error('Error during finalization:', error);
|
||||
@@ -409,6 +815,36 @@ const ModuleLoader = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display module timing data to help with weight optimization
|
||||
*/
|
||||
function displayModuleTimings() {
|
||||
console.group('Module Loading Performance Data');
|
||||
console.log('This data can be used to optimize module weights:');
|
||||
|
||||
// Format timing data as tuples [moduleId, totalTime, weight]
|
||||
const timingData = Object.entries(moduleTimings)
|
||||
.filter(([moduleId, timing]) => timing.totalTime !== undefined)
|
||||
.map(([moduleId, timing]) => {
|
||||
return [moduleId, Math.round(timing.totalTime), moduleWeights[moduleId] || 1];
|
||||
})
|
||||
.sort((a, b) => b[1] - a[1]); // Sort by total time (descending)
|
||||
|
||||
// Create a table for easy reading
|
||||
console.table(timingData.map(([moduleId, time, weight]) => {
|
||||
return {
|
||||
moduleId,
|
||||
'totalTime (ms)': time,
|
||||
'current weight': weight,
|
||||
'suggested weight': Math.max(1, Math.round(time / 50)) // Simple heuristic based on time
|
||||
};
|
||||
}));
|
||||
|
||||
console.log('Raw timing data:');
|
||||
console.table(moduleTimings);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the loading overlay with a fade out animation
|
||||
* Then completely remove it from the DOM
|
||||
@@ -445,16 +881,6 @@ const ModuleLoader = (function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Fallback in case the transition event doesn't fire
|
||||
setTimeout(() => {
|
||||
if (loadingOverlay && loadingOverlay.parentNode) {
|
||||
console.log('Module Loader: Removing overlay from DOM (fallback)');
|
||||
loadingOverlay.parentNode.removeChild(loadingOverlay);
|
||||
loadingOverlay = null;
|
||||
}
|
||||
// Execute callback in fallback as well
|
||||
if (callback) callback();
|
||||
}, 1000); // Wait longer than the transition duration
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -477,11 +903,12 @@ const ModuleLoader = (function() {
|
||||
'status-waiting',
|
||||
'status-initializing',
|
||||
'status-finished',
|
||||
'status-error'
|
||||
'status-error',
|
||||
'status-fetching'
|
||||
);
|
||||
|
||||
// Add appropriate class and text
|
||||
let statusText = '';
|
||||
// Set the new status
|
||||
let statusText = 'Unknown';
|
||||
switch (state) {
|
||||
case ModuleState.PENDING:
|
||||
statusElement.classList.add('status-pending');
|
||||
@@ -491,6 +918,10 @@ const ModuleLoader = (function() {
|
||||
statusElement.classList.add('status-loading');
|
||||
statusText = 'Loading';
|
||||
break;
|
||||
case ModuleState.FETCHING:
|
||||
statusElement.classList.add('status-fetching');
|
||||
statusText = 'Fetching';
|
||||
break;
|
||||
case ModuleState.WAITING:
|
||||
statusElement.classList.add('status-waiting');
|
||||
statusText = 'Waiting';
|
||||
@@ -508,25 +939,9 @@ const ModuleLoader = (function() {
|
||||
statusText = 'Error';
|
||||
break;
|
||||
}
|
||||
|
||||
statusElement.textContent = statusText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the progress of a module
|
||||
* @param {string} id - Module ID
|
||||
* @param {number} progress - Progress percentage (0-100)
|
||||
*/
|
||||
function updateModuleProgress(id, progress) {
|
||||
// Module states are now managed by the module itself
|
||||
|
||||
// Update any additional UI elements for module progress if needed
|
||||
const moduleItem = document.getElementById(`module-${id}`);
|
||||
if (moduleItem) {
|
||||
// Update progress display if needed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the status text of a module in the UI
|
||||
* @param {string} id - Module ID
|
||||
|
||||
Reference in New Issue
Block a user