Initialization System Quick Reference
Overviewโ
The PianoRhythm initialization system prevents race conditions through a dependency-based state machine. This guide provides quick reference for common development tasks.
๐ Full Documentation: See Initialization System for comprehensive technical details and architecture overview.
Quick Startโ
Using the Initialization Serviceโ
import { useService } from "solid-services";
import InitializationService from "~/services/initialization.service";
import { InitializationStep } from "~/types/initialization.types";
const initService = useService(InitializationService);
// Execute a step
await initService.executeStep(InitializationStep.MyStep, {
execute: async () => {
// Your initialization logic here
},
validate: async () => {
// Optional validation
return true;
}
});
// Wait for a step to complete
await initService.waitForStep(InitializationStep.ClientLoaded);
// Check step status
if (initService.isStepCompleted(InitializationStep.AudioService)) {
// Step is complete
}
Common Patternsโ
Adding a New Initialization Stepโ
- Add to enum (
src/types/initialization.types.ts):
export enum InitializationStep {
// ... existing steps
MyNewStep = "my-new-step"
}
- Define dependencies (
src/services/initialization.service.ts):
const stepDependencies: Record<InitializationStep, InitializationStep[]> = {
// ... existing dependencies
[InitializationStep.MyNewStep]: [InitializationStep.AudioService],
};
- Execute in app loading (
src/routes/app-loading.tsx):
await initializationService().executeStep(InitializationStep.MyNewStep, {
execute: async () => {
// Implementation
await myService().initialize();
},
validate: async () => {
return myService().isReady();
}
});
Waiting for Dependenciesโ
// Wait for multiple dependencies
await Promise.all([
initService.waitForStep(InitializationStep.ClientLoaded),
initService.waitForStep(InitializationStep.AudioService)
]);
// Wait with custom timeout
await initService.waitForStep(InitializationStep.CoreWasm, 60000); // 60 seconds
Error Handlingโ
try {
await initService.executeStep(InitializationStep.MyStep, {
execute: async () => {
// This might fail
await riskyOperation();
}
});
} catch (error) {
// Handle initialization failure
console.error("Step failed:", error);
}
Step Dependencies Reference (Refactored Order)โ
UserGesture
โโโ CoreWasm
โ โโโ AppState
โ โ โโโ WebsocketIdentity
โ โ โ โโโ WebsocketConnection
โ โ โ โ โโโ AudioService โ Audio service before WelcomeEvent
โ โ โ โ โ โโโ SynthEngine
โ โ โ โ โ โ โโโ WelcomeEvent โ WelcomeEvent after audio setup
โ โ โ โ โ โ โ โโโ ClientLoaded
โ โ โ โ โ โ โ โ โโโ ClientSocketId
โ โ โ โ โ โ โ โ โ โโโ ClientAddedToSynth
โ โ โ โ โ โ โ โ โ โโโ Soundfont โ Soundfont after audio
โ โ โ โ โ โ โ โ โ โโโ AppSettings
โ โ โ โ โ โ โ โ โ โ โโโ UsersService
โ โ โ โ โ โ โ โ โ โ โ โโโ ChatService
โ โ โ โ โ โ โ โ โ โ โ โ โโโ RoomsService
โ โ โ โ โ โ โ โ โ โ โ โ โโโ MonitorService
โ โ โ โ โ โ โ โ โ โ โ โ โ โโโ Complete
Critical Stepsโ
ClientAddedToSynthโ
Most important step - prevents race conditions between synth engine and client socket ID:
await initializationService().executeStep(InitializationStep.ClientAddedToSynth, {
execute: async () => {
await raceTimeout(until(() => {
return appService().clientLoaded() &&
appService().getSocketID() &&
audioService().clientAdded();
}), DEFAULT_SERVICE_TIMEOUT, true, "Client never properly added to synth.");
},
validate: async () => {
return appService().clientLoaded() &&
!!appService().getSocketID() &&
audioService().clientAdded();
}
});
Testingโ
Unit Test Templateโ
import { describe, it, expect, beforeEach } from 'vitest';
import { createRoot } from 'solid-js';
import InitializationService from '~/services/initialization.service';
import { InitializationStep } from '~/types/initialization.types';
describe('MyInitializationStep', () => {
let initService: any;
beforeEach(() => {
createRoot(() => {
initService = InitializationService();
});
});
it('should execute my step successfully', async () => {
const mockExecutor = {
execute: vi.fn().mockResolvedValue(undefined)
};
await initService.executeStep(InitializationStep.MyStep, mockExecutor);
expect(mockExecutor.execute).toHaveBeenCalled();
expect(initService.isStepCompleted(InitializationStep.MyStep)).toBe(true);
});
});
Debuggingโ
Enable Debug Loggingโ
// In initialization service config
const config: InitializationConfig = {
enableLogging: true, // Enable detailed logging
defaultTimeout: 30000,
maxRetries: 3,
retryDelay: 1000
};
Common Debug Scenariosโ
- Step hanging: Check timeout configuration and dependencies
- Race conditions: Verify proper step ordering and dependencies
- Retry failures: Check error messages and increase retry count if needed
- Validation failures: Ensure validation logic matches execution results
Progress Monitoringโ
// Set up progress callback
initService.setProgressCallback((step, status, progress) => {
console.log(`Step ${step}: ${status} (${progress}%)`);
});
// Set up error callback
initService.setErrorCallback((step, error) => {
console.error(`Step ${step} failed:`, error);
});
Configurationโ
Default Settingsโ
const config: InitializationConfig = {
defaultTimeout: 30000, // 30 seconds
maxRetries: 3, // 3 retry attempts
retryDelay: 1000, // 1 second between retries
enableLogging: true // Debug logging enabled
};
Custom Timeoutsโ
// Set custom timeout for specific step
const state = initService.state();
const stepInfo = state.steps.get(InitializationStep.MyStep);
if (stepInfo) {
stepInfo.timeout = 60000; // 60 seconds
}
Best Practicesโ
- Always define dependencies - Never skip dependency declaration
- Use validation - Add validation for steps with verifiable outcomes
- Handle errors gracefully - Provide meaningful error messages
- Test thoroughly - Write unit tests for new steps
- Monitor progress - Use callbacks for user feedback
- Keep steps atomic - Each step should do one thing well
- Document dependencies - Explain why dependencies are needed
Common Pitfallsโ
- Circular dependencies - Will cause initialization to hang
- Missing dependencies - Can cause race conditions
- Overly long steps - Break down complex operations
- Insufficient error handling - Always handle potential failures
- Skipping validation - Can lead to false positive completions
Performance Tipsโ
- Parallel execution - Independent steps can run in parallel
- Lazy loading - Only initialize what's needed immediately
- Caching - Cache expensive initialization results
- Timeouts - Set appropriate timeouts for network operations
- Progress feedback - Keep users informed during long operations
For complete technical details, see Technical Documentation.