-
-
Notifications
You must be signed in to change notification settings - Fork 934
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Added HasGameReference mixin (#1828)
This almost exactly like the current HasGameRef mixin, except that: The property is called game instead of gameRef (the "gameRef" violates Dart naming conventions against using abbreviations in variable names); The template type T supports all Games, not only FlameGames; Better integration with the SingleGameInstance mixin; The new mixin is within experimental, to reduce chance that it will confuse the users.
- Loading branch information
Showing
10 changed files
with
234 additions
and
52 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
76 changes: 47 additions & 29 deletions
76
packages/flame/lib/src/components/mixins/has_game_ref.dart
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,59 @@ | ||
import 'package:flame/components.dart'; | ||
import 'package:flame/game.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flame/src/components/core/component.dart'; | ||
import 'package:flame/src/experimental/has_game_reference.dart'; | ||
import 'package:flame/src/game/flame_game.dart'; | ||
import 'package:flame/src/game/game.dart'; | ||
import 'package:flame/src/game/mixins/single_game_instance.dart'; | ||
import 'package:meta/meta.dart'; | ||
|
||
/// [HasGameRef] mixin provides property [game] (or [gameRef]), which is the | ||
/// cached accessor for the top-level game instance. | ||
/// | ||
/// The type [T] on the mixin is the type of your game class. This type will be | ||
/// the type of the [game] reference, and the mixin will check at runtime that | ||
/// the actual type matches the expectation. | ||
/// | ||
/// [HasGameReference] is a newer version of this mixin, and will replace it in | ||
/// Flame v2.0. | ||
mixin HasGameRef<T extends FlameGame> on Component { | ||
T? _gameRef; | ||
T? _game; | ||
|
||
T get gameRef { | ||
if (_gameRef == null) { | ||
var c = parent; | ||
while (c != null) { | ||
if (c is HasGameRef<T>) { | ||
_gameRef = c.gameRef; | ||
return _gameRef!; | ||
} else if (c is T) { | ||
_gameRef = c; | ||
return c; | ||
} else { | ||
c = c.parent; | ||
} | ||
} | ||
throw StateError('Cannot find reference $T in the component tree'); | ||
} | ||
return _gameRef!; | ||
} | ||
/// Reference to the top-level Game instance that owns this component. | ||
/// | ||
/// This property is accessible in the component's `onLoad` and later. It may | ||
/// be accessible earlier too, but only if your game uses the | ||
/// [SingleGameInstance] mixin. | ||
T get game => _game ??= _findGameAndCheck(); | ||
|
||
@override | ||
void onRemove() { | ||
super.onRemove(); | ||
_gameRef = null; | ||
} | ||
/// Allows you to set the game instance explicitly. This may be useful in | ||
/// tests, or if you're planning to move the component to another game | ||
/// instance. | ||
set game(T? value) => _game = value; | ||
|
||
/// Equivalent to the [game] property. | ||
T get gameRef => game; | ||
|
||
/// Directly assigns (and override if one is already set) a [gameRef] to the | ||
/// component. | ||
/// | ||
/// This is meant to be used only for testing purposes. | ||
@visibleForTesting | ||
void mockGameRef(T gameRef) { | ||
_gameRef = gameRef; | ||
@Deprecated('Use .game setter instead. Will be removed in 1.5.0') | ||
void mockGameRef(T gameRef) => _game = gameRef; | ||
|
||
@override | ||
Game? findGame() => _game ?? super.findGame(); | ||
|
||
T _findGameAndCheck() { | ||
final game = findGame(); | ||
assert( | ||
game != null, | ||
'Could not find Game instance: the component is detached from the ' | ||
'component tree', | ||
); | ||
assert( | ||
game! is T, | ||
'Found game of type ${game.runtimeType}, while type $T was expected', | ||
); | ||
return game! as T; | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
packages/flame/lib/src/experimental/has_game_reference.dart
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import 'package:flame/src/components/core/component.dart'; | ||
import 'package:flame/src/components/mixins/has_game_ref.dart'; | ||
import 'package:flame/src/game/game.dart'; | ||
import 'package:flame/src/game/mixins/single_game_instance.dart'; | ||
|
||
/// [HasGameReference] mixin provides property [game], which is the cached | ||
/// accessor for the top-level game instance. | ||
/// | ||
/// The type [T] on the mixin is the type of your game class. This type will be | ||
/// the type of the [game] reference, and the mixin will check at runtime that | ||
/// the actual type matches the expectation. | ||
/// | ||
/// This class is equivalent to [HasGameRef] in all respects except that its | ||
/// generic parameter [T] can be any [Game], not just a "FlameGame". | ||
mixin HasGameReference<T extends Game> on Component { | ||
T? _game; | ||
|
||
/// Reference to the top-level Game instance that owns this component. | ||
/// | ||
/// This property is accessible in the component's `onLoad` and later. It may | ||
/// be accessible earlier too, but only if your game uses the | ||
/// [SingleGameInstance] mixin. | ||
T get game => _game ??= _findGameAndCheck(); | ||
|
||
/// Allows you to set the game instance explicitly. This may be useful in | ||
/// tests, or if you're planning to move the component to another game | ||
/// instance. | ||
set game(T? value) => _game = value; | ||
|
||
@override | ||
Game? findGame() => _game ?? super.findGame(); | ||
|
||
T _findGameAndCheck() { | ||
final game = findGame(); | ||
assert( | ||
game != null, | ||
'Could not find Game instance: the component is detached from the ' | ||
'component tree', | ||
); | ||
assert( | ||
game! is T, | ||
'Found game of type ${game.runtimeType}, while type $T was expected', | ||
); | ||
return game! as T; | ||
} | ||
} |
This file contains 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
122 changes: 122 additions & 0 deletions
122
packages/flame/test/experimental/has_game_reference_test.dart
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import 'package:flame/components.dart'; | ||
import 'package:flame/experimental.dart'; | ||
import 'package:flame/game.dart'; | ||
import 'package:flame_test/flame_test.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:mocktail/mocktail.dart'; | ||
|
||
void main() { | ||
group('HasGameReference', () { | ||
testWithFlameGame( | ||
'component with default HasGameReference', | ||
(game) async { | ||
final component1 = _Component<FlameGame>(); | ||
final component2 = _Component<Game>(); | ||
game.addAll([component1, component2]); | ||
expect(component1.game, game); | ||
expect(component2.game, game); | ||
}, | ||
); | ||
|
||
testWithGame<_MyGame>( | ||
'component with typed HasGameReference', | ||
_MyGame.new, | ||
(game) async { | ||
final component = _Component<_MyGame>(); | ||
game.add(component); | ||
expect(component.game, game); | ||
}, | ||
); | ||
|
||
testWithFlameGame( | ||
'game reference accessed too early', | ||
(game) async { | ||
final component = _Component(); | ||
expect( | ||
() => component.game, | ||
failsAssert( | ||
'Could not find Game instance: the component is detached from the ' | ||
'component tree', | ||
), | ||
); | ||
}, | ||
); | ||
|
||
testWithFlameGame( | ||
'game reference of wrong type', | ||
(game) async { | ||
final component = _Component<_MyGame>(); | ||
game.add(component); | ||
expect( | ||
() => component.game, | ||
failsAssert( | ||
'Found game of type FlameGame, while type _MyGame was expected', | ||
), | ||
); | ||
}, | ||
); | ||
|
||
testWithFlameGame( | ||
'game reference can be set explicitly', | ||
(game) async { | ||
final component = _Component<FlameGame>(); | ||
component.game = game; | ||
expect(component.game, game); | ||
|
||
component.game = null; | ||
expect( | ||
() => component.game, | ||
failsAssert( | ||
'Could not find Game instance: the component is detached from the ' | ||
'component tree', | ||
), | ||
); | ||
}, | ||
); | ||
|
||
testWithFlameGame( | ||
'game reference propagates quickly', | ||
(game) async { | ||
final component1 = _Component()..addToParent(game); | ||
final component2 = _Component()..addToParent(component1); | ||
final component3 = _Component()..addToParent(component2); | ||
expect(component3.game, game); | ||
}, | ||
); | ||
|
||
testWithGame<_MyGame>('simple test', _MyGame.new, (game) async { | ||
final c = _FooComponent(); | ||
game.add(c); | ||
c.foo(); | ||
expect(game.calledFoo, true); | ||
}); | ||
|
||
testWithGame<_MyGame>('gameRef can be mocked', _MyGame.new, (game) async { | ||
final component = _BarComponent(); | ||
await game.ensureAdd(component); | ||
|
||
component.game = MockFlameGame(); | ||
|
||
expect(component.game, isA<MockFlameGame>()); | ||
}); | ||
}); | ||
} | ||
|
||
class _Component<T extends Game> extends Component with HasGameReference<T> {} | ||
|
||
class _MyGame extends FlameGame { | ||
bool calledFoo = false; | ||
void foo() { | ||
calledFoo = true; | ||
} | ||
} | ||
|
||
class _FooComponent extends Component with HasGameReference<_MyGame> { | ||
void foo() { | ||
game.foo(); | ||
} | ||
} | ||
|
||
class _BarComponent extends Component with HasGameReference<_MyGame> {} | ||
|
||
class MockFlameGame extends Mock implements _MyGame {} |