Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d7d770b
perf!: Replace OrderedSet children container with flat sorted-array C…
spydon Jul 22, 2026
3166f68
docs: Update children container docs after OrderedSet removal
spydon Jul 22, 2026
2409020
refactor!: Rename ComponentSet to ComponentList, replace childrenFact…
spydon Jul 22, 2026
1a25aea
test: Add golden tests for lifecycle ordering, hit-test order, and eq…
spydon Jul 22, 2026
4d16a9e
feat!: Make updateTree non-virtual behind a CustomTraversal seam
spydon Jul 22, 2026
ba83b9f
perf!: Drive the update pass from a root-owned flattened traversal list
spydon Jul 22, 2026
5410d72
feat: Add updatePaused for pausing the update pass of a subtree
spydon Jul 22, 2026
9b70ffc
docs: Document CustomTraversal, updatePaused, and the HasTimeScale re…
spydon Jul 22, 2026
4ce8e67
perf: Rebuild the flat update list through internal arrays and cache …
spydon Jul 22, 2026
eb5a439
perf: Fuse the flat-list rebuild into the update pass and remove hot-…
spydon Jul 22, 2026
554c11c
refactor!: Move updateSubtree onto Component so traversal mixins carr…
spydon Jul 22, 2026
98b9f07
refactor: Use update and updatePaused in examples that do not need a …
spydon Jul 22, 2026
59d606a
refactor!: Base Route.stopTime on updatePaused instead of zeroing tim…
spydon Jul 22, 2026
b3b320c
style: Spell out abbreviated identifiers introduced by this branch
spydon Jul 22, 2026
f177c18
style: Finish the set-to-list rename in names and docs
spydon Jul 22, 2026
6ac33d9
refactor!: Turn CustomTraversal into a marker interface instead of an…
spydon Jul 22, 2026
c9b8bc2
test: Pin that HasTimeScale scales the dt of the component it is mixe…
spydon Jul 22, 2026
0a31208
perf: Iterate query caches with indexed loops in clear and rebalance
spydon Jul 22, 2026
734d8f6
docs: Add the FCS core rewrite to the v2.0.0 migration guide
spydon Jul 22, 2026
cc84561
perf: Collect the removal teardown through the backing array
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 @@ -533,3 +533,89 @@ if (game.isPaused) {
game.isPaused = false;
}
```


### `children` is now a `ComponentList` instead of an `OrderedSet`
Comment thread
spydon marked this conversation as resolved.

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
1 change: 1 addition & 0 deletions packages/flame/lib/components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export 'src/components/components_notifier.dart';
export 'src/components/core/component.dart';
export 'src/components/core/component_key.dart';
export 'src/components/core/component_render_context.dart';
export 'src/components/core/custom_traversal.dart';
export 'src/components/custom_painter_component.dart';
export 'src/components/fps_component.dart';
export 'src/components/fps_text_component.dart';
Expand Down
Loading
Loading