Skip to content

Restore separate methods for World::get_many_entities #18234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

chescock
Copy link
Contributor

Objective

Reduce the complexity of the World::get_entity API by removing the F: WorldEntityFetch type parameter and restoring separate World::get_many_entities methods.

It would be good for consistency to have Query::get and World::get_entity work the same way, but per discussion on #18036 (comment), WorldEntityFetch was too much complexity for Query::get. Instead, let's remove WorldEntityFetch and make World::get_entity work like Query::get.

This undoes #15614 (although I did not attempt to implement it as a revert). A few things have changed since then that makes the duplication less of a problem than it was when WorldEntityFetch was introduced:

One change is that Bevy has improved handling for Results, so we no longer expect to offer panicking versions of all APIs, and we do not need to restore the panicking many_entities and many_entities_mut.

Another change is the introduction of EntitySet and related traits in #16547. This lets us generalize over types that hold sets of unique Entitys, so we do not need a separate method for every such type.

Solution

Remove F: WorldEntityFetch parameter from World::get_entity() and related methods, and have it only accept Entity.

Introduce new methods to handle the various "many" cases:

  • get_many_entities takes an array and returns an array of EntityRef. These are read-only, so duplicates are okay.
  • iter_entities takes an iterator and returns an iterator of EntityRef. These are read-only, so duplicates are okay.
  • get_many_entities_mut takes an array and returns an array of EntityMut. It performs the O(N^2) duplicate check. This is acceptable because we expect N to be small for arrays.
  • get_many_entities_unique_mut takes a UniqueEntityArray and returns an array of EntityMut. UniqueEntityArray ensures there are no duplicates.
  • iter_entities_mut takes an EntitySet and returns an iterator of EntityMut. EntitySet ensures there are no duplicates.

Also rename the existing iter_entities(_mut) methods to iter_all_entities(_mut), to free up the name.

Migration Guide

World::entity(), World::entity_mut(), World::get_entity(), and World::get_entity_mut() now only take a single Entity instead of being generic and accepting collections. If you were passing a collection, you will need to call a different method.

If you were using a panicking variant, first replace it with get_entity(e).unwrap() or get_entity_mut(e).unwrap().

Then, replace:

  • World::get_entity::<[Entity; N]> -> World::get_many_entities()
  • World::get_entity_mut::<[Entity; N]> -> World::get_many_entities_mut()
  • World::get_entity::<&[Entity]> -> World::iter_entities().collect::<Vec<_>>()
  • World::get_entity_mut::<&[Entity]> -> This has no direct equivalent, as it performed an O(N^2) check that can be expensive for large slices. First convert the collection to an EntitySet, such as EntityHashSet, then call World::iter_entities(e).
  • World::get_entity::<&EntityHashSet> -> World::iter_entities(e).map(|e| (e.id(), e)).collect::<EntityHashMap<_>>()
  • World::get_entity_mut::<&EntityHashSet> -> World::iter_entities_mut(e).map(|e| (e.id(), e)).collect::<EntityHashMap<_>>()

In addition, World::iter_entities(_mut) has been renamed iter_all_entities(_mut) to make the name available for these new methods. If you were calling iter_entities or iter_entities_mut, the functionality is still available under the new name.

chescock added 2 commits March 9, 2025 09:54
Add `get_many_entities` and `iter_entities` methods to handle lookups of multiple entities, instead.
@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Mar 10, 2025
@chescock chescock requested review from ItsDoot and Victoronz March 10, 2025 17:56
}

