Skip to content

Commit 70d1954

Browse files
committed
Add serde chapter to the guide
#223
1 parent 354b98f commit 70d1954

4 files changed

Lines changed: 180 additions & 0 deletions

File tree

guide/master/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- [Systems](./fundamentals/systems.md)
2222
- [Going Further](./going-further.md)
2323
- [Tracking](./going-further/tracking.md)
24+
- [Serde](./going-further/serde.md)
2425
- [Parallelism](./going-further/parallelism.md)
2526
- [Custom Views](./going-further/custom-views.md)
2627
- [!Send and !Sync Components](./going-further/non-send-sync.md)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Serde
2+
3+
Serializing an ECS can be very easy or difficult, it all depends on what you're trying to achieve.\
4+
For example, let's say we only want to serialize two components: `Player` and `Position`.\
5+
We have many format options:
6+
7+
1. An array of `(EntityId, Player)` then another array of `(EntityId, Position)`.\
8+
This could include entities that have either component or only the ones that have both.
9+
2. An array of `(EntityId, (Player, Position))`.
10+
3. An array of `EntityId` then an array of `(Player, Position)`.
11+
4. An array of `EntityId` then another of `Player` then yet another of `Position`.
12+
5. The list goes on.
13+
14+
So which option is the best? It depends on the use case.\
15+
Option 1, for example, is one of the worst at memory but the best at retrieving random components.
16+
17+
There are as many options possible when deserializing: should components be overwritten? should `EntityId`s match? ...
18+
19+
## EntityId
20+
21+
Serializing [`EntityId`](https://docs.rs/shipyard/latest/shipyard/struct.EntityId.html) is technically all that is needed to serialize all entities and components in a [`World`](https://docs.rs/shipyard/latest/shipyard/struct.World.html).\
22+
It will require lots of work on the user's side but is the most flexible.\
23+
This will let us pick the best format for our use case.
24+
25+
[`EntityId`](https://docs.rs/shipyard/latest/shipyard/struct.EntityId.html) is very easy to (de)serialize.
26+
Example:
27+
28+
```rs
29+
{{#include ../../../../tests/book/serde.rs:entity_id}}
30+
```
31+
32+
A `Vec<EntityId>` would be just as simple.
33+
34+
## Views
35+
36+
This is where our options become limited.\
37+
If `shipyard` does the entire view(s) serialization, it has to pick a format.
38+
39+
The current implementation leaves the door open for future user customization.\
40+
For now, only Option 1 is implemented. Each component will create an array of `(EntityId, Component)`.\
41+
When deserializing, components will be attributed to the same [`EntityId`](https://docs.rs/shipyard/latest/shipyard/struct.EntityId.html) they were serialized with. They will override any existing component.
42+
43+
```rs
44+
{{#include ../../../../tests/book/serde.rs:single_view}}
45+
```
46+
47+
Serializing multiple components is not that much more work.
48+
49+
```rs
50+
{{#include ../../../../tests/book/serde.rs:multiple_views}}
51+
```

tests/book/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ mod non_send_sync;
1616
mod parallelism;
1717
mod remove_components;
1818
mod run;
19+
#[cfg(feature = "serde1")]
20+
mod serde;
1921
mod sparse_set;
2022
mod syntactic_peculiarities;
2123
mod systems;

tests/book/serde.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#[rustfmt::skip]
2+
#[test]
3+
fn entity_id() {
4+
// ANCHOR: entity_id
5+
use shipyard::{EntityId, World};
6+
7+
let mut world = World::new();
8+
9+
let eid1 = world.add_entity(());
10+
11+
let serialized = serde_json::to_string(&eid1).unwrap();
12+
assert_eq!(serialized, r#"{"index":0,"gen":0}"#);
13+
14+
let new_eid: EntityId = serde_json::from_str(&serialized).unwrap();
15+
assert_eq!(new_eid, eid1);
16+
// ANCHOR_END: entity_id
17+
}
18+
19+
#[rustfmt::skip]
20+
#[test]
21+
fn single_view() {
22+
// ANCHOR: single_view
23+
use shipyard::{Component, EntityId, View, ViewMut, World};
24+
25+
#[derive(Component, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
26+
struct Name(String);
27+
28+
let mut world = World::new();
29+
30+
let eid1 = world.add_entity(Name("Alice".to_string()));
31+
let eid2 = world.add_entity(Name("Bob".to_string()));
32+
33+
// There is also a World::serialize
34+
let serialized = world.run(|v_name: View<Name>| serde_json::to_string(&v_name).unwrap());
35+
36+
drop(world);
37+
38+
let mut world = World::new();
39+
40+
let mut deserializer = serde_json::de::Deserializer::from_str(&serialized);
41+
world
42+
.deserialize::<_, ViewMut<Name>>(&mut deserializer)
43+
.unwrap();
44+
45+
assert_eq!(world.get::<&Name>(eid2).unwrap().0, "Bob");
46+
assert_eq!(world.get::<&Name>(eid1).unwrap().0, "Alice");
47+
48+
// Note that we never added eid1 or eid2 to this second World
49+
// they weren't added during deserialization either
50+
// the World is currently in an unstable state
51+
52+
assert_eq!(world.is_entity_alive(eid1), false);
53+
54+
// To fix it, we can use `World::spawn` for example
55+
// we could've also created empty entities
56+
// or (de)serialized EntitiesViewMut
57+
58+
world.spawn(eid1);
59+
world.spawn(eid2);
60+
61+
assert_eq!(world.is_entity_alive(eid1), true);
62+
assert_eq!(world.is_entity_alive(eid2), true);
63+
// ANCHOR_END: single_view
64+
}
65+
66+
#[rustfmt::skip]
67+
#[test]
68+
fn multiple_views() {
69+
// ANCHOR: multiple_views
70+
use shipyard::{
71+
error, Component, EntitiesViewMut, EntityId, View, ViewMut, World, WorldBorrow,
72+
};
73+
74+
#[derive(Component, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
75+
struct Name(String);
76+
77+
#[derive(Component, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
78+
enum FavoriteLanguage {
79+
Rust,
80+
}
81+
82+
#[derive(WorldBorrow, serde::Serialize, serde::Deserialize)]
83+
struct LanguagesViewMut<'v> {
84+
#[serde(borrow)]
85+
entities: EntitiesViewMut<'v>,
86+
#[serde(borrow)]
87+
vm_name: ViewMut<'v, Name>,
88+
#[serde(borrow)]
89+
vm_favorite_language: ViewMut<'v, FavoriteLanguage>,
90+
}
91+
92+
let mut world = World::new();
93+
94+
let eid1 = world.add_entity((Name("Alice".to_string()), FavoriteLanguage::Rust));
95+
let eid2 = world.add_entity(Name("Bob".to_string()));
96+
97+
let serialized =
98+
world.run(|vm_languages: LanguagesViewMut| serde_json::to_string(&vm_languages).unwrap());
99+
100+
drop(world);
101+
102+
let mut world = World::new();
103+
104+
let mut deserializer = serde_json::de::Deserializer::from_str(&serialized);
105+
world
106+
.deserialize::<_, LanguagesViewMut>(&mut deserializer)
107+
.unwrap();
108+
109+
assert_eq!(world.get::<&Name>(eid1).unwrap().0, "Alice");
110+
assert_eq!(
111+
*world.get::<&FavoriteLanguage>(eid1).unwrap(),
112+
&FavoriteLanguage::Rust
113+
);
114+
assert_eq!(world.get::<&Name>(eid2).unwrap().0, "Bob");
115+
assert!(matches!(
116+
world.get::<&FavoriteLanguage>(eid2),
117+
Err(error::GetComponent::MissingComponent(_))
118+
));
119+
120+
// This time we serialized EntitiesViewMut
121+
// so no unstable state
122+
123+
assert_eq!(world.is_entity_alive(eid1), true);
124+
assert_eq!(world.is_entity_alive(eid2), true);
125+
// ANCHOR_END: multiple_views
126+
}

0 commit comments

Comments
 (0)