From 3116eb1046a2dc5565fa99665096dc99f43e00a5 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 28 Nov 2022 21:47:51 -0700 Subject: [PATCH 01/10] Add SceneFilter --- crates/bevy_scene/src/dynamic_scene.rs | 9 +- .../bevy_scene/src/dynamic_scene_builder.rs | 269 ++++++++++++++++-- crates/bevy_scene/src/lib.rs | 5 +- crates/bevy_scene/src/scene_filter.rs | 105 +++++++ crates/bevy_scene/src/serde.rs | 26 +- examples/scene/scene.rs | 20 +- 6 files changed, 386 insertions(+), 48 deletions(-) create mode 100644 crates/bevy_scene/src/scene_filter.rs diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 0ec7c99eca92d..6ede572834283 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -45,14 +45,13 @@ pub struct DynamicEntity { impl DynamicScene { /// Create a new dynamic scene from a given scene. - pub fn from_scene(scene: &Scene, type_registry: &AppTypeRegistry) -> Self { - Self::from_world(&scene.world, type_registry) + pub fn from_scene(scene: &Scene) -> Self { + Self::from_world(&scene.world) } /// Create a new dynamic scene from a given world. - pub fn from_world(world: &World, type_registry: &AppTypeRegistry) -> Self { - let mut builder = - DynamicSceneBuilder::from_world_with_type_registry(world, type_registry.clone()); + pub fn from_world(world: &World) -> Self { + let mut builder = DynamicSceneBuilder::from_world(world); builder.extract_entities(world.iter_entities().map(|entity| entity.id())); builder.extract_resources(); diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 484262ba18dd9..5c9bd68970fe9 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -1,5 +1,6 @@ -use crate::{DynamicEntity, DynamicScene}; -use bevy_ecs::component::ComponentId; +use crate::{DynamicEntity, DynamicScene, SceneFilter}; +use bevy_ecs::component::{Component, ComponentId}; +use bevy_ecs::system::Resource; use bevy_ecs::{ prelude::Entity, reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, @@ -11,9 +12,23 @@ use std::collections::BTreeMap; /// A [`DynamicScene`] builder, used to build a scene from a [`World`] by extracting some entities and resources. /// +/// # Component Extraction +/// +/// By default, all components registered with [`ReflectComponent`] type data in a world's [`AppTypeRegistry`] will be extracted. +/// (this type data is added automatically during registration if [`Reflect`] is derived with the `#[reflect(Component)]` attribute). +/// This can be changed by [specifying a filter](DynamicSceneBuilder::with_filter) or by explicitly +/// [allowing](DynamicSceneBuilder::allow)/[denying](DynamicSceneBuilder::deny) certain components. +/// +/// # Resource Extraction +/// +/// By default, all resources registered with [`ReflectResource`] type data in a world's [`AppTypeRegistry`] will be extracted. +/// (this type data is added automatically during registration if [`Reflect`] is derived with the `#[reflect(Resource)]` attribute). +/// This can be changed by [specifying a filter](DynamicSceneBuilder::with_resource_filter) or by explicitly +/// [allowing](DynamicSceneBuilder::allow_resource)/[denying](DynamicSceneBuilder::deny_resource) certain resources. +/// /// # Entity Order /// -/// Extracted entities will always be stored in ascending order based on their [id](Entity::index). +/// Extracted entities will always be stored in ascending order based on their [index](Entity::index). /// This means that inserting `Entity(1v0)` then `Entity(0v0)` will always result in the entities /// being ordered as `[Entity(0v0), Entity(1v0)]`. /// @@ -38,31 +53,101 @@ use std::collections::BTreeMap; pub struct DynamicSceneBuilder<'w> { extracted_resources: BTreeMap>, extracted_scene: BTreeMap, - type_registry: AppTypeRegistry, + component_filter: Option, + resource_filter: Option, original_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`] resource will be extracted. pub fn from_world(world: &'w World) -> Self { Self { extracted_resources: default(), extracted_scene: default(), - type_registry: world.resource::().clone(), + component_filter: None, + resource_filter: None, original_world: 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 { - extracted_resources: default(), - extracted_scene: default(), - type_registry, - original_world: world, - } + /// Specify a custom component [`SceneFilter`] to be used with this builder. + pub fn with_filter(&mut self, filter: Option) -> &mut Self { + self.component_filter = filter; + self + } + + /// Specify a custom resource [`SceneFilter`] to be used with this builder. + pub fn with_resource_filter(&mut self, filter: Option) -> &mut Self { + self.resource_filter = filter; + self + } + + /// Allows the given component type, `T`, to be included in the generated scene. + /// + /// This method may be called for multiple components. + /// + /// Please note that this method is mutually exclusive with the [`deny`](Self::deny) method. + /// Calling this one will replace the denylist with a new allowlist. + pub fn allow(&mut self) -> &mut Self { + self.component_filter = Some( + self.component_filter + .take() + .unwrap_or_else(SceneFilter::new_allowlist) + .allow::(), + ); + + self + } + + /// Denies the given component type, `T`, from being included in the generated scene. + /// + /// This method may be called for multiple components. + /// + /// Please note that this method is mutually exclusive with the [`allow`](Self::allow) method. + /// Calling this one will replace the allowlist with a new denylist. + pub fn deny(&mut self) -> &mut Self { + self.component_filter = Some( + self.component_filter + .take() + .unwrap_or_else(SceneFilter::new_denylist) + .deny::(), + ); + + self + } + + /// Allows the given resource type, `T`, to be included in the generated scene. + /// + /// This method may be called for multiple resources. + /// + /// Please note that this method is mutually exclusive with the [`deny_resource`](Self::deny_resource) method. + /// Calling this one will replace the denylist with a new allowlist. + pub fn allow_resource(&mut self) -> &mut Self { + self.resource_filter = Some( + self.resource_filter + .take() + .unwrap_or_else(SceneFilter::new_allowlist) + .allow::(), + ); + + self + } + + /// Denies the given resource type, `T`, from being included in the generated scene. + /// + /// This method may be called for multiple resources. + /// + /// Please note that this method is mutually exclusive with the [`allow_resource`](Self::allow_resource) method. + /// Calling this one will replace the allowlist with a new denylist. + pub fn deny_resource(&mut self) -> &mut Self { + self.resource_filter = Some( + self.resource_filter + .take() + .unwrap_or_else(SceneFilter::new_denylist) + .deny::(), + ); + + self } /// Consume the builder, producing a [`DynamicScene`]. @@ -97,7 +182,10 @@ impl<'w> DynamicSceneBuilder<'w> { /// /// Re-extracting an entity that was already extracted will have no effect. /// - /// Extracting entities can be used to extract entities from a query: + /// To control which components are extracted, use the [`allow`] or + /// [`deny`] helper methods. + /// + /// This method may be used to extract entities from a query: /// ``` /// # use bevy_scene::DynamicSceneBuilder; /// # use bevy_ecs::reflect::AppTypeRegistry; @@ -118,8 +206,13 @@ impl<'w> DynamicSceneBuilder<'w> { /// builder.extract_entities(query.iter(&world)); /// let scene = builder.build(); /// ``` + /// + /// Note that components extracted from queried entities must still pass through the filter if one is set. + /// + /// [`allow`]: Self::allow + /// [`deny`]: Self::deny pub fn extract_entities(&mut self, entities: impl Iterator) -> &mut Self { - let type_registry = self.type_registry.read(); + let type_registry = self.original_world.resource::().read(); for entity in entities { if self.extracted_scene.contains_key(&entity) { @@ -139,6 +232,18 @@ impl<'w> DynamicSceneBuilder<'w> { .components() .get_info(component_id)? .type_id()?; + + let is_denied = self + .component_filter + .as_ref() + .map(|filter| filter.is_denied_by_id(type_id)) + .unwrap_or_default(); + + if is_denied { + // Component is either in the denylist or _not_ in the allowlist + return None; + } + let component = type_registry .get(type_id)? .data::()? @@ -151,14 +256,16 @@ impl<'w> DynamicSceneBuilder<'w> { self.extracted_scene.insert(entity, entry); } - drop(type_registry); self } /// Extract resources from the builder's [`World`]. /// - /// Only resources registered in the builder's [`AppTypeRegistry`] will be extracted. /// Re-extracting a resource that was already extracted will have no effect. + /// + /// To control which resources are extracted, use the [`allow_resource`] or + /// [`deny_resource`] helper methods. + /// /// ``` /// # use bevy_scene::DynamicSceneBuilder; /// # use bevy_ecs::reflect::AppTypeRegistry; @@ -176,8 +283,12 @@ impl<'w> DynamicSceneBuilder<'w> { /// builder.extract_resources(); /// let scene = builder.build(); /// ``` + /// + /// [`allow_resource`]: Self::allow_resource + /// [`deny_resource`]: Self::deny_resource pub fn extract_resources(&mut self) -> &mut Self { - let type_registry = self.type_registry.read(); + let type_registry = self.original_world.resource::().read(); + for (component_id, _) in self.original_world.storages().resources.iter() { let mut extract_and_push = || { let type_id = self @@ -185,6 +296,18 @@ impl<'w> DynamicSceneBuilder<'w> { .components() .get_info(component_id)? .type_id()?; + + let is_denied = self + .resource_filter + .as_ref() + .map(|filter| filter.is_denied_by_id(type_id)) + .unwrap_or_default(); + + if is_denied { + // Resource is either in the denylist or _not_ in the allowlist + return None; + } + let resource = type_registry .get(type_id)? .data::()? @@ -225,6 +348,10 @@ mod tests { #[reflect(Resource)] struct ResourceA; + #[derive(Resource, Reflect, Default, Eq, PartialEq, Debug)] + #[reflect(Resource)] + struct ResourceB; + #[test] fn extract_one_entity() { let mut world = World::default(); @@ -401,4 +528,106 @@ mod tests { assert_eq!(scene.resources.len(), 1); assert!(scene.resources[0].represents::()); } + + #[test] + fn should_extract_allowed_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_a_b = world.spawn((ComponentA, ComponentB)).id(); + let entity_a = world.spawn(ComponentA).id(); + let entity_b = world.spawn(ComponentB).id(); + + let mut builder = DynamicSceneBuilder::from_world(&world); + builder + .allow::() + .extract_entities([entity_a_b, entity_a, entity_b].into_iter()); + let scene = builder.build(); + + assert_eq!(scene.entities.len(), 3); + assert!(scene.entities[0].components[0].represents::()); + assert!(scene.entities[1].components[0].represents::()); + assert_eq!(scene.entities[2].components.len(), 0); + } + + #[test] + fn should_not_extract_denied_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_a_b = world.spawn((ComponentA, ComponentB)).id(); + let entity_a = world.spawn(ComponentA).id(); + let entity_b = world.spawn(ComponentB).id(); + + let mut builder = DynamicSceneBuilder::from_world(&world); + builder + .deny::() + .extract_entities([entity_a_b, entity_a, entity_b].into_iter()); + let scene = builder.build(); + + assert_eq!(scene.entities.len(), 3); + assert!(scene.entities[0].components[0].represents::()); + assert_eq!(scene.entities[1].components.len(), 0); + assert!(scene.entities[2].components[0].represents::()); + } + + #[test] + fn should_extract_allowed_resources() { + let mut world = World::default(); + + let atr = AppTypeRegistry::default(); + { + let mut register = atr.write(); + register.register::(); + register.register::(); + } + world.insert_resource(atr); + + world.insert_resource(ResourceA); + world.insert_resource(ResourceB); + + let mut builder = DynamicSceneBuilder::from_world(&world); + builder.allow_resource::().extract_resources(); + let scene = builder.build(); + + assert_eq!(scene.resources.len(), 1); + assert!(scene.resources[0].represents::()); + } + + #[test] + fn should_not_extract_denied_resources() { + let mut world = World::default(); + + let atr = AppTypeRegistry::default(); + { + let mut register = atr.write(); + register.register::(); + register.register::(); + } + world.insert_resource(atr); + + world.insert_resource(ResourceA); + world.insert_resource(ResourceB); + + let mut builder = DynamicSceneBuilder::from_world(&world); + builder.deny_resource::().extract_resources(); + let scene = builder.build(); + + assert_eq!(scene.resources.len(), 1); + assert!(scene.resources[0].represents::()); + } } diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 4185d1886285c..e3ca8a2fee573 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -4,6 +4,7 @@ mod bundle; mod dynamic_scene; mod dynamic_scene_builder; mod scene; +mod scene_filter; mod scene_loader; mod scene_spawner; @@ -14,13 +15,15 @@ pub use bundle::*; pub use dynamic_scene::*; pub use dynamic_scene_builder::*; pub use scene::*; +pub use scene_filter::*; pub use scene_loader::*; pub use scene_spawner::*; pub mod prelude { #[doc(hidden)] pub use crate::{ - DynamicScene, DynamicSceneBuilder, DynamicSceneBundle, Scene, SceneBundle, SceneSpawner, + DynamicScene, DynamicSceneBuilder, DynamicSceneBundle, Scene, SceneBundle, SceneFilter, + SceneSpawner, }; } diff --git a/crates/bevy_scene/src/scene_filter.rs b/crates/bevy_scene/src/scene_filter.rs new file mode 100644 index 0000000000000..2257831c1a3dc --- /dev/null +++ b/crates/bevy_scene/src/scene_filter.rs @@ -0,0 +1,105 @@ +use bevy_utils::HashSet; +use std::any::{Any, TypeId}; + +/// A filter used to control which types can be added to a [`DynamicScene`]. +/// +/// This scene filter _can_ be used more generically to represent a filter for any given type; +/// however, note that its intended usage with `DynamicScene` only considers [components] and [resources]. +/// Adding types that are not a component or resource will have no effect when used with `DynamicScene`. +/// +/// [`DynamicScene`]: crate::DynamicScene +/// [components]: bevy_ecs::prelude::Component +/// [resources]: bevy_ecs::prelude::Resource +pub enum SceneFilter { + /// Contains the set of permitted types by their [`TypeId`]. + /// + /// Types not contained within this set should not be allowed to be saved to an associated [`DynamicScene`]. + /// + /// [`DynamicScene`]: crate::DynamicScene + Allowlist(HashSet), + /// Contains the set of prohibited types by their [`TypeId`]. + /// + /// Types contained within this set should not be allowed to be saved to an associated [`DynamicScene`]. + /// + /// [`DynamicScene`]: crate::DynamicScene + Denylist(HashSet), +} + +impl SceneFilter { + /// Create a new [allowlist](Self::Allowlist). + pub fn new_allowlist() -> Self { + Self::Allowlist(HashSet::new()) + } + + /// Create a new [denylist](Self::Denylist). + pub fn new_denylist() -> Self { + Self::Denylist(HashSet::new()) + } + + /// Allow the given type, `T`. + /// + /// If this filter is already set as a [denylist](Self::Denylist), + /// then it will be completely replaced by a new [allowlist](Self::Allowlist). + pub fn allow(self) -> Self { + self.allow_by_id(TypeId::of::()) + } + + /// Allow the given type. + /// + /// If this filter is already set as a [denylist](Self::Denylist), + /// then it will be completely replaced by a new [allowlist](Self::Allowlist). + pub fn allow_by_id(mut self, type_id: TypeId) -> Self { + match self { + Self::Allowlist(ref mut list) => { + list.insert(type_id); + self + } + Self::Denylist(_) => Self::Allowlist(HashSet::from([type_id])), + } + } + + /// Deny the given type, `T`. + /// + /// If this filter is already set as an [allowlist](Self::Allowlist), + /// then it will be completely replaced by a new [denylist](Self::Denylist). + pub fn deny(self) -> Self { + self.deny_by_id(TypeId::of::()) + } + + /// Deny the given type. + /// + /// If this filter is already set as an [allowlist](Self::Allowlist), + /// then it will be completely replaced by a new [denylist](Self::Denylist). + pub fn deny_by_id(mut self, type_id: TypeId) -> Self { + match self { + Self::Allowlist(_) => Self::Denylist(HashSet::from([type_id])), + Self::Denylist(ref mut list) => { + list.insert(type_id); + self + } + } + } + + /// Returns true if the given type, `T`, is allowed by the filter. + pub fn is_allowed(&self) -> bool { + self.is_allowed_by_id(TypeId::of::()) + } + + /// Returns true if the given type is allowed by the filter. + pub fn is_allowed_by_id(&self, type_id: TypeId) -> bool { + match self { + SceneFilter::Allowlist(list) => list.contains(&type_id), + SceneFilter::Denylist(list) => !list.contains(&type_id), + } + } + + /// Returns true if the given type, `T`, is denied by the filter. + pub fn is_denied(&self) -> bool { + self.is_denied_by_id(TypeId::of::()) + } + + /// Returns true if the given type is denied by the filter. + pub fn is_denied_by_id(&self, type_id: TypeId) -> bool { + !self.is_allowed_by_id(type_id) + } +} diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index b686bf9566950..2780445f2f327 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -630,7 +630,7 @@ mod tests { let registry = world.resource::(); - let scene = DynamicScene::from_world(&world, registry); + let scene = DynamicScene::from_world(&world); let serialized = scene .serialize_ron(&world.resource::().0) @@ -680,7 +680,7 @@ mod tests { let registry = world.resource::(); - let scene = DynamicScene::from_world(&world, registry); + let scene = DynamicScene::from_world(&world); let scene_serializer = SceneSerializer::new(&scene, ®istry.0); let serialized_scene = postcard::to_allocvec(&scene_serializer).unwrap(); @@ -718,7 +718,7 @@ mod tests { let registry = world.resource::(); - let scene = DynamicScene::from_world(&world, registry); + let scene = DynamicScene::from_world(&world); let scene_serializer = SceneSerializer::new(&scene, ®istry.0); let mut buf = Vec::new(); @@ -761,7 +761,7 @@ mod tests { let registry = world.resource::(); - let scene = DynamicScene::from_world(&world, registry); + let scene = DynamicScene::from_world(&world); let scene_serializer = SceneSerializer::new(&scene, ®istry.0); let serialized_scene = bincode::serialize(&scene_serializer).unwrap(); @@ -835,8 +835,7 @@ mod tests { #[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::(); - let scene_a = DynamicScene::from_world(&world, registry); + let scene_a = DynamicScene::from_world(&world); world.spawn(MyComponent { foo: [1, 2, 3], @@ -844,8 +843,7 @@ mod tests { baz: MyEnum::Unit, }); - let registry = world.resource::(); - let scene_b = DynamicScene::from_world(&world, registry); + let scene_b = DynamicScene::from_world(&world); assert_scene_eq(&scene_a, &scene_b); } @@ -863,8 +861,7 @@ mod tests { }) .id(); - let registry = world.resource::(); - let scene_a = DynamicScene::from_world(&world, registry); + let scene_a = DynamicScene::from_world(&world); world.entity_mut(entity).insert(MyComponent { foo: [3, 2, 1], @@ -872,8 +869,7 @@ mod tests { baz: MyEnum::Unit, }); - let registry = world.resource::(); - let scene_b = DynamicScene::from_world(&world, registry); + let scene_b = DynamicScene::from_world(&world); assert_scene_eq(&scene_a, &scene_b); } @@ -891,13 +887,11 @@ mod tests { }) .id(); - let registry = world.resource::(); - let scene_a = DynamicScene::from_world(&world, registry); + let scene_a = DynamicScene::from_world(&world); world.entity_mut(entity).remove::(); - let registry = world.resource::(); - let scene_b = DynamicScene::from_world(&world, registry); + let scene_b = DynamicScene::from_world(&world); assert_scene_eq(&scene_a, &scene_b); } diff --git a/examples/scene/scene.rs b/examples/scene/scene.rs index ad4dd8d3efea2..e4e7b508befa8 100644 --- a/examples/scene/scene.rs +++ b/examples/scene/scene.rs @@ -100,9 +100,18 @@ fn log_system( } fn save_scene_system(world: &mut World) { - // Scenes can be created from any ECS World. You can either create a new one for the scene or - // use the current World. + // Scenes can be created from any ECS World. + // You can either create a new one for the scene or use the current World. + // For demonstration purposes, we'll create a new one. let mut scene_world = World::new(); + + // The `TypeRegistry` resource contains information about all registered types (including components). + // This is used to construct scenes, so we'll want to ensure that our previous type registrations + // exist in this new scene world as well. + // To do this, we can simply clone the `AppTypeRegistry` resource. + let type_registry = world.resource::().clone(); + scene_world.insert_resource(type_registry); + let mut component_b = ComponentB::from_world(world); component_b.value = "hello".to_string(); scene_world.spawn(( @@ -113,12 +122,11 @@ fn save_scene_system(world: &mut World) { scene_world.spawn(ComponentA { x: 3.0, y: 4.0 }); scene_world.insert_resource(ResourceA { score: 1 }); - // The TypeRegistry resource contains information about all registered types (including - // components). This is used to construct scenes. - let type_registry = world.resource::(); - let scene = DynamicScene::from_world(&scene_world, type_registry); + // With our sample world ready to go, we can now create our scene: + let scene = DynamicScene::from_world(&scene_world); // Scenes can be serialized like this: + let type_registry = world.resource::(); let serialized_scene = scene.serialize_ron(type_registry).unwrap(); // Showing the scene in the console From ddfb3d2492e213aa1d365f08fed41c4b35e42707 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 31 Dec 2022 16:32:32 -0800 Subject: [PATCH 02/10] Add SceneFilter::None --- .../bevy_scene/src/dynamic_scene_builder.rs | 60 +++++-------------- crates/bevy_scene/src/scene_filter.rs | 41 ++++++++----- 2 files changed, 43 insertions(+), 58 deletions(-) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 5c9bd68970fe9..2f8cb034b28cd 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -53,8 +53,8 @@ use std::collections::BTreeMap; pub struct DynamicSceneBuilder<'w> { extracted_resources: BTreeMap>, extracted_scene: BTreeMap, - component_filter: Option, - resource_filter: Option, + component_filter: SceneFilter, + resource_filter: SceneFilter, original_world: &'w World, } @@ -64,20 +64,20 @@ impl<'w> DynamicSceneBuilder<'w> { Self { extracted_resources: default(), extracted_scene: default(), - component_filter: None, - resource_filter: None, + component_filter: SceneFilter::None, + resource_filter: SceneFilter::None, original_world: world, } } /// Specify a custom component [`SceneFilter`] to be used with this builder. - pub fn with_filter(&mut self, filter: Option) -> &mut Self { + pub fn with_filter(&mut self, filter: SceneFilter) -> &mut Self { self.component_filter = filter; self } /// Specify a custom resource [`SceneFilter`] to be used with this builder. - pub fn with_resource_filter(&mut self, filter: Option) -> &mut Self { + pub fn with_resource_filter(&mut self, filter: SceneFilter) -> &mut Self { self.resource_filter = filter; self } @@ -89,13 +89,8 @@ impl<'w> DynamicSceneBuilder<'w> { /// Please note that this method is mutually exclusive with the [`deny`](Self::deny) method. /// Calling this one will replace the denylist with a new allowlist. pub fn allow(&mut self) -> &mut Self { - self.component_filter = Some( - self.component_filter - .take() - .unwrap_or_else(SceneFilter::new_allowlist) - .allow::(), - ); - + self.component_filter = + core::mem::replace(&mut self.component_filter, SceneFilter::None).allow::(); self } @@ -106,13 +101,8 @@ impl<'w> DynamicSceneBuilder<'w> { /// Please note that this method is mutually exclusive with the [`allow`](Self::allow) method. /// Calling this one will replace the allowlist with a new denylist. pub fn deny(&mut self) -> &mut Self { - self.component_filter = Some( - self.component_filter - .take() - .unwrap_or_else(SceneFilter::new_denylist) - .deny::(), - ); - + self.component_filter = + core::mem::replace(&mut self.component_filter, SceneFilter::None).deny::(); self } @@ -123,13 +113,8 @@ impl<'w> DynamicSceneBuilder<'w> { /// Please note that this method is mutually exclusive with the [`deny_resource`](Self::deny_resource) method. /// Calling this one will replace the denylist with a new allowlist. pub fn allow_resource(&mut self) -> &mut Self { - self.resource_filter = Some( - self.resource_filter - .take() - .unwrap_or_else(SceneFilter::new_allowlist) - .allow::(), - ); - + self.resource_filter = + core::mem::replace(&mut self.resource_filter, SceneFilter::None).allow::(); self } @@ -140,13 +125,8 @@ impl<'w> DynamicSceneBuilder<'w> { /// Please note that this method is mutually exclusive with the [`allow_resource`](Self::allow_resource) method. /// Calling this one will replace the allowlist with a new denylist. pub fn deny_resource(&mut self) -> &mut Self { - self.resource_filter = Some( - self.resource_filter - .take() - .unwrap_or_else(SceneFilter::new_denylist) - .deny::(), - ); - + self.resource_filter = + core::mem::replace(&mut self.resource_filter, SceneFilter::None).deny::(); self } @@ -233,11 +213,7 @@ impl<'w> DynamicSceneBuilder<'w> { .get_info(component_id)? .type_id()?; - let is_denied = self - .component_filter - .as_ref() - .map(|filter| filter.is_denied_by_id(type_id)) - .unwrap_or_default(); + let is_denied = self.component_filter.is_denied_by_id(type_id); if is_denied { // Component is either in the denylist or _not_ in the allowlist @@ -297,11 +273,7 @@ impl<'w> DynamicSceneBuilder<'w> { .get_info(component_id)? .type_id()?; - let is_denied = self - .resource_filter - .as_ref() - .map(|filter| filter.is_denied_by_id(type_id)) - .unwrap_or_default(); + let is_denied = self.resource_filter.is_denied_by_id(type_id); if is_denied { // Resource is either in the denylist or _not_ in the allowlist diff --git a/crates/bevy_scene/src/scene_filter.rs b/crates/bevy_scene/src/scene_filter.rs index 2257831c1a3dc..f32f96a8847ee 100644 --- a/crates/bevy_scene/src/scene_filter.rs +++ b/crates/bevy_scene/src/scene_filter.rs @@ -11,6 +11,14 @@ use std::any::{Any, TypeId}; /// [components]: bevy_ecs::prelude::Component /// [resources]: bevy_ecs::prelude::Resource pub enum SceneFilter { + /// Represents an unset filter. + /// + /// This is the equivalent of an empty [`Denylist`] or a [`Allowlist`] containing every type— + /// essentially, all types are permissible. + /// + /// [`Denylist`]: SceneFilter::Denylist + /// [`Allowlist`]: SceneFilter::Allowlist + None, /// Contains the set of permitted types by their [`TypeId`]. /// /// Types not contained within this set should not be allowed to be saved to an associated [`DynamicScene`]. @@ -26,16 +34,6 @@ pub enum SceneFilter { } impl SceneFilter { - /// Create a new [allowlist](Self::Allowlist). - pub fn new_allowlist() -> Self { - Self::Allowlist(HashSet::new()) - } - - /// Create a new [denylist](Self::Denylist). - pub fn new_denylist() -> Self { - Self::Denylist(HashSet::new()) - } - /// Allow the given type, `T`. /// /// If this filter is already set as a [denylist](Self::Denylist), @@ -54,7 +52,7 @@ impl SceneFilter { list.insert(type_id); self } - Self::Denylist(_) => Self::Allowlist(HashSet::from([type_id])), + Self::None | Self::Denylist(_) => Self::Allowlist(HashSet::from([type_id])), } } @@ -72,7 +70,7 @@ impl SceneFilter { /// then it will be completely replaced by a new [denylist](Self::Denylist). pub fn deny_by_id(mut self, type_id: TypeId) -> Self { match self { - Self::Allowlist(_) => Self::Denylist(HashSet::from([type_id])), + Self::None | Self::Allowlist(_) => Self::Denylist(HashSet::from([type_id])), Self::Denylist(ref mut list) => { list.insert(type_id); self @@ -81,25 +79,40 @@ impl SceneFilter { } /// Returns true if the given type, `T`, is allowed by the filter. + /// + /// If the filter is set to [`SceneFilter::None`], this will always return `true`. pub fn is_allowed(&self) -> bool { self.is_allowed_by_id(TypeId::of::()) } /// Returns true if the given type is allowed by the filter. + /// + /// If the filter is set to [`SceneFilter::None`], this will always return `true`. pub fn is_allowed_by_id(&self, type_id: TypeId) -> bool { match self { - SceneFilter::Allowlist(list) => list.contains(&type_id), - SceneFilter::Denylist(list) => !list.contains(&type_id), + Self::None => true, + Self::Allowlist(list) => list.contains(&type_id), + Self::Denylist(list) => !list.contains(&type_id), } } /// Returns true if the given type, `T`, is denied by the filter. + /// + /// If the filter is set to [`SceneFilter::None`], this will always return `false`. pub fn is_denied(&self) -> bool { self.is_denied_by_id(TypeId::of::()) } /// Returns true if the given type is denied by the filter. + /// + /// If the filter is set to [`SceneFilter::None`], this will always return `false`. pub fn is_denied_by_id(&self, type_id: TypeId) -> bool { !self.is_allowed_by_id(type_id) } } + +impl Default for SceneFilter { + fn default() -> Self { + Self::None + } +} From 66de3b7a5aea6a9bfa0016a6189aa53d8869e60f Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 31 Dec 2022 16:41:32 -0800 Subject: [PATCH 03/10] Improve allow/deny logic --- .../bevy_scene/src/dynamic_scene_builder.rs | 36 +++++++-------- crates/bevy_scene/src/scene_filter.rs | 44 ++++++++++++++----- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 2f8cb034b28cd..5f8880ea0f82a 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -84,49 +84,45 @@ impl<'w> DynamicSceneBuilder<'w> { /// Allows the given component type, `T`, to be included in the generated scene. /// - /// This method may be called for multiple components. + /// This method may be called multiple times for any number of components. /// - /// Please note that this method is mutually exclusive with the [`deny`](Self::deny) method. - /// Calling this one will replace the denylist with a new allowlist. + /// This is the inverse of [`deny`](Self::deny). + /// If `T` has already been denied, then it will be removed from the denylist. pub fn allow(&mut self) -> &mut Self { - self.component_filter = - core::mem::replace(&mut self.component_filter, SceneFilter::None).allow::(); + self.component_filter.allow::(); self } /// Denies the given component type, `T`, from being included in the generated scene. /// - /// This method may be called for multiple components. + /// This method may be called multiple times for any number of components. /// - /// Please note that this method is mutually exclusive with the [`allow`](Self::allow) method. - /// Calling this one will replace the allowlist with a new denylist. + /// This is the inverse of [`allow`](Self::allow). + /// If `T` has already been allowed, then it will be removed from the allowlist. pub fn deny(&mut self) -> &mut Self { - self.component_filter = - core::mem::replace(&mut self.component_filter, SceneFilter::None).deny::(); + self.component_filter.deny::(); self } /// Allows the given resource type, `T`, to be included in the generated scene. /// - /// This method may be called for multiple resources. + /// This method may be called multiple times for any number of resources. /// - /// Please note that this method is mutually exclusive with the [`deny_resource`](Self::deny_resource) method. - /// Calling this one will replace the denylist with a new allowlist. + /// This is the inverse of [`deny_resource`](Self::deny_resource). + /// If `T` has already been denied, then it will be removed from the denylist. pub fn allow_resource(&mut self) -> &mut Self { - self.resource_filter = - core::mem::replace(&mut self.resource_filter, SceneFilter::None).allow::(); + self.resource_filter.allow::(); self } /// Denies the given resource type, `T`, from being included in the generated scene. /// - /// This method may be called for multiple resources. + /// This method may be called multiple times for any number of resources. /// - /// Please note that this method is mutually exclusive with the [`allow_resource`](Self::allow_resource) method. - /// Calling this one will replace the allowlist with a new denylist. + /// This is the inverse of [`allow_resource`](Self::allow_resource). + /// If `T` has already been allowed, then it will be removed from the allowlist. pub fn deny_resource(&mut self) -> &mut Self { - self.resource_filter = - core::mem::replace(&mut self.resource_filter, SceneFilter::None).deny::(); + self.resource_filter.deny::(); self } diff --git a/crates/bevy_scene/src/scene_filter.rs b/crates/bevy_scene/src/scene_filter.rs index f32f96a8847ee..84f1d398b37d9 100644 --- a/crates/bevy_scene/src/scene_filter.rs +++ b/crates/bevy_scene/src/scene_filter.rs @@ -37,45 +37,65 @@ impl SceneFilter { /// Allow the given type, `T`. /// /// If this filter is already set as a [denylist](Self::Denylist), + /// then the given type will be removed from the denied set. + /// + /// If this filter is already set as [`SceneFilter::None`], /// then it will be completely replaced by a new [allowlist](Self::Allowlist). - pub fn allow(self) -> Self { + pub fn allow(&mut self) -> &mut Self { self.allow_by_id(TypeId::of::()) } /// Allow the given type. /// /// If this filter is already set as a [denylist](Self::Denylist), + /// then the given type will be removed from the denied set. + /// + /// If this filter is already set as [`SceneFilter::None`], /// then it will be completely replaced by a new [allowlist](Self::Allowlist). - pub fn allow_by_id(mut self, type_id: TypeId) -> Self { + pub fn allow_by_id(&mut self, type_id: TypeId) -> &mut Self { match self { - Self::Allowlist(ref mut list) => { + Self::None => { + *self = Self::Allowlist(HashSet::from([type_id])); + } + Self::Allowlist(list) => { list.insert(type_id); - self } - Self::None | Self::Denylist(_) => Self::Allowlist(HashSet::from([type_id])), + Self::Denylist(list) => { + list.remove(&type_id); + } } + self } /// Deny the given type, `T`. /// - /// If this filter is already set as an [allowlist](Self::Allowlist), + /// If this filter is already set as a [allowlist](Self::Allowlist), + /// then the given type will be removed from the allowed set. + /// + /// If this filter is already set as [`SceneFilter::None`], /// then it will be completely replaced by a new [denylist](Self::Denylist). - pub fn deny(self) -> Self { + pub fn deny(&mut self) -> &mut Self { self.deny_by_id(TypeId::of::()) } /// Deny the given type. /// - /// If this filter is already set as an [allowlist](Self::Allowlist), + /// If this filter is already set as a [allowlist](Self::Allowlist), + /// then the given type will be removed from the allowed set. + /// + /// If this filter is already set as [`SceneFilter::None`], /// then it will be completely replaced by a new [denylist](Self::Denylist). - pub fn deny_by_id(mut self, type_id: TypeId) -> Self { + pub fn deny_by_id(&mut self, type_id: TypeId) -> &mut Self { match self { - Self::None | Self::Allowlist(_) => Self::Denylist(HashSet::from([type_id])), - Self::Denylist(ref mut list) => { + Self::None => *self = Self::Denylist(HashSet::from([type_id])), + Self::Allowlist(list) => { + list.remove(&type_id); + } + Self::Denylist(list) => { list.insert(type_id); - self } } + self } /// Returns true if the given type, `T`, is allowed by the filter. From 234ecea0a3451f7cdc349bef35ba4e62d66f4453 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 31 Dec 2022 17:02:55 -0800 Subject: [PATCH 04/10] Add traits for SceneFilter --- crates/bevy_scene/src/scene_filter.rs | 39 ++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/crates/bevy_scene/src/scene_filter.rs b/crates/bevy_scene/src/scene_filter.rs index 84f1d398b37d9..f165c9d19589c 100644 --- a/crates/bevy_scene/src/scene_filter.rs +++ b/crates/bevy_scene/src/scene_filter.rs @@ -1,3 +1,4 @@ +use bevy_utils::hashbrown::hash_set::IntoIter; use bevy_utils::HashSet; use std::any::{Any, TypeId}; @@ -10,6 +11,7 @@ use std::any::{Any, TypeId}; /// [`DynamicScene`]: crate::DynamicScene /// [components]: bevy_ecs::prelude::Component /// [resources]: bevy_ecs::prelude::Resource +#[derive(Default, Debug, Clone, PartialEq, Eq)] pub enum SceneFilter { /// Represents an unset filter. /// @@ -18,6 +20,7 @@ pub enum SceneFilter { /// /// [`Denylist`]: SceneFilter::Denylist /// [`Allowlist`]: SceneFilter::Allowlist + #[default] None, /// Contains the set of permitted types by their [`TypeId`]. /// @@ -129,10 +132,40 @@ impl SceneFilter { pub fn is_denied_by_id(&self, type_id: TypeId) -> bool { !self.is_allowed_by_id(type_id) } + + /// Returns an iterator over the items in the filter. + pub fn iter(&self) -> Box + '_> { + match self { + Self::None => Box::new(core::iter::empty()), + Self::Allowlist(list) | Self::Denylist(list) => Box::new(list.iter()), + } + } + + /// Returns the number of items in the filter. + pub fn len(&self) -> usize { + match self { + Self::None => 0, + Self::Allowlist(list) | Self::Denylist(list) => list.len(), + } + } + + /// Returns true if there are zero items in the filter. + pub fn is_empty(&self) -> bool { + match self { + Self::None => true, + Self::Allowlist(list) | Self::Denylist(list) => list.is_empty(), + } + } } -impl Default for SceneFilter { - fn default() -> Self { - Self::None +impl IntoIterator for SceneFilter { + type Item = TypeId; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self { + Self::None => HashSet::new().into_iter(), + Self::Allowlist(list) | Self::Denylist(list) => list.into_iter(), + } } } From 90feb6f54ab926bd9e94f8d46ed04426f2efc68d Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 31 Dec 2022 17:09:23 -0800 Subject: [PATCH 05/10] Add tests for SceneFilter --- crates/bevy_scene/src/scene_filter.rs | 52 +++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/crates/bevy_scene/src/scene_filter.rs b/crates/bevy_scene/src/scene_filter.rs index f165c9d19589c..81735a86909da 100644 --- a/crates/bevy_scene/src/scene_filter.rs +++ b/crates/bevy_scene/src/scene_filter.rs @@ -169,3 +169,55 @@ impl IntoIterator for SceneFilter { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_set_list_type_if_none() { + let mut filter = SceneFilter::None; + filter.allow::(); + assert!(matches!(filter, SceneFilter::Allowlist(_))); + + let mut filter = SceneFilter::None; + filter.deny::(); + assert!(matches!(filter, SceneFilter::Denylist(_))); + } + + #[test] + fn should_add_to_list() { + let mut filter = SceneFilter::default(); + filter.allow::(); + filter.allow::(); + assert_eq!(2, filter.len()); + assert!(filter.is_allowed::()); + assert!(filter.is_allowed::()); + + let mut filter = SceneFilter::default(); + filter.deny::(); + filter.deny::(); + assert_eq!(2, filter.len()); + assert!(filter.is_denied::()); + assert!(filter.is_denied::()); + } + + #[test] + fn should_remove_from_list() { + let mut filter = SceneFilter::default(); + filter.allow::(); + filter.allow::(); + filter.deny::(); + assert_eq!(1, filter.len()); + assert!(filter.is_allowed::()); + assert!(!filter.is_allowed::()); + + let mut filter = SceneFilter::default(); + filter.deny::(); + filter.deny::(); + filter.allow::(); + assert_eq!(1, filter.len()); + assert!(filter.is_denied::()); + assert!(!filter.is_denied::()); + } +} From 82f44daa6a9446f1865ec4832bbbdac434fd9241 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 20 Feb 2023 11:52:32 -0700 Subject: [PATCH 06/10] Rename SceneFilter::None -> SceneFilter::Unset for clarity --- .../bevy_scene/src/dynamic_scene_builder.rs | 4 +- crates/bevy_scene/src/scene_filter.rs | 65 +++++++++++-------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 5f8880ea0f82a..48d7c36d51592 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -64,8 +64,8 @@ impl<'w> DynamicSceneBuilder<'w> { Self { extracted_resources: default(), extracted_scene: default(), - component_filter: SceneFilter::None, - resource_filter: SceneFilter::None, + component_filter: SceneFilter::default(), + resource_filter: SceneFilter::default(), original_world: world, } } diff --git a/crates/bevy_scene/src/scene_filter.rs b/crates/bevy_scene/src/scene_filter.rs index 81735a86909da..5c25c64cbdd1f 100644 --- a/crates/bevy_scene/src/scene_filter.rs +++ b/crates/bevy_scene/src/scene_filter.rs @@ -15,13 +15,18 @@ use std::any::{Any, TypeId}; pub enum SceneFilter { /// Represents an unset filter. /// - /// This is the equivalent of an empty [`Denylist`] or a [`Allowlist`] containing every type— + /// This is the equivalent of an empty [`Denylist`] or an [`Allowlist`] containing every type— /// essentially, all types are permissible. /// + /// [Allowing] a type will convert this filter to an `Allowlist`. + /// Similarly, [denying] a type will convert this filter to a `Denylist`. + /// /// [`Denylist`]: SceneFilter::Denylist /// [`Allowlist`]: SceneFilter::Allowlist + /// [Allowing]: SceneFilter::allow + /// [denying]: SceneFilter::deny #[default] - None, + Unset, /// Contains the set of permitted types by their [`TypeId`]. /// /// Types not contained within this set should not be allowed to be saved to an associated [`DynamicScene`]. @@ -39,25 +44,25 @@ pub enum SceneFilter { impl SceneFilter { /// Allow the given type, `T`. /// - /// If this filter is already set as a [denylist](Self::Denylist), + /// If this filter is already set as a [`SceneFilter::Denylist`], /// then the given type will be removed from the denied set. /// - /// If this filter is already set as [`SceneFilter::None`], - /// then it will be completely replaced by a new [allowlist](Self::Allowlist). + /// If this filter is already set as [`SceneFilter::Unset`], + /// then it will be completely replaced by a new [`SceneFilter::Allowlist`]. pub fn allow(&mut self) -> &mut Self { self.allow_by_id(TypeId::of::()) } /// Allow the given type. /// - /// If this filter is already set as a [denylist](Self::Denylist), + /// If this filter is already set as a [`SceneFilter::Denylist`], /// then the given type will be removed from the denied set. /// - /// If this filter is already set as [`SceneFilter::None`], - /// then it will be completely replaced by a new [allowlist](Self::Allowlist). + /// If this filter is already set as [`SceneFilter::Unset`], + /// then it will be completely replaced by a new [`SceneFilter::Allowlist`]. pub fn allow_by_id(&mut self, type_id: TypeId) -> &mut Self { match self { - Self::None => { + Self::Unset => { *self = Self::Allowlist(HashSet::from([type_id])); } Self::Allowlist(list) => { @@ -72,25 +77,25 @@ impl SceneFilter { /// Deny the given type, `T`. /// - /// If this filter is already set as a [allowlist](Self::Allowlist), + /// If this filter is already set as an [`SceneFilter::Allowlist`], /// then the given type will be removed from the allowed set. /// - /// If this filter is already set as [`SceneFilter::None`], - /// then it will be completely replaced by a new [denylist](Self::Denylist). + /// If this filter is already set as [`SceneFilter::Unset`], + /// then it will be completely replaced by a new [`SceneFilter::Denylist`]. pub fn deny(&mut self) -> &mut Self { self.deny_by_id(TypeId::of::()) } /// Deny the given type. /// - /// If this filter is already set as a [allowlist](Self::Allowlist), + /// If this filter is already set as an [`SceneFilter::Allowlist`], /// then the given type will be removed from the allowed set. /// - /// If this filter is already set as [`SceneFilter::None`], - /// then it will be completely replaced by a new [denylist](Self::Denylist). + /// If this filter is already set as [`SceneFilter::Unset`], + /// then it will be completely replaced by a new [`SceneFilter::Denylist`]. pub fn deny_by_id(&mut self, type_id: TypeId) -> &mut Self { match self { - Self::None => *self = Self::Denylist(HashSet::from([type_id])), + Self::Unset => *self = Self::Denylist(HashSet::from([type_id])), Self::Allowlist(list) => { list.remove(&type_id); } @@ -103,17 +108,17 @@ impl SceneFilter { /// Returns true if the given type, `T`, is allowed by the filter. /// - /// If the filter is set to [`SceneFilter::None`], this will always return `true`. + /// If the filter is set to [`SceneFilter::Unset`], this will always return `true`. pub fn is_allowed(&self) -> bool { self.is_allowed_by_id(TypeId::of::()) } /// Returns true if the given type is allowed by the filter. /// - /// If the filter is set to [`SceneFilter::None`], this will always return `true`. + /// If the filter is set to [`SceneFilter::Unset`], this will always return `true`. pub fn is_allowed_by_id(&self, type_id: TypeId) -> bool { match self { - Self::None => true, + Self::Unset => true, Self::Allowlist(list) => list.contains(&type_id), Self::Denylist(list) => !list.contains(&type_id), } @@ -121,38 +126,44 @@ impl SceneFilter { /// Returns true if the given type, `T`, is denied by the filter. /// - /// If the filter is set to [`SceneFilter::None`], this will always return `false`. + /// If the filter is set to [`SceneFilter::Unset`], this will always return `false`. pub fn is_denied(&self) -> bool { self.is_denied_by_id(TypeId::of::()) } /// Returns true if the given type is denied by the filter. /// - /// If the filter is set to [`SceneFilter::None`], this will always return `false`. + /// If the filter is set to [`SceneFilter::Unset`], this will always return `false`. pub fn is_denied_by_id(&self, type_id: TypeId) -> bool { !self.is_allowed_by_id(type_id) } /// Returns an iterator over the items in the filter. + /// + /// If the filter is set to [`SceneFilter::Unset`], this will return an empty iterator. pub fn iter(&self) -> Box + '_> { match self { - Self::None => Box::new(core::iter::empty()), + Self::Unset => Box::new(core::iter::empty()), Self::Allowlist(list) | Self::Denylist(list) => Box::new(list.iter()), } } /// Returns the number of items in the filter. + /// + /// If the filter is set to [`SceneFilter::Unset`], this will always return a length of zero. pub fn len(&self) -> usize { match self { - Self::None => 0, + Self::Unset => 0, Self::Allowlist(list) | Self::Denylist(list) => list.len(), } } /// Returns true if there are zero items in the filter. + /// + /// If the filter is set to [`SceneFilter::Unset`], this will always return `true`. pub fn is_empty(&self) -> bool { match self { - Self::None => true, + Self::Unset => true, Self::Allowlist(list) | Self::Denylist(list) => list.is_empty(), } } @@ -164,7 +175,7 @@ impl IntoIterator for SceneFilter { fn into_iter(self) -> Self::IntoIter { match self { - Self::None => HashSet::new().into_iter(), + Self::Unset => HashSet::new().into_iter(), Self::Allowlist(list) | Self::Denylist(list) => list.into_iter(), } } @@ -176,11 +187,11 @@ mod tests { #[test] fn should_set_list_type_if_none() { - let mut filter = SceneFilter::None; + let mut filter = SceneFilter::Unset; filter.allow::(); assert!(matches!(filter, SceneFilter::Allowlist(_))); - let mut filter = SceneFilter::None; + let mut filter = SceneFilter::Unset; filter.deny::(); assert!(matches!(filter, SceneFilter::Denylist(_))); } From 6e91779ea368447515dcded2391683ea2a26d9ce Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 20 Feb 2023 11:58:17 -0700 Subject: [PATCH 07/10] Adjust doc links --- crates/bevy_scene/src/scene_filter.rs | 64 +++++++++++++++++++-------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/crates/bevy_scene/src/scene_filter.rs b/crates/bevy_scene/src/scene_filter.rs index 5c25c64cbdd1f..e57ee77e19b35 100644 --- a/crates/bevy_scene/src/scene_filter.rs +++ b/crates/bevy_scene/src/scene_filter.rs @@ -44,22 +44,28 @@ pub enum SceneFilter { impl SceneFilter { /// Allow the given type, `T`. /// - /// If this filter is already set as a [`SceneFilter::Denylist`], + /// If this filter is already set as a [`Denylist`], /// then the given type will be removed from the denied set. /// - /// If this filter is already set as [`SceneFilter::Unset`], - /// then it will be completely replaced by a new [`SceneFilter::Allowlist`]. + /// If this filter is [`Unset`], then it will be completely replaced by a new [`Allowlist`]. + /// + /// [`Denylist`]: SceneFilter::Denylist + /// [`Unset`]: SceneFilter::Unset + /// [`Allowlist`]: SceneFilter::Allowlist pub fn allow(&mut self) -> &mut Self { self.allow_by_id(TypeId::of::()) } /// Allow the given type. /// - /// If this filter is already set as a [`SceneFilter::Denylist`], + /// If this filter is already set as a [`Denylist`], /// then the given type will be removed from the denied set. /// - /// If this filter is already set as [`SceneFilter::Unset`], - /// then it will be completely replaced by a new [`SceneFilter::Allowlist`]. + /// If this filter is [`Unset`], then it will be completely replaced by a new [`Allowlist`]. + /// + /// [`Denylist`]: SceneFilter::Denylist + /// [`Unset`]: SceneFilter::Unset + /// [`Allowlist`]: SceneFilter::Allowlist pub fn allow_by_id(&mut self, type_id: TypeId) -> &mut Self { match self { Self::Unset => { @@ -77,22 +83,28 @@ impl SceneFilter { /// Deny the given type, `T`. /// - /// If this filter is already set as an [`SceneFilter::Allowlist`], + /// If this filter is already set as an [`Allowlist`], /// then the given type will be removed from the allowed set. /// - /// If this filter is already set as [`SceneFilter::Unset`], - /// then it will be completely replaced by a new [`SceneFilter::Denylist`]. + /// If this filter is [`Unset`], then it will be completely replaced by a new [`Denylist`]. + /// + /// [`Allowlist`]: SceneFilter::Allowlist + /// [`Unset`]: SceneFilter::Unset + /// [`Denylist`]: SceneFilter::Denylist pub fn deny(&mut self) -> &mut Self { self.deny_by_id(TypeId::of::()) } /// Deny the given type. /// - /// If this filter is already set as an [`SceneFilter::Allowlist`], + /// If this filter is already set as an [`Allowlist`], /// then the given type will be removed from the allowed set. /// - /// If this filter is already set as [`SceneFilter::Unset`], - /// then it will be completely replaced by a new [`SceneFilter::Denylist`]. + /// If this filter is [`Unset`], then it will be completely replaced by a new [`Denylist`]. + /// + /// [`Allowlist`]: SceneFilter::Allowlist + /// [`Unset`]: SceneFilter::Unset + /// [`Denylist`]: SceneFilter::Denylist pub fn deny_by_id(&mut self, type_id: TypeId) -> &mut Self { match self { Self::Unset => *self = Self::Denylist(HashSet::from([type_id])), @@ -108,14 +120,18 @@ impl SceneFilter { /// Returns true if the given type, `T`, is allowed by the filter. /// - /// If the filter is set to [`SceneFilter::Unset`], this will always return `true`. + /// If the filter is [`Unset`], this will always return `true`. + /// + /// [`Unset`]: SceneFilter::Unset pub fn is_allowed(&self) -> bool { self.is_allowed_by_id(TypeId::of::()) } /// Returns true if the given type is allowed by the filter. /// - /// If the filter is set to [`SceneFilter::Unset`], this will always return `true`. + /// If the filter is [`Unset`], this will always return `true`. + /// + /// [`Unset`]: SceneFilter::Unset pub fn is_allowed_by_id(&self, type_id: TypeId) -> bool { match self { Self::Unset => true, @@ -126,21 +142,27 @@ impl SceneFilter { /// Returns true if the given type, `T`, is denied by the filter. /// - /// If the filter is set to [`SceneFilter::Unset`], this will always return `false`. + /// If the filter is [`Unset`], this will always return `false`. + /// + /// [`Unset`]: SceneFilter::Unset pub fn is_denied(&self) -> bool { self.is_denied_by_id(TypeId::of::()) } /// Returns true if the given type is denied by the filter. /// - /// If the filter is set to [`SceneFilter::Unset`], this will always return `false`. + /// If the filter is [`Unset`], this will always return `false`. + /// + /// [`Unset`]: SceneFilter::Unset pub fn is_denied_by_id(&self, type_id: TypeId) -> bool { !self.is_allowed_by_id(type_id) } /// Returns an iterator over the items in the filter. /// - /// If the filter is set to [`SceneFilter::Unset`], this will return an empty iterator. + /// If the filter is [`Unset`], this will return an empty iterator. + /// + /// [`Unset`]: SceneFilter::Unset pub fn iter(&self) -> Box + '_> { match self { Self::Unset => Box::new(core::iter::empty()), @@ -150,7 +172,9 @@ impl SceneFilter { /// Returns the number of items in the filter. /// - /// If the filter is set to [`SceneFilter::Unset`], this will always return a length of zero. + /// If the filter is [`Unset`], this will always return a length of zero. + /// + /// [`Unset`]: SceneFilter::Unset pub fn len(&self) -> usize { match self { Self::Unset => 0, @@ -160,7 +184,9 @@ impl SceneFilter { /// Returns true if there are zero items in the filter. /// - /// If the filter is set to [`SceneFilter::Unset`], this will always return `true`. + /// If the filter is [`Unset`], this will always return `true`. + /// + /// [`Unset`]: SceneFilter::Unset pub fn is_empty(&self) -> bool { match self { Self::Unset => true, From bc4ca4c84bc84cee4ec662147dd2e8b30bc3e9bc Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 20 Feb 2023 12:03:45 -0700 Subject: [PATCH 08/10] Add allow_all and deny_all methods --- .../bevy_scene/src/dynamic_scene_builder.rs | 40 +++++++++++++++++++ crates/bevy_scene/src/scene_filter.rs | 18 +++++++++ 2 files changed, 58 insertions(+) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 48d7c36d51592..7740f3af5ef1c 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -104,6 +104,26 @@ impl<'w> DynamicSceneBuilder<'w> { self } + /// Updates the filter to allow all component types. + /// + /// This is useful for resetting the filter so that types may be selectively [denied]. + /// + /// [denied]: Self::deny + pub fn allow_all(&mut self) -> &mut Self { + self.component_filter = SceneFilter::allow_all(); + self + } + + /// Updates the filter to deny all component types. + /// + /// This is useful for resetting the filter so that types may be selectively [allowed]. + /// + /// [allowed]: Self::allow + pub fn deny_all(&mut self) -> &mut Self { + self.component_filter = SceneFilter::deny_all(); + self + } + /// Allows the given resource type, `T`, to be included in the generated scene. /// /// This method may be called multiple times for any number of resources. @@ -126,6 +146,26 @@ impl<'w> DynamicSceneBuilder<'w> { self } + /// Updates the filter to allow all resource types. + /// + /// This is useful for resetting the filter so that types may be selectively [denied]. + /// + /// [denied]: Self::deny_resource + pub fn allow_all_resources(&mut self) -> &mut Self { + self.resource_filter = SceneFilter::allow_all(); + self + } + + /// Updates the filter to deny all resource types. + /// + /// This is useful for resetting the filter so that types may be selectively [allowed]. + /// + /// [allowed]: Self::allow_resource + pub fn deny_all_resources(&mut self) -> &mut Self { + self.resource_filter = SceneFilter::deny_all(); + self + } + /// Consume the builder, producing a [`DynamicScene`]. /// /// To make sure the dynamic scene doesn't contain entities without any components, call diff --git a/crates/bevy_scene/src/scene_filter.rs b/crates/bevy_scene/src/scene_filter.rs index e57ee77e19b35..a5076e5d9d668 100644 --- a/crates/bevy_scene/src/scene_filter.rs +++ b/crates/bevy_scene/src/scene_filter.rs @@ -42,6 +42,24 @@ pub enum SceneFilter { } impl SceneFilter { + /// Creates a filter where all types are allowed. + /// + /// This is the equivalent of creating an empty [`Denylist`]. + /// + /// [`Denylist`]: SceneFilter::Denylist + pub fn allow_all() -> Self { + Self::Denylist(HashSet::new()) + } + + /// Creates a filter where all types are denied. + /// + /// This is the equivalent of creating an empty [`Allowlist`]. + /// + /// [`Allowlist`]: SceneFilter::Allowlist + pub fn deny_all() -> Self { + Self::Allowlist(HashSet::new()) + } + /// Allow the given type, `T`. /// /// If this filter is already set as a [`Denylist`], From b01e12b5ace8c6b4c95a28e12ccdb6cfb56029ba Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 20 Feb 2023 12:06:21 -0700 Subject: [PATCH 09/10] Update doc about entity extraction --- 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 7740f3af5ef1c..62e5a3ed1f348 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -19,6 +19,8 @@ use std::collections::BTreeMap; /// This can be changed by [specifying a filter](DynamicSceneBuilder::with_filter) or by explicitly /// [allowing](DynamicSceneBuilder::allow)/[denying](DynamicSceneBuilder::deny) certain components. /// +/// Extraction happens immediately and uses the filter as it exists during the time of extraction. +/// /// # Resource Extraction /// /// By default, all resources registered with [`ReflectResource`] type data in a world's [`AppTypeRegistry`] will be extracted. @@ -26,6 +28,8 @@ use std::collections::BTreeMap; /// This can be changed by [specifying a filter](DynamicSceneBuilder::with_resource_filter) or by explicitly /// [allowing](DynamicSceneBuilder::allow_resource)/[denying](DynamicSceneBuilder::deny_resource) certain resources. /// +/// Extraction happens immediately and uses the filter as it exists during the time of extraction. +/// /// # Entity Order /// /// Extracted entities will always be stored in ascending order based on their [index](Entity::index). From fc577638b0833c4a5b03c07857a2be6bcf0e794f Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sun, 25 Jun 2023 17:09:08 -0700 Subject: [PATCH 10/10] Add scene_builder example --- Cargo.toml | 10 ++ examples/README.md | 1 + examples/scene/scene_builder.rs | 172 ++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 examples/scene/scene_builder.rs diff --git a/Cargo.toml b/Cargo.toml index 42267ed699c09..f1e23fbb75f8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1446,6 +1446,16 @@ description = "Demonstrates loading from and saving scenes to files" category = "Scene" wasm = false +[[example]] +name = "scene_builder" +path = "examples/scene/scene_builder.rs" + +[package.metadata.example.scene_builder] +name = "Scene Builder" +description = "Demonstrates building a fine-tuned scene from a world" +category = "Scene" +wasm = false + # Shaders [[package.metadata.example_category]] name = "Shaders" diff --git a/examples/README.md b/examples/README.md index b8d73e29c61c0..b4d596b188893 100644 --- a/examples/README.md +++ b/examples/README.md @@ -268,6 +268,7 @@ Example | Description Example | Description --- | --- [Scene](../examples/scene/scene.rs) | Demonstrates loading from and saving scenes to files +[Scene Builder](../examples/scene/scene_builder.rs) | Demonstrates building a fine-tuned scene from a world ## Shaders diff --git a/examples/scene/scene_builder.rs b/examples/scene/scene_builder.rs new file mode 100644 index 0000000000000..e85fea21dafa0 --- /dev/null +++ b/examples/scene/scene_builder.rs @@ -0,0 +1,172 @@ +//! This example illustrates building fine-tuned scenes from a world. + +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + // Make sure all components and resources you want to use in your scene are registered, + // and also register the `ReflectComponent` and `ReflectResource` type data, respectively. + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .add_systems(Startup, (initialize_world, infotext_system)) + .add_systems(Update, create_snapshot.run_if(run_once())) + .run(); +} + +#[derive(Component, Reflect, Default)] +#[reflect(Component)] +struct Unit; + +#[derive(Component, Reflect, Default)] +#[reflect(Component)] +struct Magic(u8); + +#[derive(Component, Reflect, Default)] +#[reflect(Component)] +struct Melee(u8); + +#[derive(Component, Reflect, Default)] +#[reflect(Component)] +struct Archery(u8); + +#[derive(Component, Reflect, Default)] +#[reflect(Component)] +struct Health(u16); + +#[derive(Resource, Reflect, Default)] +#[reflect(Resource)] +struct CastleHealth(u16); + +#[derive(Resource, Reflect, Default)] +#[reflect(Resource)] +struct TotalScore(u16); + +fn initialize_world(mut commands: Commands) { + commands.spawn((Unit, Health(100), TransformBundle::default())); + commands.spawn((Unit, Melee(22), Health(150), TransformBundle::default())); + commands.spawn((Unit, Magic(16), Health(100), TransformBundle::default())); + commands.spawn((Unit, Magic(10), Health(75), TransformBundle::default())); + commands.spawn((Unit, Archery(12), Health(85), TransformBundle::default())); + + commands.insert_resource(CastleHealth(1000)); + commands.insert_resource(TotalScore(0)); +} + +fn create_snapshot(world: &mut World) { + // 1. The simplest thing we can do is extract a single entity. + { + let entity = world.spawn((Unit, Archery(1))).id(); + + let mut builder = DynamicSceneBuilder::from_world(world); + + // By default, this will extract all components that registered `ReflectComponent` from the entity. + builder.extract_entity(entity); + + print_scene("Single Entity", world, builder.build()); + } + + // 2. We can also use a query (or any `Entity` iterator) to extract multiple entities. + // Here, we'll extract all entities that contain a `Unit` and `Magic` component. + { + let mut query = world.query_filtered::, With)>(); + + // Note: `DynamicSceneBuilder` holds onto a reference to the world. + // This is why we had to create `query` first, since it requires `&mut World`. + let mut builder = DynamicSceneBuilder::from_world(world); + + // Any iterator that yields `Entity` can be used, even a query! + builder.extract_entities(query.iter(world)); + + print_scene("Entity Iterator", world, builder.build()); + } + + // 3. The builder can also specify a filter for more control over what gets extracted. + // Let's filter that same query to only include the `Magic` data. + { + let mut query = world.query_filtered::, With)>(); + + let mut builder = DynamicSceneBuilder::from_world(world); + + // Set up our filters: + builder.allow::(); + // Alternatively (if we know exactly what we want to exclude): + // builder + // .deny::() + // .deny::() + // .deny::() + // .deny::(); + + builder.extract_entities(query.iter(world)); + + print_scene("Filtered", world, builder.build()); + } + + // 4. Resources can also be extracted. + // There are a lot of resources in a default Bevy world (and some that aren't serializable), + // so let's employ the same filtering technique as above. + { + let mut builder = DynamicSceneBuilder::from_world(world); + + // Set up our filters: + builder + .allow_resource::() + .allow_resource::(); + + builder.extract_resources(); + + print_scene("Resources", world, builder.build()); + } + + // 5. Extraction can only be performed once per entity/resource. + // The first extraction will use whatever filter is set at the time. + { + let entity = world.spawn((Unit, Health(100), Melee(16))).id(); + + let mut builder = DynamicSceneBuilder::from_world(world); + + // Extract the entity (again, this can only be done once): + builder.allow::().extract_entity(entity); + + // Let's attempt to extract the same entity, but with a modified filter: + builder.allow::().extract_entity(entity); + + // Note that we only captured the `Melee` component in the built scene. + print_scene("Duplicate Extraction", world, builder.build()); + } +} + +fn print_scene(title: &str, world: &World, scene: DynamicScene) { + let type_registry = world.resource::(); + let serialized_scene = scene.serialize_ron(type_registry).unwrap(); + + info!( + "==============[ {} ]==============\n{}", + title, serialized_scene + ); +} + +// This is only necessary for the info message in the UI. See examples/ui/text.rs for a standalone +// text example. +fn infotext_system(mut commands: Commands) { + commands.spawn(Camera2dBundle::default()); + commands.spawn( + TextBundle::from_section( + "Nothing to see in this window! Check the console output!", + TextStyle { + font_size: 50.0, + color: Color::WHITE, + ..default() + }, + ) + .with_style(Style { + align_self: AlignSelf::FlexEnd, + ..default() + }), + ); +}