Skip to content

refactor!: Make descendants return an eagerly collected snapshot list - #4009

Open
spydon wants to merge 1 commit into
mainfrom
refactor/eager-descendants
Open

refactor!: Make descendants return an eagerly collected snapshot list#4009
spydon wants to merge 1 commit into
mainfrom
refactor/eager-descendants

Conversation

@spydon

@spydon spydon commented Aug 16, 2026

Copy link
Copy Markdown
Member

Description

descendants() was a lazy sync* generator, which allocates an iterator per tree level and pays a delegation chain walk per element. After #3981 it was the last generator-shaped traversal of the component tree; this PR makes it delegate to the same _collectDescendants collection pass, so there is only one traversal shape left to care about.

The method now returns a List<Component> that is an eagerly collected snapshot of the tree at the moment of the call. This means the whole subtree is always traversed (early stopping no longer saves traversal work), and iterating while adding or removing components is safe instead of surfacing a ConcurrentModificationError.

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.

Migration instructions

Most call sites need no changes, since List implements Iterable. Overrides of descendants must update their return type from Iterable<Component> to List<Component>. Code that relied on the lazy iteration semantics (early stopping to avoid traversing large trees, or catching ConcurrentModificationError on mutation during iteration) now gets a full-tree snapshot instead; query methods such as firstWhere still stop early, they just iterate an already collected list.

Related Issues

Relates to #3957

void _remove(Component parent) {
parent._internalChildren.remove(this);
for (final component in _collectDescendants()) {
for (final component in _collectDescendants(reversed: true)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why not use the public descendants here?

);

testWithFlameGame('descendants() iterator is lazy', (game) async {
testWithFlameGame('descendants() supports early stopping', (game) async {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is no longer a feature of descendants but rather just how dart streams work, this test is misleading as the list is now eagerly collected (arguably it was wrong to begin with bc even before it was not testing what it was intending to)

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.

2 participants