Skip to content

Commit ccf7c65

Browse files
committed
dynamic scene builder (#6227)
# Objective - make it easier to build dynamic scenes ## Solution - add a builder to create a dynamic scene from a world. it can extract an entity or an iterator of entities - alternative to #6013, leaving the "hierarchy iteration" part to #6185 which does it better - alternative to #6004 - using a builder makes it easier to chain several extractions
1 parent c0a93aa commit ccf7c65

File tree

3 files changed

+256
-39
lines changed

3 files changed

+256
-39
lines changed

crates/bevy_scene/src/dynamic_scene.rs

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{serde::SceneSerializer, Scene, SceneSpawnError};
1+
use crate::{serde::SceneSerializer, DynamicSceneBuilder, Scene, SceneSpawnError};
22
use anyhow::Result;
33
use bevy_app::AppTypeRegistry;
44
use bevy_ecs::{
@@ -33,47 +33,23 @@ pub struct DynamicEntity {
3333

3434
impl DynamicScene {
3535
/// Create a new dynamic scene from a given scene.
36-
pub fn from_scene(scene: &Scene, type_registry: &TypeRegistryArc) -> Self {
36+
pub fn from_scene(scene: &Scene, type_registry: &AppTypeRegistry) -> Self {
3737
Self::from_world(&scene.world, type_registry)
3838
}
3939

4040
/// Create a new dynamic scene from a given world.
41-
pub fn from_world(world: &World, type_registry: &TypeRegistryArc) -> Self {
42-
let mut scene = DynamicScene::default();
43-
let type_registry = type_registry.read();
44-
45-
for archetype in world.archetypes().iter() {
46-
let entities_offset = scene.entities.len();
47-
48-
// Create a new dynamic entity for each entity of the given archetype
49-
// and insert it into the dynamic scene.
50-
for entity in archetype.entities() {
51-
scene.entities.push(DynamicEntity {
52-
entity: entity.id(),
53-
components: Vec::new(),
54-
});
55-
}
56-
57-
// Add each reflection-powered component to the entity it belongs to.
58-
for component_id in archetype.components() {
59-
let reflect_component = world
60-
.components()
61-
.get_info(component_id)
62-
.and_then(|info| type_registry.get(info.type_id().unwrap()))
63-
.and_then(|registration| registration.data::<ReflectComponent>());
64-
if let Some(reflect_component) = reflect_component {
65-
for (i, entity) in archetype.entities().iter().enumerate() {
66-
if let Some(component) = reflect_component.reflect(world, *entity) {
67-
scene.entities[entities_offset + i]
68-
.components
69-
.push(component.clone_value());
70-
}
71-
}
72-
}
73-
}
74-
}
75-
76-
scene
41+
pub fn from_world(world: &World, type_registry: &AppTypeRegistry) -> Self {
42+
let mut builder =
43+
DynamicSceneBuilder::from_world_with_type_registry(world, type_registry.clone());
44+
45+
builder.extract_entities(
46+
world
47+
.archetypes()
48+
.iter()
49+
.flat_map(|archetype| archetype.entities().iter().copied()),
50+
);
51+
52+
builder.build()
7753
}
7854

7955
/// Write the dynamic entities and their corresponding components to the given world.
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
use crate::{DynamicEntity, DynamicScene};
2+
use bevy_app::AppTypeRegistry;
3+
use bevy_ecs::{prelude::Entity, reflect::ReflectComponent, world::World};
4+
use bevy_utils::{default, HashMap};
5+
6+
/// A [`DynamicScene`] builder, used to build a scene from a [`World`] by extracting some entities.
7+
///
8+
/// ```
9+
/// # use bevy_scene::DynamicSceneBuilder;
10+
/// # use bevy_app::AppTypeRegistry;
11+
/// # use bevy_ecs::{
12+
/// # component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World,
13+
/// # };
14+
/// # use bevy_reflect::Reflect;
15+
/// # #[derive(Component, Reflect, Default, Eq, PartialEq, Debug)]
16+
/// # #[reflect(Component)]
17+
/// # struct ComponentA;
18+
/// # let mut world = World::default();
19+
/// # world.init_resource::<AppTypeRegistry>();
20+
/// # let entity = world.spawn(ComponentA).id();
21+
/// let mut builder = DynamicSceneBuilder::from_world(&world);
22+
/// builder.extract_entity(entity);
23+
/// let dynamic_scene = builder.build();
24+
/// ```
25+
pub struct DynamicSceneBuilder<'w> {
26+
scene: HashMap<u32, DynamicEntity>,
27+
type_registry: AppTypeRegistry,
28+
world: &'w World,
29+
}
30+
31+
impl<'w> DynamicSceneBuilder<'w> {
32+
/// Prepare a builder that will extract entities and their component from the given [`World`].
33+
/// All components registered in that world's [`AppTypeRegistry`] resource will be extracted.
34+
pub fn from_world(world: &'w World) -> Self {
35+
Self {
36+
scene: default(),
37+
type_registry: world.resource::<AppTypeRegistry>().clone(),
38+
world,
39+
}
40+
}
41+
42+
/// Prepare a builder that will extract entities and their component from the given [`World`].
43+
/// Only components registered in the given [`AppTypeRegistry`] will be extracted.
44+
pub fn from_world_with_type_registry(world: &'w World, type_registry: AppTypeRegistry) -> Self {
45+
Self {
46+
scene: default(),
47+
type_registry,
48+
world,
49+
}
50+
}
51+
52+
/// Consume the builder, producing a [`DynamicScene`].
53+
pub fn build(self) -> DynamicScene {
54+
DynamicScene {
55+
entities: self.scene.into_values().collect(),
56+
}
57+
}
58+
59+
/// Extract one entity from the builder's [`World`].
60+
///
61+
/// Re-extracting an entity that was already extracted will have no effect.
62+
pub fn extract_entity(&mut self, entity: Entity) -> &mut Self {
63+
self.extract_entities(std::iter::once(entity))
64+
}
65+
66+
/// Extract entities from the builder's [`World`].
67+
///
68+
/// Re-extracting an entity that was already extracted will have no effect.
69+
///
70+
/// Extracting entities can be used to extract entities from a query:
71+
/// ```
72+
/// # use bevy_scene::DynamicSceneBuilder;
73+
/// # use bevy_app::AppTypeRegistry;
74+
/// # use bevy_ecs::{
75+
/// # component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World,
76+
/// # };
77+
/// # use bevy_reflect::Reflect;
78+
/// #[derive(Component, Default, Reflect)]
79+
/// #[reflect(Component)]
80+
/// struct MyComponent;
81+
///
82+
/// # let mut world = World::default();
83+
/// # world.init_resource::<AppTypeRegistry>();
84+
/// # let _entity = world.spawn(MyComponent).id();
85+
/// let mut query = world.query_filtered::<Entity, With<MyComponent>>();
86+
///
87+
/// let mut builder = DynamicSceneBuilder::from_world(&world);
88+
/// builder.extract_entities(query.iter(&world));
89+
/// let scene = builder.build();
90+
/// ```
91+
pub fn extract_entities(&mut self, entities: impl Iterator<Item = Entity>) -> &mut Self {
92+
let type_registry = self.type_registry.read();
93+
94+
for entity in entities {
95+
if self.scene.contains_key(&entity.id()) {
96+
continue;
97+
}
98+
99+
let mut entry = DynamicEntity {
100+
entity: entity.id(),
101+
components: Vec::new(),
102+
};
103+
104+
for component_id in self.world.entity(entity).archetype().components() {
105+
let reflect_component = self
106+
.world
107+
.components()
108+
.get_info(component_id)
109+
.and_then(|info| type_registry.get(info.type_id().unwrap()))
110+
.and_then(|registration| registration.data::<ReflectComponent>());
111+
112+
if let Some(reflect_component) = reflect_component {
113+
if let Some(component) = reflect_component.reflect(self.world, entity) {
114+
entry.components.push(component.clone_value());
115+
}
116+
}
117+
}
118+
119+
self.scene.insert(entity.id(), entry);
120+
}
121+
122+
drop(type_registry);
123+
self
124+
}
125+
}
126+
127+
#[cfg(test)]
128+
mod tests {
129+
use bevy_app::AppTypeRegistry;
130+
use bevy_ecs::{
131+
component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World,
132+
};
133+
134+
use bevy_reflect::Reflect;
135+
136+
use super::DynamicSceneBuilder;
137+
138+
#[derive(Component, Reflect, Default, Eq, PartialEq, Debug)]
139+
#[reflect(Component)]
140+
struct ComponentA;
141+
#[derive(Component, Reflect, Default, Eq, PartialEq, Debug)]
142+
#[reflect(Component)]
143+
struct ComponentB;
144+
145+
#[test]
146+
fn extract_one_entity() {
147+
let mut world = World::default();
148+
149+
let atr = AppTypeRegistry::default();
150+
atr.write().register::<ComponentA>();
151+
world.insert_resource(atr);
152+
153+
let entity = world.spawn((ComponentA, ComponentB)).id();
154+
155+
let mut builder = DynamicSceneBuilder::from_world(&world);
156+
builder.extract_entity(entity);
157+
let scene = builder.build();
158+
159+
assert_eq!(scene.entities.len(), 1);
160+
assert_eq!(scene.entities[0].entity, entity.id());
161+
assert_eq!(scene.entities[0].components.len(), 1);
162+
assert!(scene.entities[0].components[0].represents::<ComponentA>());
163+
}
164+
165+
#[test]
166+
fn extract_one_entity_twice() {
167+
let mut world = World::default();
168+
169+
let atr = AppTypeRegistry::default();
170+
atr.write().register::<ComponentA>();
171+
world.insert_resource(atr);
172+
173+
let entity = world.spawn((ComponentA, ComponentB)).id();
174+
175+
let mut builder = DynamicSceneBuilder::from_world(&world);
176+
builder.extract_entity(entity);
177+
builder.extract_entity(entity);
178+
let scene = builder.build();
179+
180+
assert_eq!(scene.entities.len(), 1);
181+
assert_eq!(scene.entities[0].entity, entity.id());
182+
assert_eq!(scene.entities[0].components.len(), 1);
183+
assert!(scene.entities[0].components[0].represents::<ComponentA>());
184+
}
185+
186+
#[test]
187+
fn extract_one_entity_two_components() {
188+
let mut world = World::default();
189+
190+
let atr = AppTypeRegistry::default();
191+
{
192+
let mut register = atr.write();
193+
register.register::<ComponentA>();
194+
register.register::<ComponentB>();
195+
}
196+
world.insert_resource(atr);
197+
198+
let entity = world.spawn((ComponentA, ComponentB)).id();
199+
200+
let mut builder = DynamicSceneBuilder::from_world(&world);
201+
builder.extract_entity(entity);
202+
let scene = builder.build();
203+
204+
assert_eq!(scene.entities.len(), 1);
205+
assert_eq!(scene.entities[0].entity, entity.id());
206+
assert_eq!(scene.entities[0].components.len(), 2);
207+
assert!(scene.entities[0].components[0].represents::<ComponentA>());
208+
assert!(scene.entities[0].components[1].represents::<ComponentB>());
209+
}
210+
211+
#[test]
212+
fn extract_query() {
213+
let mut world = World::default();
214+
215+
let atr = AppTypeRegistry::default();
216+
{
217+
let mut register = atr.write();
218+
register.register::<ComponentA>();
219+
register.register::<ComponentB>();
220+
}
221+
world.insert_resource(atr);
222+
223+
let entity_a_b = world.spawn((ComponentA, ComponentB)).id();
224+
let entity_a = world.spawn(ComponentA).id();
225+
let _entity_b = world.spawn(ComponentB).id();
226+
227+
let mut query = world.query_filtered::<Entity, With<ComponentA>>();
228+
let mut builder = DynamicSceneBuilder::from_world(&world);
229+
builder.extract_entities(query.iter(&world));
230+
let scene = builder.build();
231+
232+
assert_eq!(scene.entities.len(), 2);
233+
let mut scene_entities = vec![scene.entities[0].entity, scene.entities[1].entity];
234+
scene_entities.sort();
235+
assert_eq!(scene_entities, [entity_a_b.id(), entity_a.id()]);
236+
}
237+
}

crates/bevy_scene/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
mod bundle;
22
mod dynamic_scene;
3+
mod dynamic_scene_builder;
34
mod scene;
45
mod scene_loader;
56
mod scene_spawner;
67
pub mod serde;
78

89
pub use bundle::*;
910
pub use dynamic_scene::*;
11+
pub use dynamic_scene_builder::*;
1012
pub use scene::*;
1113
pub use scene_loader::*;
1214
pub use scene_spawner::*;
1315

1416
pub mod prelude {
1517
#[doc(hidden)]
16-
pub use crate::{DynamicScene, DynamicSceneBundle, Scene, SceneBundle, SceneSpawner};
18+
pub use crate::{
19+
DynamicScene, DynamicSceneBuilder, DynamicSceneBundle, Scene, SceneBundle, SceneSpawner,
20+
};
1721
}
1822

1923
use bevy_app::prelude::*;

0 commit comments

Comments
 (0)