Skip to content

Commit

Permalink
Add binary format support for DynamicScene (de)serializers
Browse files Browse the repository at this point in the history
  • Loading branch information
MrGVSV committed Oct 31, 2022
1 parent 29542b1 commit 940d207
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 3 deletions.
4 changes: 4 additions & 0 deletions crates/bevy_scene/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ ron = "0.8.0"
uuid = { version = "1.1", features = ["v4", "serde"] }
anyhow = "1.0.4"
thiserror = "1.0"

[dev-dependencies]
postcard = { version = "1.0", features = ["alloc"] }
bincode = "1.3"
241 changes: 238 additions & 3 deletions crates/bevy_scene/src/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,22 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> {
formatter.write_str("entities")
}

fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let entity = seq
.next_element::<u32>()?
.ok_or_else(|| Error::missing_field(ENTITY_FIELD_ENTITY))?;
let components = seq
.next_element_seed(ComponentDeserializer {
registry: self.registry,
})?
.ok_or_else(|| Error::missing_field(ENTITY_FIELD_COMPONENTS))?;

Ok(DynamicEntity { entity, components })
}

fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
Expand Down Expand Up @@ -376,12 +392,13 @@ impl<'a, 'de> Visitor<'de> for ComponentVisitor<'a> {

#[cfg(test)]
mod tests {
use crate::serde::SceneDeserializer;
use crate::DynamicSceneBuilder;
use crate::serde::{SceneDeserializer, SceneSerializer};
use crate::{DynamicScene, DynamicSceneBuilder};
use bevy_app::AppTypeRegistry;
use bevy_ecs::entity::EntityMap;
use bevy_ecs::prelude::{Component, ReflectComponent, World};
use bevy_reflect::Reflect;
use bevy_reflect::{FromReflect, Reflect, ReflectSerialize};
use bincode::Options;
use serde::de::DeserializeSeed;

#[derive(Component, Reflect, Default)]
Expand All @@ -394,6 +411,24 @@ mod tests {
#[reflect(Component)]
struct Baz(i32);

#[derive(Component, Reflect, Default)]
#[reflect(Component)]
struct MyComponent {
foo: [usize; 3],
bar: (f32, f32),
baz: MyEnum,
}

#[derive(Reflect, FromReflect, Default)]
enum MyEnum {
#[default]
Unit,
Tuple(String),
Struct {
value: u32,
},
}

fn create_world() -> World {
let mut world = World::new();
let registry = AppTypeRegistry::default();
Expand All @@ -402,6 +437,12 @@ mod tests {
registry.register::<Foo>();
registry.register::<Bar>();
registry.register::<Baz>();
registry.register::<MyComponent>();
registry.register::<MyEnum>();
registry.register::<String>();
registry.register_type_data::<String, ReflectSerialize>();
registry.register::<[usize; 3]>();
registry.register::<(f32, f32)>();
}
world.insert_resource(registry);
world
Expand Down Expand Up @@ -499,4 +540,198 @@ mod tests {
assert_eq!(2, dst_world.query::<&Bar>().iter(&dst_world).count());
assert_eq!(1, dst_world.query::<&Baz>().iter(&dst_world).count());
}

#[test]
fn should_roundtrip_postcard() {
let mut world = create_world();

world.spawn(MyComponent {
foo: [1, 2, 3],
bar: (1.3, 3.7),
baz: MyEnum::Tuple("Hello World!".to_string()),
});

let registry = world.resource::<AppTypeRegistry>();

let scene = DynamicScene::from_world(&world, registry);

let scene_serializer = SceneSerializer::new(&scene, &registry.0);
let serialized_scene = postcard::to_allocvec(&scene_serializer).unwrap();

assert_eq!(
vec![
1, 0, 1, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, 58, 58, 115, 101, 114,
100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111,
110, 101, 110, 116, 1, 2, 3, 102, 102, 166, 63, 205, 204, 108, 64, 1, 12, 72, 101,
108, 108, 111, 32, 87, 111, 114, 108, 100, 33
],
serialized_scene
);

let scene_deserializer = SceneDeserializer {
type_registry: &registry.0.read(),
};
let deserialized_scene = scene_deserializer
.deserialize(&mut postcard::Deserializer::from_bytes(&serialized_scene))
.unwrap();

assert_eq!(1, deserialized_scene.entities.len());
assert_scene_eq(&scene, &deserialized_scene);
}

#[test]
fn should_roundtrip_bincode() {
let mut world = create_world();

world.spawn(MyComponent {
foo: [1, 2, 3],
bar: (1.3, 3.7),
baz: MyEnum::Tuple("Hello World!".to_string()),
});

let registry = world.resource::<AppTypeRegistry>();

let scene = DynamicScene::from_world(&world, registry);

let scene_serializer = SceneSerializer::new(&scene, &registry.0);
let serialized_scene = bincode::serialize(&scene_serializer).unwrap();

assert_eq!(
vec![
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0,
0, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, 58, 58, 115, 101, 114, 100, 101,
58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101,
110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
102, 102, 166, 63, 205, 204, 108, 64, 1, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 72, 101,
108, 108, 111, 32, 87, 111, 114, 108, 100, 33
],
serialized_scene
);

let scene_deserializer = SceneDeserializer {
type_registry: &registry.0.read(),
};

let deserialized_scene = bincode::DefaultOptions::new()
.with_fixint_encoding()
.deserialize_seed(scene_deserializer, &serialized_scene)
.unwrap();

assert_eq!(1, deserialized_scene.entities.len());
assert_scene_eq(&scene, &deserialized_scene);
}

/// A crude equality checker for [`DynamicScene`], used solely for testing purposes.
fn assert_scene_eq(expected: &DynamicScene, received: &DynamicScene) {
assert_eq!(
expected.entities.len(),
received.entities.len(),
"entity count did not match",
);

for expected in &expected.entities {
let received = received
.entities
.iter()
.find(|dynamic_entity| dynamic_entity.entity == expected.entity)
.unwrap_or_else(|| panic!("missing entity (expected: `{}`)", expected.entity));

assert_eq!(expected.entity, received.entity, "entities did not match",);

for expected in &expected.components {
let received = received
.components
.iter()
.find(|component| component.type_name() == expected.type_name())
.unwrap_or_else(|| {
panic!("missing component (expected: `{}`)", expected.type_name())
});

assert!(
expected
.reflect_partial_eq(received.as_ref())
.unwrap_or_default(),
"components did not match: (expected: `{:?}`, received: `{:?}`)",
expected,
received
);
}
}
}

/// These tests just verify that that the [`assert_scene_eq`] function is working properly for our tests.
mod assert_scene_eq_tests {
use super::*;

#[test]
#[should_panic(expected = "entity count did not match")]
fn should_panic_when_entity_count_not_eq() {
let mut world = create_world();
let registry = world.resource::<AppTypeRegistry>();
let scene_a = DynamicScene::from_world(&world, registry);

world.spawn(MyComponent {
foo: [1, 2, 3],
bar: (1.3, 3.7),
baz: MyEnum::Unit,
});

let registry = world.resource::<AppTypeRegistry>();
let scene_b = DynamicScene::from_world(&world, registry);

assert_scene_eq(&scene_a, &scene_b);
}

#[test]
#[should_panic(expected = "components did not match")]
fn should_panic_when_components_not_eq() {
let mut world = create_world();

let entity = world
.spawn(MyComponent {
foo: [1, 2, 3],
bar: (1.3, 3.7),
baz: MyEnum::Unit,
})
.id();

let registry = world.resource::<AppTypeRegistry>();
let scene_a = DynamicScene::from_world(&world, registry);

world.entity_mut(entity).insert(MyComponent {
foo: [3, 2, 1],
bar: (1.3, 3.7),
baz: MyEnum::Unit,
});

let registry = world.resource::<AppTypeRegistry>();
let scene_b = DynamicScene::from_world(&world, registry);

assert_scene_eq(&scene_a, &scene_b);
}

#[test]
#[should_panic(expected = "missing component")]
fn should_panic_when_missing_component() {
let mut world = create_world();

let entity = world
.spawn(MyComponent {
foo: [1, 2, 3],
bar: (1.3, 3.7),
baz: MyEnum::Unit,
})
.id();

let registry = world.resource::<AppTypeRegistry>();
let scene_a = DynamicScene::from_world(&world, registry);

world.entity_mut(entity).remove::<MyComponent>();

let registry = world.resource::<AppTypeRegistry>();
let scene_b = DynamicScene::from_world(&world, registry);

assert_scene_eq(&scene_a, &scene_b);
}
}
}

0 comments on commit 940d207

Please sign in to comment.