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..62e5a3ed1f348 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,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>, extracted_scene: BTreeMap, - 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::().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(&mut self) -> &mut Self { + self.component_filter.allow::(); + 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(&mut self) -> &mut Self { + self.component_filter.deny::(); + 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(&mut self) -> &mut Self { + self.resource_filter.allow::(); + 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(&mut self) -> &mut Self { + self.resource_filter.deny::(); + 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) -> &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 +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::()? @@ -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::().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::()? @@ -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::()); } + + #[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..a5076e5d9d668 --- /dev/null +++ b/crates/bevy_scene/src/scene_filter.rs @@ -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), + /// 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 { + /// 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(&mut self) -> &mut Self { + self.allow_by_id(TypeId::of::()) + } + + /// 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(&mut self) -> &mut Self { + self.deny_by_id(TypeId::of::()) + } + + /// 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(&self) -> bool { + self.is_allowed_by_id(TypeId::of::()) + } + + /// 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(&self) -> bool { + self.is_denied_by_id(TypeId::of::()) + } + + /// 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 + '_> { + 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; + + 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::(); + assert!(matches!(filter, SceneFilter::Allowlist(_))); + + let mut filter = SceneFilter::Unset; + 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::()); + } +} 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