Skip to content

[Batch 3] Save manager: async thread, dirty tracking, auto-save #380

@MichaelFisher1997

Description

@MichaelFisher1997

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:

  1. Track which chunks have been modified (dirty flag)
  2. Periodically serialize and write dirty chunks to region files
  3. Auto-save on world close
  4. 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: bool field to ChunkData in chunk_storage.zig
  • Set dirty on:
    • Block break (player.breakTargetBlock())
    • Block place (player.placeBlock())
    • Light updates (future)
  • 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 immediately
  • loadChunk() is synchronous: blocks until read + deserialize complete

Step 5: Integration with WorldStreamer

  • WorldStreamer.generateChunk(): try SaveManager.loadChunk() first, fall back to worldgen if not found
  • WorldStreamer.update(): periodically scan for dirty chunks and enqueue saves
  • GameSession.deinit(): call SaveManager.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 orchestrator
  • src/world/persistence/level_data.zig — level metadata

Files to Modify

  • src/world/chunk_storage.zig — dirty flag on ChunkData
  • src/world/world_streamer.zig — load-from-save integration, periodic save triggers
  • src/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

Metadata

Metadata

Assignees

No one assigned

    Labels

    batch-3Batch 3: IntegrationbugSomething isn't workingdocumentationImprovements or additions to documentationenhancementNew feature or requestquestionFurther information is requestedworld/persistenceWorld save/load system

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions