Skip to content

Conversation

gnarhard
Copy link
Contributor

@gnarhard gnarhard commented Jul 21, 2025

Description

This PR adds functionality to make it easier to manage BatchItems within a SpriteBatch. .add() and .addTransform() now return the generated index for tracking the BatchItem. This now uses a Free List Strategy to avoid concurrent modifications and allow manipulations on the BatchItems from different components in the same frame.

Originally, this PR added an ID for BatchItem management, but that was replaced with index management using the Free List Strategy.

Checklist

  • I have followed the Contributor Guide when preparing my PR.
  • I have updated/added tests for ALL new/updated/fixed functionality.
  • I have updated/added relevant documentation in docs and added dartdoc comments with ///.
  • I have updated/added relevant examples in examples or docs.

Breaking Change?

  • Yes, this PR is a breaking change.
  • No, this PR is not a breaking change.

Related Issues

Copy link
Member

@erickzanardo erickzanardo left a comment

Choose a reason for hiding this comment

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

Great idea! I added one comment and I think we need tests for the added functionality!

@spydon
Copy link
Member

spydon commented Jul 25, 2025

Can you explain your use-case a bit further? I'm not sure I understand why this is needed to do animations based on sprite batches, and whether that is even anything that the sprite batch should be aware of. It seems to me like one would want all the sprites related to an animation within the atlas, not replace the entries.

@gnarhard
Copy link
Contributor Author

In my use case, I often spawn hundreds of orbs that contain health. Sprite batching significantly improves performance here. Each orb has four states: materialize, idle, dissipate, and collect.
• Materialize plays once and transitions to idle, which loops 4 times.
• Then it switches to dissipate, unless a collision occurs.
• On collision, the current animation is interrupted and replaced with collect.

All animations are texture-packed into a single image. I’m generating BatchItems dynamically for the materialize animation, then replacing and eventually removing them.

Currently, SpriteBatch management is index-based. While I could store and track indices in my orb model, it’s unreliable—especially when animations change rapidly. More importantly, there’s no built-in way to remove a BatchItem by index, which is what inspired this PR.

You wouldn’t manage database indexes outside the database—likewise, managing BatchItems by a unique ID makes CRUD operations more reliable and intuitive. This approach ensures I always target the correct item, even as orb states change.

Regarding your comment about animation sprites needing to be within the atlas: to use drawAtlas() efficiently for hundreds of animated orbs, I need to create and replace BatchItems on every frame. Unless there’s a better methodology I’ve missed, that’s currently the only way to animate with sprite batching.

Copy link
Member

@spydon spydon left a comment

Choose a reason for hiding this comment

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

  • It would be good to have a simple example of how to use it with animations.
  • removeAt, removeById and ids are lacking tests.

Other than that and the comment on ids I think it looks good!

@gnarhard
Copy link
Contributor Author

@spydon A problem I'm running into currently with this implementation is race conditions if you're mutating the index from separate components. I've implemented a Free List Strategy to account for this and it's working perfectly in my game. Here's an overview of my changes:

Error safety improvements

  • Fixes race conditions caused by mutating indexes from multiple different sources
    • By not shifting indices or compacting arrays during mutation, render/update logic stays consistent.
    • Prevents bugs caused by index mismatches or invalid lookups during concurrent access within a single frame.
  • Enables Safe Deferred Mutation
    • Works perfectly with a “schedule and flush” mutation model (e.g. deferred removeById()), which avoids unsafe access during the render loop.

BONUS! Performance improvements

  • No shifting of indices
    • removeAt() is O(1), no map updates
  • Reuse of index slots
    • Reduces memory churn, avoids growth
  • Sparse batch safety
    • Works even when many items are removed
  • Fast add and remove
    • Constant time for typical operations

Copy link
Member

@spydon spydon left a comment

Choose a reason for hiding this comment

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

Maybe this PR should be split up in two, one that is introducing the id, which should be quite simple to land. And then one with the performance changes.

@gnarhard gnarhard changed the title feat: Add optional ID to BatchItem and methods for managing items by ID in SpriteBatch feat: Use a Free List Strategy on BatchItem indexes within SpriteBatch and return index from .add() Aug 16, 2025
Copy link
Member

@spydon spydon left a comment

Choose a reason for hiding this comment

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

Lgtm, Thanks for your contribution!

@spydon spydon enabled auto-merge (squash) August 18, 2025 15:20
@spydon
Copy link
Member

spydon commented Aug 18, 2025

@gnarhard can you have a look at why the tests are failing?
I updated the PR to align with Flutter 3.35, so remember to pull first.

Copy link
Member

@spydon spydon left a comment

Choose a reason for hiding this comment

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

Something is going wrong in here, it seems to remove some images that it shouldn't remove, check this diff with the updated goldens: a474672

auto-merge was automatically disabled August 23, 2025 18:04

Head branch was pushed to by a user without write access

@gnarhard
Copy link
Contributor Author

gnarhard commented Aug 23, 2025

@spydon I need your help on fixing these tests. These tests were passing before the move to Flutter 3.35 and other changes to main. I'm wondering if the precision issues are related to Matrix4 returning a Float32List instead of a Float64List. I combed through the file and compared it to main, but I can't figure out why the goldens are different and I don't see a reason for that to happen from my updates.

@spydon spydon force-pushed the feat/manage_sprite_batch_items_by_id branch from 5f09d9d to 16ae942 Compare August 24, 2025 14:20
@spydon spydon force-pushed the feat/manage_sprite_batch_items_by_id branch from 16ae942 to 69ae8a4 Compare August 24, 2025 14:21
@spydon
Copy link
Member

spydon commented Aug 24, 2025

@spydon I need your help on fixing these tests. These tests were passing before the move to Flutter 3.35 and other changes to main. I'm wondering if the precision issues are related to Matrix4 returning a Float32List instead of a Float64List. I combed through the file and compared it to main, but I can't figure out why the goldens are different and I don't see a reason for that to happen from my updates.

I squashed all the commits in this PR so that it can easily cherry-picked into an older version of Flame and try the tests there. I don't think this is related to any precision issues, because as you can see in the image diffs that I linked, some tiles are just completely gone.

@spydon
Copy link
Member

spydon commented Aug 25, 2025

@gnarhard I cherry-picked this PR onto Flame v1.29.0 and used Flutter 3.27.1 to run the tests, and the same goldens still fail, so I'm pretty certain the bug is within this PR and not external.

@gnarhard
Copy link
Contributor Author

Okay, thanks for doing that. I'll look into it again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants