Skip to content

Commit e071c3d

Browse files
committed
feat(one): add QueryFilter struct WithOne
Authored-by: RobWalt <[email protected]>
1 parent 516543c commit e071c3d

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

src/one.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,3 +759,117 @@ impl<Trait: ?Sized + TraitQuery> QueryFilter for OneChanged<Trait> {
759759
<Self as WorldQuery>::fetch(fetch, entity, table_row)
760760
}
761761
}
762+
763+
/// [`WorldQuery`] filter for entities with exactly [one](crate::One) component
764+
/// implementing a trait.
765+
pub struct WithOne<Trait: ?Sized + TraitQuery>(PhantomData<&'static Trait>);
766+
767+
// this takes inspiration from `With` in bevy's main repo
768+
unsafe impl<Trait: ?Sized + TraitQuery> WorldQuery for WithOne<Trait> {
769+
type Item<'w> = ();
770+
type Fetch<'w> = ();
771+
type State = TraitQueryState<Trait>;
772+
773+
#[inline]
774+
fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> {
775+
item
776+
}
777+
778+
#[inline]
779+
unsafe fn init_fetch<'w>(
780+
_world: UnsafeWorldCell<'w>,
781+
_state: &Self::State,
782+
_last_run: Tick,
783+
_this_run: Tick,
784+
) {
785+
}
786+
787+
const IS_DENSE: bool = false;
788+
// const IS_ARCHETYPAL: bool = false;
789+
790+
#[inline]
791+
unsafe fn set_archetype<'w>(
792+
_fetch: &mut (),
793+
_state: &Self::State,
794+
_archetype: &'w bevy_ecs::archetype::Archetype,
795+
_table: &'w bevy_ecs::storage::Table,
796+
) {
797+
}
798+
799+
#[inline]
800+
unsafe fn set_table<'w>(
801+
_fetch: &mut (),
802+
_state: &Self::State,
803+
_table: &'w bevy_ecs::storage::Table,
804+
) {
805+
}
806+
807+
#[inline]
808+
unsafe fn fetch<'w>(
809+
_fetch: &mut Self::Fetch<'w>,
810+
_entity: Entity,
811+
_table_row: TableRow,
812+
) -> Self::Item<'w> {
813+
}
814+
815+
#[inline]
816+
fn update_component_access(
817+
state: &Self::State,
818+
access: &mut bevy_ecs::query::FilteredAccess<ComponentId>,
819+
) {
820+
let mut new_access = access.clone();
821+
let mut not_first = false;
822+
for &component in &*state.components {
823+
assert!(
824+
!access.access().has_write(component),
825+
"&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
826+
std::any::type_name::<Trait>(),
827+
);
828+
if not_first {
829+
let mut intermediate = access.clone();
830+
intermediate.add_read(component);
831+
new_access.append_or(&intermediate);
832+
new_access.extend_access(&intermediate);
833+
} else {
834+
new_access.and_with(component);
835+
new_access.access_mut().add_read(component);
836+
not_first = true;
837+
}
838+
}
839+
*access = new_access;
840+
}
841+
842+
#[inline]
843+
fn init_state(world: &mut World) -> Self::State {
844+
TraitQueryState::init(world)
845+
}
846+
847+
#[inline]
848+
fn get_state(world: &World) -> Option<Self::State> {
849+
TraitQueryState::get(world)
850+
}
851+
852+
#[inline]
853+
fn matches_component_set(
854+
state: &Self::State,
855+
set_contains_id: &impl Fn(ComponentId) -> bool,
856+
) -> bool {
857+
state.matches_component_set_one(set_contains_id)
858+
}
859+
}
860+
861+
/// 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> {
867+
const IS_ARCHETYPAL: bool = false;
868+
unsafe fn filter_fetch(
869+
_fetch: &mut Self::Fetch<'_>,
870+
_entity: Entity,
871+
_table_row: TableRow,
872+
) -> bool {
873+
true
874+
}
875+
}

src/tests.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,48 @@ fn print_one_changed_filter_info(
494494
output.0.push(Default::default());
495495
}
496496

497+
#[test]
498+
fn with_one_filter() {
499+
let mut world = World::new();
500+
world.init_resource::<Output>();
501+
world
502+
.register_component_as::<dyn Person, Human>()
503+
.register_component_as::<dyn Person, Dolphin>();
504+
505+
world.spawn(Human("Henry".to_owned(), 22));
506+
world.spawn((Human("Henry".to_owned(), 22), Dolphin(22)));
507+
world.spawn(Dolphin(22));
508+
509+
let mut schedule = Schedule::default();
510+
schedule.add_systems(print_with_one_filter_info);
511+
512+
schedule.run(&mut world);
513+
514+
assert_eq!(
515+
world.resource::<Output>().0,
516+
&[
517+
"People that are either Human or Dolphin but not both:",
518+
"0v1",
519+
"2v1",
520+
"",
521+
]
522+
);
523+
}
524+
525+
// Prints the entity id of every `Person` with exactly one component implementing the trait
526+
fn print_with_one_filter_info(
527+
people: Query<Entity, WithOne<dyn Person>>,
528+
mut output: ResMut<Output>,
529+
) {
530+
output
531+
.0
532+
.push("People that are either Human or Dolphin but not both:".to_string());
533+
for person in (&people).into_iter() {
534+
output.0.push(format!("{person:?}"));
535+
}
536+
output.0.push(Default::default());
537+
}
538+
497539
#[queryable]
498540
pub trait Messages {
499541
fn send(&mut self, _: &dyn Display);

0 commit comments

Comments
 (0)