Checkpoint System
A slot-based checkpoint service that separates the business rule of when to save from the storage detail of how and where.
What the system is for
Every game that lets players save their progress ends up writing the same plumbing. Slot management, serialization, file paths, atomic writes and load validation all accumulate into a tangled mass that is hard to test and even harder to swap out when the storage medium changes.
The Checkpoint System in Serenity gives you a single service interface to call when saving, loading or deleting game state. The serialized blob is treated as an opaque byte array, so the business rule that governs when a checkpoint may be taken is fully decoupled from the storage backend that persists it.
The Unity problem
A Unity save system that grows organically tends to mix concerns. The code that decides when to save a checkpoint ends up knowing about file paths, JSON formats or PlayerPrefs keys. The code that reads a save slot ends up deserializing directly inside a MonoBehaviour. When you need to add a new slot, swap the storage backend or support cloud saves, the blast radius is unpredictable.
Without a clear boundary between the save trigger and the storage mechanism, testing is manual and every storage change requires touching game logic. The result is save bugs that only appear on certain platforms or after specific session sequences.
How Serenity approaches it
Serenity exposes checkpoint behavior through ICheckpointService, which provides Save, TryLoad, HasCheckpoint, Delete and QueryAvailable. Slots are modeled by the CheckpointSlot enum, which defines Auto, Manual1, Manual2, Manual3 and QuickSave. Each save produces a CheckpointId and is paired with a CheckpointMetadata value that records a timestamp, a label and a stage name.
The storage side is abstracted behind ICheckpointStore, and the immutable read model returned on load is CheckpointSnapshot, carrying the slot, the opaque byte array and the metadata. The concrete infrastructure uses UnityCheckpointStore wired through UnityCheckpointInstaller, sitting on top of the Persistence port hierarchy and FilePersistence for atomic file writes.
How it fits into Serenity
The Checkpoint System lives in the Serenity.Checkpoint namespace and follows the foundation's layered structure. The Domain layer defines the CheckpointSlot enum and the value objects CheckpointId, CheckpointMetadata and CheckpointSnapshot. The Application layer exposes ICheckpointService and ICheckpointStore as the service and storage contracts. The Infrastructure layer provides UnityCheckpointService and UnityCheckpointStore. The Installation layer wires everything through CheckpointInstaller and UnityCheckpointInstaller.
Checkpoint composes with the Persistence port hierarchy — IPersistenceStore, IBlobStore, IKeyValueStore — and with FilePersistence, which provides atomic file writes through FileStore and IFileWriterService. This means swapping the storage backend is a matter of providing a different ICheckpointStore implementation without touching any business logic.
Practical workflow
- Implement or configure your game-state serializer to produce a byte array representing the current state.
- Call ICheckpointService.Save with the target CheckpointSlot, the byte array and a CheckpointMetadata describing the save point.
- On load, call TryLoad with either a CheckpointSlot or a CheckpointId and receive a CheckpointSnapshot containing the opaque data and its metadata.
- Use QueryAvailable to list all existing checkpoints and build a save-slot UI from the returned CheckpointMetadata array.
- Call HasCheckpoint before overwriting a slot to detect conflicts and prompt the player for confirmation.
- Call Delete to remove a slot when the player clears a save file.
What you get
- Checkpoint service ICheckpointService with Save, TryLoad, HasCheckpoint, Delete and QueryAvailable
- Slot enum CheckpointSlot defining Auto, Manual1, Manual2, Manual3 and QuickSave
- Immutable read model CheckpointSnapshot carrying slot, opaque byte array and metadata
- Value object CheckpointMetadata with timestamp ticks, label and stage name
- Stable identifier CheckpointId as an immutable struct with GUID generation
- Storage port ICheckpointStore fully decoupled from business rules
- Concrete infrastructure UnityCheckpointStore and UnityCheckpointInstaller for Unity projects
- Composition with FilePersistence and FileStore for atomic, platform-safe writes
When to use this
- Games that need named save slots — auto-save, quick-save and multiple manual slots — without coupling slot logic to file I/O.
- Projects that need to swap or layer storage backends, for example moving from local files to cloud saves, without rewriting game logic.
- Codebases that want save and load behavior covered by unit tests that run without a file system.
- Teams that want a consistent save interface usable across different scenes and game modes, coordinated with Game Session and Game Settings.
Related systems
Use Serenity when you want save and load behavior that already speaks to session management and settings, but still treats the serialization format and the storage medium as details you can swap independently.
English
Español
Català