bevy_persistence_database
Persistence for Bevy ECS to ArangoDB or Postgres with an idiomatic Bevy Query API.
Highlights
- Bevy Query API consistency
PersistenceQuerySystemParam derefs to Bevy's Query, so iter/get/single/get_many/contains/iter_combinations work as usual.- Explicit load trigger:
ensure_loadedperforms DB I/O at the point the user deems most useful, then you use pass-throughs on world data.
- Smart caching and explicit refresh
- Identical loads in the same frame coalesce into a single DB call.
- Repeated identical loads hit the cache. Use
force_refreshto bypass it.
- Presence and value filters
- Type-driven presence via
With<T>,Without<T>, andOr<(…)>. - Optional components in Q (
Option<&T>) are fetched when present. - Value filters: eq, ne, gt, gte, lt, lte, combined with and/or/in.
- Key filtering via
Guid::key_field().eq("...")or.in_([...]).
- Type-driven presence via
- Resources
#[persist(resource)]resources are persisted and fetched alongside any query.
- Optimistic concurrency and conflict handling
- Per-document versioning with conflict detection.
- Batching and parallel commit
- Configurable batching with a thread pool and concurrent transaction execution.
- Backends
- ArangoDB and Postgres, selected at build time via features.
Install
Add the crate with the backends you need:
[]
= { = "0.16" }
= { = "0.1", = ["arango", "postgres"] }
Backends and features
- Enable features on
bevy_persistence_database:arango: builds the ArangoDB backend (arangors).postgres: builds the Postgres backend (tokio-postgres).
- Provide an
Arc<dyn DatabaseConnection>at startup:- Arango:
await ensure_database let db = connect.await? - Postgres:
ensure_database.await? let db = connect.await?
- Arango:
Quickstart
1) Persist-able types
use persist;
2) Add the plugin with a real database connection
use *;
use ;
use Arc;
Loading pattern
- Use
PersistenceQueryas a SystemParam. - Add value filters via
.where(...)(alias of.filter(...)). - Explicitly trigger the load with
.ensure_loaded(); then use pass-throughs on world-only data.
use *;
use ;
Pass-throughs are world-only
After a load, PersistenceQuery derefs to bevy::prelude::Query. Use standard accessors without new DB I/O:
Key filtering
- Single key:
use ;
- Multiple keys via IN:
let _ = pq.where.ensure_loaded;
Optional Q semantics and presence via F
Option<&T>in Q fetches T only when present, without gating presence.- Presence is driven by type-level filters in F (
With<T>,Without<T>,Or<(... )>), which gate backend queries.
Caching and force-refresh
- Default
CachePolicyisUseCache. Identical loads coalesce within a frame and reuse cached results across frames. - Use
force_refreshto bypass cache and overwrite world state:
let _ = pq.force_refresh.ensure_loaded;
Resources
#[persist(resource)]resources are fetched alongside any query call; versions are tracked and updated on commits automatically.
Committing Changes
Manual Commit Required
Changes are NOT automatically committed. You must explicitly trigger commits:
use TriggerCommit;
Or use the convenience functions:
use ;
// Async commit (non-blocking)
commit;
// Synchronous commit (blocks until complete)
commit_sync;
The commit system processes any TriggerCommit events each frame and clears the event queue after committing.
Joining
Efficiently join query results without loading unnecessary entities:
use ;
The join operation is smart and efficient:
- Only loads entities at the intersection of both queries
- Materializes components only for matched entities
- Avoids loading unneeded entities into the world
- Can be used in a single system without separate load steps
Transmutation
Convert between component types dynamically:
use QueryDataToComponents;
Advanced Configuration
Plugin Configuration
use ;
let config = PersistencePluginConfig ;
let plugin = new.with_config;
app.add_plugins;
Version Management
The library includes automatic version management for optimistic concurrency control:
use VersionManager;
// Versions are automatically tracked and incremented on commits
// Conflicts are detected and can be handled via error handling
Scheduling notes
- Loads can run in Update or PostUpdate.
- Deferred world mutations from loads are applied before PreCommit.
- Schedule readers after
PersistenceSystemSet::PreCommitto observe fresh data in the same frame. - The commit system runs in
PersistenceSystemSet::Commit.
Testing
The library includes comprehensive test utilities for both backends:
Error Handling
All database operations return Result<T, PersistenceError>:
use PersistenceError;
match pq.ensure_loaded