Skip to content

Add BundleRemover #18521

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

Merged
merged 12 commits into from
May 6, 2025
Merged

Conversation

ElliottjPierce
Copy link
Contributor

@ElliottjPierce ElliottjPierce commented Mar 24, 2025

Objective

It has long been a todo item in the ecs to create a BundleRemover alongside the inserter, spawner, etc.

This is an uncontroversial first step of #18514.

Solution

Move existing code from complex helper functions to one generalized BundleRemover.

Testing

Existing tests.

@alice-i-cecile alice-i-cecile added this to the 0.17 milestone Mar 24, 2025
@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change S-Needs-Review Needs reviewer attention (from anyone!) to move forward D-Unsafe Touches with unsafe code in some way labels Mar 24, 2025
Copy link
Contributor

@chescock chescock left a comment

Choose a reason for hiding this comment

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

Note that this is likely to have merge conflicts with #17360.

Option<&mut Table>,
&Components,
&[ComponentId],
) -> (bool, T),
Copy link
Contributor

Choose a reason for hiding this comment

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

The old code intentionally used a const generic instead of an ordinary bool to make sure that the branches optimized away. Do we need to preserve that?

Even if we don't, it looks like there are only two functions passed as pre_remove, and one has hard-coded true and the other hard-coded false. It might be simpler to make a separate drop: bool parameter to avoid the tuple and make it clear that it doesn't depend on the values passed to pre_remove.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The old code intentionally used a const generic instead of an ordinary bool to make sure that the branches optimized away. Do we need to preserve that?

It's an impl Fn, and with only two closures types, monomorphization will effectively the same thing. They are both generic, and both bools are constants.

It might be simpler to make a separate drop: bool parameter to avoid the tuple and make it clear that it doesn't depend on the values passed to pre_remove.

I mean, we could add that restriction, but I don't see the point. Letting it depend on it seems more flexible for the future.

&mut deferred_world,
old_archetype,
let mut remover = BundleRemover::new::<T>(self.world, self.location.archetype_id, true)?;
// SAFETY: The passed location has the sane archetype as the remover, since they came from the same location.
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 pass the full EntityLocation to BundleRemover::new instead of just ArchetypeId? That would let you get rid of the safety requirement here.

And do you also need to require that this is the right EntityLocation for the given Entity? Or would that just be a logic bug and not a soundness bug?

Copy link
Contributor Author

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 pass the full EntityLocation to BundleRemover::new instead of just ArchetypeId? That would let you get rid of the safety requirement here.

We could but then the remover would be entity specific instead of archetype specific IIUC. So I'd rather leave it as is.

And do you also need to require that this is the right EntityLocation for the given Entity? Or would that just be a logic bug and not a soundness bug?

Only the archetypes need to match for now I think. In principal, we could make remove take just a table row and store the archetype id and row and table id on the remover. But this was just easier IMO. I'm open to changing that though!

pub(crate) struct BundleRemover<'w> {
world: UnsafeWorldCell<'w>,
bundle_info: ConstNonNull<BundleInfo>,
old_and_new_table: Option<(NonNull<Table>, NonNull<Table>)>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it worth trying to make this look more like BundleInserter? That records the current table and archetype pointers along with an enum ArchetypeMoveType that has the new ones.

Although I suppose one major difference is that you don't need to do anything on a remove that doesn't change archetypes, while an insert might be changing the values.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's been a while since I wrote this, so I don't remember the details. But IIRC, I originally tried to do this with effectively the same structure as BundleInserter and ended up running into issues. I think it was easier to justify safety where accessing the new table mutably could be the same as the old table's mutable pointer. I could take another stab at it, but I don't feel it would be worth it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I agree it's not worth revisiting. I was just surprised that it looked so different! But if you already tried it that way then there must be a good reason for the differences!

@@ -1361,6 +1364,274 @@ impl<'w> BundleInserter<'w> {
}
}

// SAFETY: We have exclusive world access so our pointers can't be invalidated externally
Copy link
Contributor

Choose a reason for hiding this comment

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

This safety comment is on a struct rather than an unsafe block, so I don't think it will get seen. Did you mean to put it somewhere else, or to put it as a doc comment for the struct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was just copied from BundleInserter. I don't mind it, but I can understand your point. I wouldn't be opposed to moving it for all Bundle*er maybe in a different pr.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, okay, I didn't realize this was an existing pattern. That makes sense, then!

require_all: bool,
) -> Option<Self> {
let bundle_info = world.bundles.get_unchecked(bundle_id);
// SAFETY: Ensured by caller and that intersections are never `None`.
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand this safety comment. What does "and that intersections are never None" refer to?

The safety requirements for remove_bundle_from_archetype are "archetype_id must exist and components in bundle_info must exist". The caller is ensuring that archetype_id exists/is valid. Does the fact that you have a BundleId prove that the components all exist?

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, my comment was poorly written. And yeah, you can't make a bundle without having component ids, so we should be good there.

@ElliottjPierce
Copy link
Contributor Author

I don't know if it's policy or just a pattern I've noticed, but since there's two approvals (thanks!), I'll mark ready for final. Let me know if there's some step I'm missing.

@ElliottjPierce ElliottjPierce added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 2, 2025
@chescock
Copy link
Contributor

chescock commented May 2, 2025

I don't know if it's policy or just a pattern I've noticed, but since there's two approvals (thanks!), I'll mark ready for final. Let me know if there's some step I'm missing.

Yup, that's the policy! See https://bevyengine.org/learn/contribute/helping-out/reviewing-pull-requests/#how-pull-requests-are-merged:

Relatively uncontroversial PRs can be merged following approval from at least two community members (including Maintainers) with appropriate expertise.

(Sorry, I probably should have changed the tag when I approved!)

@ElliottjPierce
Copy link
Contributor Author

You're good! I thought I read that somewhere...

Merged via the queue into bevyengine:main with commit f654350 May 6, 2025
36 checks passed
andrewzhurov pushed a commit to andrewzhurov/bevy that referenced this pull request May 17, 2025
# Objective

It has long been a todo item in the ecs to create a `BundleRemover`
alongside the inserter, spawner, etc.

This is an uncontroversial first step of bevyengine#18514.

## Solution

Move existing code from complex helper functions to one generalized
`BundleRemover`.

## Testing

Existing tests.
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-Code-Quality A section of code that is hard to understand or change D-Unsafe Touches with unsafe code in some way S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Uncontroversial This work is generally agreed upon
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants