perf!: Rebuild the FCS core: ComponentList children, allocation hygiene, and flattened update traversal - #3960
Open
spydon wants to merge 26 commits into
Open
perf!: Rebuild the FCS core: ComponentList children, allocation hygiene, and flattened update traversal#3960spydon wants to merge 26 commits into
spydon wants to merge 26 commits into
Conversation
spydon
marked this pull request as ready for review
July 22, 2026 12:02
erickzanardo
approved these changes
Jul 22, 2026
spydon
requested review from
luanpotter,
renancaraujo,
ufrshubham and
wolfenrain
July 22, 2026 12:33
…ory with per-component comparator support
…ual-priority ordering
…es, trim hot-path overhead
…y the CustomTraversal marker
spydon
force-pushed
the
perf/component-set-backing
branch
from
July 22, 2026 13:50
3c6251e to
b083b5f
Compare
spydon
commented
Jul 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Rebuilds the core of the Flame Component System for performance (implements #3957).
Children live in a Flame-owned
ComponentListThe
ordered_setdependency is gone. Children are stored in a single flat array sorted by(priority, insertion order), and each component intrusively stores its container and slot index:removeandcontainsare O(1), with no hashing or tree walks;addis an O(1) append in the common case.nulltombstones that are compacted once per parent per tick, so removing k children among n costs O(k + n) instead of O(k*n).register<T>()/query<T>()cache surface is kept, and results are now always in priority order.Component.childrenFactoryis replaced by an overridablecreateComponentList(), which accepts an optionalComparator<Component>for custom orderings such as y-sort.Three backing designs were implemented and benchmarked before settling on this one; see the comparison in the issue.
The update pass runs over a flattened traversal list
Component.updateTreeis now@nonVirtual. Components that manage their own subtree traversal implement theCustomTraversalmarker interface and overrideComponent.updateSubtree; traversal mixins carry the marker viaimplements, so plainwith HasTimeScalekeeps working and implementations compose throughsuper.updateSubtree.The root updates everything through a flattened pre-order list that is rebuilt lazily, only on ticks where the tree structure changed (and then fused into that tick's update pass, so the rebuild costs no extra traversal).
CustomTraversalcomponents act as barriers that drive their own subtrees. The render pass intentionally stays recursive and virtual, sincerenderTreehas many legitimate overriders (decorators, visibility, snapshot, cameras).New
Component.updatePaused: pauses updates for a component and its whole subtree while rendering, event handling, and lifecycle processing continue; paused subtrees cost nothing per tick.Route.stopTime()is built on it.Per-frame allocations and other hot-path work removed
processLifecycleEventsreturns immediately on an empty queue and allocates nothing per tick.super.renderTreetear-off perPositionComponentper frame), the camera'srenderWorldclosure, render-context stacks, and the debug caches are cached or lazily created.containsEventHandlerAtanswers without a tree walk for games without handlers.Sweepbroadphase re-sorts its nearly-sorted items with an insertion sort and prunes its active list with a swap-remove.Golden tests pin lifecycle-event ordering, hit-test order, and equal-priority ordering across the rewrite.
Benchmarks (JIT, same machine, ms per run, lower is better)
AOT device numbers are still pending; expect smaller (but same-ranked) multiples under AOT.
Checklist
docsand added dartdoc comments with///.examplesordocs.Breaking Change?
Migration instructions
childrenis now aComponentListinstead of anOrderedSet. The iterable surface,query<T>(),register<T>(), andreversed()are unchanged, so most code compiles as is. Theordered_setdependency is gone.Component.childrenFactoryis removed. OverridecreateComponentList()on the component instead; it accepts an optionalComparator<Component>for custom orderings (for example y-sort).Component.updateTreeis non-virtual. If you overrode it, addimplements CustomTraversaland overrideComponent.updateSubtreeinstead; callsuper.updateSubtree(dt)for the standard traversal.HasTimeScaleusage is unchanged (with HasTimeScalestill works; the mixin carries the marker itself).Route.stopTime()now setsupdatePausedinstead of zeroingtimeScale: while stopped,timeScalekeeps its previous value (a slow-motion factor survives a stop/resume cycle), and assigning a newtimeScaleno longer resumes a stopped route; useresumeTime()orupdatePaused = false. Pending lifecycle events on a stopped route now still complete.childrenwhile iterating it now tolerates removals and tail appends; only position-shifting operations (mid-list insertion, reorder, compaction) throwConcurrentModificationError.Related Issues
Closes #3957