From 13caa12bc51ff59c1edb0b4b27dfe88d0e32b06a Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 5 Aug 2026 18:32:09 +0200 Subject: [PATCH] perf: Replace generator-based tree teardown and propagation with an explicit collection pass --- .../lib/src/components/core/component.dart | 66 +++++++++++++------ .../flame/lib/src/events/messages/event.dart | 18 +++-- 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/packages/flame/lib/src/components/core/component.dart b/packages/flame/lib/src/components/core/component.dart index 620a7fa624e..4215cece32d 100644 --- a/packages/flame/lib/src/components/core/component.dart +++ b/packages/flame/lib/src/components/core/component.dart @@ -430,10 +430,21 @@ class Component { bool Function(T) handler, { bool includeSelf = false, }) { - return descendants( - reversed: true, - includeSelf: includeSelf, - ).whereType().every(handler); + final children = _children; + if (children != null) { + for (final child in children.reversed()) { + if (!child.propagateToChildren(handler, includeSelf: true)) { + return false; + } + } + } + if (includeSelf) { + final self = this; + if (self is T && !handler(self)) { + return false; + } + } + return true; } @internal @@ -1183,24 +1194,41 @@ class Component { void _remove(Component parent) { parent._internalChildren.remove(this); - propagateToChildren( - (Component component) { - component - ..onRemove() - .._unregisterKey() - .._clearMountedBit() - .._clearRemovingBit() - .._setRemovedBit() - .._removeCompleter?.complete() - .._removeCompleter = null - .._parent!.onChildrenChanged(component, ChildrenChangeType.removed); - return true; - }, - includeSelf: true, - ); + for (final component in _collectDescendants()) { + component + ..onRemove() + .._unregisterKey() + .._clearMountedBit() + .._clearRemovingBit() + .._setRemovedBit() + .._removeCompleter?.complete() + .._removeCompleter = null + .._parent!.onChildrenChanged(component, ChildrenChangeType.removed); + } _parent = null; } + /// Collects this component and all its descendants into a list, in the + /// order that `descendants(reversed: true, includeSelf: true)` would + /// produce: leaves first, siblings in reverse order, ancestors after their + /// subtrees. The snapshot allows [_remove] to run user callbacks that + /// mutate the tree while it walks the subtree, without allocating generator + /// frames per tree level the way [descendants] does. + /// + /// The [out] parameter is only used by the recursive calls, so that the + /// whole subtree is collected into a single list. + List _collectDescendants([List? out]) { + out ??= []; + final children = _children; + if (children != null) { + for (final child in children.reversed()) { + child._collectDescendants(out); + } + } + out.add(this); + return out; + } + void _unregisterKey() { if (key != null) { final game = findGame(); diff --git a/packages/flame/lib/src/events/messages/event.dart b/packages/flame/lib/src/events/messages/event.dart index 8b388d51542..774699ee987 100644 --- a/packages/flame/lib/src/events/messages/event.dart +++ b/packages/flame/lib/src/events/messages/event.dart @@ -27,15 +27,13 @@ abstract class Event { Component rootComponent, void Function(T component) eventHandler, ) { - for (final child - in rootComponent - .descendants(reversed: true, includeSelf: true) - .whereType()) { - continuePropagation = false; - eventHandler(child); - if (!continuePropagation) { - break; - } - } + rootComponent.propagateToChildren( + (component) { + continuePropagation = false; + eventHandler(component); + return continuePropagation; + }, + includeSelf: true, + ); } }