diff --git a/src/one.rs b/src/one.rs index 1c4c240..06ca4ad 100644 --- a/src/one.rs +++ b/src/one.rs @@ -789,7 +789,6 @@ unsafe impl WorldQuery for WithOne { } const IS_DENSE: bool = false; - // const IS_ARCHETYPAL: bool = false; #[inline] unsafe fn set_archetype<'w>( @@ -817,25 +816,105 @@ unsafe impl WorldQuery for WithOne { access: &mut bevy_ecs::query::FilteredAccess, ) { let mut new_access = access.clone(); - let mut not_first = false; + for &component in state.components.iter() { + let mut intermediate = access.clone(); + intermediate.and_with(component); + new_access.append_or(&intermediate); + } + *access = new_access; + } + + #[inline] + fn init_state(world: &mut World) -> Self::State { + TraitQueryState::init(world) + } + + #[inline] + fn get_state(_: &Components) -> Option { + // TODO: fix this https://github.com/bevyengine/bevy/issues/13798 + panic!("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"); + } + + #[inline] + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + state.matches_component_set_one(set_contains_id) + } +} + +/// SAFETY: read-only access +impl QueryFilter for WithOne { + const IS_ARCHETYPAL: bool = false; + unsafe fn filter_fetch( + _fetch: &mut Self::Fetch<'_>, + _entity: Entity, + _table_row: TableRow, + ) -> bool { + true + } +} + +/// [`WorldQuery`] filter for entities without any [one](crate::One) component +/// implementing a trait. +pub struct WithoutAny(PhantomData<&'static Trait>); + +// this takes inspiration from `With` in bevy's main repo +unsafe impl WorldQuery for WithoutAny { + type Item<'w> = (); + type Fetch<'w> = (); + type State = TraitQueryState; + + #[inline] + fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { + item + } + + #[inline] + unsafe fn init_fetch( + _world: UnsafeWorldCell<'_>, + _state: &Self::State, + _last_run: Tick, + _this_run: Tick, + ) { + } + + const IS_DENSE: bool = false; + + #[inline] + unsafe fn set_archetype<'w>( + _fetch: &mut (), + _state: &Self::State, + _archetype: &'w bevy_ecs::archetype::Archetype, + _table: &'w bevy_ecs::storage::Table, + ) { + } + + #[inline] + unsafe fn set_table(_fetch: &mut (), _state: &Self::State, _table: &bevy_ecs::storage::Table) {} + + #[inline] + unsafe fn fetch<'w>( + _fetch: &mut Self::Fetch<'w>, + _entity: Entity, + _table_row: TableRow, + ) -> Self::Item<'w> { + } + + #[inline] + fn update_component_access( + state: &Self::State, + access: &mut bevy_ecs::query::FilteredAccess, + ) { for &component in &*state.components { assert!( !access.access().has_write(component), "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", std::any::type_name::(), ); - if not_first { - let mut intermediate = access.clone(); - intermediate.add_read(component); - new_access.append_or(&intermediate); - new_access.extend_access(&intermediate); - } else { - new_access.and_with(component); - new_access.access_mut().add_read(component); - not_first = true; - } + access.and_without(component); } - *access = new_access; } #[inline] @@ -854,16 +933,12 @@ unsafe impl WorldQuery for WithOne { state: &Self::State, set_contains_id: &impl Fn(ComponentId) -> bool, ) -> bool { - state.matches_component_set_one(set_contains_id) + !state.components.iter().any(|&id| set_contains_id(id)) } } /// SAFETY: read-only access -unsafe impl QueryData for WithOne { - type ReadOnly = Self; -} -unsafe impl ReadOnlyQueryData for WithOne {} -impl QueryFilter for WithOne { +impl QueryFilter for WithoutAny { const IS_ARCHETYPAL: bool = false; unsafe fn filter_fetch( _fetch: &mut Self::Fetch<'_>, diff --git a/src/tests.rs b/src/tests.rs index 0c007fc..00c1386 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -537,6 +537,44 @@ fn print_with_one_filter_info( output.0.push(Default::default()); } +#[test] +fn without_any_filter() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component_as::() + .register_component_as::(); + + world.spawn(Human("Henry".to_owned(), 22)); + world.spawn((Human("Henry".to_owned(), 22), Dolphin(22))); + world.spawn(Dolphin(22)); + world.spawn(Fem); + + let mut schedule = Schedule::default(); + schedule.add_systems(print_without_any_filter_info); + + schedule.run(&mut world); + + assert_eq!( + world.resource::().0, + &["People that are neither Human or Dolphin:", "3v1", "",] + ); +} + +// Prints the entity id of every Entity where none of its components implement the trait +fn print_without_any_filter_info( + people: Query>, + mut output: ResMut, +) { + output + .0 + .push("People that are neither Human or Dolphin:".to_string()); + for person in (&people).into_iter() { + output.0.push(format!("{person}")); + } + output.0.push(Default::default()); +} + #[queryable] pub trait Messages { fn send(&mut self, _: &dyn Display);