SuperAPI AI Directory

bevy-save-load

Design & creation

View repository

Updated 10-15 17:33

About this skill

Bevy 0.19 — save and load

When to use this skill

  • Gameplay state must remain loadable across ECS/content/code refactors.
  • Large worlds need base snapshots, sparse deltas, compaction, and recovery.
  • Native and browser builds need one asynchronous save-service contract.
  • Reflected world serialization is being considered as a durable save format.

A save file is a long-lived product API. Serialize an explicit, versioned data model and convert it to/from the live ECS. Runtime Entity values, dense palette indices, query order, Rust enum discriminants, and reflected component layout are not stable persistence identities.

Canonical pattern

use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] #[serde(transparent)] struct SaveId(String);

#[derive(Debug, Deserialize, Serialize)] struct SaveV3 { world_seed: u64, player: SaveId, actors: Vec, }

#[derive(Debug, Deserialize, Serialize)] struct ActorV3 { id: SaveId, archetype: String, position: [f32; 3], }


```text
live Bevy World
  -> validate/capture one logical revision
  -> current Save DTO with stable IDs
  -> encode + checksum envelope
  -> platform storage transaction

platform bytes
  -> size/magic/checksum validation
  -> decode declared old DTO
  -> migrate one version at a time
  -> validate current DTO
  -> allocate entities, then resolve stable-ID references
  -> rebuild derived/runtime-only state

Never partially mutate the active world while bytes are still being decoded or migrated. Load into staging, validate, then commit or replace the gameplay world.

Version the envelope and payload

Wrap encoded payload bytes with magic, format version, schema/content version, payload length, codec/compression identifiers, and checksum. A checksum detects accidental corruption; it does not prevent malicious tampering. Use authentication or a signature when trust is part of the requirement.

Keep migrations as explicit V1 -> V2 -> V3 pure functions. Test golden fixtures from every supported version, including corrupt, truncated, duplicated-ID, unknown-ID, and future-version files. Never deserialize an old version directly into today's struct and hope Serde defaults reproduce old semantics.

Stable IDs and two-pass load

Use immutable domain IDs for saved objects, content, quests, items, and voxel block types. During load:

  1. validate ID uniqueness and all size/count limits;
  2. spawn/allocate objects and build SaveId -> Entity or stable-ID -> dense-palette maps;
  3. resolve relationships in a second pass;
  4. reject or explicitly preserve unknown content according to policy;
  5. rebuild caches, handles, physics state, tasks, diagnostics, and presentation.

Storage is platform-specific

Native writes should use a temporary file in the destination directory, flush and sync it as required, then atomically replace/rename the committed slot. Retain a validated backup or two-slot generation when recovery matters. Do blocking file I/O on IoTaskPool, and send the result back to the main world.

Browsers have no ordinary save-file path. Use IndexedDB for game saves and blobs; small preferences may use local storage. Handle asynchronous completion, quota, eviction, private modes, tab races, export/import, and page termination. A “save complete” UI state requires committed storage confirmation.

Bevy 0.19's SettingsPlugin targets user settings with native/local-storage backends; it is not a general campaign/world save schema.

Gotchas: world serialization boundary

bevy_world_serialization and DynamicWorldBuilder can extract reflected entities and resources with component/resource allow/deny filters. This is useful for authored world assets, editor tooling, controlled snapshots, and short-lived internal formats. It does not automatically create stable gameplay IDs, schema migrations, corruption recovery, transactional storage, or compatibility across component refactors.

Use an explicit DTO for durable player saves. If a filtered DynamicWorld is nested inside that format, version and migrate the surrounding contract and audit every reflected type intentionally.

Choose the relevant deep dive

Problem Read
DTO versions, migrations, stable IDs, validation and golden fixtures Schema, migrations, and IDs
Atomic native writes, browser storage, async state and recovery Native and browser I/O
Full snapshots, sparse deltas, compaction, world-serialization boundary Snapshots, deltas, and world serialization

Review checklist

  • Save schema types are separate from live ECS/runtime components.
  • Every persistent reference uses a documented stable ID.
  • Old versions migrate sequentially and have committed fixtures.
  • Decoder limits untrusted lengths/counts before allocation/decompression.
  • Native power-loss and browser quota/termination failures leave a recoverable slot.
  • Success is reported only after the platform commit completes.
  • Snapshot/delta generations cannot be mixed accidentally.
  • Unknown future versions fail safely without overwriting the original.

See also