Storage
plant_runner provides a unified storage seam: a trait-based abstraction
over key-value stores and snapshot persistence, plus optional feature-gated
backends for YAML-file and redb-backed storage.
Core Traits
Both traits originate in plant_atom::storage and are re-exported from
plant_runner::storage so hosts can depend on a single crate.
KvStore
pub trait KvStore: Send + Sync {
fn get(&self, key: &str) -> Option<Vec<u8>>;
fn set(&self, key: &str, value: Vec<u8>);
fn delete(&self, key: &str);
fn list_prefix(&self, prefix: &str) -> Vec<String>;
}
The primary runtime key-value store. Keys are plain strings; values are raw bytes. Used for live simulation state (hob positions, inventory, deformation state, etc.).
SnapshotStore
pub trait SnapshotStore: Send + Sync {
fn save_snapshot(&self, key: &str, value: &[u8]) -> Result<(), String>;
fn load_snapshot(&self, key: &str) -> Result<Option<Vec<u8>>, String>;
fn list_snapshots(&self) -> Result<Vec<String>, String>;
fn delete_snapshot(&self, key: &str) -> Result<(), String>;
}
Used for durable persistence of simulation checkpoints. The server host
configures a SnapshotStore implementation; the primary simulation loop uses
KvStore for hot-path reads and writes.
Built-in Implementations
MemoryKvStore
pub struct MemoryKvStore { /* Arc<RwLock<BTreeMap<String, Vec<u8>>>> */ }
In-process, non-persistent store backed by a BTreeMap. Always available
(no feature flag required). Used as the default in tests and single-process
configurations.
let store = MemoryKvStore::new();
store.set("my_key", b"hello".to_vec());
assert_eq!(store.get("my_key"), Some(b"hello".to_vec()));
CachedKvStore
pub struct CachedKvStore<Inner: KvStore> { /* write-through LRU cache */ }
A write-through cache layer wrapping any KvStore. Reduces contention on the
inner store for frequently read keys. Capacity is set at construction:
let cached = CachedKvStore::new(inner_store, 1024 /* max entries */);
Feature-Gated Backends
YamlSnapshotStore (feature = server)
#[cfg(feature = "server")]
pub struct YamlSnapshotStore { /* directory path + async tokio I/O */ }
Persists each snapshot as a YAML file in a configured directory. Suitable for development and low-throughput server deployments.
let store = YamlSnapshotStore::new("/var/lib/plantangenet/snapshots");
store.save_snapshot("world_v1", &bytes)?;
Requires features = ["server"] in Cargo.toml.
RedbSnapshotStore (feature = redb)
#[cfg(feature = "redb")]
pub struct RedbSnapshotStore { /* redb::Database handle */ }
Persists snapshots in a redb embedded database file. Lower write latency than YAML files for high-frequency snapshot workloads.
let store = RedbSnapshotStore::open("/var/lib/plantangenet/snapshots.redb")?;
Requires features = ["redb"] in Cargo.toml.
GenericSpecStore
plant_runner also provides GenericSpecStore (the internal type is
SpecStore, re-exported under the alias):
pub use spec_store::SpecStore as GenericSpecStore;
This is a thread-safe in-memory map from (kind: String, id: String) to raw
YAML strings. It backs the spec upload surface used during provisioning.
let store = GenericSpecStore::new();
store.put_spec("region", "forest_north", yaml_string);
let yaml = store.get_spec("region", "forest_north");
let ids = store.list_specs("region");
plant_world::spec_store wraps this with world-specific helpers
(put_world, put_region, resolve_region_name) that reference the world's
own id conventions. The generic layer lives in plant_runner so any
forged kind can use it without pulling in world.
Selecting a Backend
| Scenario | Recommended |
|---|---|
| Tests and local development | MemoryKvStore + YamlSnapshotStore |
| Production server | CachedKvStore<MemoryKvStore> + RedbSnapshotStore |
| Browser / WASM | MemoryKvStore only (no snapshot) |