Skip to content

The persistent saving framework

k-a-s-s-0-9 edited this page Oct 21, 2026 · 1 revision

Persistent Saving Framework

Table of Contents


Overview

The Persistent Saving Framework is responsible for serialising and restoring the player's game state between sessions. It supports:

  • Checkpoint-based autosaves: Triggered automatically when the player activates a checkpoint.
  • Slot-based saves: Up to SLOT_COUNT independent save files persisted to disk.
  • Save completion tracking: Marks saves as Completed when the player finishes the game through the WIN path or Calypso boss defeat.
  • Tutorial progression tracking: Persists which tutorial steps have been completed across sessions.
  • Deferred world/player restoration: Pending save state is staged and applied at the appropriate time after level/area loading to avoid race conditions.
  • Event-driven architecture: Decouples save triggers, such as checkpoints and game end, from persistence logic through the game's event handlers.

Purpose

This framework exists to allow players to:

  1. Return to their progress at the last activated checkpoint through autosaving.
  2. Manage multiple save slots from the Load/Save screen.
  3. See completion status (Completed) on finished runs in the save selection UI.
  4. Resume tutorial progress correctly across sessions, deaths, and reloads.
  5. Restore player stats, inventory-related state, and world state relevant to the active level.

Architecture

The saving system is divided into data, persistence, orchestration, events, UI, and trigger components. Communication is primarily event-driven through ServiceLocator event handlers, with GdxGame acting as the central coordinator.

Checkpoint autosave flow

CheckpointComponent (trigger)
    └── GameArea / Events
        └── GameSaveEventHandler ("autosave")
            └── LevelsGameScreen
                └── GdxGame.autosave(level, area)
                    └── SaveManager.save(slot, SaveFile)
                        └── Disk (serialised SaveFile)

Game completion flow

Game End (WIN / Boss Defeat)
    └── GameEndEventHandler("gameEnd", WIN)
        OR direct call on boss defeat
            └── LevelsGameScreen / GdxGame.markSaveCompleted()
                └── SaveManager.save(activeSlot, completed SaveFile)

Load flow

LoadSaveDisplay ("Load", slot)
    └── GdxGame.loadGame(slot)
        └── SaveManager.get(slot)
            └── SaveFile stored as pending state
                └── Applied later through:
                    ├── applyPendingSavePlayer()
                    └── applyPendingSaveWorld()

Core Components

SaveFile (Data Model)

com.csse3200.game.files.SaveFile is the serialisable data container representing a single save slot's state.

Responsibilities:

  • Hold player state.
  • Store current level and area context.
  • Track completion flags.
  • Store completed tutorial identifiers.
  • Hold entity state snapshots.
  • Store metadata, including the last-modified timestamp.

Usage:

A SaveFile is constructed at save time, written by SaveManager, then read back and held as pending state by GdxGame when loading a save.

Key characteristics:

  • currentLevel: Current level identifier, such as level1, level2, level3, or boss.
  • lastModifiedEpochMs: Timestamp used by the UI.
  • completed: Whether the game has been completed successfully.
  • tutorialCompleted: Collection or set of completed tutorial IDs/keys.
  • player: Nested object containing health, maximum health, gold, and other player state.
  • entities: List or map representing world entity state needed for restoration.

SaveManager (Persistence Layer)

com.csse3200.game.files.SaveManager handles disk I/O and save-slot management.

Responsibilities:

  • listAll(): Enumerate available saves for the Load/Save UI.
  • get(slot): Read a specific save slot.
  • save(slot, saveFile): Persist a SaveFile to a slot.
  • delete(slot): Delete a save slot.
  • SLOT_COUNT: Define the number of available save slots.

Behaviour:

  • Slots are indexed from 0 to SLOT_COUNT - 1.
  • When a save is written, lastModifiedEpochMs is typically refreshed to the current time.
  • listAll() returns an array of SaveFile objects, with null entries representing empty slots.
  • The UI treats null entries as empty slots.
  • Storage is filesystem-based under the game's save directory, managed by the game's file service.
  • File paths are scoped to individual save slots.

GdxGame (Save Orchestration)

com.csse3200.game.GdxGame is the application entry point and central coordinator for the save/load lifecycle.

Key methods:

autosave(String level, GameArea currentGameArea)

  • Captures the current state of the active game area and player.
  • Builds or updates a SaveFile.
  • Determines or uses the active save slot.
  • Writes the save through SaveManager.save().
  • Called when a checkpoint is activated.

markSaveCompleted()

  • Loads the active save or uses the current in-memory context.
  • Sets completed = true.
  • Persists the updated save.
  • Called when the generic WIN event occurs or when Calypso is defeated.

loadGame(int slot)

  • Reads the selected save through SaveManager.get(slot).
  • Stores the save as pending state, including pendingPlayerState, pendingSaveFile, and pending world/level context.
  • Does not immediately mutate the world.
  • Defers application until the appropriate gameplay screen and entities have been initialised.

applyPendingSavePlayer(Entity player)

Applies pending player state to the supplied player entity. This restores health, maximum health, gold, and other player fields captured in the save.

Called after the player entity exists.

applyPendingSaveWorld(GameArea area, String level)

Applies pending world/entity state to the specified game area and level.

The pending save must match the target level before world state is restored.

restorePlayerState(Entity player)

Restores or initialises player state from baseline or in-memory session state, complementing pending save restoration.

Active save slot

getActiveSaveSlot() and related tracking identify the active save slot, generally the last slot loaded or saved to. markSaveCompleted() uses the active slot when marking the current run as finished.

Design note: Deferring state application through pending fields avoids race conditions when the target game area, player entity, or assets have not yet been initialised after a screen or level transition.

GameSaveEventHandler (Event Bus)

The save event handler is registered through ServiceLocator.registerGameSaveEventHandler(...).

Events:

  • autosave: Fired by areas or checkpoints to request an autosave.

The event does not carry heavy state arguments in the current wiring. Instead, LevelsGameScreen listens for the event and calls game.autosave(level, currentGameArea) using its current level and game-area context.

This keeps the event lightweight and avoids serialising game state through the event system.

Registration:

LevelsGameScreen registers a new event handler instance for saves, scoped to that screen's context. Checkpoints and areas forward autosave requests through this dispatcher.

LevelsGameScreen (Wiring and Autosave)

Clone this wiki locally