Skip to main content

bevy_trait_query/one/impls/
one_added.rs

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