mirror of https://github.com/flutter/pinball.git
feat: simplified Blueprint implementation (#212)
* feat: adjusted Blueprint implementation * refactor: improved tests * refactor: renamed attach * feat: tested components setter * refactor: removed empty line Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * refactor: removed unecessary InitialPosition * fix: awaited test * refactor: moved Sensor to ControlledSparkyComputer * fix: coverage * refactor: renamed ContactCallback * docs: renamed template * docs: renamed template * refactor: renamed mock ans sensor * refactor: simplified spaceship_ramp * refactor: reduced redundancy code * feat: changes test query * test: pump on spaceship ramp golden tests * test: fixed golden tests for inactive spaceship ramp * refactor: golden tests Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> Co-authored-by: RuiAlonso <rui.alonso@verygood.ventures>pull/216/head
parent
0bc597d34f
commit
c73d36d22b
@ -1,101 +1,43 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
const _attachedErrorMessage = "Can't add to attached Blueprints";
|
||||
|
||||
// TODO(erickzanardo): Keeping this inside our code base
|
||||
// so we can experiment with the idea, but this is a
|
||||
// potential upstream change on Flame.
|
||||
|
||||
/// A [Blueprint] is a virtual way of grouping [Component]s
|
||||
/// that are related, but they need to be added directly on
|
||||
/// the [FlameGame] level.
|
||||
/// {@template blueprint}
|
||||
/// A [Blueprint] is a virtual way of grouping [Component]s that are related,
|
||||
/// but they need to be added directly on the [FlameGame] level.
|
||||
/// {@endtemplate blueprint}
|
||||
// TODO(alestiago): refactor with feat/make-blueprint-extend-component.
|
||||
abstract class Blueprint<T extends FlameGame> extends Component {
|
||||
final List<Component> _components = [];
|
||||
final List<Blueprint> _blueprints = [];
|
||||
|
||||
bool _isAttached = false;
|
||||
|
||||
/// Called before the the [Component]s managed
|
||||
/// by this blueprint is added to the [FlameGame]
|
||||
void build(T gameRef);
|
||||
|
||||
/// Attach the [Component]s built on [build] to the [game]
|
||||
/// instance
|
||||
@mustCallSuper
|
||||
Future<void> attach(T game) async {
|
||||
build(game);
|
||||
await Future.wait([
|
||||
game.addAll(_components),
|
||||
..._blueprints.map(game.addFromBlueprint).toList(),
|
||||
]);
|
||||
_isAttached = true;
|
||||
class Blueprint extends Component {
|
||||
/// {@macro blueprint}
|
||||
Blueprint({
|
||||
Iterable<Component>? components,
|
||||
Iterable<Blueprint>? blueprints,
|
||||
}) {
|
||||
if (components != null) _components.addAll(components);
|
||||
if (blueprints != null) {
|
||||
for (final blueprint in blueprints) {
|
||||
_components.addAll(blueprint.components);
|
||||
}
|
||||
|
||||
/// Adds a single [Component] to this blueprint.
|
||||
@override
|
||||
Future<void> add(Component component) async {
|
||||
assert(!_isAttached, _attachedErrorMessage);
|
||||
_components.add(component);
|
||||
}
|
||||
|
||||
/// Adds a list of [Blueprint]s to this blueprint.
|
||||
void addAllBlueprints(List<Blueprint> blueprints) {
|
||||
assert(!_isAttached, _attachedErrorMessage);
|
||||
_blueprints.addAll(blueprints);
|
||||
}
|
||||
|
||||
/// Adds a single [Blueprint] to this blueprint.
|
||||
void addBlueprint(Blueprint blueprint) {
|
||||
assert(!_isAttached, _attachedErrorMessage);
|
||||
_blueprints.add(blueprint);
|
||||
}
|
||||
|
||||
/// Returns a copy of the components built by this blueprint
|
||||
List<Component> get components => List.unmodifiable(_components);
|
||||
|
||||
/// Returns a copy of the children blueprints
|
||||
List<Blueprint> get blueprints => List.unmodifiable(_blueprints);
|
||||
}
|
||||
|
||||
/// A [Blueprint] that provides additional
|
||||
/// structures specific to flame_forge2d
|
||||
abstract class Forge2DBlueprint extends Blueprint<Forge2DGame> {
|
||||
final List<ContactCallback> _callbacks = [];
|
||||
|
||||
/// Adds a single [ContactCallback] to this blueprint
|
||||
void addContactCallback(ContactCallback callback) {
|
||||
assert(!_isAttached, _attachedErrorMessage);
|
||||
_callbacks.add(callback);
|
||||
}
|
||||
|
||||
/// Adds a collection of [ContactCallback]s to this blueprint
|
||||
void addAllContactCallback(List<ContactCallback> callbacks) {
|
||||
assert(!_isAttached, _attachedErrorMessage);
|
||||
_callbacks.addAll(callbacks);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> attach(Forge2DGame game) async {
|
||||
await super.attach(game);
|
||||
final List<Component> _components = [];
|
||||
|
||||
for (final callback in _callbacks) {
|
||||
game.addContactCallback(callback);
|
||||
}
|
||||
Future<void> _addToParent(Component parent) async {
|
||||
await parent.addAll(_components);
|
||||
}
|
||||
|
||||
/// Returns a copy of the callbacks built by this blueprint
|
||||
List<ContactCallback> get callbacks => List.unmodifiable(_callbacks);
|
||||
/// Returns a copy of the components built by this blueprint.
|
||||
List<Component> get components => List.unmodifiable(_components);
|
||||
}
|
||||
|
||||
/// Adds helper methods regardin [Blueprint]s to [FlameGame]
|
||||
extension FlameGameBlueprint on FlameGame {
|
||||
/// Shortcut to attach a [Blueprint] instance to this game
|
||||
/// equivalent to `MyBluepinrt().attach(game)`
|
||||
/// Adds helper methods regarding [Blueprint]s to [FlameGame].
|
||||
extension FlameGameBlueprint on Component {
|
||||
/// Shortcut to add a [Blueprint]s components to its parent.
|
||||
Future<void> addFromBlueprint(Blueprint blueprint) async {
|
||||
await blueprint.attach(this);
|
||||
await blueprint._addToParent(this);
|
||||
}
|
||||
}
|
||||
|
@ -1,138 +1,74 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/contact_callbacks.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';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
import '../helpers/helpers.dart';
|
||||
|
||||
class TestContactCallback extends ContactCallback<dynamic, dynamic> {}
|
||||
|
||||
class MyBlueprint extends Blueprint {
|
||||
@override
|
||||
void build(_) {
|
||||
add(Component());
|
||||
addAll([Component(), Component()]);
|
||||
}
|
||||
}
|
||||
|
||||
class MyOtherBlueprint extends Blueprint {
|
||||
@override
|
||||
void build(_) {
|
||||
add(Component());
|
||||
}
|
||||
}
|
||||
|
||||
class YetMyOtherBlueprint extends Blueprint {
|
||||
@override
|
||||
void build(_) {
|
||||
add(Component());
|
||||
}
|
||||
}
|
||||
|
||||
class MyComposedBlueprint extends Blueprint {
|
||||
@override
|
||||
void build(_) {
|
||||
addBlueprint(MyBlueprint());
|
||||
addAllBlueprints([MyOtherBlueprint(), YetMyOtherBlueprint()]);
|
||||
}
|
||||
}
|
||||
|
||||
class MyForge2dBlueprint extends Forge2DBlueprint {
|
||||
@override
|
||||
void build(_) {
|
||||
addContactCallback(MockContactCallback());
|
||||
addAllContactCallback([MockContactCallback(), MockContactCallback()]);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Blueprint', () {
|
||||
setUpAll(() {
|
||||
registerFallbackValue(MyBlueprint());
|
||||
registerFallbackValue(Component());
|
||||
});
|
||||
|
||||
test('components can be added to it', () {
|
||||
final blueprint = MyBlueprint()..build(MockForge2DGame());
|
||||
|
||||
expect(blueprint.components.length, equals(3));
|
||||
});
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
test('blueprints can be added to it', () {
|
||||
final blueprint = MyComposedBlueprint()..build(MockForge2DGame());
|
||||
|
||||
expect(blueprint.blueprints.length, equals(3));
|
||||
});
|
||||
|
||||
test('adds the components to a game on attach', () {
|
||||
final mockGame = MockForge2DGame();
|
||||
when(() => mockGame.addAll(any())).thenAnswer((_) async {});
|
||||
MyBlueprint().attach(mockGame);
|
||||
|
||||
verify(() => mockGame.addAll(any())).called(1);
|
||||
});
|
||||
|
||||
test('adds components from a child Blueprint the to a game on attach', () {
|
||||
final mockGame = MockForge2DGame();
|
||||
when(() => mockGame.addAll(any())).thenAnswer((_) async {});
|
||||
MyComposedBlueprint().attach(mockGame);
|
||||
group('Blueprint', () {
|
||||
final flameTester = FlameTester(FlameGame.new);
|
||||
|
||||
test('correctly sets and gets components', () {
|
||||
final component1 = Component();
|
||||
final component2 = Component();
|
||||
final blueprint = Blueprint(
|
||||
components: [
|
||||
component1,
|
||||
component2,
|
||||
],
|
||||
);
|
||||
|
||||
verify(() => mockGame.addAll(any())).called(4);
|
||||
expect(blueprint.components.length, 2);
|
||||
expect(blueprint.components, contains(component1));
|
||||
expect(blueprint.components, contains(component2));
|
||||
});
|
||||
|
||||
test(
|
||||
'throws assertion error when adding to an already attached blueprint',
|
||||
() async {
|
||||
final mockGame = MockForge2DGame();
|
||||
when(() => mockGame.addAll(any())).thenAnswer((_) async {});
|
||||
final blueprint = MyBlueprint();
|
||||
await blueprint.attach(mockGame);
|
||||
|
||||
expect(() => blueprint.add(Component()), throwsAssertionError);
|
||||
expect(() => blueprint.addAll([Component()]), throwsAssertionError);
|
||||
},
|
||||
flameTester.test('adds the components to parent on attach', (game) async {
|
||||
final blueprint = Blueprint(
|
||||
components: [
|
||||
Component(),
|
||||
Component(),
|
||||
],
|
||||
);
|
||||
});
|
||||
await game.addFromBlueprint(blueprint);
|
||||
await game.ready();
|
||||
|
||||
group('Forge2DBlueprint', () {
|
||||
setUpAll(() {
|
||||
registerFallbackValue(TestContactCallback());
|
||||
for (final component in blueprint.components) {
|
||||
expect(game.children.contains(component), isTrue);
|
||||
}
|
||||
});
|
||||
|
||||
test('callbacks can be added to it', () {
|
||||
final blueprint = MyForge2dBlueprint()..build(MockForge2DGame());
|
||||
|
||||
expect(blueprint.callbacks.length, equals(3));
|
||||
});
|
||||
flameTester.test('adds components from a child Blueprint', (game) async {
|
||||
final childBlueprint = Blueprint(
|
||||
components: [
|
||||
Component(),
|
||||
Component(),
|
||||
],
|
||||
);
|
||||
final parentBlueprint = Blueprint(
|
||||
components: [
|
||||
Component(),
|
||||
Component(),
|
||||
],
|
||||
blueprints: [
|
||||
childBlueprint,
|
||||
],
|
||||
);
|
||||
|
||||
test('adds the callbacks to a game on attach', () async {
|
||||
final mockGame = MockForge2DGame();
|
||||
when(() => mockGame.addAll(any())).thenAnswer((_) async {});
|
||||
when(() => mockGame.addContactCallback(any())).thenAnswer((_) async {});
|
||||
await MyForge2dBlueprint().attach(mockGame);
|
||||
await game.addFromBlueprint(parentBlueprint);
|
||||
await game.ready();
|
||||
|
||||
verify(() => mockGame.addContactCallback(any())).called(3);
|
||||
for (final component in childBlueprint.components) {
|
||||
expect(game.children, contains(component));
|
||||
expect(parentBlueprint.components, contains(component));
|
||||
}
|
||||
for (final component in parentBlueprint.components) {
|
||||
expect(game.children, contains(component));
|
||||
}
|
||||
});
|
||||
|
||||
test(
|
||||
'throws assertion error when adding to an already attached blueprint',
|
||||
() async {
|
||||
final mockGame = MockForge2DGame();
|
||||
when(() => mockGame.addAll(any())).thenAnswer((_) async {});
|
||||
when(() => mockGame.addContactCallback(any())).thenAnswer((_) async {});
|
||||
final blueprint = MyForge2dBlueprint();
|
||||
await blueprint.attach(mockGame);
|
||||
|
||||
expect(
|
||||
() => blueprint.addContactCallback(MockContactCallback()),
|
||||
throwsAssertionError,
|
||||
);
|
||||
expect(
|
||||
() => blueprint.addAllContactCallback([MockContactCallback()]),
|
||||
throwsAssertionError,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in new issue