bevy_trait_query/one/impls/
one_changed.rs

1use bevy_ecs::ptr::UnsafeCellDeref;
2use std::marker::PhantomData;
3
4use bevy_ecs::{
5    archetype::Archetype,
6    component::{ComponentId, Components, Tick},
7    prelude::{Entity, World},
8    query::{FilteredAccess, QueryData, QueryFilter, ReadOnlyQueryData, WorldQuery},
9    storage::{Table, TableRow},
10    world::unsafe_world_cell::UnsafeWorldCell,
11};
12
13use crate::{TraitQuery, TraitQueryState, debug_unreachable};
14
15use crate::{ChangeDetectionFetch, ChangeDetectionStorage};
16
17/// [`WorldQuery`] filter for entities with exactly [one](crate::One) component
18/// implementing a trait, which was added since the last time the system ran.
19pub struct OneChanged<Trait: ?Sized + TraitQuery> {
20    marker: PhantomData<&'static Trait>,
21}
22
23unsafe impl<Trait: ?Sized + TraitQuery> QueryData for OneChanged<Trait> {
24    type ReadOnly = Self;
25
26    /// SAFETY: read-only access
27    const IS_READ_ONLY: bool = true;
28
29    type Item<'w, 's> = bool;
30
31    fn shrink<'wlong: 'wshort, 'wshort, 's>(
32        item: Self::Item<'wlong, 's>,
33    ) -> Self::Item<'wshort, 's> {
34        item
35    }
36
37    #[inline(always)]
38    unsafe fn fetch<'w, 's>(
39        _state: &'s Self::State,
40        fetch: &mut Self::Fetch<'w>,
41        entity: Entity,
42        table_row: TableRow,
43    ) -> Self::Item<'w, 's> {
44        unsafe {
45            let ticks_ptr = match fetch.storage {
46                ChangeDetectionStorage::Uninit => {
47                    // set_archetype must have been called already
48                    debug_unreachable()
49                }
50                ChangeDetectionStorage::Table { ticks } => ticks.get(table_row.index()),
51                ChangeDetectionStorage::SparseSet { components } => components
52                    .get_changed_tick(entity)
53                    .unwrap_or_else(|| debug_unreachable()),
54            };
55
56            (*ticks_ptr)
57                .deref()
58                .is_newer_than(fetch.last_run, fetch.this_run)
59        }
60    }
61}
62
63unsafe impl<Trait: ?Sized + TraitQuery> WorldQuery for OneChanged<Trait> {
64    type Fetch<'w> = ChangeDetectionFetch<'w>;
65    type State = TraitQueryState<Trait>;
66
67    unsafe fn init_fetch<'w>(
68        world: UnsafeWorldCell<'w>,
69        _state: &Self::State,
70        last_run: Tick,
71        this_run: Tick,
72    ) -> Self::Fetch<'w> {
73        unsafe {
74            Self::Fetch::<'w> {
75                storage: ChangeDetectionStorage::Uninit,
76                sparse_sets: &world.storages().sparse_sets,
77                last_run,
78                this_run,
79            }
80        }
81    }
82
83    // This will always be false for us, as we (so far) do not know at compile time whether the
84    // components our trait has been impl'd for are stored in table or in sparse set
85    const IS_DENSE: bool = false;
86
87    #[inline]
88    unsafe fn set_archetype<'w>(
89        fetch: &mut Self::Fetch<'w>,
90        state: &Self::State,
91        _archetype: &'w Archetype,
92        table: &'w Table,
93    ) {
94        unsafe {
95            // Search for a registered trait impl that is present in the archetype.
96            // We check the table components first since it is faster to retrieve data of this type.
97            for &component in &*state.components {
98                if let Some(changed) = table.get_changed_ticks_slice_for(component) {
99                    fetch.storage = ChangeDetectionStorage::Table {
100                        ticks: changed.into(),
101                    };
102                    return;
103                }
104            }
105            for &component in &*state.components {
106                if let Some(components) = fetch.sparse_sets.get(component) {
107                    fetch.storage = ChangeDetectionStorage::SparseSet { components };
108                    return;
109                }
110            }
111            // At least one of the components must be present in the table/sparse set.
112            debug_unreachable()
113        }
114    }
115
116    #[inline]
117    unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
118        unsafe {
119            // only gets called if IS_DENSE == true, which does not hold for us
120            debug_unreachable()
121        }
122    }
123
124    #[inline]
125    fn update_component_access(state: &Self::State, access: &mut FilteredAccess) {
126        let mut new_access = access.clone();
127        let mut not_first = false;
128        for &component in &*state.components {
129            assert!(
130                !access.access().has_component_write(component),
131                "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
132                std::any::type_name::<Trait>(),
133            );
134            if not_first {
135                let mut intermediate = access.clone();
136                intermediate.add_component_read(component);
137                new_access.append_or(&intermediate);
138                new_access.extend_access(&intermediate);
139            } else {
140                new_access.and_with(component);
141                new_access.access_mut().add_component_read(component);
142                not_first = true;
143            }
144        }
145        *access = new_access;
146    }
147
148    #[inline]
149    fn init_state(world: &mut World) -> Self::State {
150        TraitQueryState::init(world)
151    }
152
153    #[inline]
154    fn get_state(_: &Components) -> Option<Self::State> {
155        // TODO: fix this https://github.com/bevyengine/bevy/issues/13798
156        panic!(
157            "transmuting and any other operations concerning the state of a query are currently broken and shouldn't be used. See https://github.com/JoJoJet/bevy-trait-query/issues/59"
158        );
159    }
160
161    fn matches_component_set(
162        state: &Self::State,
163        set_contains_id: &impl Fn(ComponentId) -> bool,
164    ) -> bool {
165        state.matches_component_set_one(set_contains_id)
166    }
167
168    #[inline]
169    fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
170        fetch
171    }
172}
173
174unsafe impl<Trait: ?Sized + TraitQuery> ReadOnlyQueryData for OneChanged<Trait> {}
175unsafe impl<Trait: ?Sized + TraitQuery> QueryFilter for OneChanged<Trait> {
176    const IS_ARCHETYPAL: bool = false;
177    unsafe fn filter_fetch(
178        state: &Self::State,
179        fetch: &mut Self::Fetch<'_>,
180        entity: Entity,
181        table_row: TableRow,
182    ) -> bool {
183        unsafe { <Self as QueryData>::fetch(state, fetch, entity, table_row) }
184    }
185}