/// Returns an [`Entity`] iterator of current entities.
///
/// This is useful in contexts where you only have read-only access to the [`World`].
#[inline]
pub fn iter_entities(&self) -> impl Iterator<Item = EntityRef<'_>> + '_ {
pub fn iter_all_entities(&self) -> impl Iterator<Item = EntityRef<'_>> + '_ {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also be impl EntitySetIterator!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, but that should be a separate PR (especially if I wind up reverting the name change and not even touching this line).

@@ -966,7 +947,7 @@ impl World {
}

/// Returns a mutable iterator over all entities in the `World`.
pub fn iter_entities_mut(&mut self) -> impl Iterator<Item = EntityMut<'_>> + '_ {
pub fn iter_all_entities_mut(&mut self) -> impl Iterator<Item = EntityMut<'_>> + '_ {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, impl EntitySetIterator.

@Victoronz
Copy link
Contributor

Thanks for the PR!

Why make iter_entities the EntitySet version? Usually the naming conventions goes:
"I want to iterate it fully" -> no suffix
"I want to iterate a partial list, allowing duplicates" -> many suffix
"I want to iterate a subset" -> many_unique suffix
We could offer all of them, and retain the pattern.

For consistency, we also want both get_many_entities_unique and get_many_unique_mut, the impls can be shared the same way as in #18315!

When adding entities to a method name, does it make sense to follow human language? "I want to get many unique entities."
Meaning the name could be get_many_unique_entities_mut.
The mut suffix is already an exception to this, but I wonder what adjective placement makes more sense here.

@chescock
Copy link
Contributor Author

Why make iter_entities the EntitySet version?

Honestly, I've been looking at these methods for so long that the words have lost all meaning :). It does seem weird that adding many to the name returns fewer things, but I guess that's already true for Query::iter_many.

So, your proposal relative to this is renaming:
iter_all_entities(_mut) => iter_entities(_mut) (meaning, revert that part)
iter_entities => iter_many_entities
iter_entities_mut => iter_many_unique_entities_mut
get_many_entities_unique_mut => get_many_unique_entities_mut
and adding:
get_many_unique_entities (which is the same as get_many_entities but with a stricter argument type, and so still seems a bit pointless to me)
iter_many_unique_entities (which is different from iter_many_entities because it returns impl EntitySetIterator instead of impl Iterator, so I can see how it could be useful)

The result is that we're adding all 8 combinations of (get|iter)_many(_unique)?_entities(_mut)? - except for iter_many_entities_mut, because there's no obviously correct way to check an iterator for duplicates.

Is that right? Sure, I can do that.

@Victoronz
Copy link
Contributor

Why make iter_entities the EntitySet version?

Honestly, I've been looking at these methods for so long that the words have lost all meaning :). It does seem weird that adding many to the name returns fewer things, but I guess that's already true for Query::iter_many.

So, your proposal relative to this is renaming: iter_all_entities(_mut) => iter_entities(_mut) (meaning, revert that part) iter_entities => iter_many_entities iter_entities_mut => iter_many_unique_entities_mut get_many_entities_unique_mut => get_many_unique_entities_mut and adding: get_many_unique_entities (which is the same as get_many_entities but with a stricter argument type, and so still seems a bit pointless to me) iter_many_unique_entities (which is different from iter_many_entities because it returns impl EntitySetIterator instead of impl Iterator, so I can see how it could be useful)

The result is that we're adding all 8 combinations of (get|iter)_many(_unique)?_entities(_mut)? - except for iter_many_entities_mut, because there's no obviously correct way to check an iterator for duplicates.

Is that right? Sure, I can do that.

Yeah!

As an aside, for iter_many_entities_mut there is technically a way to do it, which is filtering the entity iterator in its next call with a HashSet of its previously yielded items.
But that still goes in the category of "Too expensive for an implicit cost", because you are then both doing the iteration and building a HashSet one step at a time...
So better left out here :)

/// // Trying to access the same entity multiple times will fail.
/// assert!(world.get_many_entities_mut([id1, id1]).is_err());
/// ```
pub fn get_many_entities_mut<T: TrustedEntityBorrow, const N: usize>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#18315 (comment)

it looks like I left a few as T: TrustedEntityBorrow. Should I switch those back to Entity, too?

Did you? I don't see them!

Here's one! I'll switch get_many_entities_mut to [Entity; N] and get_many_entities_unique_mut to UniqueEntityArray<N>.

Restrict arrays and `UniqueEntityArray`s from `T: TrustedEntityBorrow` to `Entity`.
iter_entities => iter_many_entities
iter_entities_mut => iter_many_unique_entities_mut
get_many_entities_unique_mut => get_many_unique_entities_mut
@chescock chescock added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Mar 24, 2025
Copy link
Contributor

@ItsDoot ItsDoot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things:

  • Seems to be missing DeferredWorld::iter_many_entities as a counterpart to World::iter_many_entities.
  • We'll also need these functions on EntityFetcher which was added since.

Overall I'm alright with this change, the unique entity set stuff certainly helps compared to the original situation.

/// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if any of the given `entities` do not exist in the world.
/// - Only the first entity found to be missing will be returned.
/// - Returns [`EntityMutableFetchError::AliasedMutability`] if the same entity is requested multiple times.
/// [`EntityDoesNotExistError`] if any entity does not exist in the world.
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
///
///
/// # Examples
///

/// ## Single [`Entity`]
/// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`.
/// This will panic if the `entity` does not exist. Use [`DeferredWorld::get_entity_mut`] if you want
/// to check for entity existence instead of implicitly panic-ing.
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
///
///
/// # Examples
///

pub fn get_many_entities_mut<const N: usize>(
&mut self,
entities: [Entity; N],
) -> Result<[EntityMut<'_>; N], EntityMutableFetchError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if at some point we should provide a variant that returns [Result<EntityMut<'_>, EntityMutableFetchError>; N]. 🤔 Unfortunately std doesn't provide a transpose function for results <-> arrays, so we'd have to provide that function ourselves or make the user perform it themselves.

/// ## Single [`Entity`]
/// Retrieves an [`EntityRef`] that exposes read-only operations for the given `entity`.
/// This will panic if the `entity` does not exist. Use [`World::get_entity`] if you want
/// to check for entity existence instead of implicitly panic-ing.
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
///
///
/// # Examples
///

/// ## Single [`Entity`]
/// Retrieves an [`EntityWorldMut`] that exposes read and write operations for the given `entity`.
/// This will panic if the `entity` does not exist. Use [`World::get_entity_mut`] if you want
/// to check for entity existence instead of implicitly panic-ing.
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
///
///
/// # Examples
///

/// - Only the first entity found to be missing will be returned.
/// - Returns [`EntityMutableFetchError::AliasedMutability`] if the same entity is requested multiple times.
/// [`EntityDoesNotExistError`] if any entity does not exist in the world.
pub fn get_many_unique_entities<const N: usize>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example?

@chescock
Copy link
Contributor Author

  • Seems to be missing DeferredWorld::iter_many_entities as a counterpart to World::iter_many_entities.

I think the &World methods are covered by DeferredWorld: Deref<Target = World>.

  • We'll also need these functions on EntityFetcher which was added since.

Yeah, I got stalled on this PR once that got merged because I didn't really want three copies, and it seemed like there ought to be a way to combine World, DeferredWorld, and EntityFetcher using Deref or generics or something. But I haven't actually come up with a good way to do that, so maybe I should just update this PR with the third copy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Straightforward Simple bug fixes and API improvements, docs, test and examples M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants