Skip to content

refactor: split save_load_mixin.zig into save/load modules (behavior-preserving) - #694

Merged
apotema merged 1 commit into
mainfrom
refactor/split-saveload
Jul 5, 2026
Merged

apotema merged 1 commit into
mainfrom
refactor/split-saveload

Conversation

@apotema

@apotema apotema commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

What

Pure, behavior-preserving extraction of src/game/save_load_mixin.zig (~1156 lines) along the SAVE vs LOAD seam. No logic, signature, or public-API changes — code moved verbatim, only shared-helper call sites re-pointed at the common mixin.

How it's split

File Lines Contents
src/game/save_load_mixin.zig 31 Thin composition barrel — re-exports the 5 public fns
src/game/save_load/common.zig 61 entityToU64, isRegistered, collectEntities, SAVE_VERSION (shared by both directions, not duplicated)
src/game/save_load/save.zig 303 saveGameState + private writeJsonString (save-only)
src/game/save_load/load.zig 849 loadGameState (Phase 1a/1b/1c → Phase 2 kept together), post-load render gate, load-only JSON accessors

Each is a Mixin(Game) instantiated by the barrel against the same Game — the exact idiom loop_mixin already uses for AtlasMixin/InputEventsMixin. Shared helpers are reached via Common.<fn>.

Public surface (unchanged)

game.zig aliases SaveLoadMixin.{saveGameState, loadGameState, armPostLoadRenderGate, updatePostLoadRenderGate, releaseLoadAcquired} — all still resolve, so every call site works unchanged. Load's phase sequence stays intact in one file (load-path symmetry preserved).

Verification

  • zig build — compiles on 0.16.0
  • zig build test — green (incl. save_load_mixin_test.zig + save_load_two_phase_test.zig)
  • zig build spec — green
  • zig fmt --check — clean on all new/changed files

Do not merge without review.

https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

…preserving)

Pure extraction along the SAVE vs LOAD seam. `save_load_mixin.zig` becomes a
thin composition barrel re-exporting the same 5 public functions
(saveGameState, loadGameState, armPostLoadRenderGate, updatePostLoadRenderGate,
releaseLoadAcquired), so game.zig's aliases resolve unchanged.

- src/game/save_load/common.zig — entityToU64, isRegistered, collectEntities
  and SAVE_VERSION (shared by both directions, not duplicated).
- src/game/save_load/save.zig — saveGameState + private writeJsonString.
- src/game/save_load/load.zig — loadGameState with its Phase 1a/1b/1c → Phase 2
  sequence kept together, the post-load render gate, and the load-only JSON
  accessors.

Each is a Mixin(Game) instantiated by the barrel against the same Game (the
idiom loop_mixin uses for AtlasMixin/InputEventsMixin). Shared helpers reached
via Common.<fn>. No logic or signature changes.

zig build / zig build test / zig build spec all green on 0.16.0; zig fmt clean.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2f54ac1d-a682-453b-8c86-f278da1a76a0

📥 Commits

Reviewing files that changed from the base of the PR and between d8a9f33 and 6e4c9e5.

📒 Files selected for processing (4)
  • src/game/save_load/common.zig
  • src/game/save_load/load.zig
  • src/game/save_load/save.zig
  • src/game/save_load_mixin.zig

📝 Walkthrough

Walkthrough

The monolithic save/load mixin implementation is refactored into three new files under src/game/save_load/: common.zig (shared helpers), save.zig (JSON serialization), and load.zig (JSON deserialization, entity reconstruction, and post-load render gating). save_load_mixin.zig becomes a thin re-export barrel.

Changes

Save/Load Module Split

Layer / File(s) Summary
Shared helper mixin
src/game/save_load/common.zig
Defines SAVE_VERSION and a Mixin providing entityToU64, isRegistered, and collectEntities for use by both save and load.
Save serialization
src/game/save_load/save.zig
Implements saveGameState: entity collection via registry sweep and prefab-tag views, JSON string escaping, per-entity component writing with built-in-component guards, ref-array aggregation, and file writing.
Load reconstruction and component restoration
src/game/save_load/load.zig
Implements loadGameState: JSON parsing/version checks, phased entity reconstruction (prefab roots, nested children, remaining entities), and restoration of Position, registered components, and built-in Parent/PrefabInstance/PrefabChild with ID remapping.
Post-load scene wiring and render gate
src/game/save_load/load.zig
Restores ref arrays, registers entities into the scene, re-tracks renderer visuals, runs postLoad hooks, and adds armPostLoadRenderGate, releaseLoadAcquired, and updatePostLoadRenderGate to gate rendering until atlases are loaded.
Re-export barrel
src/game/save_load_mixin.zig
Reduced to import and delegate saveGameState, loadGameState, and render-gate functions from save.Mixin and load.Mixin.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Game
  participant LoadMixin
  participant ECS
  participant Renderer
  participant AtlasManager
  Game->>LoadMixin: loadGameState(filename)
  LoadMixin->>LoadMixin: parse JSON, validate version
  LoadMixin->>ECS: spawn prefab roots, map children, create remaining entities
  LoadMixin->>ECS: restore components via id_map
  LoadMixin->>ECS: register entities into scene
  LoadMixin->>Renderer: re-track visual components
  LoadMixin->>LoadMixin: armPostLoadRenderGate(saved_scene)
  loop each frame until ready or deadline
    Game->>LoadMixin: updatePostLoadRenderGate()
    LoadMixin->>AtlasManager: isLoaded()?
    AtlasManager-->>LoadMixin: ready/failed status
  end
  LoadMixin-->>Game: render gate cleared
