mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
bevy_scene: Add SceneFilter
(#6793)
# Objective Currently, `DynamicScene`s extract all components listed in the given (or the world's) type registry. This acts as a quasi-filter of sorts. However, it can be troublesome to use effectively and lacks decent control. For example, say you need to serialize only the following component over the network: ```rust #[derive(Reflect, Component, Default)] #[reflect(Component)] struct NPC { name: Option<String> } ``` To do this, you'd need to: 1. Create a new `AppTypeRegistry` 2. Register `NPC` 3. Register `Option<String>` If we skip Step 3, then the entire scene might fail to serialize as `Option<String>` requires registration. Not only is this annoying and easy to forget, but it can leave users with an impossible task: serializing a third-party type that contains private types. Generally, the third-party crate will register their private types within a plugin so the user doesn't need to do it themselves. However, this means we are now unable to serialize _just_ that type— we're forced to allow everything! ## Solution Add the `SceneFilter` enum for filtering components to extract. This filter can be used to optionally allow or deny entire sets of components/resources. With the `DynamicSceneBuilder`, users have more control over how their `DynamicScene`s are built. To only serialize a subset of components, use the `allow` method: ```rust let scene = builder .allow::<ComponentA>() .allow::<ComponentB>() .extract_entity(entity) .build(); ``` To serialize everything _but_ a subset of components, use the `deny` method: ```rust let scene = builder .deny::<ComponentA>() .deny::<ComponentB>() .extract_entity(entity) .build(); ``` Or create a custom filter: ```rust let components = HashSet::from([type_id]); let filter = SceneFilter::Allowlist(components); // let filter = SceneFilter::Denylist(components); let scene = builder .with_filter(Some(filter)) .extract_entity(entity) .build(); ``` Similar operations exist for resources: <details> <summary>View Resource Methods</summary> To only serialize a subset of resources, use the `allow_resource` method: ```rust let scene = builder .allow_resource::<ResourceA>() .extract_resources() .build(); ``` To serialize everything _but_ a subset of resources, use the `deny_resource` method: ```rust let scene = builder .deny_resource::<ResourceA>() .extract_resources() .build(); ``` Or create a custom filter: ```rust let resources = HashSet::from([type_id]); let filter = SceneFilter::Allowlist(resources); // let filter = SceneFilter::Denylist(resources); let scene = builder .with_resource_filter(Some(filter)) .extract_resources() .build(); ``` </details> ### Open Questions - [x] ~~`allow` and `deny` are mutually exclusive. Currently, they overwrite each other. Should this instead be a panic?~~ Took @soqb's suggestion and made it so that the opposing method simply removes that type from the list. - [x] ~~`DynamicSceneBuilder` extracts entity data as soon as `extract_entity`/`extract_entities` is called. Should this behavior instead be moved to the `build` method to prevent ordering mixups (e.g. `.allow::<Foo>().extract_entity(entity)` vs `.extract_entity(entity).allow::<Foo>()`)? The tradeoff would be iterating over the given entities twice: once at extraction and again at build.~~ Based on the feedback from @Testare it sounds like it might be better to just keep the current functionality (if anything we can open a separate PR that adds deferred methods for extraction, so the choice/performance hit is up to the user). - [ ] An alternative might be to remove the filter from `DynamicSceneBuilder` and have it as a separate parameter to the extraction methods (either in the existing ones or as added `extract_entity_with_filter`-type methods). Is this preferable? - [x] ~~Should we include constructors that include common types to allow/deny? For example, a `SceneFilter::standard_allowlist` that includes things like `Parent` and `Children`?~~ Consensus suggests we should. I may split this out into a followup PR, though. - [x] ~~Should we add the ability to remove types from the filter regardless of whether an allowlist or denylist (e.g. `filter.remove::<Foo>()`)?~~ See the first list item - [x] ~~Should `SceneFilter` be an enum? Would it make more sense as a struct that contains an `is_denylist` boolean?~~ With the added `SceneFilter::None` state (replacing the need to wrap in an `Option` or rely on an empty `Denylist`), it seems an enum is better suited now - [x] ~~Bikeshed: Do we like the naming convention? Should we instead use `include`/`exclude` terminology?~~ Sounds like we're sticking with `allow`/`deny`! - [x] ~~Does this feature need a new example? Do we simply include it in the existing one (maybe even as a comment?)? Should this be done in a followup PR instead?~~ Example will be added in a followup PR ### Followup Tasks - [ ] Add a dedicated `SceneFilter` example - [ ] Possibly add default types to the filter (e.g. deny things like `ComputedVisibility`, allow `Parent`, etc) --- ## Changelog - Added the `SceneFilter` enum for filtering components and resources when building a `DynamicScene` - Added methods: - `DynamicSceneBuilder::with_filter` - `DynamicSceneBuilder::allow` - `DynamicSceneBuilder::deny` - `DynamicSceneBuilder::allow_all` - `DynamicSceneBuilder::deny_all` - `DynamicSceneBuilder::with_resource_filter` - `DynamicSceneBuilder::allow_resource` - `DynamicSceneBuilder::deny_resource` - `DynamicSceneBuilder::allow_all_resources` - `DynamicSceneBuilder::deny_all_resources` - Removed methods: - `DynamicSceneBuilder::from_world_with_type_registry` - `DynamicScene::from_scene` and `DynamicScene::from_world` no longer require an `AppTypeRegistry` reference ## Migration Guide - `DynamicScene::from_scene` and `DynamicScene::from_world` no longer require an `AppTypeRegistry` reference: ```rust // OLD let registry = world.resource::<AppTypeRegistry>(); let dynamic_scene = DynamicScene::from_world(&world, registry); // let dynamic_scene = DynamicScene::from_scene(&scene, registry); // NEW let dynamic_scene = DynamicScene::from_world(&world); // let dynamic_scene = DynamicScene::from_scene(&scene); ``` - Removed `DynamicSceneBuilder::from_world_with_type_registry`. Now the registry is automatically taken from the given world: ```rust // OLD let registry = world.resource::<AppTypeRegistry>(); let builder = DynamicSceneBuilder::from_world_with_type_registry(&world, registry); // NEW let builder = DynamicSceneBuilder::from_world(&world); ```
This commit is contained in:
parent
9655acebb6
commit
d96933ad9c
6 changed files with 571 additions and 48 deletions
|
@ -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();
|
||||
|
|
|
@ -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,27 @@ 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.
|
||||
///
|
||||
/// 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.
|
||||
/// (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.
|
||||
///
|
||||
/// 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 [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 +57,117 @@ use std::collections::BTreeMap;
|
|||
pub struct DynamicSceneBuilder<'w> {
|
||||
extracted_resources: BTreeMap<ComponentId, Box<dyn Reflect>>,
|
||||
extracted_scene: BTreeMap<Entity, DynamicEntity>,
|
||||
type_registry: AppTypeRegistry,
|
||||
component_filter: SceneFilter,
|
||||
resource_filter: SceneFilter,
|
||||
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::<AppTypeRegistry>().clone(),
|
||||
component_filter: SceneFilter::default(),
|
||||
resource_filter: SceneFilter::default(),
|
||||
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: 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: SceneFilter) -> &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 multiple times for any number of components.
|
||||
///
|
||||
/// 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<T: Component>(&mut self) -> &mut Self {
|
||||
self.component_filter.allow::<T>();
|
||||
self
|
||||
}
|
||||
|
||||
/// Denies the given component type, `T`, from being included in the generated scene.
|
||||
///
|
||||
/// This method may be called multiple times for any number of components.
|
||||
///
|
||||
/// 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<T: Component>(&mut self) -> &mut Self {
|
||||
self.component_filter.deny::<T>();
|
||||
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.
|
||||
///
|
||||
/// 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<T: Resource>(&mut self) -> &mut Self {
|
||||
self.resource_filter.allow::<T>();
|
||||
self
|
||||
}
|
||||
|
||||
/// Denies the given resource type, `T`, from being included in the generated scene.
|
||||
///
|
||||
/// This method may be called multiple times for any number of resources.
|
||||
///
|
||||
/// 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<T: Resource>(&mut self) -> &mut Self {
|
||||
self.resource_filter.deny::<T>();
|
||||
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`].
|
||||
|
@ -97,7 +202,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 +226,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<Item = Entity>) -> &mut Self {
|
||||
let type_registry = self.type_registry.read();
|
||||
let type_registry = self.original_world.resource::<AppTypeRegistry>().read();
|
||||
|
||||
for entity in entities {
|
||||
if self.extracted_scene.contains_key(&entity) {
|
||||
|
@ -139,6 +252,14 @@ impl<'w> DynamicSceneBuilder<'w> {
|
|||
.components()
|
||||
.get_info(component_id)?
|
||||
.type_id()?;
|
||||
|
||||
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
|
||||
return None;
|
||||
}
|
||||
|
||||
let component = type_registry
|
||||
.get(type_id)?
|
||||
.data::<ReflectComponent>()?
|
||||
|
@ -151,14 +272,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 +299,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::<AppTypeRegistry>().read();
|
||||
|
||||
for (component_id, _) in self.original_world.storages().resources.iter() {
|
||||
let mut extract_and_push = || {
|
||||
let type_id = self
|
||||
|
@ -185,6 +312,14 @@ impl<'w> DynamicSceneBuilder<'w> {
|
|||
.components()
|
||||
.get_info(component_id)?
|
||||
.type_id()?;
|
||||
|
||||
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
|
||||
return None;
|
||||
}
|
||||
|
||||
let resource = type_registry
|
||||
.get(type_id)?
|
||||
.data::<ReflectResource>()?
|
||||
|
@ -225,6 +360,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 +540,106 @@ mod tests {
|
|||
assert_eq!(scene.resources.len(), 1);
|
||||
assert!(scene.resources[0].represents::<ResourceA>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_extract_allowed_components() {
|
||||
let mut world = World::default();
|
||||
|
||||
let atr = AppTypeRegistry::default();
|
||||
{
|
||||
let mut register = atr.write();
|
||||
register.register::<ComponentA>();
|
||||
register.register::<ComponentB>();
|
||||
}
|
||||
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::<ComponentA>()
|
||||
.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::<ComponentA>());
|
||||
assert!(scene.entities[1].components[0].represents::<ComponentA>());
|
||||
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::<ComponentA>();
|
||||
register.register::<ComponentB>();
|
||||
}
|
||||
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::<ComponentA>()
|
||||
.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::<ComponentB>());
|
||||
assert_eq!(scene.entities[1].components.len(), 0);
|
||||
assert!(scene.entities[2].components[0].represents::<ComponentB>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_extract_allowed_resources() {
|
||||
let mut world = World::default();
|
||||
|
||||
let atr = AppTypeRegistry::default();
|
||||
{
|
||||
let mut register = atr.write();
|
||||
register.register::<ResourceA>();
|
||||
register.register::<ResourceB>();
|
||||
}
|
||||
world.insert_resource(atr);
|
||||
|
||||
world.insert_resource(ResourceA);
|
||||
world.insert_resource(ResourceB);
|
||||
|
||||
let mut builder = DynamicSceneBuilder::from_world(&world);
|
||||
builder.allow_resource::<ResourceA>().extract_resources();
|
||||
let scene = builder.build();
|
||||
|
||||
assert_eq!(scene.resources.len(), 1);
|
||||
assert!(scene.resources[0].represents::<ResourceA>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_extract_denied_resources() {
|
||||
let mut world = World::default();
|
||||
|
||||
let atr = AppTypeRegistry::default();
|
||||
{
|
||||
let mut register = atr.write();
|
||||
register.register::<ResourceA>();
|
||||
register.register::<ResourceB>();
|
||||
}
|
||||
world.insert_resource(atr);
|
||||
|
||||
world.insert_resource(ResourceA);
|
||||
world.insert_resource(ResourceB);
|
||||
|
||||
let mut builder = DynamicSceneBuilder::from_world(&world);
|
||||
builder.deny_resource::<ResourceA>().extract_resources();
|
||||
let scene = builder.build();
|
||||
|
||||
assert_eq!(scene.resources.len(), 1);
|
||||
assert!(scene.resources[0].represents::<ResourceB>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
278
crates/bevy_scene/src/scene_filter.rs
Normal file
278
crates/bevy_scene/src/scene_filter.rs
Normal file
|
@ -0,0 +1,278 @@
|
|||
use bevy_utils::hashbrown::hash_set::IntoIter;
|
||||
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
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SceneFilter {
|
||||
/// Represents an unset filter.
|
||||
///
|
||||
/// 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]
|
||||
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`].
|
||||
///
|
||||
/// [`DynamicScene`]: crate::DynamicScene
|
||||
Allowlist(HashSet<TypeId>),
|
||||
/// 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<TypeId>),
|
||||
}
|
||||
|
||||
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`],
|
||||
/// then the given type will be removed from the denied set.
|
||||
///
|
||||
/// 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<T: Any>(&mut self) -> &mut Self {
|
||||
self.allow_by_id(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// Allow the given type.
|
||||
///
|
||||
/// If this filter is already set as a [`Denylist`],
|
||||
/// then the given type will be removed from the denied set.
|
||||
///
|
||||
/// 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 => {
|
||||
*self = Self::Allowlist(HashSet::from([type_id]));
|
||||
}
|
||||
Self::Allowlist(list) => {
|
||||
list.insert(type_id);
|
||||
}
|
||||
Self::Denylist(list) => {
|
||||
list.remove(&type_id);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Deny the given type, `T`.
|
||||
///
|
||||
/// If this filter is already set as an [`Allowlist`],
|
||||
/// then the given type will be removed from the allowed set.
|
||||
///
|
||||
/// 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<T: Any>(&mut self) -> &mut Self {
|
||||
self.deny_by_id(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// Deny the given type.
|
||||
///
|
||||
/// If this filter is already set as an [`Allowlist`],
|
||||
/// then the given type will be removed from the allowed set.
|
||||
///
|
||||
/// 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])),
|
||||
Self::Allowlist(list) => {
|
||||
list.remove(&type_id);
|
||||
}
|
||||
Self::Denylist(list) => {
|
||||
list.insert(type_id);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns true if the given type, `T`, is allowed by the filter.
|
||||
///
|
||||
/// If the filter is [`Unset`], this will always return `true`.
|
||||
///
|
||||
/// [`Unset`]: SceneFilter::Unset
|
||||
pub fn is_allowed<T: Any>(&self) -> bool {
|
||||
self.is_allowed_by_id(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// Returns true if the given type is allowed by the filter.
|
||||
///
|
||||
/// 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,
|
||||
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 [`Unset`], this will always return `false`.
|
||||
///
|
||||
/// [`Unset`]: SceneFilter::Unset
|
||||
pub fn is_denied<T: Any>(&self) -> bool {
|
||||
self.is_denied_by_id(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// Returns true if the given type is denied by the filter.
|
||||
///
|
||||
/// 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 [`Unset`], this will return an empty iterator.
|
||||
///
|
||||
/// [`Unset`]: SceneFilter::Unset
|
||||
pub fn iter(&self) -> Box<dyn ExactSizeIterator<Item = &TypeId> + '_> {
|
||||
match self {
|
||||
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 [`Unset`], this will always return a length of zero.
|
||||
///
|
||||
/// [`Unset`]: SceneFilter::Unset
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
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 [`Unset`], this will always return `true`.
|
||||
///
|
||||
/// [`Unset`]: SceneFilter::Unset
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Self::Unset => true,
|
||||
Self::Allowlist(list) | Self::Denylist(list) => list.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for SceneFilter {
|
||||
type Item = TypeId;
|
||||
type IntoIter = IntoIter<TypeId>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
match self {
|
||||
Self::Unset => HashSet::new().into_iter(),
|
||||
Self::Allowlist(list) | Self::Denylist(list) => list.into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_set_list_type_if_none() {
|
||||
let mut filter = SceneFilter::Unset;
|
||||
filter.allow::<i32>();
|
||||
assert!(matches!(filter, SceneFilter::Allowlist(_)));
|
||||
|
||||
let mut filter = SceneFilter::Unset;
|
||||
filter.deny::<i32>();
|
||||
assert!(matches!(filter, SceneFilter::Denylist(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_add_to_list() {
|
||||
let mut filter = SceneFilter::default();
|
||||
filter.allow::<i16>();
|
||||
filter.allow::<i32>();
|
||||
assert_eq!(2, filter.len());
|
||||
assert!(filter.is_allowed::<i16>());
|
||||
assert!(filter.is_allowed::<i32>());
|
||||
|
||||
let mut filter = SceneFilter::default();
|
||||
filter.deny::<i16>();
|
||||
filter.deny::<i32>();
|
||||
assert_eq!(2, filter.len());
|
||||
assert!(filter.is_denied::<i16>());
|
||||
assert!(filter.is_denied::<i32>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_remove_from_list() {
|
||||
let mut filter = SceneFilter::default();
|
||||
filter.allow::<i16>();
|
||||
filter.allow::<i32>();
|
||||
filter.deny::<i32>();
|
||||
assert_eq!(1, filter.len());
|
||||
assert!(filter.is_allowed::<i16>());
|
||||
assert!(!filter.is_allowed::<i32>());
|
||||
|
||||
let mut filter = SceneFilter::default();
|
||||
filter.deny::<i16>();
|
||||
filter.deny::<i32>();
|
||||
filter.allow::<i32>();
|
||||
assert_eq!(1, filter.len());
|
||||
assert!(filter.is_denied::<i16>());
|
||||
assert!(!filter.is_denied::<i32>());
|
||||
}
|
||||
}
|
|
@ -630,7 +630,7 @@ mod tests {
|
|||
|
||||
let registry = world.resource::<AppTypeRegistry>();
|
||||
|
||||
let scene = DynamicScene::from_world(&world, registry);
|
||||
let scene = DynamicScene::from_world(&world);
|
||||
|
||||
let serialized = scene
|
||||
.serialize_ron(&world.resource::<AppTypeRegistry>().0)
|
||||
|
@ -680,7 +680,7 @@ mod tests {
|
|||
|
||||
let registry = world.resource::<AppTypeRegistry>();
|
||||
|
||||
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::<AppTypeRegistry>();
|
||||
|
||||
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::<AppTypeRegistry>();
|
||||
|
||||
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::<AppTypeRegistry>();
|
||||
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::<AppTypeRegistry>();
|
||||
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::<AppTypeRegistry>();
|
||||
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::<AppTypeRegistry>();
|
||||
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::<AppTypeRegistry>();
|
||||
let scene_a = DynamicScene::from_world(&world, registry);
|
||||
let scene_a = DynamicScene::from_world(&world);
|
||||
|
||||
world.entity_mut(entity).remove::<MyComponent>();
|
||||
|
||||
let registry = world.resource::<AppTypeRegistry>();
|
||||
let scene_b = DynamicScene::from_world(&world, registry);
|
||||
let scene_b = DynamicScene::from_world(&world);
|
||||
|
||||
assert_scene_eq(&scene_a, &scene_b);
|
||||
}
|
||||
|
|
|
@ -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::<AppTypeRegistry>().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::<AppTypeRegistry>();
|
||||
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::<AppTypeRegistry>();
|
||||
let serialized_scene = scene.serialize_ron(type_registry).unwrap();
|
||||
|
||||
// Showing the scene in the console
|
||||
|
|
Loading…
Reference in a new issue