Skip to content

perf: Replace generator-based tree teardown and propagation with an explicit collection pass - #3981

Merged
spydon merged 1 commit into
mainfrom
perf/removal-teardown-buffer
Aug 16, 2026
Merged

perf: Replace generator-based tree teardown and propagation with an explicit collection pass#3981
spydon merged 1 commit into
mainfrom
perf/removal-teardown-buffer

Conversation

@spydon

@spydon spydon commented Aug 5, 2026

Copy link
Copy Markdown
Member

Description

The removal teardown and propagateToChildren walked the subtree through the recursive descendants sync* generator, allocating generator frames per tree level on every traversal. The teardown now collects the subtree into a local buffer (same leaves-first order) via _collectDescendants and iterates that snapshot, since onRemove callbacks may mutate the tree mid-walk. propagateToChildren instead walks the tree with direct recursion and unwinds as soon as a handler stops propagation, so it neither allocates a buffer nor visits more components than the lazy generator did. Event delivery (deliverToComponents) is routed through propagateToChildren, so tap, drag, and keyboard propagation benefit as well.

The public descendants() method keeps its documented lazy semantics, since user code relies on early stopping and live iteration there.

Extracted from #3960 so the data-structure change there stands alone (as requested in this comment).

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

Relates to #3957

},
includeSelf: true,
);
final buffer = _teardownBuffer.isEmpty ? _teardownBuffer : <Component>[];

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.

is allocating one empty list per component removal that significant? feels like this static cache is introducing potential future concurrency problems or hard to find headaches. seems that major gain here is the non-generator teardown and not the brittle [] cache?

Comment thread packages/flame/lib/src/components/core/component.dart Outdated
@spydon
spydon force-pushed the perf/removal-teardown-buffer branch 2 times, most recently from 707869e to f9caf7d Compare August 16, 2026 13:42
@spydon
spydon force-pushed the perf/removal-teardown-buffer branch from f9caf7d to ed22345 Compare August 16, 2026 13:44
@spydon
spydon force-pushed the perf/removal-teardown-buffer branch from ed22345 to ad280e1 Compare August 16, 2026 14:42
@spydon
spydon force-pushed the perf/removal-teardown-buffer branch from ad280e1 to 8d97b08 Compare August 16, 2026 14:52
Base automatically changed from perf/lazy-render-contexts to main August 16, 2026 15:05
@spydon
spydon force-pushed the perf/removal-teardown-buffer branch from 8d97b08 to 1b48f55 Compare August 16, 2026 15:05
/// subtrees. This matches the order that
/// `descendants(reversed: true, includeSelf: true)` would produce, without
/// allocating generator frames on every removal.
void _collectTeardown(List<Component> out) {

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.

wondering if descendants should just use this implementation, then they 1 stay in sync and 2 get the perf benefit?

@spydon
spydon force-pushed the perf/removal-teardown-buffer branch 2 times, most recently from c817249 to 933bb82 Compare August 16, 2026 15:17
@spydon spydon changed the title perf: Replace generator-based removal teardown with an explicit collection pass perf: Replace generator-based tree teardown and propagation with an explicit collection pass Aug 16, 2026
@spydon
spydon force-pushed the perf/removal-teardown-buffer branch from 933bb82 to 13caa12 Compare August 16, 2026 15:27

@luanpotter luanpotter left a comment

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.

LGTM, but it is unfortunate that now we have 3 shapes of traversal (before we had 1)

  • descendants - still kept, lazy using generators, can stop at any time, pays (apparently) huge cost because generators suck?
  • propagateToChildren - fastest and can stop, but does not collect list
  • _collectDescendants - fast and creates list, but eager

And we are saying that _remove cannot use propagateToChildren because of concurrent modifications, but it is not clear to me when that happens.

@spydon
spydon merged commit f7246c1 into main Aug 16, 2026
9 checks passed
@spydon
spydon deleted the perf/removal-teardown-buffer branch August 16, 2026 15:38
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