Skip to content

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)