The game would freeze when starting the map, making it appear unresponsive to users.
The generateMap() function in src/world/generation.ts was processing 3,223 entities (including 3,000 grass instances) synchronously in a single JavaScript call. This blocked the main thread and prevented the browser from updating the UI, causing the freeze.
Implemented chunked asynchronous processing to allow the browser to remain responsive during map generation:
-
Async Function Signature (
src/world/generation.ts:280)export async function generateMap( weatherSystem: WeatherSystem, chunkSize: number = 100, onProgress?: (current: number, total: number) => void ): Promise<void>
-
Chunked Processing Loop
- Processes entities in batches (default 100 per chunk)
- Yields control to browser between chunks using
await new Promise(resolve => setTimeout(resolve, 0)) - Allows UI updates and prevents blocking
-
Progress Callbacks (
main.js:692)await generateMap(weatherSystem, 100, (current, total) => { const percent = Math.floor((current / total) * 100); startButton.innerHTML = `<span class="spinner"></span>Generating ${percent}%... 🍭`; });
-
Helper Function Extraction
- Created
processMapEntity()to handle individual entity creation - Made code more modular and testable
- Created
-
Async Procedural Generation
- Made
populateProceduralExtras()async with chunking (100 per chunk) - Ensures all generation stages remain non-blocking
- Made
-
Named Constants
- Added
DEFAULT_MAP_CHUNK_SIZE = 100for map entity chunks - Added
DEFAULT_PROCEDURAL_CHUNK_SIZE = 100for procedural extra chunks - Improves maintainability and consistency
- Added
- ❌ UI freeze for several seconds
- ❌ No visual feedback during generation
- ❌ Browser "Not Responding" warnings possible
- ❌ Poor user experience
- ✅ Responsive UI throughout generation
- ✅ Real-time progress updates (0-100%)
- ✅ Smooth, professional loading experience
- ✅ Browser remains interactive
- Map entities: 100 entities per chunk (~32 chunks for 3,223 entities)
- Procedural extras: 100 per chunk (4 chunks for 400 extras)
- Yield frequency: Every chunk via
setTimeout(resolve, 0)
With 100 entities per chunk and typical processing speed:
- Each chunk: ~10-50ms processing
- Yield between chunks: ~4-16ms
- Total time: Similar to synchronous (maybe +10-20% overhead)
- Key difference: Browser can update UI between chunks
Process Chunk 1 (100 entities) → Yield → UI Update →
Process Chunk 2 (100 entities) → Yield → UI Update →
...
Process Chunk N → Complete
- ✅ Code compiles without errors
- ✅ Page loads successfully
- ✅ No JavaScript runtime errors
⚠️ Full WebGPU testing requires compatible browser
- Web Workers: Move generation to background thread for even better performance
- Streaming Generation: Start rendering visible entities first, load distant objects later
- Incremental Loading: Load map in spatial chunks based on camera position
- IndexedDB Caching: Cache generated map for faster subsequent loads
src/world/generation.ts- Core generation logicmain.js- Button handler and progress UIassets/map.json- Map data (3,223 entities)