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 20, 2022
1 parent f124314 commit 7050953
Show file tree
Hide file tree
Showing 2 changed files with 251 additions and 0 deletions.
4 changes: 4 additions & 0 deletions crates/bevy_scene/Cargo.toml
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"
247 changes: 247 additions & 0 deletions crates/bevy_scene/src/serde.rs
Expand Up @@ -169,6 +169,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(ComponentVecDeserializer {
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 @@ -251,3 +267,234 @@ impl<'a, 'de> Visitor<'de> for ComponentSeqVisitor<'a> {
Ok(dynamic_properties)
}
}

#[cfg(test)]
mod tests {
use super::*;
use bevy_app::{App, AppTypeRegistry};
use bevy_ecs::prelude::*;
use bevy_reflect::{FromReflect, ReflectSerialize};
use bincode::Options;

#[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_app() -> App {
let mut app = App::new();

app.register_type::<MyComponent>();
app.register_type::<MyEnum>();
app.register_type::<String>();
app.register_type_data::<String, ReflectSerialize>();
app.register_type::<[usize; 3]>();
app.register_type::<(f32, f32)>();

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

app
}

#[test]
fn should_roundtrip_postcard() {
let app = create_app();
let world = app.world;
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, 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 app = create_app();
let world = app.world;
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, 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 app = create_app();
let world = &mut app.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 app = create_app();
let world = &mut app.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 app = create_app();
let world = &mut app.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 7050953

Please sign in to comment.