-
Notifications
You must be signed in to change notification settings - Fork 0
[Batch 3] Save manager: async thread, dirty tracking, auto-save #380
Copy link
Copy link
Closed
Labels
batch-3Batch 3: IntegrationBatch 3: IntegrationbugSomething isn't workingSomething isn't workingdocumentationImprovements or additions to documentationImprovements or additions to documentationenhancementNew feature or requestNew feature or requestquestionFurther information is requestedFurther information is requestedworld/persistenceWorld save/load systemWorld save/load system
Description
Summary
Implement a background save manager that orchestrates chunk serialization and region file writes. This ties together the region file format (#372) and chunk serializer (#377) into a working save/load system.
Depends on: #372 (region file format), #377 (chunk serialization)
Current State
No chunk data leaves the process. When the player closes the game, all changes are lost. The save manager needs to:
- Track which chunks have been modified (dirty flag)
- Periodically serialize and write dirty chunks to region files
- Auto-save on world close
- Load chunks from region files when they're needed (before generating fresh terrain)
Implementation Plan
Step 1: Save directory structure
~/.config/zigcraft/saves/
<world-name>/
level.dat — world metadata (seed, generator, created, modified)
regions/
r.0.0.mca
r.-1.0.mca
...
Step 2: Level metadata
const LevelData = struct {
seed: u64,
generator_name: []const u8, // "overworld", "flat"
created_timestamp: i64,
last_played_timestamp: i64,
spawn_x: i32,
spawn_z: i32,
// Future: game mode, time of day, player position
};- Serialize as JSON (simple, human-readable, stdlib support)
- Write on world create, update on save
Step 3: Dirty tracking
- Add
dirty: boolfield toChunkDatainchunk_storage.zig - Set dirty on:
- Block break (
player.breakTargetBlock()) - Block place (
player.placeBlock()) - Light updates (future)
- Block break (
- Clear dirty after successful save
Step 4: Save thread
const SaveManager = struct {
thread: Thread,
queue: SaveQueue, // chunks pending save
region_cache: RegionCache, // open region files, LRU-evicted
allocator: Allocator,
running: bool,
pub fn start(allocator: Allocator, save_dir: []const u8) !*SaveManager;
pub fn stop(self: *SaveManager) void; // flush remaining, join thread
pub fn saveChunk(self: *SaveManager, chunk: *const Chunk) void; // enqueue
pub fn loadChunk(self: *SaveManager, cx: i32, cz: i32) ?Chunk; // sync load
};- Background thread polls queue, dequeues dirty chunks, serializes, writes to region file
- Region files opened lazily, cached (max ~16 open at once, LRU close)
saveChunk()is non-blocking: enqueues and returns immediatelyloadChunk()is synchronous: blocks until read + deserialize complete
Step 5: Integration with WorldStreamer
WorldStreamer.generateChunk(): trySaveManager.loadChunk()first, fall back to worldgen if not foundWorldStreamer.update(): periodically scan for dirty chunks and enqueue savesGameSession.deinit(): callSaveManager.stop()which flushes all pending saves
Step 6: Auto-save
- Every 60 seconds: scan all loaded chunks for dirty flag, enqueue saves
- On session close: force-save all dirty chunks
- On game exit:
stop()ensures thread completes before process exits
Files to Create
src/world/persistence/save_manager.zig— main orchestratorsrc/world/persistence/level_data.zig— level metadata
Files to Modify
src/world/chunk_storage.zig— dirty flag on ChunkDatasrc/world/world_streamer.zig— load-from-save integration, periodic save triggerssrc/game/session.zig— create SaveManager on init, stop on deinit
Testing
- Create world, modify blocks, close → reopen → modifications persisted
- Auto-save fires after 60 seconds of play
- Fresh chunk not in save directory generates normally via worldgen
- Region files created on first write to that region
- level.dat created with correct seed and generator
- Multiple rapid modifications to same chunk → only latest state saved
- Save thread handles errors gracefully (full disk, corrupt region file)
Roadmap: docs/PERFORMANCE_ROADMAP.md — Batch 3, Issue 1B-3
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
batch-3Batch 3: IntegrationBatch 3: IntegrationbugSomething isn't workingSomething isn't workingdocumentationImprovements or additions to documentationImprovements or additions to documentationenhancementNew feature or requestNew feature or requestquestionFurther information is requestedFurther information is requestedworld/persistenceWorld save/load systemWorld save/load system