recompose_core/
scope.rs

1use crate::{
2    state::{Dependency, DynState, GetStateId, State, StateId, TypedStateId},
3    unique_id, AnyCompose, ChildIndex, StateChanged,
4};
5use bevy_ecs::{
6    entity::Entity,
7    system::{BoxedSystem, IntoSystem},
8};
9use std::{
10    any::Any,
11    fmt::{Debug, Display},
12    sync::Arc,
13};
14
15#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
16pub struct ScopeId(usize);
17
18/// A scope can be thought of as a "sum" of all modifications done by the [`compose`](crate::Compose::compose) function
19/// of the [`Compose`](crate::Compose) trait. It holds the state of the composable and its children scopes. It is the
20/// "actual" node in the tree-structure of the composables.
21pub struct Scope<'a> {
22    pub(crate) id: ScopeId,
23
24    /// Indicates the index of the scope when it was "recomposed" relative to the scopes parent scope. It is not
25    /// necessarily the same as the index in the parent's children vector.
26    pub(crate) index: usize,
27
28    /// The accumulated index of the scope relative to the closest relative with an entity.
29    pub(crate) child_index: ChildIndex,
30
31    /// For composables that spawn an entity, this is the field that holds the rerefence to the entity.
32    pub(crate) entity: Option<Entity>,
33
34    /// The entity of the scopes most immediate parent with an entity.
35    pub(crate) parent_entity: Entity,
36
37    /// Indicates if the scope will decompose on before the next recomposition.
38    pub(crate) will_decompose: bool,
39
40    /// A copy of the composer that created this scope. It is used to recompose this scope when one of the states was
41    /// changed, but the parent scope was recomposed.
42    pub(crate) composer: Arc<dyn AnyCompose + 'a>,
43
44    /// The index counter of the states when running the `compose` function. It is used to keep track of the states.
45    pub(crate) state_index: usize,
46
47    /// The states of the composable.
48    pub(crate) states: Vec<DynState>,
49
50    /// The children of the composable.
51    pub(crate) children: Vec<Scope<'a>>,
52
53    /// The "collected" systems after the `compose`-function was executed. The systems are run and discarded after the
54    /// recomposition.
55    pub(crate) queued_systems: Vec<BoxedSystem<(), ()>>,
56}
57
58impl Debug for Scope<'_> {
59    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
60        fmt_scope_with_indents(self, f, 0)
61    }
62}
63
64fn fmt_scope_with_indents(
65    scope: &Scope,
66    f: &mut std::fmt::Formatter,
67    level: usize,
68) -> std::fmt::Result {
69    let indents = "  ".repeat(level);
70    let name = scope.composer.get_name();
71
72    if scope.children.is_empty() {
73        return writeln!(f, "{}<{} id={{{}}}/>", indents, name, scope.id.0);
74    }
75
76    writeln!(f, "{}<{} id={{{}}}>", indents, name, scope.id.0)?;
77
78    for child in scope.children.iter() {
79        fmt_scope_with_indents(child, f, level + 1)?;
80    }
81
82    writeln!(f, "{}</{}>", indents, name)
83}
84
85impl Display for Scope<'_> {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        let name = self.composer.get_name();
88        write!(f, "Scope(name: {}, id: {})", name, self.id.0)
89    }
90}
91
92impl Scope<'_> {
93    pub(crate) fn new(
94        composer: Arc<dyn AnyCompose>,
95        index: usize,
96        parent_entity: Entity,
97        mut parent_child_index: ChildIndex,
98    ) -> Self {
99        parent_child_index.push(index);
100
101        Self {
102            id: ScopeId(unique_id()),
103            index,
104            child_index: parent_child_index,
105            entity: None,
106            parent_entity,
107            will_decompose: false,
108            composer: composer.clone(),
109            state_index: 0,
110            states: Vec::new(),
111            children: Vec::new(),
112            queued_systems: Vec::new(),
113        }
114    }
115
116    pub(crate) fn as_root_scope(entity: Entity, composer: Arc<dyn AnyCompose>) -> Self {
117        Self {
118            id: ScopeId(unique_id()),
119            index: 0,
120            child_index: ChildIndex::new(0),
121            entity: Some(entity),
122            parent_entity: entity,
123            will_decompose: false,
124            composer: composer.clone(),
125            state_index: 0,
126            states: Vec::new(),
127            children: Vec::new(),
128            queued_systems: Vec::new(),
129        }
130    }
131
132    /// Creates a new state. States are persisted between each recomposition of the composable. Each time a state
133    /// changes, the scope it belongs to is scheduled for recomposition.
134    pub fn use_state<T: Any + Send + Sync>(&mut self, initial_value: T) -> State<T> {
135        if let Some(existing_state) = self.states.get(self.state_index) {
136            self.state_index += 1;
137            return existing_state.to_state::<T>();
138        }
139
140        let value = Arc::new(initial_value);
141
142        let dyn_state = DynState {
143            id: StateId::Generated(unique_id()),
144            changed: StateChanged::Changed,
145            value: value.clone(),
146        };
147
148        let state = dyn_state.to_state();
149
150        self.states.push(dyn_state);
151        self.state_index += 1;
152
153        state
154    }
155
156    /// Creates a new state with a given id. It is useful for cases where you want to reference a state in an external
157    /// system or a different composable.
158    pub fn use_state_with_id<T: Any + Send + Sync>(
159        &mut self,
160        state_id: TypedStateId<T>,
161        initial_value: T,
162    ) -> State<T> {
163        if let Some(existing_state) = self.states.iter().find(|s| s.id == state_id.get_id()) {
164            self.state_index += 1;
165            return existing_state.to_state::<T>();
166        }
167
168        let value = Arc::new(initial_value);
169
170        let dyn_state = DynState {
171            id: state_id.get_id(),
172            changed: StateChanged::Changed,
173            value: value.clone(),
174        };
175
176        let state = dyn_state.to_state();
177
178        self.states.push(dyn_state);
179        self.state_index += 1;
180
181        state
182    }
183
184    /// Sets the value of the given state. The change happens immediately.
185    pub fn set_state<T: Send + Sync + 'static>(&mut self, state: impl GetStateId<T>, value: T) {
186        let state = self
187            .states
188            .iter_mut()
189            .find(|s| s.id == state.get_id())
190            .unwrap_or_else(|| panic!("State not found."));
191
192        if !state.value.is::<T>() {
193            panic!("State value type mismatch.");
194        }
195
196        state.value = Arc::new(value);
197        state.changed = StateChanged::Queued;
198    }
199
200    /// Sets the value of the given state without triggering a recomposition. The change happens immediately.
201    pub fn set_state_unchanged<T: Send + Sync + 'static>(
202        &mut self,
203        state: impl GetStateId<T>,
204        value: T,
205    ) {
206        let state = self
207            .states
208            .iter_mut()
209            .find(|s| s.id == state.get_id())
210            .unwrap_or_else(|| panic!("State not found."));
211
212        if !state.value.is::<T>() {
213            panic!("State value type mismatch.");
214        }
215
216        state.value = Arc::new(value);
217    }
218
219    pub(crate) fn get_state_by_index<T: Any + Send + Sync>(&self, index: usize) -> State<T> {
220        let dyn_state = self
221            .states
222            .get(index)
223            .unwrap_or_else(|| panic!("State not found."));
224
225        dyn_state.to_state()
226    }
227
228    /// A callback that is run only if the dependencies have changed.
229    pub fn effect(&mut self, effect: impl Fn(), dependecies: impl Dependency) {
230        if !dependecies.has_changed() {
231            return;
232        }
233
234        effect();
235    }
236
237    /// Runs a callback when the component is first composed.
238    pub fn use_mount(&mut self, callback: impl Fn()) {
239        let once = self.use_state(());
240        self.effect(callback, once);
241    }
242
243    /// Runs a system. The system is not cached and is "rebuilt" every time the composable recomposes. It is therefore
244    /// not the most efficient way to to interact with the ECS world.
245    pub fn run_system<M>(&mut self, system: impl IntoSystem<(), (), M>) {
246        let sys: BoxedSystem<(), ()> = Box::from(IntoSystem::into_system(system));
247        self.queued_systems.push(sys);
248    }
249
250    /// Runs a system when the composable is first composed.
251    pub fn use_system_once<M>(&mut self, system: impl IntoSystem<(), (), M>) {
252        let once = self.use_state(());
253
254        if matches!(once.changed, StateChanged::Changed) {
255            self.run_system(system);
256        }
257    }
258
259    pub(crate) fn set_entity(&mut self, entity: Entity) {
260        self.entity = Some(entity);
261    }
262}