Data Serialization (Save/Load System)
Relevant source files
The following files were used as context for generating this wiki page:
- SPD-classes/src/main/java/com/watabou/utils/Bundlable.java
- SPD-classes/src/main/java/com/watabou/utils/Bundle.java
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java
- SPD-classes/src/main/java/com/watabou/utils/FileUtils.java
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Rankings.java
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/journal/Document.java
The data serialization system in Shattered Pixel Dungeon is responsible for saving and loading the entire game state. This allows players to quit the game and resume their progress later. The system is designed to be robust, preventing data corruption from interruptions, and modular, allowing various game components to manage their own serialization logic.
The core of the system revolves around the Bundle class, a key-value data container, and the Bundlable interface, which provides a contract for objects that can be serialized into and deserialized from a Bundle. The Dungeon class orchestrates the process, gathering data from all relevant game objects, while the FileUtils class handles the low-level file I/O, including an atomic write mechanism to ensure save integrity.
Core Components
The serialization framework is built upon a few key classes and an interface.
| Component | Role | Source File |
|---|---|---|
Bundlable | Interface that defines the contract for serializable objects with storeInBundle() and restoreFromBundle() methods. | SPD-classes/src/main/java/com/watabou/utils/Bundlable.java |
Bundle | A key-value data container that can store primitives, strings, arrays, and other Bundlable objects. | SPD-classes/src/main/java/com/watabou/utils/Bundle.java |
Dungeon | Central class that orchestrates the save/load process by gathering and restoring game state. | core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java |
FileUtils | Utility class that handles the physical reading and writing of Bundle objects to files, including atomic writes. | SPD-classes/src/main/java/com/watabou/utils/FileUtils.java |
The Bundlable Interface
Any class that needs to be saved as part of the game state typically implements the Bundlable interface. This provides a standardized way to handle serialization and deserialization.
// SPD-classes/src/main/java/com/watabou/utils/Bundlable.java:22-24
public interface Bundlable {
public void storeInBundle( Bundle bundle );
public void restoreFromBundle( Bundle bundle );
}
Sources: Bundlable.java:22-24
Many core game objects like Hero, Item, and Level implement this interface.
The Bundle Class
The Bundle class acts as a map, storing data with string keys. It supports various data types, including other Bundle objects, which allows for a hierarchical data structure. This is the intermediary format used before writing data to a file.
Sources: Bundle.java
Class Diagram
This diagram illustrates the relationship between the core serialization components and some of the key game objects that use them.
Sources: Bundlable.java, Bundle.java, Dungeon.java, FileUtils.java, Hero.java, Level.java, Item.java, Rankings.java
Serialization Process (Saving)
The game is saved via the Dungeon.saveAll() method, which is the primary entry point. This method orchestrates the saving of both the general game state and the current level data into separate files.
Sources: Dungeon.java:708-719
Atomic Write Mechanism
To prevent save file corruption (e.g., if the game closes during a write), the system uses an atomic write strategy.
- Data is first written to a temporary file with a
.tmpextension. - If the write is successful, the original save file is deleted.
- The temporary file is then renamed to the original save file's name.
This ensures that a valid save file (either the old or the new one) is always present on disk.
Sources: FileUtils.java:169-178
Game State Serialization
The Dungeon.saveGame() method is responsible for collecting all global game state into a single Bundle. This includes:
- Game version and seed information.
- Challenge flags.
- The
Heroobject. - Current
depthandbranch. goldandenergy.- Items dropped on other floors (
droppedItems). - State of limited-spawn items (
LimitedDrops). - Quest status (e.g.,
Ghost.Quest,Wandmaker.Quest). - Game statistics, notes, and badge progress.
- A list of all generated level depths.
The following table details some of the key fields stored in the main game save Bundle.
| Key Constant | Description | Source |
|---|---|---|
VERSION | The game's version code at the time of saving. | Dungeon.java:669 |
SEED | The master seed for the run's procedural generation. | Dungeon.java:670 |
CHALLENGES | An integer bitmask representing active challenges. | Dungeon.java:674 |
HERO | The serialized Hero object, which is Bundlable. | Dungeon.java:676 |
DEPTH | The hero's current dungeon depth. | Dungeon.java:677 |
LEVEL | The serialized Level object (in a separate file). | Dungeon.java:683 |
QUESTS | A sub-Bundle containing the state of all major quests. | Dungeon.java:687 |
BADGES | A sub-Bundle containing progress for local badges. | Dungeon.java:688 |
Sources: Dungeon.java:668-688, 691-730
Level Serialization
The Dungeon.saveLevel() method saves the current Level object to a file specific to its depth and branch (e.g., depth_1_0.dat). The Level class implements Bundlable and its storeInBundle method saves:
- The tile
maparray. visitedandmappedboolean arrays.- Collections of
mobs,heaps(items on the ground),plants, andtraps. - The level's
feeling(e.g.,CHASM,WATER).
Sources: Dungeon.java:732-737, Level.java:274-297
Save Sequence Diagram
This diagram shows the sequence of calls during the saveAll process.
Sources: Dungeon.java:708-719, FileUtils.java:169-185
Deserialization Process (Loading)
Loading a game is initiated by Dungeon.loadGame(), which reads the main game file, followed by Dungeon.loadLevel() for the specific floor the player was on.
Save File Recovery
The FileUtils.cleanTempFiles() method provides robustness against interrupted saves. When the game starts, it scans for .tmp files. If a temporary file is found, it compares it with the original. If the temp file is valid and newer, it replaces the original, effectively completing the interrupted save operation.
// SPD-classes/src/main/java/com/watabou/utils/FileUtils.java:90-107
if (file.name().endsWith(".tmp")){
FileHandle temp = file;
FileHandle original = getFileHandle( defaultFileType, "", temp.path().replace(".tmp", "") );
//replace the base file with the temp one if base is invalid or temp is valid and newer
try {
bundleFromStream(temp.read());
try {
bundleFromStream(original.read());
if (temp.lastModified() > original.lastModified()) {
temp.moveTo(original);
} else {
temp.delete();
}
} catch (Exception e) {
temp.moveTo(original);
}
} catch (Exception e) {
temp.delete();
}
foundTemp = true;
}
Sources: FileUtils.java:90-110
Game and Level Deserialization
The Dungeon.loadGame() method reads the main save file using FileUtils.bundleFromFile(). It then restores the game state by extracting values from the resulting Bundle using the same keys that were used for saving. It calls restoreFromBundle() on Bundlable objects and static restore() methods on manager classes like LimitedDrops and Document.
After the global state is loaded, Dungeon.loadLevel() is called to load the specific level data from its corresponding file.
Sources: Dungeon.java:743-818, Level.java:214-272, Document.java:375-393
Other Serialized Data
Besides the main game state, other parts of the application use the same bundling system for persistence.
Rankings
The Rankings enum manages the high score table. It has its own save() and load() methods that serialize a list of Record objects. Each Record is itself Bundlable and contains a snapshot of the game state at the end of a run, including a bundled Hero object and game statistics. This allows the end-game screen to display detailed information about past runs.
Sources: Rankings.java:353-380, 492-520
Journal Documents
The Document enum, which manages lore and guidebook pages, uses a static store() and restore() pattern. The store method iterates through all documents, bundles their read/found states, and adds this data to the main game save Bundle. The restore method performs the reverse operation. This pattern is used for objects that are managed globally as a collection rather than as individual instances.
Sources: Document.java:354-393
Conclusion
Shattered Pixel Dungeon's serialization system is a modular and robust framework for persisting game state. Its use of the Bundlable interface and Bundle data container allows for decentralized serialization logic, where each component is responsible for its own state. The orchestration by the Dungeon class and the atomic file-writing strategy in FileUtils ensure that game saves are both comprehensive and resilient to corruption, providing a reliable experience for the player.