Loading

Poem

A rabbit split one burrow wide,
Into save and load, side by side,
Common paths for both to share,
Prefabs mapped with gentle care,
Gates held closed till atlases glow —
Then hop! The saved world starts to show. 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main refactor: splitting save_load_mixin.zig into save/load modules without behavior changes.
Description check ✅ Passed The description is detailed and directly matches the refactor, module split, and preserved public surface described in the changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/split-saveload

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the save/load system by modularizing the monolithic save_load_mixin.zig file. The implementation is split into three new files under src/game/save_load/: common.zig for shared helpers, save.zig for serialization logic, and load.zig for deserialization and post-load render gating. The original save_load_mixin.zig is simplified into a thin composition barrel that re-exports the public API, preserving identical behavior. No review comments were provided, and the refactoring is clean and well-structured.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@apotema
apotema merged commit 1f7f301 into main Jul 5, 2026
3 checks passed
@apotema
apotema deleted the refactor/split-saveload branch July 5, 2026 16:25
apotema added a commit that referenced this pull request Jul 11, 2026
…764)

* refactor: extract post-load render gate from load.zig into render_gate.zig (#762)

Completes the save_load_mixin split into focused modules. PR #694 split
the 1156-line monolith by direction (save/load/common); load.zig was left
at 977 lines because it still carried two concerns: the rehydration/load
seams (loadGameState + its Phase 1a/1b/1c -> Phase 2 sequence) AND the
transient post-load render-gate machinery.

This extracts the gate machinery -- armPostLoadRenderGate,
updatePostLoadRenderGate, releaseLoadAcquired, plus the private
armPostLoadRenderGateFromEntry / postLoadGateHasImage helpers and the
POST_LOAD_GATE_MAX_FRAMES constant -- verbatim into a new
save_load/render_gate.zig Mixin(Game). It is a distinct concern with its
own per-frame lifecycle (armed at end of load, ticked from tick, released
on next load / teardown) and touches none of the reader's rehydration
seams. loadGameState reaches it through self.armPostLoadRenderGate(...),
aliased onto Game in game.zig, so no reader<->gate import edge is needed.

The four focused modules now map to the issue's vision: serialization walk
(save.zig), rehydration/load seams (load.zig, now ~747 lines), transient
render-gate machinery (render_gate.zig), and the thin composition barrel
(save_load_mixin.zig). Behaviour-preserving: the barrel re-exports the same
5-fn public surface, so game.zig aliases resolve exactly as before.

zig build test: no change in results (697/744 tests pass; the 47 failures
are pre-existing and unrelated -- asset_catalog compile error + loopback
socket tests that the sandbox blocks). save_load_mixin_test,
save_load_two_phase_test, and post_load_render_gate_test all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor: extract load-only JSON accessors from load.zig into json_read.zig (#762)

Second focused extraction for #762. load.zig still mixed two concerns: the
rehydration walk (loadGameState + its Phase 1a/1b/1c -> Phase 2 sequence)
and a layer of pure, tag-checked JSON shape-guards. The seven accessors
(parentFlag, getComponentsObject, getObjectField, getStringField,
getU64Field, getSavedId, getNumberField) are std.json-only -- no Game, no
Entity, no registry -- so they move verbatim into save_load/json_read.zig
as plain pub fns. load.zig re-aliases them at mixin scope, so every call
site (getComponentsObject(entry), getObjectField(...), ...) reads exactly
as before.

This leaves load.zig focused on the rehydration sequence (down to 695
lines from 977 pre-#762) and gives the shape-guards a standalone home. The
save_load cluster is now the focused-module split the ticket calls for:
  * save.zig        -- serialization walk (writer)
  * load.zig        -- rehydration/load seams (reader)
  * json_read.zig   -- load-only JSON accessors
  * render_gate.zig -- transient post-load render-gate machinery
  * common.zig      -- shared helpers + SAVE_VERSION
  * save_load_mixin.zig -- thin composition root (barrel)

Behaviour-preserving: the accessors' malformed-save hardening stays covered
by test/save_load_mixin_test.zig + test/save_load_two_phase_test.zig, which
drive the full load path.

zig build test: 697/744 pass -- identical to baseline (the 47 failures are
pre-existing, unrelated: sandbox-blocked loopback-socket tests + pre-existing
`expected void found u64` compile errors in unrelated test files). Zero
regressions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant