Skip to content

Commit 5df9eb3

Browse files
authored
Merge pull request #58 from RobWalt/feat/without-one
solves #50 feat: Implement `WithoutAny` filter This PR: 1. implements `WithoutAny`, a `QueryFilter` which is supposed to be the opposite of `WithOne` 2. cleans up some parts of `WithOne` to make it `QueryFilter` only Note that 2. is a breaking change since we can't use it in the Data position anymore. However, I think this is fine since there is `One` which already fills this gap. So this is really a fix since it clears up the separation of concerns of the two structs. --- Small pseudo code example: ```rust struct Food; trait Fruit {} struct Banana; impl Fruit for Banana {} struct Apple; impl Fruit for Apple {} struct Sweets; struct Cake; fn eat_unhealthy( mut commands: Commands, q_non_fruits: Query<Entity, (With<Food>, WithoutAny<&dyn Fruit>)> ) { q_non_fruits.iter().for_each(|food| { // only sweets and cakes without fruits commands.eat(food); }); } ```
2 parents 4328255 + bf3bd03 commit 5df9eb3

File tree

2 files changed

+132
-19
lines changed

2 files changed

+132
-19
lines changed

src/one.rs

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,6 @@ unsafe impl<Trait: ?Sized + TraitQuery> WorldQuery for WithOne<Trait> {
789789
}
790790

791791
const IS_DENSE: bool = false;
792-
// const IS_ARCHETYPAL: bool = false;
793792

794793
#[inline]
795794
unsafe fn set_archetype<'w>(
@@ -817,25 +816,105 @@ unsafe impl<Trait: ?Sized + TraitQuery> WorldQuery for WithOne<Trait> {
817816
access: &mut bevy_ecs::query::FilteredAccess<ComponentId>,
818817
) {
819818
let mut new_access = access.clone();
820-
let mut not_first = false;
819+
for &component in state.components.iter() {
820+
let mut intermediate = access.clone();
821+
intermediate.and_with(component);
822+
new_access.append_or(&intermediate);
823+
}
824+
*access = new_access;
825+
}
826+
827+
#[inline]
828+
fn init_state(world: &mut World) -> Self::State {
829+
TraitQueryState::init(world)
830+
}
831+
832+
#[inline]
833+
fn get_state(_: &Components) -> Option<Self::State> {
834+
// TODO: fix this https://github.com/bevyengine/bevy/issues/13798
835+
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");
836+
}
837+
838+
#[inline]
839+
fn matches_component_set(
840+
state: &Self::State,
841+
set_contains_id: &impl Fn(ComponentId) -> bool,
842+
) -> bool {
843+
state.matches_component_set_one(set_contains_id)
844+
}
845+
}
846+
847+
/// SAFETY: read-only access
848+
impl<Trait: ?Sized + TraitQuery> QueryFilter for WithOne<Trait> {
849+
const IS_ARCHETYPAL: bool = false;
850+
unsafe fn filter_fetch(
851+
_fetch: &mut Self::Fetch<'_>,
852+
_entity: Entity,
853+
_table_row: TableRow,
854+
) -> bool {
855+
true
856+
}
857+
}
858+
859+
/// [`WorldQuery`] filter for entities without any [one](crate::One) component
860+
/// implementing a trait.
861+
pub struct WithoutAny<Trait: ?Sized + TraitQuery>(PhantomData<&'static Trait>);
862+
863+
// this takes inspiration from `With` in bevy's main repo
864+
unsafe impl<Trait: ?Sized + TraitQuery> WorldQuery for WithoutAny<Trait> {
865+
type Item<'w> = ();
866+
type Fetch<'w> = ();
867+
type State = TraitQueryState<Trait>;
868+
869+
#[inline]
870+
fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> {
871+
item
872+
}
873+
874+
#[inline]
875+
unsafe fn init_fetch(
876+
_world: UnsafeWorldCell<'_>,
877+
_state: &Self::State,
878+
_last_run: Tick,
879+
_this_run: Tick,
880+
) {
881+
}
882+
883+
const IS_DENSE: bool = false;
884+
885+
#[inline]
886+
unsafe fn set_archetype<'w>(
887+
_fetch: &mut (),
888+
_state: &Self::State,
889+
_archetype: &'w bevy_ecs::archetype::Archetype,
890+
_table: &'w bevy_ecs::storage::Table,
891+
) {
892+
}
893+
894+
#[inline]
895+
unsafe fn set_table(_fetch: &mut (), _state: &Self::State, _table: &bevy_ecs::storage::Table) {}
896+
897+
#[inline]
898+
unsafe fn fetch<'w>(
899+
_fetch: &mut Self::Fetch<'w>,
900+
_entity: Entity,
901+
_table_row: TableRow,
902+
) -> Self::Item<'w> {
903+
}
904+
905+
#[inline]
906+
fn update_component_access(
907+
state: &Self::State,
908+
access: &mut bevy_ecs::query::FilteredAccess<ComponentId>,
909+
) {
821910
for &component in &*state.components {
822911
assert!(
823912
!access.access().has_write(component),
824913
"&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
825914
std::any::type_name::<Trait>(),
826915
);
827-
if not_first {
828-
let mut intermediate = access.clone();
829-
intermediate.add_read(component);
830-
new_access.append_or(&intermediate);
831-
new_access.extend_access(&intermediate);
832-
} else {
833-
new_access.and_with(component);
834-
new_access.access_mut().add_read(component);
835-
not_first = true;
836-
}
916+
access.and_without(component);
837917
}
838-
*access = new_access;
839918
}
840919

841920
#[inline]
@@ -854,16 +933,12 @@ unsafe impl<Trait: ?Sized + TraitQuery> WorldQuery for WithOne<Trait> {
854933
state: &Self::State,
855934
set_contains_id: &impl Fn(ComponentId) -> bool,
856935
) -> bool {
857-
state.matches_component_set_one(set_contains_id)
936+
!state.components.iter().any(|&id| set_contains_id(id))
858937
}
859938
}
860939

861940
/// SAFETY: read-only access
862-
unsafe impl<Trait: ?Sized + TraitQuery> QueryData for WithOne<Trait> {
863-
type ReadOnly = Self;
864-
}
865-
unsafe impl<Trait: ?Sized + TraitQuery> ReadOnlyQueryData for WithOne<Trait> {}
866-
impl<Trait: ?Sized + TraitQuery> QueryFilter for WithOne<Trait> {
941+
impl<Trait: ?Sized + TraitQuery> QueryFilter for WithoutAny<Trait> {
867942
const IS_ARCHETYPAL: bool = false;
868943
unsafe fn filter_fetch(
869944
_fetch: &mut Self::Fetch<'_>,

src/tests.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,44 @@ fn print_with_one_filter_info(
537537
output.0.push(Default::default());
538538
}
539539

540+
#[test]
541+
fn without_any_filter() {
542+
let mut world = World::new();
543+
world.init_resource::<Output>();
544+
world
545+
.register_component_as::<dyn Person, Human>()
546+
.register_component_as::<dyn Person, Dolphin>();
547+
548+
world.spawn(Human("Henry".to_owned(), 22));
549+
world.spawn((Human("Henry".to_owned(), 22), Dolphin(22)));
550+
world.spawn(Dolphin(22));
551+
world.spawn(Fem);
552+
553+
let mut schedule = Schedule::default();
554+
schedule.add_systems(print_without_any_filter_info);
555+
556+
schedule.run(&mut world);
557+
558+
assert_eq!(
559+
world.resource::<Output>().0,
560+
&["People that are neither Human or Dolphin:", "3v1", "",]
561+
);
562+
}
563+
564+
// Prints the entity id of every Entity where none of its components implement the trait
565+
fn print_without_any_filter_info(
566+
people: Query<Entity, WithoutAny<dyn Person>>,
567+
mut output: ResMut<Output>,
568+
) {
569+
output
570+
.0
571+
.push("People that are neither Human or Dolphin:".to_string());
572+
for person in (&people).into_iter() {
573+
output.0.push(format!("{person}"));
574+
}
575+
output.0.push(Default::default());
576+
}
577+
540578
#[queryable]
541579
pub trait Messages {
542580
fn send(&mut self, _: &dyn Display);

0 commit comments

Comments
 (0)