Skip to content

Commit

Permalink
fix: Lifecycle completers to be called for FlameGame (#3007)
Browse files Browse the repository at this point in the history
Previously the `mounted`, `loaded` and `removed` completers for
`FlameGame` weren't called since `FlameGame` doesn't go through the
normal component lifecycle flow, this PR adds so that the completers are
completed properly.

Closes #3003
  • Loading branch information
spydon committed Jan 28, 2024
1 parent 1e56293 commit 3804f52
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 11 deletions.
26 changes: 23 additions & 3 deletions packages/flame/lib/src/components/core/component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import 'dart:math' as math;

import 'package:collection/collection.dart';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/src/cache/value_cache.dart';
import 'package:flame/src/components/core/component_tree_root.dart';
import 'package:flame/src/effects/provider_interfaces.dart';
import 'package:flame/src/game/flame_game.dart';
import 'package:flame/src/game/game.dart';
import 'package:flutter/painting.dart';
import 'package:meta/meta.dart';

Expand Down Expand Up @@ -927,13 +926,34 @@ class Component {
}
}

/// Used by the [FlameGame] to set the loaded state of the component, since
/// the game isn't going through the whole normal component life cycle.
@internal
void setMounted() {
void setLoaded() {
_setLoadedBit();
_loadCompleter?.complete();
_loadCompleter = null;
}

/// Used by the [FlameGame] to set the mounted state of the component, since
/// the game isn't going through the whole normal component life cycle.
@internal
void setMounted() {
_setMountedBit();
_mountCompleter?.complete();
_mountCompleter = null;
_reAddChildren();
}

/// Used by the [FlameGame] to set the removed state of the component, since
/// the game isn't going through the whole normal component life cycle.
@internal
void setRemoved() {
_setRemovedBit();
_removeCompleter?.complete();
_removeCompleter = null;
}

void _remove() {
assert(_parent != null, 'Trying to remove a component with no parent');

Expand Down
14 changes: 14 additions & 0 deletions packages/flame/lib/src/game/flame_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,27 @@ class FlameGame<W extends World> extends ComponentTreeRoot
@override
Vector2 get size => camera.viewport.virtualSize;

@override
@internal
FutureOr<void> load() async {
await super.load();
setLoaded();
}

@override
@internal
void mount() {
super.mount();
setMounted();
}

@override
@internal
void finalizeRemoval() {
super.finalizeRemoval();
setRemoved();
}

/// This implementation of render renders each component, making sure the
/// canvas is reset for each one.
///
Expand Down
10 changes: 8 additions & 2 deletions packages/flame/lib/src/game/game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ abstract mixin class Game {
bool _debugOnLoadStarted = false;

@internal
FutureOr<void> get onLoadFuture {
FutureOr<void> load() async {
assert(
() {
_debugOnLoadStarted = true;
Expand Down Expand Up @@ -101,6 +101,12 @@ abstract mixin class Game {
onMount();
}

@mustCallSuper
@internal
void finalizeRemoval() {
onRemove();
}

/// Current game viewport size, updated every resize via the [onGameResize]
/// method hook.
Vector2 get size {
Expand Down Expand Up @@ -221,7 +227,7 @@ abstract mixin class Game {
}

/// Called when the game is about to be removed from the Flutter widget tree,
/// but before it is actually removed. See the docs for an example on how to
/// but before it is actually removed. See the docs for an example on how to
/// do cleanups to avoid memory leaks.
void onRemove() {}

Expand Down
7 changes: 2 additions & 5 deletions packages/flame/lib/src/game/game_widget/game_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,7 @@ class GameWidgetState<T extends Game> extends State<GameWidget<T>> {
Future<void> get loaderFuture => _loaderFuture ??= (() async {
final game = currentGame;
assert(game.hasLayout);
final onLoad = game.onLoadFuture;
if (onLoad != null) {
await onLoad;
}
await game.load();
game.mount();
if (!game.paused) {
game.update(0);
Expand Down Expand Up @@ -277,7 +274,7 @@ class GameWidgetState<T extends Game> extends State<GameWidget<T>> {
void disposeCurrentGame({bool callGameOnDispose = false}) {
currentGame.removeGameStateListener(_onGameStateChange);
currentGame.lifecycleStateChange(AppLifecycleState.paused);
currentGame.onRemove();
currentGame.finalizeRemoval();
if (callGameOnDispose) {
currentGame.onDispose();
}
Expand Down
47 changes: 47 additions & 0 deletions packages/flame/test/game/flame_game_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,41 @@ void main() {
},
);

group('completers', () {
testWidgets(
'game calls loaded completer',
(WidgetTester tester) async {
final game = _CompleterGame();

await tester.pumpWidget(GameWidget(game: game));
expect(game.loadedCompleterCount, 1);
expect(game.mountedCompleterCount, 1);
},
);

testWithGame(
'game calls mount completer',
_CompleterGame.new,
(game) async {
await game.mounted;
expect(game.mountedCompleterCount, 1);
},
);

testWidgets(
'game calls loaded completer',
(WidgetTester tester) async {
final game = _CompleterGame();

await tester.pumpWidget(GameWidget(game: game));
expect(game.loadedCompleterCount, 1);
expect(game.mountedCompleterCount, 1);
await tester.pumpWidget(Container());
expect(game.removedCompleterCount, 1);
},
);
});

group('world and camera', () {
testWithFlameGame(
'game world setter',
Expand Down Expand Up @@ -440,3 +475,15 @@ class _OnAttachGame extends FlameGame {
return Future.delayed(const Duration(seconds: 1));
}
}

class _CompleterGame extends FlameGame {
int loadedCompleterCount = 0;
int mountedCompleterCount = 0;
int removedCompleterCount = 0;

_CompleterGame() {
loaded.whenComplete(() => loadedCompleterCount++);
mounted.whenComplete(() => mountedCompleterCount++);
removed.whenComplete(() => removedCompleterCount++);
}
}
3 changes: 2 additions & 1 deletion packages/flame_test/lib/src/test_flame_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ Future<void> testWithGame<T extends FlameGame>(
Future<T> initializeGame<T extends FlameGame>(CreateFunction<T> create) async {
final game = create();
game.onGameResize(Vector2(800, 600));
await game.onLoad();
// ignore: invalid_use_of_internal_member
await game.load();
// ignore: invalid_use_of_internal_member
game.mount();
game.update(0);
Expand Down

0 comments on commit 3804f52

Please sign in to comment.