Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a Dynamic Scene From a Query Filter #6004

Closed
103 changes: 102 additions & 1 deletion crates/bevy_scene/src/dynamic_scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use crate::{serde::SceneSerializer, Scene, SceneSpawnError};
use anyhow::Result;
use bevy_app::AppTypeRegistry;
use bevy_ecs::{
entity::EntityMap,
entity::{Entity, EntityMap},
query::ReadOnlyWorldQuery,
reflect::{ReflectComponent, ReflectMapEntities},
world::World,
};
Expand Down Expand Up @@ -37,6 +38,79 @@ impl DynamicScene {
Self::from_world(&scene.world, type_registry)
}

/// Create a Dynamic Scene with specific entities.
///
/// The generic parameter is used as a [`Query`] filter.
///
/// The created scene will include only the entities that match the query
/// filter provided. All components that impl `Reflect` will be included.
///
/// # Example
///
/// Here, the function is creating a scene with all the componentA's in the world, but none of the componentB's.
///
/// ```
/// use bevy_ecs::prelude::*;
/// use bevy_scene::DynamicScene;
/// use bevy_app::AppTypeRegistry;
///
/// #[derive(Component)]
/// struct ComponentA;
///
/// #[derive(Component)]
/// struct ComponentB;
///
/// let mut world = World::new();
/// world.init_resource::<AppTypeRegistry>();
///
/// let my_scene = DynamicScene::from_query_filter::<(
/// With<ComponentA>,
/// Without<ComponentB>,
/// )>(&mut world);
/// ```
pub fn from_query_filter<F>(world: &mut World) -> Self
Carter0 marked this conversation as resolved.
Show resolved Hide resolved
where
F: ReadOnlyWorldQuery + 'static,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting bound; we don't require ReadOnlyWorldQuery on the F in Query<Q, F>. Perhaps we should though?

{
let mut query = world.query_filtered::<Entity, F>();

let type_registry = world
.get_resource::<AppTypeRegistry>()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should probably force users to pass in this type explicitly, in order to clarify which type registry they want to use.

Copy link
Contributor Author

@Carter0 Carter0 Sep 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't there just be one type registry per world? Or per app? How could there be multiple?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh I see whats going on.

In order for a component to be saved to a scene, it needs to be registered in the type registry. If a user tried to use this method without registering their types first, then the scene would not be created with that component on it.

.expect("The World provided for scene generation does not contain a TypeRegistry")
.read();

let entities = query
.iter(world)
.map(|entity| {
let get_reflect_by_id = |id| {
Carter0 marked this conversation as resolved.
Show resolved Hide resolved
world
.components()
.get_info(id)
.and_then(|info| type_registry.get(info.type_id().unwrap()))
Carter0 marked this conversation as resolved.
Show resolved Hide resolved
.and_then(|reg| reg.data::<ReflectComponent>())
.and_then(|rc| rc.reflect(world, entity))
.map(|c| c.clone_value())
};

let components = world
Carter0 marked this conversation as resolved.
Show resolved Hide resolved
.entities()
.get(entity)
.and_then(|eloc| world.archetypes().get(eloc.archetype_id))
.into_iter()
.flat_map(|a| a.components())
.filter_map(get_reflect_by_id)
.collect();

DynamicEntity {
entity: entity.id(),
components,
}
})
.collect();

DynamicScene { entities }
}

/// Create a new dynamic scene from a given world.
pub fn from_world(world: &World, type_registry: &TypeRegistryArc) -> Self {
let mut scene = DynamicScene::default();
Expand Down Expand Up @@ -145,3 +219,30 @@ where
.new_line("\n".to_string());
ron::ser::to_string_pretty(&serialize, pretty_config)
}

#[cfg(test)]
mod tests {
use super::DynamicScene;
use bevy_app::AppTypeRegistry;
use bevy_ecs::prelude::*;

#[test]
fn from_query_filter_test() {
#[derive(Component)]
struct ComponentA;

#[derive(Component)]
struct ComponentB;

let mut world = World::new();
world.init_resource::<AppTypeRegistry>();

let _entity1 = world.spawn().insert(ComponentA);
let _entity2 = world.spawn().insert(ComponentB);

let my_scene =
DynamicScene::from_query_filter::<(With<ComponentA>, Without<ComponentB>)>(&mut world);

assert_eq!(my_scene.entities.len(), 1);
}
}