Saveload
saveload
is a module that provides mechanisms to serialize and deserialize a
World
, it makes use of the popular serde
library and requires the feature
flag serde
to be enabled for specs
in your Cargo.toml
file.
At a high level, it works by defining a Marker
component as well as a
MarkerAllocator
resource. Marked entities will be the only ones subject to
serialization and deserialization.
saveload
also defines SerializeComponents
and DeserializeComponents
,
these do the heavy lifting of exporting and importing.
Let's go over everything, point by point:
Marker
and MarkerAllocator
Marker
and MarkerAllocator<M: Marker>
are actually traits, simple
implementations are available with SimpleMarker<T: ?Sized>
and
SimpleMarkerAllocator<T: ?Sized>
, which you may use multiple times with
Zero Sized Types.
struct NetworkSync;
struct FilePersistent;
fn main() {
let mut world = World::new();
world.register::<SimpleMarker<NetworkSync>>();
world.insert(SimpleMarkerAllocator::<NetworkSync>::default());
world.register::<SimpleMarker<FilePersistent>>();
world.insert(SimpleMarkerAllocator::<FilePersistent>::default());
world
.create_entity()
.marked::<SimpleMarker<NetworkSync>>()
.marked::<SimpleMarker<FilePersistent>>()
.build();
}
You may also roll your own implementations like so:
use specs::{prelude::*, saveload::{MarkedBuilder, Marker, MarkerAllocator}};
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[derive(serde::Serialize, serde::Deserialize)]
struct MyMarker(u64);
impl Component for MyMarker {
type Storage = VecStorage<Self>;
}
impl Marker for MyMarker {
type Identifier = u64;
type Allocator = MyMarkerAllocator;
fn id(&self) -> u64 {
self.0
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct MyMarkerAllocator(std::collections::HashMap<u64, Entity>);
impl MarkerAllocator<MyMarker> for MyMarkerAllocator {
fn allocate(&mut self, entity: Entity, id: Option<u64>) -> MyMarker {
let id = id.unwrap_or_else(|| self.unused_key()));
self.0.insert(id, entity);
MyMarker(id)
}
fn retrieve_entity_internal(&self, id: u64) -> Option<Entity> {
self.0.get(&id).cloned()
}
fn maintain(
&mut self,
entities: &EntitiesRes,
storage: &ReadStorage<MyMarker>,
) {
// naive and possibly costly implementation, the techniques in
// chapter 12 would be useful here!
self.0 = (entities, storage)
.join()
.map(|(entity, marker)| (marker.0, entity))
.collect();
}
}
fn main() {
let mut world = World::new();
world.register::<MyMarker>();
world.insert(MyMarkerAllocator::default());
world
.create_entity()
.marked::<MyMarker>()
.build();
}
Note that the trait MarkedBuilder
must be imported to mark entities during
creation, it is implemented for EntityBuilder
and LazyBuilder
. Marking an
entity that is already present is straightforward:
fn mark_entity(
entity: Entity,
mut allocator: Write<SimpleMarkerAllocator<A>>,
mut storage: WriteStorage<SimpleMarker<A>>,
) {
use MarkerAllocator; // for MarkerAllocator::mark
match allocator.mark(entity, &mut storage) {
None => println!("entity was dead before it could be marked"),
Some((_, false)) => println!("entity was already marked"),
Some((_, true)) => println!("entity successfully marked"),
}
}
Serialization and Deserialization
As previously mentioned, SerializeComponents
and DeserializeComponents
are
the two heavy lifters. They're traits as well, however, that's just an
implementation detail, they are used like functions. They're implemented over
tuples of up to 16 ReadStorage
/WriteStorage
.
Here is an example showing how to serialize:
specs::saveload::SerializeComponents
::<Infallible, SimpleMarker<A>>
::serialize(
&(position_storage, mass_storage), // tuple of ReadStorage<'a, _>
&entities, // Entities<'a>
&marker_storage, // ReadStorage<'a, SimpleMarker<A>>
&mut serializer, // serde::Serializer
) // returns Result<Serializer::Ok, Serializer::Error>
and now, how to deserialize:
specs::saveload::DeserializeComponents
::<Infallible, SimpleMarker<A>>
::deserialize(
&mut (position_storage, mass_storage), // tuple of WriteStorage<'a, _>
&entities, // Entities<'a>
&mut marker_storage, // WriteStorage<'a SimpleMarker<A>>
&mut marker_allocator, // Write<'a, SimpleMarkerAllocator<A>>
&mut deserializer, // serde::Deserializer
) // returns Result<(), Deserializer::Error>
As you can see, all parameters but one are SystemData
, the easiest way to
access those would be through systems (chapter on this subject) or by
calling World::system_data
:
let (
entities,
mut marker_storage,
mut marker_allocator,
mut position_storage,
mut mass_storage,
) = world.system_data::<(
Entities,
WriteStorage<SimpleMarker<A>>,
Write<SimpleMarkerAllocator<A>>,
WriteStorage<Position>,
WriteStorage<Mass>,
)>();
Each Component
that you will read from and write to must implement
ConvertSaveload
, it's a benign trait and is implemented for all Component
s
that are Clone + serde::Serialize + serde::DeserializeOwned
, however you may
need to implement it (or derive it using specs-derive
). In which case, you
may introduce more bounds to the first generic parameter and will need to
replace Infallible
with a custom type, this custom type must implement
From<<TheComponent as ConvertSaveload>::Error>
for all Component
s,
basically.