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
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,11 @@ wasm = false
name = "scene"
path = "examples/scene/scene.rs"

# Scene Building
Carter0 marked this conversation as resolved.
Show resolved Hide resolved
[[example]]
name = "scene_building"
path = "examples/scene/scene_building.rs"

[package.metadata.example.scene]
name = "Scene"
description = "Demonstrates loading from and saving scenes to files"
Expand Down
55 changes: 54 additions & 1 deletion crates/bevy_scene/src/dynamic_scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ 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},
system::{Query, SystemState},
world::World,
};
use bevy_reflect::{Reflect, TypeRegistryArc, TypeUuid};
Expand Down Expand Up @@ -37,6 +39,57 @@ 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.
Carter0 marked this conversation as resolved.
Show resolved Hide resolved
Carter0 marked this conversation as resolved.
Show resolved Hide resolved
///
/// The created scene will include only the entities that match the query
/// filter provided. All components that impl `Reflect` will be included.
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 ss = SystemState::<Query<Entity, F>>::new(world);
Carter0 marked this conversation as resolved.
Show resolved Hide resolved
Carter0 marked this conversation as resolved.
Show resolved Hide resolved

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 q = ss.get(world);

let entities = q
.iter()
.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
65 changes: 65 additions & 0 deletions examples/scene/scene_building.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use bevy::prelude::*;
Carter0 marked this conversation as resolved.
Show resolved Hide resolved

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(spawn_player)
.add_startup_system(spawn_enemy)
// Exclusive needed because we are yoinking the whole world
// Needs to happen at the end of startup because a player needs to exist
.add_startup_system(create_scene.exclusive_system().at_end())
.run();
}

fn create_scene(world: &mut World) {
// quick: make a scene with all entities that match a given query filter
// (all components will be included)
let my_scene: DynamicScene =
DynamicScene::from_query_filter::<(With<Player>, Without<Enemy>)>(world);

// This should print out 1 because the player was saved to the scene
println!("{}", my_scene.entities.len());
}

#[derive(Component)]
#[allow(dead_code)]
Carter0 marked this conversation as resolved.
Show resolved Hide resolved
struct Player {
speed: f32,
}

fn spawn_player(mut commands: Commands) {
commands.spawn().insert_bundle(Camera2dBundle::default());

commands
.spawn()
.insert_bundle(SpriteBundle {
sprite: Sprite {
custom_size: Some(Vec2::new(40.0, 40.0)),
..Default::default()
},
..Default::default()
})
.insert(Player { speed: 300.0 });
}

#[derive(Component)]
#[allow(dead_code)]
pub struct Enemy {
// Speed is always positive
speed: f32,
}

fn spawn_enemy(mut commands: Commands) {
commands
.spawn()
.insert_bundle(SpriteBundle {
sprite: Sprite {
color: Color::MAROON,
custom_size: Some(Vec2::new(40.0, 40.0)),
..Default::default()
},
transform: Transform::from_xyz(300.0, 0.0, 0.0),
..Default::default()
})
.insert(Enemy { speed: 200.0 });
}