Storage implementation for Wasm
mainbevy_pkv uses Window.localStorage and serde_json for storage. This provides a simple, synchronous API for web-based applications.repository·main·Indexed 41 days ago
https://github.com/johanhelsing/bevy_pkvA cross-platform persistent key-value store for Rust applications and the Bevy game engine, version 0.16.0. It supports storing settings and save games using Serde for serialization. For Bevy, it provides a PkvStore resource and a PersistentResourcePlugin for automatic saving and loading of resources. Supports multiple native backends (redb, rocksdb, sled) and uses Window.localStorage for Wasm targets.
bevy_pkv uses Window.localStorage and serde_json for storage. This provides a simple, synchronous API for web-based applications.If you are using bevy_pkv in a non-Bevy Rust application, disable the default features in your Cargo.toml to avoid pulling in the Bevy dependency.
bevy_pkv = { version = "0.9", default-features = false }To use bevy_pkv in a Bevy application, add a PkvStore resource to your App. This creates or loads a persistent store in the appropriate system directory using the provided company and game identifiers. Once inserted, the store can be accessed in any system via Res<PkvStore> or ResMut<PkvStore>.
use bevy::prelude::*;
use bevy_pkv::PkvStore;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(PkvStore::new("FooCompany", "BarGame"))
.run();
}By default, bevy_pkv uses redb and rmp_serde (MessagePack) to create a bevy_pkv.redb database in the system's application data directory.
You can switch the storage engine by enabling different features in your Cargo.toml:
rocksdb: Uses RocksDB-based implementation.sled: Uses the sled database.Note: To use these, you must disable default-features.
Any type that implements serde::Serialize and serde::Deserialize can be stored in PkvStore. This allows you to persist complex data structures like user profiles or game states easily.
#[derive(Serialize, Deserialize)]
struct User {
name: String,
}
fn setup(mut pkv: ResMut<PkvStore>) {
if let Ok(user) = pkv.get::<User>("user") {
// Use the retrieved user
} else {
let user = User { name: "bob".to_string() };
pkv.set("user", &user).expect("failed to store user");
}
}You can retrieve and store values using the PkvStore resource. Use .get::<T>(key) to retrieve a value and .set(key, &value) or specialized methods like .set_string(key, value) to store them. Both methods return a Result, so handle potential errors (e.g., using .expect()).
fn setup(mut pkv: ResMut<PkvStore>) {
if let Ok(username) = pkv.get::<String>("username") {
// Use the retrieved value
} else {
// Store a value if it doesn't exist
pkv.set_string("username", "alice")
.expect("failed to store username");
// Or use the generic API:
pkv.set("username", &"alice".to_string())
.expect("failed to store username");
}
}The init_persistent_resource::<T>() API allows you to define a Bevy Resource that is automatically loaded from storage on startup and automatically saved to storage whenever it is mutated. The resource T must implement Resource, Serialize, Deserialize, and Default.
use bevy::prelude::*;
use bevy_pkv::prelude::*;
use serde::{Serialize, Deserialize};
#[derive(Resource, Serialize, Deserialize, Default)]
struct GameSettings {
volume: f32,
difficulty: u8,
}
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(PkvStore::new("FooCompany", "BarGame"))
.init_persistent_resource::<GameSettings>()
.run();When targeting native platforms (non-Wasm), you must select a storage backend via Cargo features. You cannot enable more than one of these at a time.
Supported Backends:
rocksdbsledredbIf you are on a native platform and do not enable one of these, the project will fail to compile.
When interacting with LocalStorageStore (which is aliased as InnerStore), operations can fail with specific error types. Understanding these errors is necessary for robust error handling in Wasm environments.
Returned by get operations:
NotFound: No value was found for the provided key.Json: Failed to deserialize the stored JSON string into the requested type.GetItem: A JavaScript error occurred during the getItem call.Returned by set or clear operations:
SetItem: A JavaScript error occurred during the setItem call.Json: Failed to serialize the value into a JSON string.Clear: A JavaScript error occurred during the clear operation.Returned by remove or remove_and_get operations:
NotFound: No value was found for the given key.Json: Failed to deserialize the JSON string.GetItem: A JavaScript error occurred during the getItem call.Clear: A JavaScript error occurred during the remove_item or clear call.The PersistentResourcePlugin<T> automatically handles loading and saving a Bevy resource using a PkvStore.
How it works:
PkvStore during the PostUpdate stage.Requirements:
T must implement Resource, Serialize, and DeserializeOwned.PkvStore resource into your App before adding the plugin.use bevy::prelude::*;
use bevy_pkv::{PkvStore, PersistentResourcePlugin};
use serde::{Deserialize, Serialize};
#[derive(Resource, Serialize, Deserialize, Default)]
struct GameSettings {
volume: f32,
difficulty: u8,
}
App::new()
.insert_resource(PkvStore::new("ExampleOrg", "ExampleApp"))
.add_plugins(PersistentResourcePlugin::<GameSettings>::new())
.run();The PkvStore uses serde for serialization. You can store any type that implements Serialize and retrieve any type that implements DeserializeOwned.
set<T: Serialize>(&mut self, key: impl AsRef<str>, value: &T): Serializes and stores a value.set_string(&mut self, key: impl AsRef<str>, value: &str): A convenience method for storing raw strings.get<T: DeserializeOwned>(&self, key: impl AsRef<str>) -> Result<T, GetError>: Retrieves and deserializes a value. Returns Err(GetError::NotFound) if the key does not exist.remove(&mut self, key: impl AsRef<str>) -> Result<(), RemoveError>: Deletes a key.remove_and_get<T: DeserializeOwned>(&mut self, key: impl AsRef<str>) -> Result<Option<T>, RemoveError>: Deletes the key and returns the value that was stored there.clear(&mut self) -> Result<(), SetError>: Wipes all data from the store.use bevy_pkv::PkvStore;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct User {
name: String,
age: u8,
}
let mut store = PkvStore::new("MyOrg", "MyGame");
// Storing a custom type
let user = User { name: "Alice".into(), age: 30 };
store.set("current_user", &user).unwrap();
// Retrieving a custom type
let retrieved_user: User = store.get("current_user").unwrap();
assert_eq!(user, retrieved_user);
// Storing a string
store.set_string("session_id", "abc-123").unwrap();
// Removing a key
store.remove("session_id").unwrap();
// Clearing everything
store.clear().unwrap();Use PkvStore to create a persistent key-value store. You can initialize it using platform-default locations (which use the organization and application names to determine the path) or by specifying a custom directory.
Note on Backends:
rocksdb, redb, or sled features.localStorage automatically.new_in_dir, new_in_dir_with_filename) are only available when using rocksdb, redb, or sled backends.use bevy_pkv::PkvStore;
// Use platform default locations
let mut store = PkvStore::new("MyOrg", "MyGame");
// Use platform default with a qualifier (e.g., "com")
let mut store = PkvStore::new_with_qualifier("com", "MyOrg", "MyGame");
// Use a custom directory (requires rocksdb, redb, or sled features)
let mut store = PkvStore::new_in_dir("./my_data_dir");
// Use a custom directory with a specific filename (requires rocksdb, redb, or sled features)
let mut store = PkvStore::new_in_dir_with_filename("./my_data_dir", "settings.db");