Skip to main content

lightyear_utils/
ecs.rs

1//! Helpers for low-level ECS operations.
2
3use bevy_ecs::archetype::ArchetypeEntity;
4use bevy_ecs::component::{ComponentId, StorageType};
5use bevy_ecs::ptr::{Ptr, PtrMut};
6use bevy_ecs::query::{IterQueryData, QueryFilter, QueryItem};
7use bevy_ecs::storage::TableId;
8use bevy_ecs::system::Query;
9use bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell;
10
11/// Iterates a mutable query serially when it contains at most a small number of items, and in
12/// parallel otherwise.
13///
14/// The default serial threshold is one item:
15///
16/// ```ignore
17/// adaptive_for_each_mut!(query, |item| process(item));
18/// ```
19///
20/// An optional threshold keeps queries with at most that many items serial. Parallel iteration is
21/// used only when the query contains more items than the threshold:
22///
23/// ```ignore
24/// adaptive_for_each_mut!(query, 4, |item| process(item));
25/// ```
26///
27/// The adapter inspects only enough read-only query items to choose a mode, then performs the
28/// mutable iteration.
29#[macro_export]
30macro_rules! adaptive_for_each_mut {
31    ($query:expr, |$item:pat_param| $body:expr $(,)?) => {
32        $crate::ecs::AdaptiveQueryIterMut::new(&mut $query, 1).for_each(|$item| $body)
33    };
34    ($query:expr, $serial_threshold:expr, |$item:pat_param| $body:expr $(,)?) => {
35        $crate::ecs::AdaptiveQueryIterMut::new(&mut $query, $serial_threshold)
36            .for_each(|$item| $body)
37    };
38    // Adapter form for composing with an existing `.for_each` call.
39    ($query:expr $(,)?) => {
40        $crate::ecs::AdaptiveQueryIterMut::new(&mut $query, 1)
41    };
42}
43
44/// Mutable query adapter returned by [`adaptive_for_each_mut!`].
45///
46/// Call [`for_each`](Self::for_each) to select serial or parallel iteration based on the number of
47/// matching query items.
48#[doc(hidden)]
49pub struct AdaptiveQueryIterMut<'query, 'world, 'state, D, F>
50where
51    D: IterQueryData,
52    F: QueryFilter,
53{
54    query: &'query mut Query<'world, 'state, D, F>,
55    serial_threshold: usize,
56}
57
58impl<'query, 'world, 'state, D, F> AdaptiveQueryIterMut<'query, 'world, 'state, D, F>
59where
60    D: IterQueryData,
61    F: QueryFilter,
62{
63    #[doc(hidden)]
64    pub fn new(query: &'query mut Query<'world, 'state, D, F>, serial_threshold: usize) -> Self {
65        Self {
66            query,
67            serial_threshold,
68        }
69    }
70
71    /// Applies `func` to every matching item.
72    pub fn for_each<Func>(self, func: Func)
73    where
74        Func: for<'item> Fn(QueryItem<'item, 'state, D>) + Send + Sync + Clone,
75    {
76        if self.query.iter().nth(self.serial_threshold).is_some() {
77            self.query.par_iter_mut().for_each(func);
78        } else {
79            self.query.iter_mut().for_each(func);
80        }
81    }
82}
83
84/// Extracts a component as [`Ptr`] and its ticks from a table or sparse set, depending on its storage type.
85///
86/// # Safety
87///
88/// The component must be present in this archetype, have the specified storage type and we must have write access to it.
89pub unsafe fn get_component_unchecked_mut<'w>(
90    unsafe_world_cell: UnsafeWorldCell<'w>,
91    entity: &'w ArchetypeEntity,
92    table_id: TableId,
93    storage: StorageType,
94    component_id: ComponentId,
95) -> PtrMut<'w> {
96    let storages = unsafe { unsafe_world_cell.storages() };
97    match storage {
98        // SAFETY: we know from the accesses that we have unique write access to these components
99        StorageType::Table => unsafe {
100            let table = storages.tables.get(table_id).unwrap_unchecked();
101            table
102                .get_component(component_id, entity.table_row())
103                .unwrap_unchecked()
104                .assert_unique()
105        },
106        StorageType::SparseSet => unsafe {
107            let sparse_set = storages.sparse_sets.get(component_id).unwrap_unchecked();
108            sparse_set
109                .get(entity.id())
110                .unwrap_unchecked()
111                .assert_unique()
112        },
113    }
114}
115
116/// Extracts a component as [`Ptr`] and its ticks from a table or sparse set, depending on its storage type.
117///
118/// # Safety
119///
120/// The component must be present in this archetype, have the specified storage type and we must have read access to it.
121pub unsafe fn get_component_unchecked<'w>(
122    unsafe_world_cell: UnsafeWorldCell<'w>,
123    entity: &'w ArchetypeEntity,
124    table_id: TableId,
125    storage: StorageType,
126    component_id: ComponentId,
127) -> Ptr<'w> {
128    let storages = unsafe { unsafe_world_cell.storages() };
129    match storage {
130        // SAFETY: we know from the accesses that we have unique write access to these components
131        StorageType::Table => unsafe {
132            let table = storages.tables.get(table_id).unwrap_unchecked();
133            table
134                .get_component(component_id, entity.table_row())
135                .unwrap_unchecked()
136        },
137        StorageType::SparseSet => unsafe {
138            let sparse_set = storages.sparse_sets.get(component_id).unwrap_unchecked();
139            sparse_set.get(entity.id()).unwrap_unchecked()
140        },
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use bevy_app::{App, TaskPoolPlugin};
147    use bevy_ecs::prelude::*;
148
149    #[derive(Component)]
150    struct Value(u32);
151
152    #[test]
153    fn adaptive_iteration_supports_default_and_custom_thresholds() {
154        let mut app = App::new();
155        app.add_plugins(TaskPoolPlugin::default());
156        let world = app.world_mut();
157        let first = world.spawn(Value(0)).id();
158        let mut query_state = world.query::<&mut Value>();
159
160        {
161            let mut query = query_state.query_mut(world);
162            crate::adaptive_for_each_mut!(query, |mut value| value.0 += 1);
163        }
164
165        let second = world.spawn(Value(0)).id();
166        {
167            let mut query = query_state.query_mut(world);
168            crate::adaptive_for_each_mut!(query, 2, |mut value| value.0 += 1);
169        }
170        {
171            let mut query = query_state.query_mut(world);
172            crate::adaptive_for_each_mut!(query, |mut value| value.0 += 1);
173        }
174
175        assert_eq!(world.get::<Value>(first).unwrap().0, 3);
176        assert_eq!(world.get::<Value>(second).unwrap().0, 2);
177    }
178}