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
    ::<NoError, 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
    ::<NoError, 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 Components 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 NoError with a custom type, this custom type must implement From<<TheComponent as ConvertSaveload>::Error> for all Components, basically.