Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cb2774e
perf!: Replace OrderedSet children container with flat sorted-array C…
spydon Jul 22, 2026
90abbe7
docs: Update children container docs after OrderedSet removal
spydon Jul 22, 2026
a311a53
refactor!: Rename ComponentSet to ComponentList, replace childrenFact…
spydon Jul 22, 2026
03d203f
test: Add golden tests for lifecycle ordering, hit-test order, and eq…
spydon Jul 22, 2026
a1b7cab
feat!: Make updateTree non-virtual behind a CustomTraversal seam
spydon Jul 22, 2026
c3582db
perf!: Drive the update pass from a root-owned flattened traversal list
spydon Jul 22, 2026
28892e5
feat: Add updatePaused for pausing the update pass of a subtree
spydon Jul 22, 2026
f900f78
docs: Document CustomTraversal, updatePaused, and the HasTimeScale re…
spydon Jul 22, 2026
af3cc7b
perf: Rebuild the flat update list through internal arrays and cache …
spydon Jul 22, 2026
fda9862
perf: Fuse the flat-list rebuild into the update pass and remove hot-…
spydon Jul 22, 2026
b90ce57
refactor!: Move updateSubtree onto Component so traversal mixins carr…
spydon Jul 22, 2026
c1566a3
refactor: Use update and updatePaused in examples that do not need a …
spydon Jul 22, 2026
541a802
refactor!: Base Route.stopTime on updatePaused instead of zeroing tim…
spydon Jul 22, 2026
2668d5d
style: Spell out abbreviated identifiers introduced by this branch
spydon Jul 22, 2026
782f761
style: Finish the set-to-list rename in names and docs
spydon Jul 22, 2026
224d68b
refactor!: Turn CustomTraversal into a marker interface instead of an…
spydon Jul 22, 2026
d991f2c
test: Pin that HasTimeScale scales the dt of the component it is mixe…
spydon Jul 22, 2026
b806d17
perf: Iterate query caches with indexed loops in clear and rebalance
spydon Jul 22, 2026
ec0c83f
docs: Add the FCS core rewrite to the v2.0.0 migration guide
spydon Jul 22, 2026
619329b
perf: Collect the removal teardown through the backing array
spydon Aug 5, 2026
d009510
Update doc/flame/migration.md
spydon Jul 23, 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 @@ -331,7 +363,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 @@ -25,3 +25,89 @@ GameWidget.managed(
gameFactory: MyGame.new,
);
```


### `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