bevy-persistent 0.4.0

A Bevy helper to easily manage resources that need to persist across game sessions.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# bevy-persistent

A [Bevy](https://bevyengine.org/) helper to easily manage resources that need to persist across game sessions.

## Background

In games, there are a lot of resources that need to persist across game sessions:
- Statistics (e.g., high scores, number of deaths, play time)
- Settings (e.g., key bindings, game difficulty, audio settings)
- States (e.g., last window position and size, saves)
- and many more...

This crate aims to simplify management of such resources!

## Installation

With all supported storage formats:

```shell
cargo add bevy-persistent --features all
```

Or explicitly:

```shell
cargo add bevy-persistent --features bincode,ini,json,toml,yaml
```

And of course, you can just pick the storage formats you're planning to use:

```shell
cargo add bevy-persistent --features bincode,toml
```

## Usage

### Prelude

You only need [Persistent\<R>](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html) and [StorageFormat](https://docs.rs/bevy-persistent/latest/bevy_persistent/storage/enum.StorageFormat.html) types to use the library, and they are exported from the prelude module.

```rust
use bevy_persistent::prelude::*;
```

### Definition

You need to define the [Resource](https://docs.rs/bevy/latest/bevy/ecs/system/trait.Resource.html) you want to persist, and it needs to implement [Serialize](https://docs.rs/serde/latest/serde/trait.Serialize.html) and [Deserialize](https://docs.rs/serde/latest/serde/trait.Deserialize.html) traits from [serde](https://github.com/serde-rs/serde).

```rust
#[derive(Resource, Serialize, Deserialize)]
struct KeyBindings {
    jump: KeyCode,
    crouch: KeyCode,
}
```

### Creation

In your setup system, you can create the persistent resource and insert it to your game.

```rust
fn setup(mut commands: Commands) {
    let config_dir = dirs::config_dir().unwrap().join("your-amazing-game");
    commands.insert_resource(
        Persistent::<KeyBindings>::builder()
            .name("key bindings")
            .format(StorageFormat::Toml)
            .path(config_dir.join("key-bindings.toml"))
            .default(KeyBindings { jump: KeyCode::Space, crouch: KeyCode::C })
            .build()
            .expect("failed to initialize key bindings")
    )
}
```

If it's the first run, the resource will have the specified default value and that default value will be saved to the specified path in the specified format. Otherwise, key bindings will be loaded from the specified path using the specified format.

### Access

To access the resource, you can have a parameter of type `Res<Persistent<R>>`.

```rust
fn access_key_bindings(key_bindings: Res<Persistent<KeyBindings>>) {
    log::info!("you can crouch using {:?}", key_bindings.crouch);
}
```

`Persistent<R>` implements `Deref<Target = R>` so you can access public fields/methods of your resource easily.

### Modification

To modify the resource, you can have a parameter of type `ResMut<Persistent<R>>`.

```rust
fn modify_key_bindings(mut key_bindings: ResMut<Persistent<KeyBindings>>) {
    key_bindings.update(|key_bindings| {
        key_bindings.crouch = KeyCode::ControlLeft;
    }).expect("failed to update key bindings");
}
```

[Persistent\<R>](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html) has [set](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.set) and [update](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.update) methods to modify the underlying resource. Both of those methods write the updated resource to the disk before returning.

If any failures happen at any point (e.g., no permission to read/write to the specified path), the error will be returned, but the underlying object would be updated, and new value would be visible for the rest of the game. However, it won't persist to the next game session!

## Manual Persistence

Some resources are updated frequently and persisting on each small update might not be desirable. Or persistence could have to be triggered manually (e.g., auto saves on certain points in the game).

For such cases, you can avoid using [set](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.set) and [update](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.update) methods and update the resource directly.

```rust
fn modify_key_bindings(mut key_bindings: ResMut<Persistent<KeyBindings>>) {
    key_bindings.crouch = KeyCode::ControlLeft;
}
```

When you want the resource to persist with its current value, you can use [persist](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.persist) method.

```rust
fn persist_key_bindings(key_bindings: Res<Persistent<KeyBindings>>) {
    key_bindings.persist().expect("failed to save new key bindings");
}
```

### Reverting

It might make sense for some persistent resources to be reverted to default. Imagine having a key bindings settings page, it's a good idea to put `Revert to default` button to this page because if players mess up their settings, it'd be much easier if they can revert everything to its default state compared to manually adjusting every key.

Such persistent resources need to be created revertibly.

```rust
fn setup(mut commands: Commands) {
    let config_dir = dirs::config_dir().unwrap().join("your-amazing-game");
    commands.insert_resource(
        Persistent::<KeyBindings>::builder()
            .name("key bindings")
            .format(StorageFormat::Toml)
            .path(config_dir.join("key-bindings.toml"))
            .default(KeyBindings { jump: KeyCode::Space, crouch: KeyCode::C })
            .revertible(true)
            //^^^^^^^^^^^^^^^ using this
            .build()
            .expect("failed to initialize key bindings")
    )
}
```

Revertible persistent resources can use [revert_to_default](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.revert_to_default) method.

```rust
fn revert_key_bindings_to_default(key_bindings: Res<Persistent<KeyBindings>>) {
    key_bindings.revert_to_default().expect("failed to revert key bindings to default");
}
```

There is also an option to revert to default automatically on deserialization errors (e.g., bad modifications by the player). When enabled, if the persistent object cannot be loaded from the persistent storage due to its content, it'll be reverted to its default.

```rust
fn setup(mut commands: Commands) {
    let config_dir = dirs::config_dir().unwrap().join("your-amazing-game");
    commands.insert_resource(
        Persistent::<KeyBindings>::builder()
            .name("key bindings")
            .format(StorageFormat::Toml)
            .path(config_dir.join("key-bindings.toml"))
            .default(KeyBindings { jump: KeyCode::Space, crouch: KeyCode::C })
            .revertible(true)
            //^^^^^^^^^^^^^^^ requires this
            .revert_to_default_on_deserialization_errors(true)
            //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ enable with this
            .build()
            .expect("failed to initialize key bindings")
    )
}
```

If `key-bindings.toml` cannot be deserialized as a `KeyBindings` object, it'll be reverted to the specified default value in persistent storage.

### Unloading/Reloading

Persistent resources are kept in memory by default. This might lead to unnecessarily high memory usage. To overcome this, you can use [unload](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.unload) method.

```rust
fn unload_key_bindings(key_bindings: Res<Persistent<KeyBindings>>) {
    key_bindings.unload().expect("failed to persist key bindings before unloading");
}
```

Once this system is run, latest version of the underlying resource will be written to the underlying storage, and underlying resource will be removed from the memory.

If you don't want to persist the latest version of the resource before unloading, or if you know the latest version is already persisted, can use [unload_without_persisting](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.unload_without_persisting) method.

```rust
fn unload_key_bindings_without_persisting(key_bindings: Res<Persistent<KeyBindings>>) {
    key_bindings.unload_without_persisting();
}
```

If immediate loading to memory upon creation is not desired, the persistent resource can be created in the unloaded state.

```rust
fn setup(mut commands: Commands) {
    let config_dir = dirs::config_dir().unwrap().join("your-amazing-game");
    commands.insert_resource(
        Persistent::<KeyBindings>::builder()
            .name("key bindings")
            .format(StorageFormat::Toml)
            .path(config_dir.join("key-bindings.toml"))
            .default(KeyBindings { jump: KeyCode::Space, crouch: KeyCode::C })
            .loaded(false)
            //^^^^^^^^^^^^ using this or ".unloaded(true)"
            .build()
            .expect("failed to initialize key bindings")
    )
}
```

When the persistent resource is unloaded, the underlying resource will not be accessible until it's loaded back to memory using [reload](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.reload) method.

```rust
fn reload_key_bindings(key_bindings: Res<Persistent<KeyBindings>>) {
    key_bindings.reload().expect("failed to reload key bindings");
}
```

Reload can also be used on already loaded persistent resources to synchronize with the external changes. This can be very useful in certain situations, such as a save editor. The save editor would update the save files, and calling reload would bring those updates to the game without restarting the game!

#### You should be careful when using unloaded persistent resources!

```rust
// all of these will panic
fn incorrect_usage_of_unloaded_key_bindings(key_bindings: Res<Persistent<KeyBindings>>) {
    // can't access the fields since it's not in memory
    println!("{}", key_bindings.crouch);
    key_bindings.crouch = KeyCode::ControlLeft;

    // can't get the underlying resource since it's not in memory
    key_bindings.get();
    key_bindings.get_mut();

    // can't update, persist, or unload the underlying resource since it's not in memory
    key_bindings.update(...);
    key_bindings.persist();
    key_bindings.unload(); // this will call persist before unloading so it'll panic as well
}

// none of these will panic
fn correct_usage_of_unloaded_key_bindings(key_bindings: Res<Persistent<KeyBindings>>) {
    // can get properties of the persistent resource
    key_bindings.name();
    key_bindings.format();
    key_bindings.storage();
    key_bindings.is_loaded();
    key_bindings.is_unloaded();

    // can try to get the underlying resource
    key_bindings.try_get();
    key_bindings.try_get_mut();

    // can set the resource to a new value
    key_bindings.set(...);

    // can unload without persisting as it's a no-op
    key_bindings.unload_without_persisting(...);

    // can reload the persistent resource
    key_bindings.reload(...);

    // can revert the persistent resource to its default
    key_bindings.revert_to_default(...);
    key_bindings.revert_to_default_in_memory(...);
}
```

## Prettifying

It's a good idea to store some resources with a prettified format during development to easily observe/modify them.

You can use `pretty` feature to enable prettified textual formats:

```toml
[features]
debug = ["bevy-persistent/pretty"]
```

And in your game:

```rust
fn setup(mut commands: Commands) {
    let config_dir = dirs::config_dir().unwrap().join("your-amazing-game");
    commands.insert_resource(
        Persistent::<KeyBindings>::builder()
            .name("key bindings")
            .format({
                #[cfg(feature = "debug")]
                {
                    StorageFormat::JsonPretty
                }
                #[cfg(not(feature = "debug"))]
                {
                    StorageFormat::Json
                }
            })
            .path(config_dir.join("key-bindings.toml"))
            .default(KeyBindings { jump: KeyCode::Space, crouch: KeyCode::C })
            .build()
            .expect("failed to initialize key bindings")
    )
}
```

Then you can develop your game using:

```shell
cargo run --features debug
```

And to release your game, you can compile using:

```shell
cargo build --release
```

## WebAssembly

### ...is supported!

When building persistent resources, you need to specify a path. Normally, this path is used to specify a location in the filesystem, but there is no filesystem in WebAssembly. Instead, it has [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) and [session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage).

Changing the API of the library, or creating a separate API for WebAssembly would make the library complicated to use. Instead, the library uses the fact that the selection of storage can be done using a path.

- `/local/settings/key-bindings.toml` would store the persistent resource in local storage with the key `settings/key-bindings.toml`
- `/session/settings/key-bindings.toml` would store the persistent resource in session storage with the key `settings/key-bindings.toml`

It might seem complicated at first, but makes it really easy to support both Native and WebAssembly application.

```rust
use std::path::Path;

fn setup(mut commands: Commands) {
    let config_dir = dirs::config_dir()
        .map(|native_config_dir| native_config_dir.join("your-amazing-game"))
        .unwrap_or(Path::new("local").join("configuration"));

    commands.insert_resource(
        Persistent::<KeyBindings>::builder()
            .name("key bindings")
            .format(StorageFormat::Json)
            .path(config_dir.join("key-bindings.toml"))
            .default(KeyBindings { jump: KeyCode::Space, crouch: KeyCode::C })
            .build()
            .expect("failed to initialize key bindings")
    )
}
```

With the code above, you don't need to have any conditional compilation to support both Native and WebAssembly application.

- In Native applications, it'll determine the configuration directory of the platform (e.g., `~/.config`) and join the name of your game to it (e.g., `~/.config/your-amazing-game`), and use it as the base directory in the filesystem.
- In WebAssembly applications, it'll use local storage with the base key of `configuration`, once you join it with `key-binding.toml`, the resource will be stored using the key `configuration/key-bindings.toml`.

If the first element of the specified path is not `"local"` or `"session"`, the library will panic!

If you don't like this approach, and want to be strict with types, you can use the [new](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.new) method of [Persistent\<R>](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html) instead.

```rust
fn setup(mut commands: Commands) {
    use bevy_persistent::Storage;

    let name = "key bindings";
    let format = StorageFormat::Toml;
    let storage = Storage::LocalStorage { key: "key bindings".to_owned() };
    let loaded = true;
    let default = KeyBindings { jump: KeyCode::Space, crouch: KeyCode::C };
    let revertible = false;
    let revert_to_default_on_deserialization_errors = false;

    commands.insert_resource(
        Persistent::new(
            name,
            format,
            storage,
            loaded,
            default,
            revertible,
            revert_to_default_on_deserialization_errors,
        )
        .expect("failed to initialize key bindings"),
    );
}
```

## Examples

There are a few examples that you can run directly and play around with in the [examples](https://github.com/umut-sahin/bevy-persistent/tree/main/examples) folder.

```shell
cargo run --release --features all --example name-of-the-example
```

To run the examples in a browser using WebAssembly, you can use [wasm-server-runner](https://github.com/jakobhellermann/wasm-server-runner).

```shell
cargo run --release --features all --target wasm32-unknown-unknown --example name-of-the-example
```

## Creating plugins on top

If you want to create plugins that use persistent resources, you can use the `library` feature to avoid specifying storage formats.

```toml
[package]
name = "bevy-amazing-library"
version = "0.1.0"
edition = "2021"

[dependencies]
bevy-persistent = { version = "0.3" }

[dev-dependencies]
bevy-persistent = { version = "0.3", features = ["all"] }

[features]
default = []
library = ["bevy-persistent/library"]
```

You can develop your library with `library` feature enabled (e.g., `cargo build --features library`), and [bevy-persistent](https://github.com/umut-sahin/bevy-persistent/) wouldn't complain when building your library, but applications would still get the error.

## Relation to

### bevy_pkv

[bevy_pkv](https://github.com/johanhelsing/bevy_pkv) is a generic key value store for [Bevy](https://bevyengine.org/). It is an excellent library, and it can be used as an alternative to [bevy-persistent](https://github.com/umut-sahin/bevy-persistent/). Here are some of the differences between the two libraries!

- **bevy_pkv is a lot more flexible**

Let's say you want to have two different `Settings` for two different users.

For the time being, this is not straightforward to do with [bevy-persistent](https://github.com/umut-sahin/bevy-persistent/) because `Persistent<R>` is a resource, there can only be a single instance of a resource. So you can't have two `Persistent<Settings>` in the same app. You can work around this by defining a custom struct (e.g., `Persistent<AllSettings>`), using a tuple (e.g., `Persistent<(Settings, Settings)>`), using a vector (e.g., `Persistent<Vec<Settings>>`), or using a hashmap (e.g., `Persistent<HashMap<Settings>>`).

This is very easy to do with [bevy_pkv](https://github.com/johanhelsing/bevy_pkv)!

```rust
fn setup(mut pkv: ResMut<PkvStore>) {
    // ...
    let blue_settings: Settings = ...;
    let red_settings: Settings = ...;
    // ...
    pkv.set("blue-settings", &blue_settings).unwrap();
    pkv.set("red-settings", &red_settings).unwrap();
    // ...
}

fn utilize_settings(pkv: Res<PkvStore>) {
    // ...
    let blue_settings: Settings = pkv.get("blue-settings").unwrap();
    let red_settings: Settings = pkv.get("red-settings").unwrap();
    // ...
}
```

Maybe [bevy-persistent](https://github.com/umut-sahin/bevy-persistent/) can provide a solution to this problem at some point if it's requested by the community! I really like the idea of providing `PersistentVec<R>` and `PersistentMap<K, R> where K: impl AsRef<str>`.

```rust
fn setup(mut settings: ResMut<PersistentMap<&'static str, Settings>>) {
    // ...
    let blue_settings: Settings = ...;
    let red_settings: Settings = ...;
    // ...
    settings.insert("blue", blue_settings)?;
    settings.insert("red", red_settings)?;
    // ...
}

fn utilize_settings(settings: Res<PersistentMap<&'static str, Settings>>) {
    // ...
    let blue_settings: &Settings = settings["blue"];
    let red_settings: &Settings = settings["red"];
    // ...
}
```

- **bevy_pkv is using extremely optimized key-value storage engines (in native apps)**

As far as I can see, the current version of [bevy_pkv](https://github.com/johanhelsing/bevy_pkv) has 2 storage options, [sled](https://sled.rs/) and [RocksDB](https://rocksdb.org/), which are extremely well optimized. This means if the persistent objects are updated frequently, [bevy_pkv](https://github.com/johanhelsing/bevy_pkv) performance will be outstanding!

[bevy-persistent](https://github.com/umut-sahin/bevy-persistent/) on the other hand stores each persistent object as a separate file, which means persisting objects trigger direct disk writes! This is okay for most use cases (e.g., settings or manual saves), but if your application requires very frequent updates, [bevy_pkv](https://github.com/johanhelsing/bevy_pkv) is the way to go!

Mandatory note on this, please please please profile before making decisions for performance reasons!

- **bevy_pkv parses the objects on each read**

Writes being extremely optimized, reads can be slow in [bevy_pkv](https://github.com/johanhelsing/bevy_pkv) because reading a persistent object requires parsing, on each read!

In [bevy-persistent](https://github.com/umut-sahin/bevy-persistent/), reads are basically free as `Persistent<R>` is just a wrapper around the actual object. That is unless you unload them using [unload](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.unload) or [unload_without_persisting](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.unload_without_persisting) methods to save memory. When you load them back using [reload](https://docs.rs/bevy-persistent/latest/bevy_persistent/persistent/struct.Persistent.html#method.reload) method, parsing will happen once and reads will be free again.

Again, always profile before making decisions for performance reasons!

- **bevy_pkv doesn't do automatic management**

With [bevy_pkv](https://github.com/johanhelsing/bevy_pkv), modifying the object and making the changes persistent is always two different steps.

```rust
fn modify_key_bindings(mut pkv: ResMut<PkvStore>) {
    let mut key_bindings = pkv.get::<KeyBindings>("key-bindings");
    key_bindings.crouch = KeyCode::ControlLeft;
    pkv.set("key-bindings", &key_bindings)
}
```

[bevy-persistent](https://github.com/umut-sahin/bevy-persistent/) on the other hand provides APIs to automate this process (see [Modification](#modification)).

- **bevy_pkv makes external edits very hard (in native apps)**

[bevy-persistent](https://github.com/umut-sahin/bevy-persistent/) stores each persistent object as a separate file in the specified format. If a textual format is used, the object becomes extremely easy to edit! The example in [Creation](#creation) will create `key-bindings.toml` in the specified location with the following contents:

```toml
jump = "Space"
crouch = "C"
```

You can easily change this file to:

```toml
jump = "Space"
crouch = "ControlLeft"
```

And it'd work perfectly in the next run!

[bevy_pkv](https://github.com/johanhelsing/bevy_pkv) on the other hand stores the objects in a single database in a binary format, which is more efficient but not suitable external modification.

- **bevy_pkv makes storing objects in different locations a bit inconvenient (in native apps)**

This one is kinda like the one above, but it's more about how things are structured to satisfy your needs! For example, you may want to synchronize user saves through [Steam](https://store.steampowered.com/) but not user settings as they need to be different across machines.

This scenario is doable with [bevy_pkv](https://github.com/johanhelsing/bevy_pkv), but it requires creating new types that wrap `PkvStore` within them and using those instead of a single `PkvStore`, which is a little inconvenient. It's very easy with [bevy-persistent](https://github.com/umut-sahin/bevy-persistent/) however as you can structure your persistent objects however you see fit in the filesystem using a single type!

- **bevy_pkv only supports JSON and Binary storage formats**

[bevy-persistent](https://github.com/umut-sahin/bevy-persistent/) supports a wide variety of storage formats! If you're porting a game from another engine to [Bevy](https://bevyengine.org/) you might be able to just define the structure of some objects (e.g., settings, saves) and let [bevy-persistent](https://github.com/umut-sahin/bevy-persistent/) handle the rest!

With [bevy_pkv](https://github.com/johanhelsing/bevy_pkv) however, you need to create a transformation layer as it's not directly compatible with any of the widely used formats.

- **bevy_pkv is not type safe**

[bevy-persistent](https://github.com/umut-sahin/bevy-persistent/) integrates with type system of [Bevy](https://bevyengine.org/). Types of persistent resources are specified in system definitions and everything is handled by [Bevy](https://bevyengine.org/).

[bevy_pkv](https://github.com/johanhelsing/bevy_pkv) on the other hand provides a single resource which you can use to query a persistent key value database with any type in the runtime. This is very flexible, but you need to specify types on each access, so it's error-prone. Also, you can't see what the system is doing easily without looking to the function body.

- **bevy_pkv can obstruct parallelism**

Each persistent resource in [bevy-persistent](https://github.com/umut-sahin/bevy-persistent/) is a separate resource. Which means systems can be scheduled to access/modify different persistent resources at the same time.

This cannot be the case with [bevy_pkv](https://github.com/johanhelsing/bevy_pkv) as it has a single resource type (`pkv: Res<PkvStore>` or `mut pkv: ResMut<PkvStore>`) for all systems, which prevents concurrent reads/writes on different persistent objects.

## License

[bevy-persistent](https://github.com/umut-sahin/bevy-persistent/) is free, open source and permissively licensed, just like [Bevy](https://bevyengine.org/)!

All code in this repository is dual-licensed under either:

- MIT License ([LICENSE-MIT](https://github.com/umut-sahin/bevy-persistent/blob/main/LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
- Apache License, Version 2.0 ([LICENSE-APACHE]((https://github.com/umut-sahin/bevy-persistent/blob/main/LICENSE-APACHE)) or <https://www.apache.org/licenses/LICENSE-2.0>)

This means you can select the license you prefer!