Serialization and deserialization
While the built in reflection system could be used for serialization, similar
to Debug
, deserialization can not. This is because the lazy registration of
components mean that the world may not know of the componenets deserialization
upfront, especially since deserialization yields a new world.
In addition, having serialization be implicit may lead to components not being serialized when they are expected to, or components which should not be serialized to be written to disk, such as local state. As such, it leads to unexpected, undesirable, or downright insecure behavior.
A similar story is to be found for deserialization, where program behaviour can be intrusively modified due to anything being able to be deserialized and put into the world.
As such [de]serialization is explicit and requires registering a subset of components.
#![allow(unused)] fn main() { component! { position: Vec3 => [Debuggable], velocity: Vec3 => [Debuggable], } tracing_subscriber::fmt().init(); use flax::serialize::{SerializationContextBuilder, SerializeFormat}; tracing::info!("It works"); let mut world = World::new(); let mut rng = StdRng::seed_from_u64(239); let mut batch = BatchSpawn::new(16); batch.set( position(), (&mut rng).sample_iter(Standard).map(|v: Vec3| v * 2.0), )?; batch.set(velocity(), (&mut rng).sample_iter(Standard))?; batch.set(name(), (0..).map(|v| format!("id.{v}")))?; batch.spawn(&mut world); let mut batch = BatchSpawn::new(8); batch.set( position(), (&mut rng).sample_iter(Standard).map(|v: Vec3| v * 2.0), )?; batch.set(name(), (16..).map(|v| format!("id.{v}")))?; batch.spawn(&mut world); }
We are interested in name
, position
, and velocity
, nothing else, even if
it implements Serialize
.
#![allow(unused)] fn main() { let (serializer, deserializer) = SerializationContextBuilder::new() .with(name()) .with(position()) .with(velocity()) .build(); let json = serde_json::to_string_pretty(&serializer.serialize(&world, SerializeFormat::RowMajor))?; // eprintln!("World: {json}"); }
When deserializing a world it is often of interest in merging it or deserializing into another world.
This is supported through the merge_with
function, which will migrate
colliding ids to new ones, returning a map in doing so.
The advantage of doing it this way is that the world is left untouched if deserialization failed.
#![allow(unused)] fn main() { // An existing world with entities in it let mut world = World::new(); let mut batch = BatchSpawn::new(32); batch.set( position(), (&mut rng).sample_iter(Standard).map(|v: Vec3| v * 2.0), )?; batch.set(name(), (0..).map(|v| format!("other_id.{v}")))?; batch.spawn(&mut world); let mut result = deserializer.deserialize(&mut serde_json::Deserializer::from_str(&json))?; // Merge `result` into `world` world.merge_with(&mut result); // eprintln!("World: {world:#?}"); }