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