Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7c4ec65
perf: Make render contexts and debug caches lazily allocated
spydon Aug 5, 2026
264fc66
refactor: Address review comments on lazy render contexts
spydon Aug 16, 2026
8d97b08
perf: Replace generator-based removal teardown with an explicit colle…
spydon Aug 5, 2026
0421745
perf: Insertion-sort the sweep broadphase and swap-remove its active …
spydon Aug 5, 2026
4dbea34
refactor: Use insertionSort from package:collection with a static com…
spydon Aug 16, 2026
d72802c
perf!: Replace OrderedSet children container with flat sorted-array C…
spydon Jul 22, 2026
6e83558
docs: Update children container docs after OrderedSet removal
spydon Jul 22, 2026
a8b937e
refactor!: Rename ComponentSet to ComponentList, replace childrenFact…
spydon Jul 22, 2026
23aa223
test: Add golden tests for lifecycle ordering, hit-test order, and eq…
spydon Jul 22, 2026
85578f0
feat!: Make updateTree non-virtual behind a CustomTraversal seam
spydon Jul 22, 2026
a6d3089
perf!: Drive the update pass from a root-owned flattened traversal list
spydon Jul 22, 2026
19a1b77
feat: Add updatePaused for pausing the update pass of a subtree
spydon Jul 22, 2026
6a9753a
docs: Document CustomTraversal, updatePaused, and the HasTimeScale re…
spydon Jul 22, 2026
39a964a
perf: Rebuild the flat update list through internal arrays and cache …
spydon Jul 22, 2026
914f090
perf: Fuse the flat-list rebuild into the update pass and remove hot-…
spydon Jul 22, 2026
738a2d8
refactor!: Move updateSubtree onto Component so traversal mixins carr…
spydon Jul 22, 2026
66c5fc3
refactor: Use update and updatePaused in examples that do not need a …
spydon Jul 22, 2026
df3a786
refactor!: Base Route.stopTime on updatePaused instead of zeroing tim…
spydon Jul 22, 2026
bea6363
style: Spell out abbreviated identifiers introduced by this branch
spydon Jul 22, 2026
364132a
style: Finish the set-to-list rename in names and docs
spydon Jul 22, 2026
1bbb293
refactor!: Turn CustomTraversal into a marker interface instead of an…
spydon Jul 22, 2026
3dfc245
test: Pin that HasTimeScale scales the dt of the component it is mixe…
spydon Jul 22, 2026
e099663
perf: Iterate query caches with indexed loops in clear and rebalance
spydon Jul 22, 2026
fdf262b
docs: Add the FCS core rewrite to the v2.0.0 migration guide
spydon Jul 22, 2026
f28286c
perf: Collect the removal teardown through the backing array
spydon Aug 5, 2026
75cb886
test: Split the type-query benchmark by cache count and cache size
spydon Aug 5, 2026
d5bda10
perf: Drop query cache entries lazily instead of searching on every r…
spydon Aug 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion doc/flame/components/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,38 @@ class MyComponent extends PositionComponent with TapCallbacks {
```


### Custom update traversal and pausing

The engine drives the update pass through a flattened traversal list owned by the game, so
`updateTree` is non-virtual and cannot be overridden. Components that need to control how their
subtree is updated (changing the effective `dt`, skipping children, or updating them manually)
should implement the `CustomTraversal` marker and override the `updateSubtree` method:

```dart
class SlowMotionArea extends Component implements CustomTraversal {
@override
void updateSubtree(double dt) => super.updateSubtree(dt / 2);
}
```

The engine treats every `CustomTraversal` component as a traversal barrier: it appears in the
flattened list itself and its `updateSubtree` drives its subtree. `updateSubtree` lives on
`Component`, but it is only invoked for components carrying the marker. Mixins that provide a
custom traversal (like `HasTimeScale`) declare `implements CustomTraversal`, so their users do not
need to add the marker themselves, and chain via `super.updateSubtree`.

To temporarily stop updating a component and its whole subtree, set `updatePaused` to true. While
paused, no `update` calls happen in the subtree, but rendering and event handling continue, and
pending lifecycle events (adds and removes) are still processed:

```dart
enemySquad.updatePaused = true; // freeze the squad
enemySquad.updatePaused = false; // resume it
```

This is unrelated to `Game.paused`, which stops the whole game loop including rendering.


### Composability of components

Sometimes it is useful to wrap other components inside of your component. For example by grouping
Expand Down Expand Up @@ -382,7 +414,7 @@ flameGame.findByKeyName('player');

### Querying child components

The children that have been added to a component live in a `QueryableOrderedSet` called
The children that have been added to a component live in a `ComponentList` called
`children`. To query for a specific type of components in the set, the `query<T>()` function can be
used. By default `strictMode` is `false` in the children set, but if you set it to true, then the
queries will have to be registered with `children.register` before a query can be used.
Expand Down
86 changes: 86 additions & 0 deletions doc/flame/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,89 @@ if (game.isPaused) {
game.isPaused = false;
}
```


### `children` is now a `ComponentList` instead of an `OrderedSet`

The `ordered_set` package is no longer used; children live in a Flame-owned `ComponentList` that
is significantly faster. The iterable surface, `query<T>()`, `register<T>()`, and `reversed()` are
unchanged, so most code compiles as is. If you imported `package:ordered_set` types to annotate
variables, use `ComponentList` (from `package:flame/components.dart`) instead:

```dart
// Before
import 'package:ordered_set/ordered_set.dart';
OrderedSet<Component> children = component.children;

// After
ComponentList children = component.children;
```

Two behavioral notes:

- `query<T>()` results are now always in priority order.
- Mutating `children` while iterating it now tolerates removals and appends at the end; only
position-shifting operations (a mid-list insertion, a reorder, or tombstone compaction) throw
`ConcurrentModificationError`.


### `Component.childrenFactory` is removed

The global children-container factory is gone. Override `createComponentList()` on the component
instead. The constructor accepts an optional `Comparator<Component>` that replaces priority
ordering for that parent, which gives custom orderings such as y-sort a supported home:

```dart
// Before
Component.childrenFactory = () => OrderedSet.mapping<num, Component>((c) => c.priority);

// After
class YSortedWorld extends World {
@override
ComponentList createComponentList() {
return ComponentList(
comparator: (a, b) => (a as PositionComponent)
.position.y
.compareTo((b as PositionComponent).position.y),
);
}
}
```


### `Component.updateTree` is non-virtual

The update pass runs over a flattened traversal list owned by the game, so `updateTree` can no
longer be overridden. If you overrode it, implement the `CustomTraversal` marker interface and
override `Component.updateSubtree` instead; call `super.updateSubtree(dt)` to run the standard
traversal:

```dart
// Before
class SlowMotionArea extends Component {
@override
void updateTree(double dt) => super.updateTree(dt / 2);
}

// After
class SlowMotionArea extends Component implements CustomTraversal {
@override
void updateSubtree(double dt) => super.updateSubtree(dt / 2);
}
```

`HasTimeScale` usage is unchanged (`with HasTimeScale` still works; the mixin carries the marker
itself). To simply stop updating a subtree, the new `updatePaused` flag replaces the common
gating override: `component.updatePaused = true` pauses the update pass for the component and its
whole subtree while rendering, event handling, and lifecycle processing continue.


### `Route.stopTime()` no longer zeroes `timeScale`

A stopped route is now paused through `updatePaused` instead of `timeScale = 0`:

- While stopped, `timeScale` keeps its previous value, so a slow-motion factor survives a
stop/resume cycle.
- Assigning a new `timeScale` no longer resumes a stopped route; use `resumeTime()` or
`updatePaused = false`.
- Pending lifecycle events (adding or removing components) on a stopped route now still complete.
13 changes: 7 additions & 6 deletions examples/lib/stories/collision_detection/quadtree_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -363,15 +363,16 @@ mixin GameCollidable on PositionComponent {

//#region Utils

/// Lets the subtree update once and then pauses it; set [updateOnce] back to
/// true to let it update once more.
mixin UpdateOnce on PositionComponent {
bool updateOnce = true;
bool get updateOnce => !updatePaused;
set updateOnce(bool value) => updatePaused = !value;

@override
void updateTree(double dt) {
if (updateOnce) {
super.updateTree(dt);
updateOnce = false;
}
void update(double dt) {
super.update(dt);
updatePaused = true;
}
}

Expand Down
4 changes: 2 additions & 2 deletions examples/lib/stories/components/time_scale_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ class _Chopper extends SpriteAnimationComponent
}

@override
void updateTree(double dt) {
void update(double dt) {
position.setFrom(position + _moveDirection * _speed * dt);
super.updateTree(dt);
super.update(dt);
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class LayoutDemo1 extends LinearLayoutComponent {
_expandedMode = value;
removeAll(children.toList());
addAll(
createComponentList(
createLayoutChildren(
expandedMode: expandedMode,
padding: padding,
inflateChild: paddingInflateChild,
Expand All @@ -122,7 +122,7 @@ class LayoutDemo1 extends LinearLayoutComponent {
FutureOr<void> onLoad() {
super.onLoad();
addAll(
createComponentList(
createLayoutChildren(
expandedMode: expandedMode,
padding: padding,
inflateChild: paddingInflateChild,
Expand All @@ -137,7 +137,7 @@ class LayoutDemo1 extends LinearLayoutComponent {
/// This needs to be a method rather than a static list
/// because each of these components needs to be recreated.
/// Otherwise, they'll be operated on by reference and re-parented.
static List<Component> createComponentList({
static List<Component> createLayoutChildren({
required bool expandedMode,
required EdgeInsets padding,
required bool inflateChild,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class LayoutDemo2 extends LinearLayoutComponent {
FutureOr<void> onLoad() {
super.onLoad();
addAll(
createComponentList(
createLayoutChildren(
direction: direction,
),
);
Expand All @@ -95,7 +95,7 @@ class LayoutDemo2 extends LinearLayoutComponent {
/// This needs to be a method rather than a static list
/// because each of these components needs to be recreated.
/// Otherwise, they'll be operated on by reference and re-parented.
static List<Component> createComponentList({
static List<Component> createLayoutChildren({
required Direction direction,
}) {
return [
Expand Down
7 changes: 6 additions & 1 deletion packages/flame/benchmark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ the benchmark results are printed above it.
changes across many parents, and the y-sort pattern where a whole container
reorders every tick.
- `type_query_benchmark.dart`: maintenance and read cost of the
`register<T>()`/`query<T>()` type-query caches under mixed-type churn.
`register<T>()`/`query<T>()` type-query caches. The churn suite varies how
many types are registered and how much of the container each cache matches;
the read suite compares a cached `query<T>()` against the `whereType<T>()`
scan that an unregistered type falls back to. Together they say what a cache
is worth, and what an accidental registration (the thing that
`Component.strictQueryMode` turns into an error) costs.
- `update_components_benchmark.dart`: end-to-end update pass with game-like
logic and inputs on a two-level tree.
- `render_components_benchmark.dart`: render pass over a randomized tree onto
Expand Down
Loading
Loading