bevy_pkv

repository·main·Indexed 41 days ago

https://github.com/johanhelsing/bevy_pkv

A 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.

Tokens
4K
Snippets
10
Records
16
Agent score
49%

What's inside bevy_pkv

  1. Integrate bevy_pkv with Bevy

    main

    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();
    }
  2. Configure storage backends for Native platforms

    main

    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.

  3. Store custom types with Serde

    main

    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");
        }
    }
  4. Read and write values with PkvStore in Bevy

    main

    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");
        }
    }
  5. Use Persistent Resources for automatic saving

    main

    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();
  6. Configure backend features for native platforms

    main

    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:

    • rocksdb
    • sled
    • redb

    If you are on a native platform and do not enable one of these, the project will fail to compile.

  7. Handle errors when using LocalStorageStore

    main

    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.

    GetError

    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.

    SetError

    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.

    RemoveError

    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.
  8. Use PersistentResourcePlugin to automatically persist Bevy resources

    main

    The PersistentResourcePlugin<T> automatically handles loading and saving a Bevy resource using a PkvStore.

    How it works:

    1. Startup: It attempts to load the resource from storage using the type's full name as the key. If not found, it uses a default value provided by the plugin.
    2. Runtime: It monitors the resource for changes. Whenever the resource is modified, the plugin automatically saves it to the PkvStore during the PostUpdate stage.

    Requirements:

    • The resource T must implement Resource, Serialize, and DeserializeOwned.
    • You must first insert a 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();
  9. Store and retrieve values with PkvStore

    main

    The PkvStore uses serde for serialization. You can store any type that implements Serialize and retrieve any type that implements DeserializeOwned.

    Key Methods:

    • 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();
  10. Initialize a PkvStore

    main

    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:

    • On Native platforms, you must enable exactly one of the rocksdb, redb, or sled features.
    • On Wasm, it uses localStorage automatically.
    • Custom directory methods (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");