recompose_core/
spawn.rs

1use crate::{
2    ChildIndex, ChildOrder, Compose, Root, Scope, SetState,
3    modify::{Modifier, Modify},
4    scope::ScopeId,
5};
6use bevy_ecs::{
7    bundle::Bundle,
8    component::Component,
9    entity::Entity,
10    system::{Commands, Query},
11};
12use bevy_hierarchy::{BuildChildren, DespawnRecursiveExt};
13use std::{collections::BTreeMap, sync::Arc};
14
15/// A composable that takes in a bundle and spawns an entity with the bundle. When the composable is recomposed, the
16/// bundle, children and observers are updated. When the composable is "decomposed", the entity is despawned from the
17/// world.
18#[derive(Clone)]
19pub struct Spawn<B: Bundle + Clone> {
20    pub(crate) bundle_generator: Arc<dyn (Fn() -> B) + Send + Sync>,
21    pub(crate) modifier: Modifier,
22}
23
24impl<B: Bundle + Clone> Spawn<B> {
25    /// Creates a new spawn with the given bundle.
26    pub fn new(bundle: B) -> Self {
27        Self {
28            bundle_generator: Arc::new(move || bundle.clone()),
29            modifier: Modifier::default(),
30        }
31    }
32}
33
34impl<B: Bundle + Clone> Modify for Spawn<B> {
35    fn modifier(&mut self) -> &mut Modifier {
36        &mut self.modifier
37    }
38}
39
40impl<B: Bundle + Clone> Compose for Spawn<B> {
41    fn compose<'a>(&self, cx: &mut Scope) -> impl Compose + 'a {
42        let entity = cx.use_state(None);
43        let should_update = cx.use_state(true);
44        let bundle_updater = cx.use_state::<Box<
45            dyn Fn(Entity, ChildIndex, &mut Commands, &mut SetState) + Send + Sync,
46        >>(Box::new(
47            |_: Entity, _: ChildIndex, _: &mut Commands, _: &mut SetState| {},
48        ));
49        let temporary_observers = cx.use_state(Vec::new());
50
51        if let Some(entity) = *entity {
52            cx.set_entity(entity);
53        };
54
55        let retained_observer_generators = self.modifier.retained_observers.clone();
56        let scope_id = cx.id;
57
58        cx.use_system_once(move |mut state: SetState, mut commands: Commands| {
59            let mut ec = commands.spawn(SpawnComposable(scope_id));
60
61            retained_observer_generators.iter().for_each(|generator| {
62                generator.generate(&mut ec);
63            });
64
65            state.set(&entity, Some(ec.id()));
66        });
67
68        let generator = self.bundle_generator.clone();
69        let temporary_observer_generators = self.modifier.temporary_observers.clone();
70        let temporary_observer_entities = temporary_observers.clone();
71        let conditional_bundles = self.modifier.bundle_modifiers.clone();
72        let parent_entity = cx.parent_entity;
73        // In order to make the Spawn-composable more efficient, we're doing some trickery to avoid using `run_system`,
74        // which proved itself to be very slow.
75        //
76        // We define the function that actualy updates the components of an entity as a state that we then read in a
77        // separate system that runs right after the `recompose` system. This way we can avoid using non-cached systems,
78        // as well as avoiding running certain things multiple times (like figuring out the scope parents for each
79        // composable separately).
80        cx.set_state_unchanged(&should_update, true);
81        cx.set_state_unchanged(
82            &bundle_updater,
83            Box::new(
84                move |entity: Entity,
85                      child_index: ChildIndex,
86                      commands: &mut Commands,
87                      state: &mut SetState| {
88                    for observer_entity in temporary_observer_entities.iter() {
89                        let Some(observer_ec) = commands.get_entity(*observer_entity) else {
90                            continue;
91                        };
92
93                        observer_ec.try_despawn_recursive();
94                    }
95
96                    let bundle = generator();
97                    let mut ec = commands.entity(entity);
98
99                    for conditional_bundle in conditional_bundles.iter() {
100                        conditional_bundle(&mut ec);
101                    }
102
103                    ec.try_insert((bundle, ChildOrder(child_index)))
104                        .set_parent(parent_entity);
105
106                    let observer_entities = temporary_observer_generators
107                        .iter()
108                        .map(|generator| generator.generate(&mut ec))
109                        .collect::<Vec<_>>();
110
111                    state.set_unchanged(&temporary_observers, observer_entities);
112                },
113            ),
114        );
115
116        self.modifier.children.clone()
117    }
118
119    fn decompose(&self, cx: &mut Scope) {
120        let entity = cx.get_state_by_index::<Option<Entity>>(0);
121
122        if let Some(entity) = *entity {
123            cx.use_system_once(move |mut commands: Commands| {
124                let Some(ec) = commands.get_entity(entity) else {
125                    return;
126                };
127                ec.try_despawn_recursive();
128            });
129        }
130    }
131
132    fn name(&self) -> String {
133        String::from("Spawn")
134    }
135}
136
137#[derive(Component, Debug)]
138pub struct SpawnComposable(ScopeId);
139
140pub fn update_spawn_composables(
141    mut commands: Commands,
142    mut state: SetState,
143    mut roots: Query<&mut Root>,
144    spawn_composables: Query<(Entity, &SpawnComposable)>,
145) {
146    let spawn_composable_lookup = spawn_composables
147        .iter()
148        .map(|sc| (sc.1.0, sc.0))
149        .collect::<BTreeMap<_, _>>();
150
151    // It would make more sense to iterate over `spawn_composables`, but it is easier to just itarate over the roots to
152    // avoid having to deal with the borrow checker rules.
153    for mut root in roots.iter_mut() {
154        let Some(scope) = &mut root.scope else {
155            continue;
156        };
157
158        let mut scopes = Vec::from([scope]);
159
160        while let Some(scope) = scopes.pop() {
161            let spawn_composable = spawn_composable_lookup.get(&scope.id);
162
163            if let Some(entity) = spawn_composable {
164                let should_update = scope.get_state_by_index::<bool>(1);
165
166                if *should_update {
167                    let bundle_updater = scope.get_state_by_index::<Box<
168                        dyn Fn(Entity, ChildIndex, &mut Commands, &mut SetState) + Send + Sync,
169                    >>(2);
170
171                    bundle_updater(
172                        *entity,
173                        // Technically, we could just get the child_index inside the scope, but we would need to clone
174                        // twice, as opposed to just once here.
175                        scope.child_index.clone(),
176                        &mut commands,
177                        &mut state,
178                    );
179
180                    scope.set_state_unchanged(&should_update, false);
181                }
182            }
183
184            scopes.extend(scope.children.iter_mut());
185        }
186    }
187}