Path: blob/main/crates/bevy_scene/src/dynamic_scene_builder.rs
6598 views
use core::any::TypeId;12use crate::reflect_utils::clone_reflect_value;3use crate::{DynamicEntity, DynamicScene, SceneFilter};4use alloc::collections::BTreeMap;5use bevy_ecs::{6component::{Component, ComponentId},7entity_disabling::DefaultQueryFilters,8prelude::Entity,9reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},10resource::Resource,11world::World,12};13use bevy_reflect::PartialReflect;14use bevy_utils::default;1516/// A [`DynamicScene`] builder, used to build a scene from a [`World`] by extracting some entities and resources.17///18/// # Component Extraction19///20/// By default, all components registered with [`ReflectComponent`] type data in a world's [`AppTypeRegistry`] will be extracted.21/// (this type data is added automatically during registration if [`Reflect`] is derived with the `#[reflect(Component)]` attribute).22/// This can be changed by [specifying a filter](DynamicSceneBuilder::with_component_filter) or by explicitly23/// [allowing](DynamicSceneBuilder::allow_component)/[denying](DynamicSceneBuilder::deny_component) certain components.24///25/// Extraction happens immediately and uses the filter as it exists during the time of extraction.26///27/// # Resource Extraction28///29/// By default, all resources registered with [`ReflectResource`] type data in a world's [`AppTypeRegistry`] will be extracted.30/// (this type data is added automatically during registration if [`Reflect`] is derived with the `#[reflect(Resource)]` attribute).31/// This can be changed by [specifying a filter](DynamicSceneBuilder::with_resource_filter) or by explicitly32/// [allowing](DynamicSceneBuilder::allow_resource)/[denying](DynamicSceneBuilder::deny_resource) certain resources.33///34/// Extraction happens immediately and uses the filter as it exists during the time of extraction.35///36/// # Entity Order37///38/// Extracted entities will always be stored in ascending order based on their [index](Entity::index).39/// This means that inserting `Entity(1v0)` then `Entity(0v0)` will always result in the entities40/// being ordered as `[Entity(0v0), Entity(1v0)]`.41///42/// # Example43/// ```44/// # use bevy_scene::DynamicSceneBuilder;45/// # use bevy_ecs::reflect::AppTypeRegistry;46/// # use bevy_ecs::{47/// # component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World,48/// # };49/// # use bevy_reflect::Reflect;50/// # #[derive(Component, Reflect, Default, Eq, PartialEq, Debug)]51/// # #[reflect(Component)]52/// # struct ComponentA;53/// # let mut world = World::default();54/// # world.init_resource::<AppTypeRegistry>();55/// # let entity = world.spawn(ComponentA).id();56/// let dynamic_scene = DynamicSceneBuilder::from_world(&world).extract_entity(entity).build();57/// ```58///59/// [`Reflect`]: bevy_reflect::Reflect60pub struct DynamicSceneBuilder<'w> {61extracted_resources: BTreeMap<ComponentId, Box<dyn PartialReflect>>,62extracted_scene: BTreeMap<Entity, DynamicEntity>,63component_filter: SceneFilter,64resource_filter: SceneFilter,65original_world: &'w World,66}6768impl<'w> DynamicSceneBuilder<'w> {69/// Prepare a builder that will extract entities and their component from the given [`World`].70pub fn from_world(world: &'w World) -> Self {71Self {72extracted_resources: default(),73extracted_scene: default(),74component_filter: SceneFilter::default(),75resource_filter: SceneFilter::default(),76original_world: world,77}78}7980/// Specify a custom component [`SceneFilter`] to be used with this builder.81#[must_use]82pub fn with_component_filter(mut self, filter: SceneFilter) -> Self {83self.component_filter = filter;84self85}8687/// Specify a custom resource [`SceneFilter`] to be used with this builder.88#[must_use]89pub fn with_resource_filter(mut self, filter: SceneFilter) -> Self {90self.resource_filter = filter;91self92}9394/// Updates the filter to allow all component and resource types.95///96/// This is useful for resetting the filter so that types may be selectively denied97/// with [`deny_component`](`Self::deny_component`) and [`deny_resource`](`Self::deny_resource`).98pub fn allow_all(mut self) -> Self {99self.component_filter = SceneFilter::allow_all();100self.resource_filter = SceneFilter::allow_all();101self102}103104/// Updates the filter to deny all component and resource types.105///106/// This is useful for resetting the filter so that types may be selectively allowed107/// with [`allow_component`](`Self::allow_component`) and [`allow_resource`](`Self::allow_resource`).108pub fn deny_all(mut self) -> Self {109self.component_filter = SceneFilter::deny_all();110self.resource_filter = SceneFilter::deny_all();111self112}113114/// Allows the given component type, `T`, to be included in the generated scene.115///116/// This method may be called multiple times for any number of components.117///118/// This is the inverse of [`deny_component`](Self::deny_component).119/// If `T` has already been denied, then it will be removed from the denylist.120#[must_use]121pub fn allow_component<T: Component>(mut self) -> Self {122self.component_filter = self.component_filter.allow::<T>();123self124}125126/// Denies the given component type, `T`, from being included in the generated scene.127///128/// This method may be called multiple times for any number of components.129///130/// This is the inverse of [`allow_component`](Self::allow_component).131/// If `T` has already been allowed, then it will be removed from the allowlist.132#[must_use]133pub fn deny_component<T: Component>(mut self) -> Self {134self.component_filter = self.component_filter.deny::<T>();135self136}137138/// Updates the filter to allow all component types.139///140/// This is useful for resetting the filter so that types may be selectively [denied].141///142/// [denied]: Self::deny_component143#[must_use]144pub fn allow_all_components(mut self) -> Self {145self.component_filter = SceneFilter::allow_all();146self147}148149/// Updates the filter to deny all component types.150///151/// This is useful for resetting the filter so that types may be selectively [allowed].152///153/// [allowed]: Self::allow_component154#[must_use]155pub fn deny_all_components(mut self) -> Self {156self.component_filter = SceneFilter::deny_all();157self158}159160/// Allows the given resource type, `T`, to be included in the generated scene.161///162/// This method may be called multiple times for any number of resources.163///164/// This is the inverse of [`deny_resource`](Self::deny_resource).165/// If `T` has already been denied, then it will be removed from the denylist.166#[must_use]167pub fn allow_resource<T: Resource>(mut self) -> Self {168self.resource_filter = self.resource_filter.allow::<T>();169self170}171172/// Denies the given resource type, `T`, from being included in the generated scene.173///174/// This method may be called multiple times for any number of resources.175///176/// This is the inverse of [`allow_resource`](Self::allow_resource).177/// If `T` has already been allowed, then it will be removed from the allowlist.178#[must_use]179pub fn deny_resource<T: Resource>(mut self) -> Self {180self.resource_filter = self.resource_filter.deny::<T>();181self182}183184/// Updates the filter to allow all resource types.185///186/// This is useful for resetting the filter so that types may be selectively [denied].187///188/// [denied]: Self::deny_resource189#[must_use]190pub fn allow_all_resources(mut self) -> Self {191self.resource_filter = SceneFilter::allow_all();192self193}194195/// Updates the filter to deny all resource types.196///197/// This is useful for resetting the filter so that types may be selectively [allowed].198///199/// [allowed]: Self::allow_resource200#[must_use]201pub fn deny_all_resources(mut self) -> Self {202self.resource_filter = SceneFilter::deny_all();203self204}205206/// Consume the builder, producing a [`DynamicScene`].207///208/// To make sure the dynamic scene doesn't contain entities without any components, call209/// [`Self::remove_empty_entities`] before building the scene.210#[must_use]211pub fn build(self) -> DynamicScene {212DynamicScene {213resources: self.extracted_resources.into_values().collect(),214entities: self.extracted_scene.into_values().collect(),215}216}217218/// Extract one entity from the builder's [`World`].219///220/// Re-extracting an entity that was already extracted will have no effect.221#[must_use]222pub fn extract_entity(self, entity: Entity) -> Self {223self.extract_entities(core::iter::once(entity))224}225226/// Despawns all entities with no components.227///228/// These were likely created because none of their components were present in the provided type registry upon extraction.229#[must_use]230pub fn remove_empty_entities(mut self) -> Self {231self.extracted_scene232.retain(|_, entity| !entity.components.is_empty());233234self235}236237/// Extract entities from the builder's [`World`].238///239/// Re-extracting an entity that was already extracted will have no effect.240///241/// To control which components are extracted, use the [`allow`] or242/// [`deny`] helper methods.243///244/// This method may be used to extract entities from a query:245/// ```246/// # use bevy_scene::DynamicSceneBuilder;247/// # use bevy_ecs::reflect::AppTypeRegistry;248/// # use bevy_ecs::{249/// # component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World,250/// # };251/// # use bevy_reflect::Reflect;252/// #[derive(Component, Default, Reflect)]253/// #[reflect(Component)]254/// struct MyComponent;255///256/// # let mut world = World::default();257/// # world.init_resource::<AppTypeRegistry>();258/// # let _entity = world.spawn(MyComponent).id();259/// let mut query = world.query_filtered::<Entity, With<MyComponent>>();260///261/// let scene = DynamicSceneBuilder::from_world(&world)262/// .extract_entities(query.iter(&world))263/// .build();264/// ```265///266/// Note that components extracted from queried entities must still pass through the filter if one is set.267///268/// [`allow`]: Self::allow_component269/// [`deny`]: Self::deny_component270#[must_use]271pub fn extract_entities(mut self, entities: impl Iterator<Item = Entity>) -> Self {272let type_registry = self.original_world.resource::<AppTypeRegistry>().read();273274for entity in entities {275if self.extracted_scene.contains_key(&entity) {276continue;277}278279let mut entry = DynamicEntity {280entity,281components: Vec::new(),282};283284let original_entity = self.original_world.entity(entity);285for component_id in original_entity.archetype().components() {286let mut extract_and_push = || {287let type_id = self288.original_world289.components()290.get_info(component_id)?291.type_id()?;292293let is_denied = self.component_filter.is_denied_by_id(type_id);294295if is_denied {296// Component is either in the denylist or _not_ in the allowlist297return None;298}299300let type_registration = type_registry.get(type_id)?;301302let component = type_registration303.data::<ReflectComponent>()?304.reflect(original_entity)?;305306let component =307clone_reflect_value(component.as_partial_reflect(), type_registration);308309entry.components.push(component);310Some(())311};312extract_and_push();313}314self.extracted_scene.insert(entity, entry);315}316317self318}319320/// Extract resources from the builder's [`World`].321///322/// Re-extracting a resource that was already extracted will have no effect.323///324/// To control which resources are extracted, use the [`allow_resource`] or325/// [`deny_resource`] helper methods.326///327/// ```328/// # use bevy_scene::DynamicSceneBuilder;329/// # use bevy_ecs::reflect::AppTypeRegistry;330/// # use bevy_ecs::prelude::{ReflectResource, Resource, World};331/// # use bevy_reflect::Reflect;332/// #[derive(Resource, Default, Reflect)]333/// #[reflect(Resource)]334/// struct MyResource;335///336/// # let mut world = World::default();337/// # world.init_resource::<AppTypeRegistry>();338/// world.insert_resource(MyResource);339///340/// let mut builder = DynamicSceneBuilder::from_world(&world).extract_resources();341/// let scene = builder.build();342/// ```343///344/// [`allow_resource`]: Self::allow_resource345/// [`deny_resource`]: Self::deny_resource346#[must_use]347pub fn extract_resources(mut self) -> Self {348// Don't extract the DefaultQueryFilters resource349let original_world_dqf_id = self350.original_world351.components()352.get_valid_resource_id(TypeId::of::<DefaultQueryFilters>());353354let type_registry = self.original_world.resource::<AppTypeRegistry>().read();355356for (component_id, _) in self.original_world.storages().resources.iter() {357if Some(component_id) == original_world_dqf_id {358continue;359}360let mut extract_and_push = || {361let type_id = self362.original_world363.components()364.get_info(component_id)?365.type_id()?;366367let is_denied = self.resource_filter.is_denied_by_id(type_id);368369if is_denied {370// Resource is either in the denylist or _not_ in the allowlist371return None;372}373374let type_registration = type_registry.get(type_id)?;375376let resource = type_registration377.data::<ReflectResource>()?378.reflect(self.original_world)379.ok()?;380381let resource =382clone_reflect_value(resource.as_partial_reflect(), type_registration);383384self.extracted_resources.insert(component_id, resource);385Some(())386};387extract_and_push();388}389390drop(type_registry);391self392}393}394395#[cfg(test)]396mod tests {397use bevy_ecs::{398component::Component,399prelude::{Entity, Resource},400query::With,401reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},402world::World,403};404405use bevy_reflect::Reflect;406407use super::DynamicSceneBuilder;408409#[derive(Component, Reflect, Default, Eq, PartialEq, Debug)]410#[reflect(Component)]411struct ComponentA;412413#[derive(Component, Reflect, Default, Eq, PartialEq, Debug)]414#[reflect(Component)]415struct ComponentB;416417#[derive(Resource, Reflect, Default, Eq, PartialEq, Debug)]418#[reflect(Resource)]419struct ResourceA;420421#[derive(Resource, Reflect, Default, Eq, PartialEq, Debug)]422#[reflect(Resource)]423struct ResourceB;424425#[test]426fn extract_one_entity() {427let mut world = World::default();428429let atr = AppTypeRegistry::default();430atr.write().register::<ComponentA>();431world.insert_resource(atr);432433let entity = world.spawn((ComponentA, ComponentB)).id();434435let scene = DynamicSceneBuilder::from_world(&world)436.extract_entity(entity)437.build();438439assert_eq!(scene.entities.len(), 1);440assert_eq!(scene.entities[0].entity, entity);441assert_eq!(scene.entities[0].components.len(), 1);442assert!(scene.entities[0].components[0].represents::<ComponentA>());443}444445#[test]446fn extract_one_entity_twice() {447let mut world = World::default();448449let atr = AppTypeRegistry::default();450atr.write().register::<ComponentA>();451world.insert_resource(atr);452453let entity = world.spawn((ComponentA, ComponentB)).id();454455let scene = DynamicSceneBuilder::from_world(&world)456.extract_entity(entity)457.extract_entity(entity)458.build();459460assert_eq!(scene.entities.len(), 1);461assert_eq!(scene.entities[0].entity, entity);462assert_eq!(scene.entities[0].components.len(), 1);463assert!(scene.entities[0].components[0].represents::<ComponentA>());464}465466#[test]467fn extract_one_entity_two_components() {468let mut world = World::default();469470let atr = AppTypeRegistry::default();471{472let mut register = atr.write();473register.register::<ComponentA>();474register.register::<ComponentB>();475}476world.insert_resource(atr);477478let entity = world.spawn((ComponentA, ComponentB)).id();479480let scene = DynamicSceneBuilder::from_world(&world)481.extract_entity(entity)482.build();483484assert_eq!(scene.entities.len(), 1);485assert_eq!(scene.entities[0].entity, entity);486assert_eq!(scene.entities[0].components.len(), 2);487assert!(scene.entities[0].components[0].represents::<ComponentA>());488assert!(scene.entities[0].components[1].represents::<ComponentB>());489}490491#[test]492fn extract_entity_order() {493let mut world = World::default();494world.init_resource::<AppTypeRegistry>();495496// Spawn entities in order497let entity_a = world.spawn_empty().id();498let entity_b = world.spawn_empty().id();499let entity_c = world.spawn_empty().id();500let entity_d = world.spawn_empty().id();501502// Insert entities out of order503let builder = DynamicSceneBuilder::from_world(&world)504.extract_entity(entity_b)505.extract_entities([entity_d, entity_a].into_iter())506.extract_entity(entity_c);507508let mut entities = builder.build().entities.into_iter();509510// Assert entities are ordered511assert_eq!(entity_d, entities.next().map(|e| e.entity).unwrap());512assert_eq!(entity_c, entities.next().map(|e| e.entity).unwrap());513assert_eq!(entity_b, entities.next().map(|e| e.entity).unwrap());514assert_eq!(entity_a, entities.next().map(|e| e.entity).unwrap());515}516517#[test]518fn extract_query() {519let mut world = World::default();520521let atr = AppTypeRegistry::default();522{523let mut register = atr.write();524register.register::<ComponentA>();525register.register::<ComponentB>();526}527world.insert_resource(atr);528529let entity_a_b = world.spawn((ComponentA, ComponentB)).id();530let entity_a = world.spawn(ComponentA).id();531let _entity_b = world.spawn(ComponentB).id();532533let mut query = world.query_filtered::<Entity, With<ComponentA>>();534let scene = DynamicSceneBuilder::from_world(&world)535.extract_entities(query.iter(&world))536.build();537538assert_eq!(scene.entities.len(), 2);539let mut scene_entities = vec![scene.entities[0].entity, scene.entities[1].entity];540scene_entities.sort();541assert_eq!(scene_entities, [entity_a, entity_a_b]);542}543544#[test]545fn remove_componentless_entity() {546let mut world = World::default();547548let atr = AppTypeRegistry::default();549atr.write().register::<ComponentA>();550world.insert_resource(atr);551552let entity_a = world.spawn(ComponentA).id();553let entity_b = world.spawn(ComponentB).id();554555let scene = DynamicSceneBuilder::from_world(&world)556.extract_entities([entity_a, entity_b].into_iter())557.remove_empty_entities()558.build();559560assert_eq!(scene.entities.len(), 1);561assert_eq!(scene.entities[0].entity, entity_a);562}563564#[test]565fn extract_one_resource() {566let mut world = World::default();567568let atr = AppTypeRegistry::default();569atr.write().register::<ResourceA>();570world.insert_resource(atr);571572world.insert_resource(ResourceA);573574let scene = DynamicSceneBuilder::from_world(&world)575.extract_resources()576.build();577578assert_eq!(scene.resources.len(), 1);579assert!(scene.resources[0].represents::<ResourceA>());580}581582#[test]583fn extract_one_resource_twice() {584let mut world = World::default();585586let atr = AppTypeRegistry::default();587atr.write().register::<ResourceA>();588world.insert_resource(atr);589590world.insert_resource(ResourceA);591592let scene = DynamicSceneBuilder::from_world(&world)593.extract_resources()594.extract_resources()595.build();596597assert_eq!(scene.resources.len(), 1);598assert!(scene.resources[0].represents::<ResourceA>());599}600601#[test]602fn should_extract_allowed_components() {603let mut world = World::default();604605let atr = AppTypeRegistry::default();606{607let mut register = atr.write();608register.register::<ComponentA>();609register.register::<ComponentB>();610}611world.insert_resource(atr);612613let entity_a_b = world.spawn((ComponentA, ComponentB)).id();614let entity_a = world.spawn(ComponentA).id();615let entity_b = world.spawn(ComponentB).id();616617let scene = DynamicSceneBuilder::from_world(&world)618.allow_component::<ComponentA>()619.extract_entities([entity_a_b, entity_a, entity_b].into_iter())620.build();621622assert_eq!(scene.entities.len(), 3);623assert!(scene.entities[2].components[0].represents::<ComponentA>());624assert!(scene.entities[1].components[0].represents::<ComponentA>());625assert_eq!(scene.entities[0].components.len(), 0);626}627628#[test]629fn should_not_extract_denied_components() {630let mut world = World::default();631632let atr = AppTypeRegistry::default();633{634let mut register = atr.write();635register.register::<ComponentA>();636register.register::<ComponentB>();637}638world.insert_resource(atr);639640let entity_a_b = world.spawn((ComponentA, ComponentB)).id();641let entity_a = world.spawn(ComponentA).id();642let entity_b = world.spawn(ComponentB).id();643644let scene = DynamicSceneBuilder::from_world(&world)645.deny_component::<ComponentA>()646.extract_entities([entity_a_b, entity_a, entity_b].into_iter())647.build();648649assert_eq!(scene.entities.len(), 3);650assert!(scene.entities[0].components[0].represents::<ComponentB>());651assert_eq!(scene.entities[1].components.len(), 0);652assert!(scene.entities[2].components[0].represents::<ComponentB>());653}654655#[test]656fn should_extract_allowed_resources() {657let mut world = World::default();658659let atr = AppTypeRegistry::default();660{661let mut register = atr.write();662register.register::<ResourceA>();663register.register::<ResourceB>();664}665world.insert_resource(atr);666667world.insert_resource(ResourceA);668world.insert_resource(ResourceB);669670let scene = DynamicSceneBuilder::from_world(&world)671.allow_resource::<ResourceA>()672.extract_resources()673.build();674675assert_eq!(scene.resources.len(), 1);676assert!(scene.resources[0].represents::<ResourceA>());677}678679#[test]680fn should_not_extract_denied_resources() {681let mut world = World::default();682683let atr = AppTypeRegistry::default();684{685let mut register = atr.write();686register.register::<ResourceA>();687register.register::<ResourceB>();688}689world.insert_resource(atr);690691world.insert_resource(ResourceA);692world.insert_resource(ResourceB);693694let scene = DynamicSceneBuilder::from_world(&world)695.deny_resource::<ResourceA>()696.extract_resources()697.build();698699assert_eq!(scene.resources.len(), 1);700assert!(scene.resources[0].represents::<ResourceB>());701}702703#[test]704fn should_use_from_reflect() {705#[derive(Resource, Component, Reflect)]706#[reflect(Resource, Component)]707struct SomeType(i32);708709let mut world = World::default();710let atr = AppTypeRegistry::default();711{712let mut register = atr.write();713register.register::<SomeType>();714}715world.insert_resource(atr);716717world.insert_resource(SomeType(123));718let entity = world.spawn(SomeType(123)).id();719720let scene = DynamicSceneBuilder::from_world(&world)721.extract_resources()722.extract_entities(vec![entity].into_iter())723.build();724725let component = &scene.entities[0].components[0];726assert!(component727.try_as_reflect()728.expect("component should be concrete due to `FromReflect`")729.is::<SomeType>());730731let resource = &scene.resources[0];732assert!(resource733.try_as_reflect()734.expect("resource should be concrete due to `FromReflect`")735.is::<SomeType>());736}737}738739740