From f08aa806e9f1b694c320a841f90212027712aa97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Tue, 11 Oct 2022 00:37:00 +0200 Subject: [PATCH 1/5] dynamic scene builder --- crates/bevy_scene/src/dynamic_scene.rs | 41 +--- .../bevy_scene/src/dynamic_scene_builder.rs | 230 ++++++++++++++++++ crates/bevy_scene/src/lib.rs | 6 +- 3 files changed, 242 insertions(+), 35 deletions(-) create mode 100644 crates/bevy_scene/src/dynamic_scene_builder.rs diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index e2e4e93be30fc..555494dbf5fb5 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,4 +1,4 @@ -use crate::{serde::SceneSerializer, Scene, SceneSpawnError}; +use crate::{serde::SceneSerializer, DynamicSceneBuilder, Scene, SceneSpawnError}; use anyhow::Result; use bevy_app::AppTypeRegistry; use bevy_ecs::{ @@ -33,47 +33,20 @@ pub struct DynamicEntity { impl DynamicScene { /// Create a new dynamic scene from a given scene. - pub fn from_scene(scene: &Scene, type_registry: &TypeRegistryArc) -> Self { + pub fn from_scene(scene: &Scene, type_registry: &AppTypeRegistry) -> Self { Self::from_world(&scene.world, type_registry) } /// Create a new dynamic scene from a given world. - pub fn from_world(world: &World, type_registry: &TypeRegistryArc) -> Self { - let mut scene = DynamicScene::default(); - let type_registry = type_registry.read(); + pub fn from_world(world: &World, type_registry: &AppTypeRegistry) -> Self { + let mut builder = + DynamicSceneBuilder::from_world_with_type_registry(world, type_registry.clone()); for archetype in world.archetypes().iter() { - let entities_offset = scene.entities.len(); - - // Create a new dynamic entity for each entity of the given archetype - // and insert it into the dynamic scene. - for entity in archetype.entities() { - scene.entities.push(DynamicEntity { - entity: entity.id(), - components: Vec::new(), - }); - } - - // Add each reflection-powered component to the entity it belongs to. - for component_id in archetype.components() { - let reflect_component = world - .components() - .get_info(component_id) - .and_then(|info| type_registry.get(info.type_id().unwrap())) - .and_then(|registration| registration.data::()); - if let Some(reflect_component) = reflect_component { - for (i, entity) in archetype.entities().iter().enumerate() { - if let Some(component) = reflect_component.reflect(world, *entity) { - scene.entities[entities_offset + i] - .components - .push(component.clone_value()); - } - } - } - } + builder.extract_entities(archetype.entities().iter().copied()); } - scene + builder.build() } /// Write the dynamic entities and their corresponding components to the given world. diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs new file mode 100644 index 0000000000000..c20af2c0dfba3 --- /dev/null +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -0,0 +1,230 @@ +use crate::{DynamicEntity, DynamicScene}; +use bevy_app::AppTypeRegistry; +use bevy_ecs::{prelude::Entity, reflect::ReflectComponent, world::World}; +use bevy_utils::{default, HashMap}; + +/// A [`DynamicScene`] builder, used to build a scene from a [`World`] by extracting some entities. +/// +/// ``` +/// # use bevy_scene::DynamicSceneBuilder; +/// # use bevy_app::AppTypeRegistry; +/// # use bevy_ecs::{ +/// # component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World, +/// # }; +/// # use bevy_reflect::Reflect; +/// # #[derive(Component, Reflect, Default, Eq, PartialEq, Debug)] +/// # #[reflect(Component)] +/// # struct ComponentA; +/// # let mut world = World::default(); +/// # world.init_resource::(); +/// # let entity = world.spawn(ComponentA).id(); +/// let mut builder = DynamicSceneBuilder::from_world(&world); +/// builder.extract_entity(entity); +/// let dynamic_scene = builder.build(); +/// ``` +pub struct DynamicSceneBuilder<'w> { + scene: HashMap, + type_registry: AppTypeRegistry, + world: &'w World, +} + +impl<'w> DynamicSceneBuilder<'w> { + /// Prepare a builder that will extract entities and their component from the given [`World`]. + /// All components registered in that world's [`AppTypeRegistry`] will be extracted. + pub fn from_world(world: &'w World) -> Self { + Self { + scene: default(), + type_registry: world.resource::().clone(), + world, + } + } + + /// Prepare a builder that will extract entities and their component from the given [`World`]. + /// Only components registered in the given [`AppTypeRegistry`] will be extracted. + pub fn from_world_with_type_registry(world: &'w World, type_registry: AppTypeRegistry) -> Self { + Self { + scene: default(), + type_registry, + world, + } + } + + /// Consume the builder, and produce the [`DynamicScene`]. + pub fn build(self) -> DynamicScene { + DynamicScene { + entities: self.scene.into_values().collect(), + } + } + + /// Extract one entity from the builder's [`World`]. + pub fn extract_entity(&mut self, entity: Entity) -> &mut Self { + self.extract_entities(std::iter::once(entity)) + } + + /// Extract entities from the builder's [`World`]. + /// + /// Extracting entities can be used to extract entities from a query: + /// ``` + /// # use bevy_scene::DynamicSceneBuilder; + /// # use bevy_app::AppTypeRegistry; + /// # use bevy_ecs::{ + /// # component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World, + /// # }; + /// # use bevy_reflect::Reflect; + /// #[derive(Component, Default, Reflect)] + /// #[reflect(Component)] + /// struct MyComponent; + /// + /// # let mut world = World::default(); + /// # world.init_resource::(); + /// # let _entity = world.spawn(MyComponent).id(); + /// let mut query = world.query_filtered::>(); + /// + /// let mut builder = DynamicSceneBuilder::from_world(&world); + /// builder.extract_entities(query.iter(&world)); + /// let scene = builder.build(); + /// ``` + pub fn extract_entities(&mut self, entities: impl Iterator) -> &mut Self { + let type_registry = self.type_registry.read(); + + for entity in entities { + if !self.scene.contains_key(&entity.id()) { + let mut entry = DynamicEntity { + entity: entity.id(), + components: Vec::new(), + }; + + for component_id in self.world.entity(entity).archetype().components() { + let reflect_component = self + .world + .components() + .get_info(component_id) + .and_then(|info| type_registry.get(info.type_id().unwrap())) + .and_then(|registration| registration.data::()); + + if let Some(reflect_component) = reflect_component { + if let Some(component) = reflect_component.reflect(self.world, entity) { + entry.components.push(component.clone_value()); + } + } + } + self.scene.insert(entity.id(), entry); + } + } + + drop(type_registry); + self + } +} + +#[cfg(test)] +mod tests { + use bevy_app::AppTypeRegistry; + use bevy_ecs::{ + component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World, + }; + + use bevy_reflect::Reflect; + + use super::DynamicSceneBuilder; + + #[derive(Component, Reflect, Default, Eq, PartialEq, Debug)] + #[reflect(Component)] + struct ComponentA; + #[derive(Component, Reflect, Default, Eq, PartialEq, Debug)] + #[reflect(Component)] + struct ComponentB; + + #[test] + fn extract_one_entity() { + let mut world = World::default(); + + let atr = AppTypeRegistry::default(); + atr.write().register::(); + world.insert_resource(atr); + + let entity = world.spawn((ComponentA, ComponentB)).id(); + + let mut builder = DynamicSceneBuilder::from_world(&world); + builder.extract_entity(entity); + let scene = builder.build(); + + assert_eq!(scene.entities.len(), 1); + assert_eq!(scene.entities[0].entity, entity.id()); + assert_eq!(scene.entities[0].components.len(), 1); + assert!(scene.entities[0].components[0].represents::()); + } + + #[test] + fn extract_one_entity_twice() { + let mut world = World::default(); + + let atr = AppTypeRegistry::default(); + atr.write().register::(); + world.insert_resource(atr); + + let entity = world.spawn((ComponentA, ComponentB)).id(); + + let mut builder = DynamicSceneBuilder::from_world(&world); + builder.extract_entity(entity); + builder.extract_entity(entity); + let scene = builder.build(); + + assert_eq!(scene.entities.len(), 1); + assert_eq!(scene.entities[0].entity, entity.id()); + assert_eq!(scene.entities[0].components.len(), 1); + assert!(scene.entities[0].components[0].represents::()); + } + + #[test] + fn extract_one_entity_two_components() { + let mut world = World::default(); + + let atr = AppTypeRegistry::default(); + { + let mut register = atr.write(); + register.register::(); + register.register::(); + } + world.insert_resource(atr); + + let entity = world.spawn((ComponentA, ComponentB)).id(); + + let mut builder = DynamicSceneBuilder::from_world(&world); + builder.extract_entity(entity); + let scene = builder.build(); + + assert_eq!(scene.entities.len(), 1); + assert_eq!(scene.entities[0].entity, entity.id()); + assert_eq!(scene.entities[0].components.len(), 2); + assert!(scene.entities[0].components[0].represents::()); + assert!(scene.entities[0].components[1].represents::()); + } + + #[test] + fn extract_query() { + let mut world = World::default(); + + let atr = AppTypeRegistry::default(); + { + let mut register = atr.write(); + register.register::(); + register.register::(); + } + world.insert_resource(atr); + + let entity_a_b = world.spawn((ComponentA, ComponentB)).id(); + let entity_a = world.spawn(ComponentA).id(); + let _entity_b = world.spawn(ComponentB).id(); + + let mut query = world.query_filtered::>(); + let mut builder = DynamicSceneBuilder::from_world(&world); + builder.extract_entities(query.iter(&world)); + let scene = builder.build(); + + assert_eq!(scene.entities.len(), 2); + let mut scene_entities = vec![scene.entities[0].entity, scene.entities[1].entity]; + scene_entities.sort(); + assert_eq!(scene_entities, [entity_a_b.id(), entity_a.id()]); + } +} diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 902518de88c4b..d00c455ff2079 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -1,5 +1,6 @@ mod bundle; mod dynamic_scene; +mod dynamic_scene_builder; mod scene; mod scene_loader; mod scene_spawner; @@ -7,13 +8,16 @@ pub mod serde; pub use bundle::*; pub use dynamic_scene::*; +pub use dynamic_scene_builder::*; pub use scene::*; pub use scene_loader::*; pub use scene_spawner::*; pub mod prelude { #[doc(hidden)] - pub use crate::{DynamicScene, DynamicSceneBundle, Scene, SceneBundle, SceneSpawner}; + pub use crate::{ + DynamicScene, DynamicSceneBuilder, DynamicSceneBundle, Scene, SceneBundle, SceneSpawner, + }; } use bevy_app::prelude::*; From 919a65ef8fbdeea7d24b3ec96f1d5d62eec47ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Tue, 11 Oct 2022 00:57:58 +0200 Subject: [PATCH 2/5] doc Co-authored-by: Alice Cecile --- crates/bevy_scene/src/dynamic_scene_builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index c20af2c0dfba3..738ea8bb71227 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -30,7 +30,7 @@ pub struct DynamicSceneBuilder<'w> { impl<'w> DynamicSceneBuilder<'w> { /// Prepare a builder that will extract entities and their component from the given [`World`]. - /// All components registered in that world's [`AppTypeRegistry`] will be extracted. + /// All components registered in that world's [`AppTypeRegistry`] resource will be extracted. pub fn from_world(world: &'w World) -> Self { Self { scene: default(), @@ -49,7 +49,7 @@ impl<'w> DynamicSceneBuilder<'w> { } } - /// Consume the builder, and produce the [`DynamicScene`]. + /// Consume the builder, producing a [`DynamicScene`]. pub fn build(self) -> DynamicScene { DynamicScene { entities: self.scene.into_values().collect(), From d4f4244bf20f33e1aa40681275f0317b1f447cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Tue, 11 Oct 2022 00:58:53 +0200 Subject: [PATCH 3/5] flatten ze iterator --- crates/bevy_scene/src/dynamic_scene.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 555494dbf5fb5..16b7a3169cf7f 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -42,9 +42,12 @@ impl DynamicScene { let mut builder = DynamicSceneBuilder::from_world_with_type_registry(world, type_registry.clone()); - for archetype in world.archetypes().iter() { - builder.extract_entities(archetype.entities().iter().copied()); - } + builder.extract_entities( + world + .archetypes() + .iter() + .flat_map(|archetype| archetype.entities().iter().copied()), + ); builder.build() } From f30bc4c4ee5e25c762ecbcf9fa5c74e768c4e9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Tue, 11 Oct 2022 01:31:46 +0200 Subject: [PATCH 4/5] less nesting Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> --- .../bevy_scene/src/dynamic_scene_builder.rs | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 738ea8bb71227..eadd2599f269d 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -88,28 +88,31 @@ impl<'w> DynamicSceneBuilder<'w> { let type_registry = self.type_registry.read(); for entity in entities { - if !self.scene.contains_key(&entity.id()) { - let mut entry = DynamicEntity { - entity: entity.id(), - components: Vec::new(), - }; - - for component_id in self.world.entity(entity).archetype().components() { - let reflect_component = self - .world - .components() - .get_info(component_id) - .and_then(|info| type_registry.get(info.type_id().unwrap())) - .and_then(|registration| registration.data::()); - - if let Some(reflect_component) = reflect_component { - if let Some(component) = reflect_component.reflect(self.world, entity) { - entry.components.push(component.clone_value()); - } + if self.scene.contains_key(&entity.id()) { + continue; + } + + let mut entry = DynamicEntity { + entity: entity.id(), + components: Vec::new(), + }; + + for component_id in self.world.entity(entity).archetype().components() { + let reflect_component = self + .world + .components() + .get_info(component_id) + .and_then(|info| type_registry.get(info.type_id().unwrap())) + .and_then(|registration| registration.data::()); + + if let Some(reflect_component) = reflect_component { + if let Some(component) = reflect_component.reflect(self.world, entity) { + entry.components.push(component.clone_value()); } } - self.scene.insert(entity.id(), entry); } + + self.scene.insert(entity.id(), entry); } drop(type_registry); From 8541fb751032a4cab773de350fe430d62e470b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Tue, 11 Oct 2022 01:34:19 +0200 Subject: [PATCH 5/5] mention that entities will be extracted only once --- crates/bevy_scene/src/dynamic_scene_builder.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index eadd2599f269d..a456138a3f8d2 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -57,12 +57,16 @@ impl<'w> DynamicSceneBuilder<'w> { } /// Extract one entity from the builder's [`World`]. + /// + /// Re-extracting an entity that was already extracted will have no effect. pub fn extract_entity(&mut self, entity: Entity) -> &mut Self { self.extract_entities(std::iter::once(entity)) } /// Extract entities from the builder's [`World`]. /// + /// Re-extracting an entity that was already extracted will have no effect. + /// /// Extracting entities can be used to extract entities from a query: /// ``` /// # use bevy_scene::DynamicSceneBuilder;