From 237d78618b25adb2ae895939b045494a7c6253de 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 removal teardown with an explicit collection pass --- .../lib/src/components/core/component.dart | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/packages/flame/lib/src/components/core/component.dart b/packages/flame/lib/src/components/core/component.dart index 7d9e8ecdc48..62992de7c15 100644 --- a/packages/flame/lib/src/components/core/component.dart +++ b/packages/flame/lib/src/components/core/component.dart @@ -1117,26 +1117,45 @@ class Component { _removeCompleter = null; } + /// Reusable buffer for [_remove]. A fresh list is used in the rare case of + /// a re-entrant teardown (e.g. a nested game disposed from `onRemove`). + static final List _teardownBuffer = []; + 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, - ); + final buffer = _teardownBuffer.isEmpty ? _teardownBuffer : []; + _collectTeardown(buffer); + for (var i = 0; i < buffer.length; i++) { + final component = buffer[i]; + component + ..onRemove() + .._unregisterKey() + .._clearMountedBit() + .._clearRemovingBit() + .._setRemovedBit() + .._removeCompleter?.complete() + .._removeCompleter = null + .._parent!.onChildrenChanged(component, ChildrenChangeType.removed); + } + buffer.clear(); _parent = null; } + /// Collects this component and all its descendants into [out] in teardown + /// order: leaves first, siblings in reverse order, ancestors after their + /// subtrees. This matches the order that + /// `descendants(reversed: true, includeSelf: true)` would produce, without + /// allocating generator frames on every removal. + void _collectTeardown(List out) { + final children = _children; + if (children != null) { + for (final child in children.reversed()) { + child._collectTeardown(out); + } + } + out.add(this); + } + void _unregisterKey() { if (key != null) { final game = findGame();