Skip to content

Fix fishy spawn batch implementation#23344

Merged
mockersf merged 5 commits intobevyengine:mainfrom
ElliottjPierce:fix-fishy-spawn-batch-implementation
Mar 15, 2026
Merged

Fix fishy spawn batch implementation#23344
mockersf merged 5 commits intobevyengine:mainfrom
ElliottjPierce:fix-fishy-spawn-batch-implementation

Conversation

@ElliottjPierce
Copy link
Copy Markdown
Contributor

Objective

Current spawn batch implementation leaks entity allocations.

For iterators that give a fixed upper bound that is greater than the lower bound,
the current implementation over-allocates entities and never frees them.
I checked and this has been a bug for a very long time, so impact is probably low.

Solution

Free the over allocated entities.
We could also use the lower bound of the iterator, but that is not enough since the iterator doesn't always meet its lower bound.

Testing

I added a test that works here but fails on main.

Other things to look at

Should we really be allocating at the high iterator bound? It's probably helpful in most cases, but it could brick the ecs and panic if the high bound is more than u32::MAX or something, and if it is significantly higher than the truth, it would cause real slow downs allocating and then freeing all those entities.

Should we restructure this to work without needing a Drop implementation. That would let us implement fold and such, which could improve performance where the iterator is the bottleneck. This would either come with a breaking change (like needing to call spawn_rest instead of just letting the spawn iterator drop) or a lot of unsafe code that would need to properly handle unwinds etc. Probably not too bad, but more complexity to maintain.

@ElliottjPierce ElliottjPierce added C-Bug An unexpected or incorrect behavior A-ECS Entities, components, systems, and events 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 13, 2026
@github-project-automation github-project-automation bot moved this to Needs SME Triage in ECS Mar 13, 2026
@alice-i-cecile alice-i-cecile added this to the 0.19 milestone Mar 13, 2026
Copy link
Copy Markdown
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.

Good catch!

Should we really be allocating at the high iterator bound? It's probably helpful in most cases, but it could brick the ecs and panic if the high bound is more than u32::MAX or something, and if it is significantly higher than the truth, it would cause real slow downs allocating and then freeing all those entities.

Wait, what do we do when the iterator is actually unbounded? ... Huh, it uses upper.unwrap_or(lower);. Ah, and it falls back to doing spawn instead of spawn_at if it runs out of preallocated entities.

Given that we allocate at the lower bound sometimes, that might be an argument for allocating at the lower bound always.

And I suppose another option is to require ExactSizeIterator.

But the change you have here seems like the right first step regardless, since it only changes the behavior in places where we were leaking before!

@ElliottjPierce
Copy link
Copy Markdown
Contributor Author

Given that we allocate at the lower bound sometimes, that might be an argument for allocating at the lower bound always.

Yeah, I agree. I think we either always use the lower bound or at least cap the upper bound to 2x the lower or something.

@SpecificProtagonist
Copy link
Copy Markdown
Contributor

or at least cap the upper bound to 2x the lower or something.

I don't think it's common to have an upper bound as well as a non-zero lower bound? I guess it sometimes happens when chaining iterators.

@ElliottjPierce
Copy link
Copy Markdown
Contributor Author

I don't think it's common to have an upper bound as well as a non-zero lower bound? I guess it sometimes happens when chaining iterators.

It also happens when doing any kind of filtering, which is probably more common.

@SpecificProtagonist
Copy link
Copy Markdown
Contributor

SpecificProtagonist commented Mar 14, 2026

Filters have to use 0 as the lower bound.

@ElliottjPierce
Copy link
Copy Markdown
Contributor Author

Filters have to use 0 as the lower bound.

Ah, right. I misread. Thanks.

@kfc35 kfc35 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 Mar 15, 2026
@mockersf mockersf added this pull request to the merge queue Mar 15, 2026
Merged via the queue into bevyengine:main with commit 7cfd9b5 Mar 15, 2026
40 checks passed
@github-project-automation github-project-automation bot moved this from Needs SME Triage to Done in ECS Mar 15, 2026
splo pushed a commit to splo/bevy that referenced this pull request Mar 31, 2026
# Objective

Current spawn batch implementation leaks entity allocations.

For iterators that give a fixed upper bound that is greater than the
lower bound,
the current implementation over-allocates entities and never frees them.
I checked and this has been a bug for a very long time, so impact is
probably low.

## Solution

Free the over allocated entities.
We could also use the lower bound of the iterator, but that is not
enough since the iterator doesn't always meet its lower bound.

## Testing

I added a test that works here but fails on main.

## Other things to look at

Should we really be allocating at the high iterator bound? It's probably
helpful in most cases, but it could brick the ecs and panic if the high
bound is more than u32::MAX or something, and if it is significantly
higher than the truth, it would cause real slow downs allocating and
then freeing all those entities.

Should we restructure this to work without needing a `Drop`
implementation. That would let us implement `fold` and such, which could
improve performance where the iterator is the bottleneck. This would
either come with a breaking change (like needing to call `spawn_rest`
instead of just letting the spawn iterator drop) or a lot of unsafe code
that would need to properly handle unwinds etc. Probably not too bad,
but more complexity to maintain.
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-Bug An unexpected or incorrect behavior D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants