Skip to content

Commit f489c77

Browse files
authored
Merge pull request #845 from AmbientRun/derived-children
Derived `children` from `parent`
2 parents 711e4b3 + a4a51b6 commit f489c77

19 files changed

Lines changed: 114 additions & 44 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ These PRs are not directly user-facing, but improve the development experience.
145145
- **Elements**: All hooks are now free functions (i.e. `use_state(hooks, ..)` instead of `hooks.use_state(..)`)
146146
- **UI**: Focus is now global across different packages, and we've removed the FocusRoot component
147147
- **API**: CursorLockGuard removed and `hide_cursor` package introduced.
148+
- **Hierarchies**: The `children` component is now automatically derived from `parent` components (unless the user opts out of this). The `children` component is also not networked any longer, since it's calculated on the client side.
148149

149150
#### Non-breaking
150151

‎app/src/server/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ fn systems(_world: &mut World) -> SystemGroup {
221221
// Can happen *during* the physics step
222222
Box::new(ambient_core::async_ecs::async_ecs_systems()),
223223
Box::new(ambient_prefab::systems()),
224+
Box::new(ambient_core::hierarchy::systems()),
224225
// Happens after the physics step
225226
ambient_physics::fetch_simulation_system(),
226227
Box::new(ambient_animation::animation_systems()),

‎campfire/src/package.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ fn check_all() -> anyhow::Result<()> {
144144
if !features.is_empty() {
145145
command.args(["--features", features]);
146146
}
147+
command.args(["--", "-A", "clippy::collapsible-if"]);
147148

148149
if !command.spawn()?.wait()?.success() {
149150
anyhow::bail!(

‎crates/app/src/lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ pub fn world_instance_systems(full: bool) -> SystemGroup {
9999
Box::new(async_ecs_systems()),
100100
remove_at_time_system(),
101101
refcount_system(),
102+
Box::new(ambient_core::hierarchy::systems()),
102103
Box::new(WorldEventsSystem),
103104
Box::new(ambient_focus::systems()),
104105
if full {

‎crates/core/src/hierarchy.rs‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,51 @@
11
use std::collections::HashSet;
22

3-
use ambient_ecs::{query, Component, ComponentValue, ECSError, Entity, EntityId, World};
3+
use ambient_ecs::{
4+
generated::hierarchy::components::unmanaged_children, query, Component, ComponentValue,
5+
ECSError, Entity, EntityId, SystemGroup, World,
6+
};
47
use itertools::Itertools;
58
use yaml_rust::YamlEmitter;
69

710
pub use ambient_ecs::generated::hierarchy::components::{children, parent};
811

912
use crate::name;
1013

14+
pub fn systems() -> SystemGroup {
15+
SystemGroup::new(
16+
"hierarchy",
17+
vec![
18+
query(parent().changed()).to_system_with_name("update_children", |q, world, qs, _| {
19+
for (id, parent) in q.collect_cloned(world, qs) {
20+
if world.has_component(parent, unmanaged_children()) {
21+
continue;
22+
}
23+
if let Ok(children) = world.get_mut(parent, children()) {
24+
if !children.contains(&id) {
25+
children.push(id);
26+
}
27+
} else {
28+
world.add_component(parent, children(), vec![id]).unwrap();
29+
}
30+
}
31+
}),
32+
query(parent()).despawned().to_system_with_name(
33+
"remove_children",
34+
|q, world, qs, _| {
35+
for (id, parent) in q.collect_cloned(world, qs) {
36+
if world.has_component(parent, unmanaged_children()) {
37+
continue;
38+
}
39+
if let Ok(children) = world.get_mut(parent, children()) {
40+
children.retain(|c| *c != id);
41+
}
42+
}
43+
},
44+
),
45+
],
46+
)
47+
}
48+
1149
pub fn despawn_recursive(world: &mut World, entity: EntityId) -> Option<Entity> {
1250
despawn_children_recursive(world, entity);
1351
world.despawn(entity)

‎crates/ecs/src/generated.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ mod raw {
240240
};
241241
use glam::{Mat4, Quat, UVec2, UVec3, UVec4, Vec2, Vec3, Vec4};
242242
use std::time::Duration;
243-
components ! ("hierarchy" , { # [doc = "**Parent**: The parent of this entity.\n\n*Attributes*: Debuggable, Networked, Store"] @ [Debuggable , Networked , Store , Name ["Parent"] , Description ["The parent of this entity."]] parent : EntityId , # [doc = "**Children**: The children of this entity.\n\n*Attributes*: Debuggable, Networked, Store, MaybeResource"] @ [Debuggable , Networked , Store , MaybeResource , Name ["Children"] , Description ["The children of this entity."]] children : Vec :: < EntityId > , });
243+
components ! ("hierarchy" , { # [doc = "**Parent**: The parent of this entity.\n\n*Attributes*: Debuggable, Networked, Store"] @ [Debuggable , Networked , Store , Name ["Parent"] , Description ["The parent of this entity."]] parent : EntityId , # [doc = "**Children**: The children of this entity.\n\n*Attributes*: Debuggable, Store, MaybeResource"] @ [Debuggable , Store , MaybeResource , Name ["Children"] , Description ["The children of this entity."]] children : Vec :: < EntityId > , # [doc = "**Unmanaged children**: This children component is not updated automatically for this entity when this component is attached.\n\n*Attributes*: Debuggable, Networked, Store, MaybeResource"] @ [Debuggable , Networked , Store , MaybeResource , Name ["Unmanaged children"] , Description ["This children component is not updated automatically for this entity when this component is attached."]] unmanaged_children : () , });
244244
}
245245
}
246246
#[allow(unused)]

‎crates/model/src/model.rs‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ use ambient_core::{
1313
},
1414
};
1515
use ambient_ecs::{
16-
generated::animation::components::bind_id, query, ComponentDesc, Entity, EntityId, World,
16+
generated::{animation::components::bind_id, hierarchy::components::unmanaged_children},
17+
query, ComponentDesc, Entity, EntityId, World,
1718
};
1819
use ambient_gpu::gpu::Gpu;
1920
use ambient_native_std::{
@@ -207,7 +208,8 @@ impl Model {
207208
.with(children(), vec![])
208209
.with(local_to_parent(), transform)
209210
.with(local_to_world(), Default::default())
210-
.with(is_model_node(), ()),
211+
.with(is_model_node(), ())
212+
.with(unmanaged_children(), ()),
211213
count,
212214
);
213215
for (transform, root) in transform_roots.iter().zip(roots.iter()) {
@@ -375,6 +377,9 @@ impl Model {
375377
}
376378
}
377379
if self.0.has_component(id, children()) {
380+
for id in &entities {
381+
world.add_component(*id, unmanaged_children(), ()).ok();
382+
}
378383
for c in self.0.get_ref(id, children()).unwrap().iter() {
379384
self.spawn_subtree(
380385
gpu,

‎docs/src/reference/hierarchies.md‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Hierarchies and transforms
22

3-
Ambient supports hierarchies of entities using the `parent` and `children` components. Both need to be present for a hierarchy to be valid - as an example, the following entities in the ECS
3+
Ambient supports hierarchies of entities using the `parent` and `children` components. The user only specifies the `parent` component, the `children` are automatically derived from the existing parents.
4+
As an example, the following entities in the ECS
45

56
```yml
67
entity a:
7-
- children: [b, c]
88
entity b:
99
- parent: a
1010
entity c:
@@ -19,8 +19,6 @@ entity a
1919
entity c
2020
```
2121

22-
If you are creating hierachies yourself, you need to make sure that both `parent` and `children` exists and are correct for the hierarchy to work.
23-
2422
The `entity::add_child` and `entity::remove_child` functions can be used to add and remove children from a parent.
2523

2624
When using the `model_from_url` or `prefab_from_url` components, the entire model sub-tree will be spawned in, with the root of the sub-tree being added as a child to the entity with the component. Each entity in the sub-tree will be part of the hierarchy using their own `parent` and `children` components.
@@ -32,7 +30,6 @@ To apply transforms to a hierarchy, `local_to_parent` must be used:
3230

3331
```yml
3432
entity a:
35-
- children: [b]
3633
- local_to_world: Mat4(..)
3734
entity b:
3835
- parent: a
@@ -46,7 +43,6 @@ In this case, `b.local_to_world` will be calculated as `a.local_to_world * b.loc
4643

4744
```yml
4845
entity a:
49-
- children: [b]
5046
- local_to_world: Mat4(..)
5147
- translation: vec3(5., 2., 9.)
5248
- rotation: quat(..)
@@ -88,3 +84,9 @@ mesh_to_world = local_to_world * mesh_to_local
8884
This also means that you can attach a mesh in the middle of a hierarchy, with an offset. For instance, if you have
8985
a bone hierarchy on a character, you can attach an mesh to the upper arm bone, but without `mesh_to_local/world` it
9086
would be rendered at the center of the arm (inside the arm), so by using `mesh_to_local/world` you can offset it.
87+
88+
## Opting out of automatically derived children
89+
90+
If you wish to manage the `children` component yourself, you can attach an `unmanaged_children` component to your
91+
entity. This stops `children` from being automatically created, and it's now up to you to populate the `children`
92+
component to create a valid hierarchy.

‎guest/rust/Cargo.lock‎

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎guest/rust/api_core/src/client/audio.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{
22
core::{
33
app::components::name,
44
audio::components::*,
5-
hierarchy::components::{children, parent},
5+
hierarchy::components::{children, parent, unmanaged_children},
66
},
77
entity,
88
prelude::{Entity, EntityId},
@@ -82,6 +82,7 @@ impl AudioPlayer {
8282
.with(is_audio_player(), ())
8383
.with(name(), "Audio player".to_string())
8484
.with(children(), vec![])
85+
.with(unmanaged_children(), ())
8586
.spawn();
8687
Self { entity: player }
8788
}

0 commit comments

Comments
 (0)