Skip to content

Commit b93e79c

Browse files
MrGVSVjames7132
authored andcommitted
bevy_scene: Stabilize entity order in DynamicSceneBuilder (#6382)
# Objective Currently, `DynamicSceneBuilder` keeps track of entities via a `HashMap`. This has an unintended side-effect in that, when building the full `DynamicScene`, we aren't guaranteed any particular order. In other words, inserting Entity A then Entity B can result in either `[A, B]` or `[B, A]`. This can be rather annoying when running tests on scenes generated via the builder as it will work sometimes but not other times. There's also the potential that this might unnecessarily clutter up VCS diffs for scene files (assuming they had an intentional order). ## Solution Store `DynamicSceneBuilder`'s entities in a `Vec` rather than a `HashMap`. --- ## Changelog * Stablized entity order in `DynamicSceneBuilder` (0.9.0-dev)
1 parent 96cb147 commit b93e79c

File tree

2 files changed

+172
-8
lines changed

2 files changed

+172
-8
lines changed

crates/bevy_scene/src/dynamic_scene_builder.rs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
use crate::{DynamicEntity, DynamicScene};
22
use bevy_app::AppTypeRegistry;
33
use bevy_ecs::{prelude::Entity, reflect::ReflectComponent, world::World};
4-
use bevy_utils::{default, HashMap};
4+
use bevy_utils::default;
5+
use std::collections::BTreeMap;
56

67
/// A [`DynamicScene`] builder, used to build a scene from a [`World`] by extracting some entities.
78
///
9+
/// # Entity Order
10+
///
11+
/// Extracted entities will always be stored in ascending order based on their [id](Entity::id).
12+
/// This means that inserting `Entity(1v0)` then `Entity(0v0)` will always result in the entities
13+
/// being ordered as `[Entity(0v0), Entity(1v0)]`.
14+
///
15+
/// # Example
816
/// ```
917
/// # use bevy_scene::DynamicSceneBuilder;
1018
/// # use bevy_app::AppTypeRegistry;
@@ -23,7 +31,7 @@ use bevy_utils::{default, HashMap};
2331
/// let dynamic_scene = builder.build();
2432
/// ```
2533
pub struct DynamicSceneBuilder<'w> {
26-
scene: HashMap<u32, DynamicEntity>,
34+
entities: BTreeMap<u32, DynamicEntity>,
2735
type_registry: AppTypeRegistry,
2836
world: &'w World,
2937
}
@@ -33,7 +41,7 @@ impl<'w> DynamicSceneBuilder<'w> {
3341
/// All components registered in that world's [`AppTypeRegistry`] resource will be extracted.
3442
pub fn from_world(world: &'w World) -> Self {
3543
Self {
36-
scene: default(),
44+
entities: default(),
3745
type_registry: world.resource::<AppTypeRegistry>().clone(),
3846
world,
3947
}
@@ -43,7 +51,7 @@ impl<'w> DynamicSceneBuilder<'w> {
4351
/// Only components registered in the given [`AppTypeRegistry`] will be extracted.
4452
pub fn from_world_with_type_registry(world: &'w World, type_registry: AppTypeRegistry) -> Self {
4553
Self {
46-
scene: default(),
54+
entities: default(),
4755
type_registry,
4856
world,
4957
}
@@ -52,7 +60,7 @@ impl<'w> DynamicSceneBuilder<'w> {
5260
/// Consume the builder, producing a [`DynamicScene`].
5361
pub fn build(self) -> DynamicScene {
5462
DynamicScene {
55-
entities: self.scene.into_values().collect(),
63+
entities: self.entities.into_values().collect(),
5664
}
5765
}
5866

@@ -92,12 +100,14 @@ impl<'w> DynamicSceneBuilder<'w> {
92100
let type_registry = self.type_registry.read();
93101

94102
for entity in entities {
95-
if self.scene.contains_key(&entity.id()) {
103+
let id = entity.id();
104+
105+
if self.entities.contains_key(&id) {
96106
continue;
97107
}
98108

99109
let mut entry = DynamicEntity {
100-
entity: entity.id(),
110+
entity: id,
101111
components: Vec::new(),
102112
};
103113

@@ -116,7 +126,7 @@ impl<'w> DynamicSceneBuilder<'w> {
116126
}
117127
}
118128

119-
self.scene.insert(entity.id(), entry);
129+
self.entities.insert(id, entry);
120130
}
121131

122132
drop(type_registry);
@@ -208,6 +218,33 @@ mod tests {
208218
assert!(scene.entities[0].components[1].represents::<ComponentB>());
209219
}
210220

221+
#[test]
222+
fn extract_entity_order() {
223+
let mut world = World::default();
224+
world.init_resource::<AppTypeRegistry>();
225+
226+
// Spawn entities in order
227+
let entity_a = world.spawn_empty().id();
228+
let entity_b = world.spawn_empty().id();
229+
let entity_c = world.spawn_empty().id();
230+
let entity_d = world.spawn_empty().id();
231+
232+
let mut builder = DynamicSceneBuilder::from_world(&world);
233+
234+
// Insert entities out of order
235+
builder.extract_entity(entity_b);
236+
builder.extract_entities([entity_d, entity_a].into_iter());
237+
builder.extract_entity(entity_c);
238+
239+
let mut entities = builder.build().entities.into_iter();
240+
241+
// Assert entities are ordered
242+
assert_eq!(entity_a.id(), entities.next().map(|e| e.entity).unwrap());
243+
assert_eq!(entity_b.id(), entities.next().map(|e| e.entity).unwrap());
244+
assert_eq!(entity_c.id(), entities.next().map(|e| e.entity).unwrap());
245+
assert_eq!(entity_d.id(), entities.next().map(|e| e.entity).unwrap());
246+
}
247+
211248
#[test]
212249
fn extract_query() {
213250
let mut world = World::default();

crates/bevy_scene/src/serde.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,130 @@ impl<'a, 'de> Visitor<'de> for ComponentVisitor<'a> {
373373
Ok(dynamic_properties)
374374
}
375375
}
376+
377+
#[cfg(test)]
378+
mod tests {
379+
use crate::serde::SceneDeserializer;
380+
use crate::DynamicSceneBuilder;
381+
use bevy_app::AppTypeRegistry;
382+
use bevy_ecs::entity::EntityMap;
383+
use bevy_ecs::prelude::{Component, ReflectComponent, World};
384+
use bevy_reflect::Reflect;
385+
use serde::de::DeserializeSeed;
386+
387+
#[derive(Component, Reflect, Default)]
388+
#[reflect(Component)]
389+
struct Foo(i32);
390+
#[derive(Component, Reflect, Default)]
391+
#[reflect(Component)]
392+
struct Bar(i32);
393+
#[derive(Component, Reflect, Default)]
394+
#[reflect(Component)]
395+
struct Baz(i32);
396+
397+
fn create_world() -> World {
398+
let mut world = World::new();
399+
let registry = AppTypeRegistry::default();
400+
{
401+
let mut registry = registry.write();
402+
registry.register::<Foo>();
403+
registry.register::<Bar>();
404+
registry.register::<Baz>();
405+
}
406+
world.insert_resource(registry);
407+
world
408+
}
409+
410+
#[test]
411+
fn should_serialize() {
412+
let mut world = create_world();
413+
414+
let a = world.spawn(Foo(123)).id();
415+
let b = world.spawn((Foo(123), Bar(345))).id();
416+
let c = world.spawn((Foo(123), Bar(345), Baz(789))).id();
417+
418+
let mut builder = DynamicSceneBuilder::from_world(&world);
419+
builder.extract_entities([a, b, c].into_iter());
420+
let scene = builder.build();
421+
422+
let expected = r#"(
423+
entities: [
424+
(
425+
entity: 0,
426+
components: {
427+
"bevy_scene::serde::tests::Foo": (123),
428+
},
429+
),
430+
(
431+
entity: 1,
432+
components: {
433+
"bevy_scene::serde::tests::Foo": (123),
434+
"bevy_scene::serde::tests::Bar": (345),
435+
},
436+
),
437+
(
438+
entity: 2,
439+
components: {
440+
"bevy_scene::serde::tests::Foo": (123),
441+
"bevy_scene::serde::tests::Bar": (345),
442+
"bevy_scene::serde::tests::Baz": (789),
443+
},
444+
),
445+
],
446+
)"#;
447+
let output = scene
448+
.serialize_ron(&world.resource::<AppTypeRegistry>().0)
449+
.unwrap();
450+
assert_eq!(expected, output);
451+
}
452+
453+
#[test]
454+
fn should_deserialize() {
455+
let world = create_world();
456+
457+
let input = r#"(
458+
entities: [
459+
(
460+
entity: 0,
461+
components: {
462+
"bevy_scene::serde::tests::Foo": (123),
463+
},
464+
),
465+
(
466+
entity: 1,
467+
components: {
468+
"bevy_scene::serde::tests::Foo": (123),
469+
"bevy_scene::serde::tests::Bar": (345),
470+
},
471+
),
472+
(
473+
entity: 2,
474+
components: {
475+
"bevy_scene::serde::tests::Foo": (123),
476+
"bevy_scene::serde::tests::Bar": (345),
477+
"bevy_scene::serde::tests::Baz": (789),
478+
},
479+
),
480+
],
481+
)"#;
482+
let mut deserializer = ron::de::Deserializer::from_str(input).unwrap();
483+
let scene_deserializer = SceneDeserializer {
484+
type_registry: &world.resource::<AppTypeRegistry>().read(),
485+
};
486+
let scene = scene_deserializer.deserialize(&mut deserializer).unwrap();
487+
488+
assert_eq!(
489+
3,
490+
scene.entities.len(),
491+
"expected `entities` to contain 3 entities"
492+
);
493+
494+
let mut map = EntityMap::default();
495+
let mut dst_world = create_world();
496+
scene.write_to_world(&mut dst_world, &mut map).unwrap();
497+
498+
assert_eq!(3, dst_world.query::<&Foo>().iter(&dst_world).count());
499+
assert_eq!(2, dst_world.query::<&Bar>().iter(&dst_world).count());
500+
assert_eq!(1, dst_world.query::<&Baz>().iter(&dst_world).count());
501+
}
502+
}

0 commit comments

Comments
 (0)