Skip to content
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

Added with_component to EntityWorldMut, DeferredWorld, and World #16668

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

Conversation

bushrat011899
Copy link
Contributor

@bushrat011899 bushrat011899 commented Dec 5, 2024

Objective

Solution

Added with_component to World and EntityWorldMut. This method "removes" a component from an entity, gives a mutable reference to it to a provided closure, and then "re-inserts" the component back onto the entity. This replacement triggers the OnReplace and OnInsert hooks, but does not cause an archetype move, as the removal is purely simulated.

Testing

  • Added doc-tests and a unit test.

Showcase

use bevy_ecs::prelude::*;

/// An immutable component.
#[derive(Component, PartialEq, Eq, Debug)]
#[component(immutable)]
struct Foo(bool);

let mut world = World::default();

let mut entity = world.spawn(Foo(false));

assert_eq!(entity.get::<Foo>(), Some(&Foo(false)));

// Before the closure is executed, the `OnReplace` hooks/observers are triggered
entity.with_component(|foo: &mut Foo| {
    foo.0 = true;
});
// After the closure is executed, `OnInsert` hooks/observers are triggered

assert_eq!(entity.get::<Foo>(), Some(&Foo(true)));

Notes

  • If the component is not available on the entity, the closure and hooks aren't executed, and None is returned. I chose this as an alternative to returning an error or panicking, but I'm open to changing that based on feedback.
  • This relies on unsafe, in particular for accessing the Archetype to trigger hooks. All the unsafe operations are contained within DeferredWorld::with_component, and I would appreciate that this function is given special attention to ensure soundness.
  • The OnAdd hook can never be triggered by this method, since the component must already be inserted. I have chosen to not trigger OnRemove, as I believe it makes sense that this method is purely a replacement operation, not an actual removal/insertion.

@bushrat011899 bushrat011899 added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events X-Uncontroversial This work is generally agreed upon D-Unsafe Touches with unsafe code in some way S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Dec 5, 2024
f: impl FnOnce(&mut T) -> R,
) -> Result<Option<R>, EntityFetchError> {
// If the component is not registered, then it doesn't exist on this entity, so no action required.
let Some(component_id) = self.component_id::<T>() else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to have a with_component_by_id method that works with dynamic components?

... oh, there's no get_mut_assume_mutable_by_id. Should we add that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That definitely could make sense! I'd rather spin that into a follow-up if this gets accepted though.

.expect("component access confirmed above")
};

let result = f(&mut component);
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be useful to pass an EntityMutExcept<T> to let the closure access the other components on the entity?

Or maybe that's dangerous because we triggered the "on replace" hooks but didn't flush their commands, so f could see an inconsistent world?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I see room for issues if you had access to the rest of the entity too. In my mind, if you need this you'd actually remove the component instead.

crates/bevy_ecs/src/world/entity_ref.rs Outdated Show resolved Hide resolved
crates/bevy_ecs/src/world/deferred_world.rs Show resolved Hide resolved
crates/bevy_ecs/src/world/entity_ref.rs Show resolved Hide resolved
@Diddykonga
Copy link

What about component_scope?
Seems to match the functionality for that terminology.

@bushrat011899
Copy link
Contributor Author

What about component_scope? Seems to match the functionality for that terminology.

The reason I chose with_component is World::resource_scope gives you access to the World still, whereas with_component does not give you access to the rest of the entity.

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-Feature A new feature, making something new possible D-Unsafe Touches with unsafe code in some way S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Uncontroversial This work is generally agreed upon
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants