bevy_bundletree/
lib.rs

1use core::marker::PhantomData;
2
3use bevy_ecs::{
4    component::{ComponentHooks, ComponentId, StorageType},
5    prelude::*,
6    world::{Command, DeferredWorld},
7};
8use bevy_hierarchy::BuildChildren;
9
10/// A component that, when added to an entity, will add a child entity with the given bundle.
11///
12/// This component will be removed from the entity, as its data is moved into the child entity.
13///
14/// You can add multiple children in this way, if and only if their bundle types are distinct.
15/// See [`ChildBundles`] for a version that supports adding multiple children of the same type.
16///
17/// Under the hood, this is done using component lifecycle hooks.
18///
19/// ```rust
20/// use bevy_ecs::prelude::*;
21/// use bevy_bundletree::ChildBundle;
22///
23/// #[derive(Component)]
24/// struct A;
25///
26/// #[derive(Component)]
27/// struct B(u8);
28///
29/// fn spawn_hierarchy(mut commands: Commands) {
30///   commands.spawn(
31///    (A, // Parent
32///     ChildBundle( // This component is removed on spawn
33///       (A, B(3)) // Child
34///     )
35///   ));
36/// }
37/// ```
38#[derive(Debug, Clone, Default)]
39pub struct ChildBundle<B: Bundle>(pub B);
40
41impl<B: Bundle> Component for ChildBundle<B> {
42    /// This is a sparse set component as it's only ever added and removed, never iterated over.
43    const STORAGE_TYPE: StorageType = StorageType::SparseSet;
44
45    fn register_component_hooks(hooks: &mut ComponentHooks) {
46        hooks.on_add(with_child_hook::<B>);
47    }
48}
49
50/// A hook that runs whenever [`ChildBundle`] is added to an entity.
51///
52/// Generates a [`ChildBundleCommand`].
53fn with_child_hook<B: Bundle>(
54    mut world: DeferredWorld<'_>,
55    entity: Entity,
56    _component_id: ComponentId,
57) {
58    // Component hooks can't perform structural changes, so we need to rely on commands.
59    world.commands().queue(ChildBundleCommand {
60        parent_entity: entity,
61        _phantom: PhantomData::<B>,
62    });
63}
64
65struct ChildBundleCommand<B> {
66    parent_entity: Entity,
67    _phantom: PhantomData<B>,
68}
69
70impl<B: Bundle> Command for ChildBundleCommand<B> {
71    fn apply(self, world: &mut World) {
72        let Ok(mut entity_mut) = world.get_entity_mut(self.parent_entity) else {
73            #[cfg(debug_assertions)]
74            panic!("Parent entity not found");
75
76            #[cfg(not(debug_assertions))]
77            return;
78        };
79
80        let Some(with_child_component) = entity_mut.take::<ChildBundle<B>>() else {
81            #[cfg(debug_assertions)]
82            panic!("ChildBundle component not found");
83
84            #[cfg(not(debug_assertions))]
85            return;
86        };
87
88        let child_entity = world.spawn(with_child_component.0).id();
89        world.entity_mut(self.parent_entity).add_child(child_entity);
90    }
91}
92
93/// A component that, when added to an entity, will add a child entity with the given bundle.
94///
95/// This component will be removed from the entity immediately upon being spawned,
96/// and the supplied iterator will be iterated to completion to generate the data needed for each child.
97/// See [`ChildBundle`] for a more convenient API when adding only one child (or multiple children with distinct bundle types).
98///
99/// Under the hood, this is done using component lifecycle hooks.
100///
101/// # Examples
102///
103/// Just like when using [`Commands::spawn_batch`], any iterator that returns a bundle of the same type can be used.
104///
105/// Working with vectors, arrays and other collections is straightforward:
106///
107/// ```rust
108/// use bevy_ecs::prelude::*;
109/// use i_cant_believe_its_not_bsn::ChildBundles;
110///
111/// #[derive(Component)]
112/// struct Name(&'static str);
113///
114/// fn spawn_hierarchy_with_vector(mut commands: Commands) {
115///   commands.spawn(
116///    (Name("Zeus"),
117///     ChildBundles([Name("Athena"), Name("Apollo"), Name("Hermes")])
118///   ));
119/// }
120///```
121///
122/// However, generator-style iterators can also be used to dynamically vary the number and property of children:
123///
124/// ```rust
125/// use bevy_ecs::prelude::*;
126/// use i_cant_believe_its_not_bsn::ChildBundles;
127///
128/// #[derive(Component)]
129/// struct A;
130///
131/// #[derive(Component)]
132/// struct ChildNumber(usize);
133///
134/// fn spawn_hierarchy_with_generator(mut commands: Commands) {
135///   commands.spawn(
136///    (A, // Component on parent
137///      ChildBundles((0..3).map(|i| (ChildNumber(i)))) // Each child will have a ChildNumber component
138///    ));
139/// }
140///```
141#[derive(Debug, Clone, Default)]
142pub struct ChildBundles<B: Bundle, I: IntoIterator<Item = B>>(pub I);
143
144impl<B: Bundle, I: IntoIterator<Item = B> + Send + Sync + 'static> Component
145    for ChildBundles<B, I>
146{
147    /// This is a sparse set component as it's only ever added and removed, never iterated over.
148    const STORAGE_TYPE: StorageType = StorageType::SparseSet;
149
150    fn register_component_hooks(hooks: &mut ComponentHooks) {
151        hooks.on_add(with_children_hook::<B, I>);
152    }
153}
154
155/// A hook that runs whenever [`ChildBundles`] is added to an entity.
156///
157/// Generates a [`ChildBundlesCommand`].
158fn with_children_hook<B: Bundle, I: IntoIterator<Item = B> + Send + Sync + 'static>(
159    mut world: DeferredWorld<'_>,
160    entity: Entity,
161    _component_id: ComponentId,
162) {
163    // Component hooks can't perform structural changes, so we need to rely on commands.
164    world.commands().queue(ChildBundlesCommand {
165        parent_entity: entity,
166        _phantom: PhantomData::<(B, I)>,
167    });
168}
169
170struct ChildBundlesCommand<B, I> {
171    parent_entity: Entity,
172    _phantom: PhantomData<(B, I)>,
173}
174
175impl<B: Bundle, I: IntoIterator<Item = B> + Send + Sync + 'static> Command
176    for ChildBundlesCommand<B, I>
177{
178    fn apply(self, world: &mut World) {
179        let Ok(mut entity_mut) = world.get_entity_mut(self.parent_entity) else {
180            #[cfg(debug_assertions)]
181            panic!("Parent entity not found");
182
183            #[cfg(not(debug_assertions))]
184            return;
185        };
186
187        let Some(with_children_component) = entity_mut.take::<ChildBundles<B, I>>() else {
188            #[cfg(debug_assertions)]
189            panic!("ChildBundle component not found");
190
191            #[cfg(not(debug_assertions))]
192            return;
193        };
194
195        for child_bundle in with_children_component.0 {
196            let child_entity = world.spawn(child_bundle).id();
197            world.entity_mut(self.parent_entity).add_child(child_entity);
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use bevy_ecs::system::RunSystemOnce;
205    use bevy_hierarchy::Children;
206
207    use super::*;
208
209    #[derive(Component, PartialEq, Debug)]
210    struct A;
211
212    #[derive(Component, PartialEq, Debug)]
213    struct B(u8);
214
215    #[derive(Bundle)]
216    struct ABBundle {
217        a: A,
218        b: B,
219    }
220
221    #[derive(Bundle)]
222    struct HierarchicalBundle {
223        a: A,
224        child: ChildBundle<ABBundle>,
225    }
226
227    #[test]
228    fn with_child() {
229        let mut world = World::default();
230
231        let parent = world.spawn(ChildBundle((A, B(3)))).id();
232        // FIXME: this should not be needed!
233        world.flush();
234
235        assert!(!world.entity(parent).contains::<ChildBundle<(A, B)>>());
236        assert!(!world.entity(parent).contains::<A>());
237        assert!(!world.entity(parent).contains::<B>());
238
239        let children = world.get::<Children>(parent).unwrap();
240        assert_eq!(children.len(), 1);
241
242        let child_entity = children[0];
243        assert_eq!(world.get::<A>(child_entity), Some(&A));
244        assert_eq!(world.get::<B>(child_entity), Some(&B(3)));
245    }
246
247    #[test]
248    fn with_children_vec() {
249        let mut world = World::default();
250
251        let parent = world.spawn(ChildBundles(vec![B(0), B(1), B(2)])).id();
252        // FIXME: this should not be needed!
253        world.flush();
254
255        assert!(!world.entity(parent).contains::<ChildBundles<B, Vec<B>>>());
256        assert!(!world.entity(parent).contains::<B>());
257
258        let children = world.get::<Children>(parent).unwrap();
259        assert_eq!(children.len(), 3);
260
261        for (i, child_entity) in children.iter().enumerate() {
262            assert_eq!(world.get::<B>(*child_entity), Some(&B(i as u8)));
263        }
264    }
265
266    #[test]
267    fn with_child_closure() {
268        let mut world = World::default();
269
270        let parent = world.spawn(ChildBundles((0..7).map(|i| B(i as u8)))).id();
271        // FIXME: this should not be needed!
272        world.flush();
273
274        assert!(!world.entity(parent).contains::<ChildBundles<B, Vec<B>>>());
275        assert!(!world.entity(parent).contains::<B>());
276
277        let children = world.get::<Children>(parent).unwrap();
278        assert_eq!(children.len(), 7);
279
280        for (i, child_entity) in children.iter().enumerate() {
281            assert_eq!(world.get::<B>(*child_entity), Some(&B(i as u8)));
282        }
283    }
284
285    #[test]
286    fn with_distinct_children() {
287        let mut world = World::default();
288
289        let parent = world.spawn((ChildBundle(A), ChildBundle(B(1)))).id();
290        // FIXME: this should not be needed!
291        world.flush();
292
293        let children = world.get::<Children>(parent).unwrap();
294        assert_eq!(children.len(), 2);
295        assert_eq!(world.get::<A>(children[0]), Some(&A));
296        assert_eq!(world.get::<B>(children[1]), Some(&B(1)));
297
298        // Ordering should matter
299        let parent = world.spawn((ChildBundle(B(1)), ChildBundle(A))).id();
300        // FIXME: this should not be needed!
301        world.flush();
302
303        let children = world.get::<Children>(parent).unwrap();
304        assert_eq!(children.len(), 2);
305        assert_eq!(world.get::<B>(children[0]), Some(&B(1)));
306        assert_eq!(world.get::<A>(children[1]), Some(&A));
307    }
308
309    #[test]
310    fn grandchildren() {
311        let mut world = World::default();
312
313        let parent = world.spawn(ChildBundle((A, ChildBundle((A, B(3)))))).id();
314        // FIXME: this should not be needed!
315        world.flush();
316
317        let children = world.get::<Children>(parent).unwrap();
318        assert_eq!(children.len(), 1);
319
320        let child_entity = children[0];
321        assert_eq!(world.get::<A>(child_entity), Some(&A));
322
323        let grandchildren = world.get::<Children>(child_entity).unwrap();
324        assert_eq!(grandchildren.len(), 1);
325
326        let grandchild_entity = grandchildren[0];
327        assert_eq!(world.get::<A>(grandchild_entity), Some(&A));
328        assert_eq!(world.get::<B>(grandchild_entity), Some(&B(3)));
329    }
330
331    #[test]
332    fn hierarchical_bundle() {
333        let mut world = World::default();
334
335        let parent = world
336            .spawn(HierarchicalBundle {
337                a: A,
338                child: ChildBundle(ABBundle { a: A, b: B(17) }),
339            })
340            .id();
341
342        // FIXME: this should not be needed!
343        world.flush();
344
345        assert!(!world.entity(parent).contains::<ChildBundle<ABBundle>>());
346        assert!(world.entity(parent).contains::<A>());
347        assert!(!world.entity(parent).contains::<B>());
348
349        let children = world.get::<Children>(parent).unwrap();
350        assert_eq!(children.len(), 1);
351
352        let child_entity = children[0];
353        assert_eq!(world.get::<A>(child_entity), Some(&A));
354        assert_eq!(world.get::<B>(child_entity), Some(&B(17)));
355    }
356
357    #[test]
358    fn command_form() {
359        fn spawn_with_child(mut commands: Commands) -> Entity {
360            commands.spawn((A, ChildBundle(B(5)))).id()
361        }
362
363        let mut world = World::new();
364        let parent = world.run_system_once(spawn_with_child).unwrap();
365
366        assert!(!world.entity(parent).contains::<ChildBundle<B>>());
367        assert!(world.entity(parent).contains::<A>());
368        assert!(!world.entity(parent).contains::<B>());
369
370        let children = world.get::<Children>(parent).unwrap();
371        assert_eq!(children.len(), 1);
372
373        let child_entity = children[0];
374        assert_eq!(world.get::<B>(child_entity), Some(&B(5)));
375    }
376}