Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added some comments to the saveload example #492

Merged
merged 5 commits into from Oct 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,11 +4,13 @@
Note this bumps the minimum supported rust version to 1.28.0 ([#447]).
* Deprecated `world::Bundle` ([#486])
* Updated Chapter 7: Setup to be more explicit, updated examples to follow that methodology ([#487])
* Added some comments to the `saveload` example ([#492])
* Updated dependency versions ([#494])

[#447]: https://github.com/slide-rs/specs/pull/447
[#486]: https://github.com/slide-rs/specs/pull/486
[#487]: https://github.com/slide-rs/specs/pull/487
[#492]: https://github.com/slide-rs/specs/pull/492
[#494]: https://github.com/slide-rs/specs/pull/494

# 0.12.3
Expand Down
59 changes: 59 additions & 0 deletions examples/saveload.rs
Expand Up @@ -9,6 +9,10 @@ use specs::error::NoError;
use specs::prelude::*;
use specs::saveload::{DeserializeComponents, MarkedBuilder, SerializeComponents, U64Marker, U64MarkerAllocator};

// This is an example of how the serialized data of two entities might look on disk.
//
// When serializing entities, they are written in an array of tuples, each tuple representing one entity.
// The entity's marker and components are written as fields into these tuples, knowing nothing about the original entity's id.
const ENTITIES: &str = "
[
(
Expand All @@ -34,6 +38,7 @@ const ENTITIES: &str = "
]
";

// A dummy component that can be serialized and deserialized.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
struct Pos {
x: f32,
Expand All @@ -44,18 +49,24 @@ impl Component for Pos {
type Storage = VecStorage<Self>;
}

// A dummy component that can be serialized and deserialized.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
struct Mass(f32);

impl Component for Mass {
type Storage = VecStorage<Self>;
}

// It is necessary to supply the `(De)SerializeComponents`-trait with an error type that implements the `Display`-trait.
// In this case we want to be able to return different errors, and we are going to use a `.ron`-file to store our data.
// Therefore we use a custom enum, which can display both the `NoError`and `ron::ser::Error` type.
// This enum could be extended to incorporate for example `std::io::Error` and more.
#[derive(Debug)]
enum Combined {
Ron(ron::ser::Error),
}

// Implementing the required `Display`-trait, by matching the `Combined` enum, allowing different error types to be displayed.
impl fmt::Display for Combined {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Expand All @@ -64,12 +75,14 @@ impl fmt::Display for Combined {
}
}

// This returns the `ron::ser:Error` in form of the `Combined` enum, which can then be matched and displayed accordingly.
impl From<ron::ser::Error> for Combined {
fn from(x: ron::ser::Error) -> Self {
Combined::Ron(x)
}
}

// This cannot be called.
impl From<NoError> for Combined {
fn from(e: NoError) -> Self {
match e {}
Expand All @@ -79,16 +92,28 @@ impl From<NoError> for Combined {
fn main() {
let mut world = World::new();

// Since in this example no system uses these resources, they have to be registered manually.
// This is typically not required.
world.register::<Pos>();
world.register::<Mass>();
world.register::<U64Marker>();

// Adds a predefined marker allocator to the world, as a resource.
// This predifined marker uses a `HashMap<u64, Entity>` to keep track of all entities that should be (de)serializable,
// as well as which ids are already in use.
world.add_resource(U64MarkerAllocator::new());

world
.create_entity()
.with(Pos { x: 1.0, y: 2.0 })
.with(Mass(0.5))
// The `.marked` function belongs to the [`MarkedBuilder`](struct.MarkedBuilder.html) trait,
// which is implemented for example for the [`EntityBuilder`](struct.EntityBuilder.html).
// It yields the next higher id, that is not yet in use.
//
// Since the `Marker` is passed as a generic type parameter, it is possible to use several different `MarkerAllocators`,
// e.g. to keep track of different types of entities, with different ids.
// **Careful when deserializing, it is not always clear for every fileforamt whether a number is supposed to be i.e. a `u32` or `u64`!**
.marked::<U64Marker>()
.build();

Expand All @@ -99,9 +124,12 @@ fn main() {
.marked::<U64Marker>()
.build();

// Here we create a system that lets us access the entities to serialize.
struct Serialize;

impl<'a> System<'a> for Serialize {
// This SystemData contains the entity-resource, as well as all components that shall be serialized,
// plus the marker component storage.
type SystemData = (
Entities<'a>,
ReadStorage<'a, Pos>,
Expand All @@ -110,7 +138,20 @@ fn main() {
);

fn run(&mut self, (ents, pos, mass, markers): Self::SystemData) {
// First we need a serializer for the format of choice, in this case the `.ron`-format.
let mut ser = ron::ser::Serializer::new(Some(Default::default()), true);

// For serialization we use the [`SerializeComponents`](struct.SerializeComponents.html)-trait's `serialize` function.
// It takes two generic parameters:
// * An unbound type -> `NoError` (However, the serialize function expects it to be bound by the `Display`-trait)
// * A type implementing the `Marker`-trait -> [U64Marker](struct.U64Marker.html) (a convenient, predefined marker)
//
// The first parameter resembles the `.join()` syntax from other specs-systems,
// every component that should be serialized has to be put inside a tuple.
//
// The second and third parameters are just the entity-storage and marker-storage, which get `.join()`ed internally.
//
// Lastly, we provide a mutable reference to the serializer of choice, which has to have the `serde::ser::Serializer`-trait implemented.
SerializeComponents::<NoError, U64Marker>::serialize(
&(&pos, &mass),
&ents,
Expand All @@ -120,17 +161,24 @@ fn main() {
// TODO: Specs should return an error which combines serialization
// and component errors.

// At this point, `ser` could be used to write its contents to a file, which is not done here.
// Instead we print the content of this pseudo-file.
println!("{}", ser.into_output_string());
}
}

// Running the system results in a print to the standard output channel, in `.ron`-format,
// showing how the serialized dummy entities look like.
Serialize.run_now(&world.res);

// -----------------

// Just like the previous Serialize-system, we write a Deserialize-system.
struct Deserialize;

impl<'a> System<'a> for Deserialize {
// This requires all the component storages our serialized entities have, mutably,
// plus a `MarkerAllocator` resource to write the deserialized ids into, so that we can later serialize again.
type SystemData = (
Entities<'a>,
Write<'a, U64MarkerAllocator>,
Expand All @@ -140,9 +188,18 @@ fn main() {
);

fn run(&mut self, (ent, mut alloc, pos, mass, mut markers): Self::SystemData) {
// The `const ENTITIES: &str` at the top of this file was formatted according to the `.ron`-specs,
// therefore we need a `.ron`-deserializer.
// Others can be used, as long as they implement the `serde::de::Deserializer`-trait.
use ron::de::Deserializer;

// Typical file operations are omitted in this example, since we do not have a seperate file, but a `const &str`.
// We use a convencience function of the `ron`-crate: `from_str`, to convert our data form the top of the file.
if let Ok(mut de) = Deserializer::from_str(ENTITIES) {
// Again, we need to pass in a type implementing the `Display`-trait,
// as well as a type implementing the `Marker`-trait.
// However, from the function parameter `&mut markers`, which refers to the `U64Marker`-storage,
// the necessary type of marker can be inferred, hence the `, _>´.
DeserializeComponents::<Combined, _>::deserialize(
&mut (pos, mass),
&ent,
Expand All @@ -154,8 +211,10 @@ fn main() {
}
}

// If we run this system now, the `ENTITIES: &str` is going to be deserialized, and two entities are created.
Deserialize.run_now(&world.res);

// Printing the `Pos`-component storage entries to show the result of deserializing.
println!(
"{:#?}",
(&world.read_storage::<Pos>()).join().collect::<Vec<_>>()
Expand Down