diff --git a/Cargo.toml b/Cargo.toml index c040a710..8c43fd9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ rand = "0.8.3" trybuild = "1.0.23" serde = { version = "1.0.117", features = ["derive"] } serde_test = "1.0.117" +bincode = "1.3.3" [[bench]] name = "bench" @@ -52,3 +53,8 @@ debug = true [workspace] members = ["macros", "tests/no-std-test-crates/macros"] + + +[[example]] +name = "serialize_to_disk" +required-features = ["column-serialize"] diff --git a/examples/serialize_to_disk.rs b/examples/serialize_to_disk.rs new file mode 100644 index 00000000..4107a823 --- /dev/null +++ b/examples/serialize_to_disk.rs @@ -0,0 +1,180 @@ +//! This example demonstrates how to save [hecs::World] instance +//! to disk and load it back to memory using serialization. It can be useful for implementing +//! a save mechanism for a game. +//! +//! The example creates a sample `World`, serializes it, saves it to disk as `saved_world.world` file, +//! loads it back from the disk, deserializes the loaded world data and validates that component +//! data of the worlds match. +//! +//! Run this example from crate root with: +//! `cargo run --example serialize_to_disk --features "column-serialize"` + +use hecs::serialize::column::{ + deserialize_column, try_serialize, try_serialize_id, DeserializeContext, SerializeContext, +}; +use hecs::{Archetype, ColumnBatchBuilder, ColumnBatchType, World}; +use serde::{Deserialize, Serialize}; +use std::any::TypeId; +use std::fs::File; +use std::io::{BufReader, Write}; +use std::path::Path; + +// Identifiers for the components we want to include in the serialization process: +#[derive(Serialize, Deserialize)] +enum ComponentId { + ComponentA, + ComponentB, +} + +// We need to implement context types for the hecs serialization process: +#[derive(Default)] +struct SaveContextSerialize {} +#[derive(Default)] +struct SaveContextDeserialize { + components: Vec, +} + +// Components of our world. +// Only Serialize and Deserialize derives are necessary. +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +struct ComponentA { + data: usize, +} + +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +struct ComponentB { + some_other_data: String, +} + +impl DeserializeContext for SaveContextDeserialize { + fn deserialize_component_ids<'de, A>(&mut self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + self.components.clear(); // Discard data from the previous archetype + let mut batch = ColumnBatchType::new(); + while let Some(id) = seq.next_element()? { + match id { + ComponentId::ComponentA => { + batch.add::(); + } + ComponentId::ComponentB => { + batch.add::(); + } + } + self.components.push(id); + } + Ok(batch) + } + + fn deserialize_components<'de, A>( + &mut self, + entity_count: u32, + mut seq: A, + batch: &mut ColumnBatchBuilder, + ) -> Result<(), A::Error> + where + A: serde::de::SeqAccess<'de>, + { + // Decode component data in the order that the component IDs appeared + for component in &self.components { + match *component { + ComponentId::ComponentA => { + deserialize_column::(entity_count, &mut seq, batch)?; + } + ComponentId::ComponentB => { + deserialize_column::(entity_count, &mut seq, batch)?; + } + } + } + Ok(()) + } +} + +impl SerializeContext for SaveContextSerialize { + fn component_count(&self, archetype: &Archetype) -> usize { + archetype + .component_types() + .filter(|&t| t == TypeId::of::() || t == TypeId::of::()) + .count() + } + + fn serialize_component_ids( + &mut self, + archetype: &Archetype, + mut out: S, + ) -> Result { + try_serialize_id::(archetype, &ComponentId::ComponentA, &mut out)?; + try_serialize_id::(archetype, &ComponentId::ComponentB, &mut out)?; + out.end() + } + + fn serialize_components( + &mut self, + archetype: &Archetype, + mut out: S, + ) -> Result { + try_serialize::(archetype, &mut out)?; + try_serialize::(archetype, &mut out)?; + out.end() + } +} + +fn main() { + // initialize world: + let mut world = World::new(); + let input_data1 = ComponentA { data: 42 }; + let input_data2 = ComponentB { + some_other_data: "Hello".to_string(), + }; + world.spawn((input_data1.clone(),)); + world.spawn((input_data2.clone(),)); + + let save_file_name = "saved_world.world"; + + // serialize and save our world to disk: + let mut buffer: Vec = Vec::new(); + let options = bincode::options(); + let mut serializer = bincode::Serializer::new(&mut buffer, options); + hecs::serialize::column::serialize( + &world, + &mut SaveContextSerialize::default(), + &mut serializer, + ) + .expect("Failed to serialize"); + let path = Path::new(save_file_name); + let mut file = match File::create(&path) { + Err(why) => panic!("couldn't create {}: {}", path.display(), why), + Ok(file) => file, + }; + file.write(&buffer) + .expect(&format!("Failed to write file: {}", save_file_name)); + println!("Saved world \'{}\' to disk.", path.display()); + + // load our world from disk and deserialize it back as world: + let open = File::open(path).expect("not found!"); + let reader = BufReader::new(open); + let mut deserializer = bincode::Deserializer::with_reader(reader, options); + match hecs::serialize::column::deserialize( + &mut SaveContextDeserialize::default(), + &mut deserializer, + ) { + Ok(world) => { + // we loaded world from disk successfully, let us confirm that its data is still + // the same: + println!("Loaded world \'{}\' from disk.", path.display()); + print!("Validating world data... "); + assert_eq!(world.len(), 2); + for (_, t) in &mut world.query::<&ComponentA>() { + assert_eq!(t, &input_data1); + } + for (_, t) in &mut world.query::<&ComponentB>() { + assert_eq!(t, &input_data2); + } + println!("Ok!"); + } + Err(err) => { + println!("Failed to deserialize world: {}", err); + } + } +}