From f818310e5b17d1f366908aef13c266b4f1feaf35 Mon Sep 17 00:00:00 2001 From: Luan Nico Date: Sun, 4 Jul 2021 02:44:15 -0400 Subject: [PATCH] Extract shared children logic when handling components to ComponentSet (#859) --- .../lib/stories/components/composability.dart | 4 +- packages/flame/CHANGELOG.md | 1 + packages/flame/example/lib/main.dart | 2 +- packages/flame/lib/components.dart | 1 + .../lib/src/components/base_component.dart | 93 ++++------ .../lib/src/components/component_set.dart | 173 ++++++++++++++++++ .../joystick/joystick_component.dart | 2 +- .../lib/src/components/mixins/draggable.dart | 2 +- .../components/mixins/has_collidables.dart | 5 +- .../src/components/mixins/has_game_ref.dart | 7 +- .../lib/src/components/mixins/hoverable.dart | 2 +- .../lib/src/components/mixins/tapable.dart | 2 +- packages/flame/lib/src/game/base_game.dart | 91 +++------ .../components/composed_component_test.dart | 20 +- .../flame/test/components/priority_test.dart | 6 +- packages/flame/test/game/base_game_test.dart | 4 +- packages/flame_forge2d/CHANGELOG.md | 5 +- .../flame_forge2d/lib/body_component.dart | 6 + packages/flame_forge2d/lib/forge2d_game.dart | 10 - 19 files changed, 279 insertions(+), 157 deletions(-) create mode 100644 packages/flame/lib/src/components/component_set.dart diff --git a/examples/lib/stories/components/composability.dart b/examples/lib/stories/components/composability.dart index 1761ccac3..3dc434ac3 100644 --- a/examples/lib/stories/components/composability.dart +++ b/examples/lib/stories/components/composability.dart @@ -9,7 +9,7 @@ class Square extends PositionComponent { } } -class ParentSquare extends Square { +class ParentSquare extends Square with HasGameRef { ParentSquare(Vector2 position, Vector2 size) : super(position, size); @override @@ -27,7 +27,7 @@ class ParentSquare extends Square { Square(Vector2(70, 200), Vector2(50, 50), angle: 5), ]; - children.forEach(addChild); + children.forEach((c) => addChild(c, gameRef: gameRef)); } } diff --git a/packages/flame/CHANGELOG.md b/packages/flame/CHANGELOG.md index 4ed813b4b..632da0d65 100644 --- a/packages/flame/CHANGELOG.md +++ b/packages/flame/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Next] - Fix camera not ending up in the correct position on long jumps - Make the `JoystickPlayer` a `PositionComponent` + - Extract shared logic when handling components set in BaseComponent and BaseGame to ComponentSet. ## [1.0.0-releasecandidate.12] - Fix link to code in example stories diff --git a/packages/flame/example/lib/main.dart b/packages/flame/example/lib/main.dart index a2d9f9bdb..dc2c0f770 100644 --- a/packages/flame/example/lib/main.dart +++ b/packages/flame/example/lib/main.dart @@ -70,7 +70,7 @@ class MyGame extends BaseGame with DoubleTapDetector, TapDetector { final handled = components.any((c) { if (c is PositionComponent && c.toRect().overlaps(touchArea)) { - remove(c); + components.remove(c); return true; } return false; diff --git a/packages/flame/lib/components.dart b/packages/flame/lib/components.dart index 383b93f73..0ce772106 100644 --- a/packages/flame/lib/components.dart +++ b/packages/flame/lib/components.dart @@ -2,6 +2,7 @@ export 'joystick.dart'; export 'src/anchor.dart'; export 'src/components/base_component.dart'; export 'src/components/component.dart'; +export 'src/components/component_set.dart'; export 'src/components/isometric_tile_map_component.dart'; export 'src/components/mixins/collidable.dart'; export 'src/components/mixins/draggable.dart'; diff --git a/packages/flame/lib/src/components/base_component.dart b/packages/flame/lib/src/components/base_component.dart index fbe5a098d..5f66a8d67 100644 --- a/packages/flame/lib/src/components/base_component.dart +++ b/packages/flame/lib/src/components/base_component.dart @@ -1,9 +1,6 @@ -import 'dart:collection'; import 'dart:ui'; import 'package:meta/meta.dart'; -import 'package:ordered_set/comparing.dart'; -import 'package:ordered_set/ordered_set.dart'; import '../../game.dart'; import '../../gestures.dart'; @@ -12,6 +9,7 @@ import '../effects/effects_handler.dart'; import '../extensions/vector2.dart'; import '../text.dart'; import 'component.dart'; +import 'component_set.dart'; import 'mixins/has_game_ref.dart'; /// This can be extended to represent a basic Component for your game. @@ -22,8 +20,7 @@ import 'mixins/has_game_ref.dart'; abstract class BaseComponent extends Component { final EffectsHandler _effectsHandler = EffectsHandler(); - final OrderedSet _children = - OrderedSet(Comparing.on((c) => c.priority)); + late final ComponentSet children = createComponentSet(); /// If the component has a parent it will be set here BaseComponent? _parent; @@ -31,14 +28,6 @@ abstract class BaseComponent extends Component { @override BaseComponent? get parent => _parent; - /// The children list shouldn't be modified directly, that is why an - /// [UnmodifiableListView] is used. If you want to add children use the - /// [addChild] method, and if you want to propagate something to the children - /// use the [propagateToChildren] method. - UnmodifiableListView get children { - return UnmodifiableListView(_children); - } - /// This is set by the BaseGame to tell this component to render additional debug information, /// like borders, coordinates, etc. /// This is very helpful while debugging. Set your BaseGame debugMode to true. @@ -69,9 +58,9 @@ abstract class BaseComponent extends Component { @mustCallSuper @override void update(double dt) { + children.updateComponentList(); _effectsHandler.update(dt); - _children.removeWhere((c) => c.shouldRemove).forEach((c) => c.onRemove()); - _children.forEach((c) => c.update(dt)); + children.forEach((c) => c.update(dt)); } @mustCallSuper @@ -84,7 +73,7 @@ abstract class BaseComponent extends Component { @override void renderTree(Canvas canvas) { render(canvas); - _children.forEach((c) { + children.forEach((c) { canvas.save(); c.renderTree(canvas); canvas.restore(); @@ -105,21 +94,21 @@ abstract class BaseComponent extends Component { @override void onGameResize(Vector2 gameSize) { super.onGameResize(gameSize); - _children.forEach((child) => child.onGameResize(gameSize)); + children.forEach((child) => child.onGameResize(gameSize)); } @mustCallSuper @override void onMount() { super.onMount(); - _children.forEach((child) => child.onMount()); + children.forEach((child) => child.onMount()); } @mustCallSuper @override void onRemove() { super.onRemove(); - _children.forEach((child) => child.onRemove()); + children.forEach((child) => child.onRemove()); } /// Called to check whether the point is to be counted as within the component @@ -145,13 +134,7 @@ abstract class BaseComponent extends Component { /// Get a list of non removed effects List get effects => _effectsHandler.effects; - /// Uses the game passed in, or uses the game from [HasGameRef] otherwise, - /// to prepare the child component before it is added to the list of children. - /// Note that this component needs to be added to the game first if - /// [this.gameRef] should be used to prepare the child. - /// For children that don't need preparation from the game instance can - /// disregard both the options given above. - Future addChild(Component child, {Game? gameRef}) async { + void prepare(Component child, {Game? gameRef}) { if (this is HasGameRef) { final c = this as HasGameRef; gameRef ??= c.hasGameRef ? c.gameRef : null; @@ -169,39 +152,35 @@ abstract class BaseComponent extends Component { child._parent = this; child.debugMode = debugMode; } - - final childOnLoadFuture = child.onLoad(); - if (childOnLoadFuture != null) { - await childOnLoadFuture; - } - _children.add(child); - if (isMounted) { - child.onMount(); - } } - Future addChildren( - Iterable children, { - Game? gameRef, - }) async { - await Future.wait( - children.map( - (child) => addChild(child, gameRef: gameRef), - ), - ); + /// Uses the game passed in, or uses the game from [HasGameRef] otherwise, + /// to prepare the child component before it is added to the list of children. + /// Note that this component needs to be added to the game first if + /// [this.gameRef] should be used to prepare the child. + /// For children that don't need preparation from the game instance can + /// disregard both the options given above. + Future addChild(Component child, {BaseGame? gameRef}) { + return children.addChild(child, gameRef: gameRef); } - bool removeChild(Component c) { - return _children.remove(c); + /// Adds mutiple children. + /// + /// See [addChild] for details (or `children.addChildren()`). + Future addChildren(List cs, {BaseGame? gameRef}) { + return children.addChildren(cs, gameRef: gameRef); } - void clearChildren() { - _children.clear(); - } + /// Whether the children list contains the given component. + /// + /// This method uses reference equality. + bool containsChild(Component c) => children.contains(c); - bool containsChild(Component c) => _children.contains(c); - - void reorderChildren() => _children.rebalanceAll(); + /// Call this if any of this component's children priorities have changed + /// at runtime. + /// + /// This will call `rebalanceAll` on the [children] ordered set. + void reorderChildren() => children.rebalanceAll(); /// This method first calls the passed handler on the leaves in the tree, /// the children without any children of their own. @@ -218,7 +197,7 @@ abstract class BaseComponent extends Component { bool Function(T) handler, ) { var shouldContinue = true; - for (final child in _children) { + for (final child in children) { if (child is BaseComponent) { shouldContinue = child.propagateToChildren(handler); } @@ -236,4 +215,12 @@ abstract class BaseComponent extends Component { Vector2 eventPosition(PositionInfo info) { return isHud ? info.eventPosition.widget : info.eventPosition.game; } + + ComponentSet createComponentSet() { + final components = ComponentSet.createDefault(prepare); + if (this is HasGameRef) { + components.register(); + } + return components; + } } diff --git a/packages/flame/lib/src/components/component_set.dart b/packages/flame/lib/src/components/component_set.dart new file mode 100644 index 000000000..6821e414c --- /dev/null +++ b/packages/flame/lib/src/components/component_set.dart @@ -0,0 +1,173 @@ +import 'package:ordered_set/comparing.dart'; +import 'package:ordered_set/queryable_ordered_set.dart'; + +import '../../components.dart'; +import '../game/base_game.dart'; + +/// This is a simple wrapper over [QueryableOrderedSet] to be used by +/// [BaseGame] and [BaseComponent]. +/// +/// Instead of immediatly modifying the component list, all insertion +/// and removal operations are queued to be performed on the next tick. +/// +/// This will avoid any concurrent modification exceptions while the game +/// iterates through the component list. +/// +/// This wrapper also garantueed that prepare, onLoad, onMount and all the +/// lifecycle methods are called properly. +class ComponentSet extends QueryableOrderedSet { + /// Components to be added on the next update. + /// + /// The component list is only changed at the start of each update to avoid + /// concurrency issues. + final List _addLater = []; + + /// Components to be removed on the next update. + /// + /// The component list is only changed at the start of each update to avoid + /// concurrency issues. + final Set _removeLater = {}; + + /// This is the "prepare" function that will be called *before* the + /// component is added to the component list by the add/addAll methods. + final void Function(Component child, {BaseGame? gameRef}) prepare; + + ComponentSet( + int Function(Component e1, Component e2)? compare, + this.prepare, + ) : super(compare); + + /// Prepares and registers one component to be added on the next game tick. + /// + /// This is the interface compliant version; if you want to provide an + /// explicit gameRef or await for the onLoad, use [addChild]. + /// + /// Note: the component is only added on the next tick. This method always + /// returns true. + @override + bool add(Component c) { + addChild(c); + return true; + } + + /// Prepares and registers a list of components to be added on the next game + /// tick. + /// + /// This is the interface compliant version; if you want to provide an + /// explicit gameRef or await for the onLoad, use [addChild]. + /// + /// Note: the components are only added on the next tick. This method always + /// returns the total lenght of the provided list. + @override + int addAll(Iterable components) { + addChildren(components); + return components.length; + } + + /// Prepares and registers one component to be added on the next game tick. + /// + /// This allows you to provide a specific gameRef if this component is being + /// added from within another component that is already on a BaseGame. + /// You can await for the onLoad function, if present. + /// This method can be considered sync for all intents and purposes if no + /// onLoad is provided by the component. + Future addChild(Component c, {BaseGame? gameRef}) async { + prepare(c, gameRef: gameRef); + + final loadFuture = c.onLoad(); + if (loadFuture != null) { + await loadFuture; + } + + _addLater.add(c); + } + + /// Prepares and registers a list of component to be added on the next game + /// tick. + /// + /// See [addChild] for more details. + Future addChildren( + Iterable components, { + BaseGame? gameRef, + }) async { + final ps = components.map((c) => addChild(c, gameRef: gameRef)); + await Future.wait(ps); + } + + /// Marks a component to be removed from the components list on the next game + /// tick. + @override + bool remove(Component c) { + _removeLater.add(c); + return true; + } + + /// Marks a list of components to be removed from the components list on the + /// next game tick. + void removeAll(Iterable components) { + _removeLater.addAll(components); + } + + /// Marks all existing components to be removed from the components list on + /// the next game tick. + @override + void clear() { + _removeLater.addAll(this); + } + + /// Materializes the component list in reversed order. + Iterable reversed() { + return toList().reversed; + } + + /// Call this on your update method. + /// + /// This method effectuates any pending operations of insertion or removal, + /// and thus actually modifies the components set. + /// Note: do not call this while iterating the set. + void updateComponentList() { + _removeLater.addAll(where((c) => c.shouldRemove)); + _removeLater.forEach((c) { + c.onRemove(); + super.remove(c); + }); + _removeLater.clear(); + + if (_addLater.isNotEmpty) { + final addNow = _addLater.toList(growable: false); + _addLater.clear(); + addNow.forEach((c) { + super.add(c); + c.onMount(); + }); + } + } + + @override + void rebalanceAll() { + final elements = toList(); + // bypass the wrapper because the components are already added + super.clear(); + elements.forEach(super.add); + } + + @override + void rebalanceWhere(bool Function(Component element) test) { + // bypass the wrapper because the components are already added + final elements = super.removeWhere(test).toList(); + elements.forEach(super.add); + } + + /// Creates a [ComponentSet] with a default value for the compare function, + /// using the Component's priority for sorting. + /// + /// You must still provide your [prepare] function depending on the context. + static ComponentSet createDefault( + void Function(Component child, {BaseGame? gameRef}) prepare, + ) { + return ComponentSet( + Comparing.on((c) => c.priority), + prepare, + ); + } +} diff --git a/packages/flame/lib/src/components/joystick/joystick_component.dart b/packages/flame/lib/src/components/joystick/joystick_component.dart index ae2a5f3fb..e42c996cc 100644 --- a/packages/flame/lib/src/components/joystick/joystick_component.dart +++ b/packages/flame/lib/src/components/joystick/joystick_component.dart @@ -53,6 +53,6 @@ class JoystickComponent extends JoystickController { void removeAction(int actionId) { final action = children .firstWhere((e) => e is JoystickAction && e.actionId == actionId); - removeChild(action); + children.remove(action); } } diff --git a/packages/flame/lib/src/components/mixins/draggable.dart b/packages/flame/lib/src/components/mixins/draggable.dart index a7737ca4d..f864150db 100644 --- a/packages/flame/lib/src/components/mixins/draggable.dart +++ b/packages/flame/lib/src/components/mixins/draggable.dart @@ -84,7 +84,7 @@ mixin HasDraggableComponents on BaseGame { } void _onGenericEventReceived(bool Function(Draggable) handler) { - for (final c in components.toList().reversed) { + for (final c in components.reversed()) { var shouldContinue = true; if (c is BaseComponent) { shouldContinue = c.propagateToChildren(handler); diff --git a/packages/flame/lib/src/components/mixins/has_collidables.dart b/packages/flame/lib/src/components/mixins/has_collidables.dart index 303c8e107..5458b2dea 100644 --- a/packages/flame/lib/src/components/mixins/has_collidables.dart +++ b/packages/flame/lib/src/components/mixins/has_collidables.dart @@ -1,12 +1,9 @@ -import 'package:ordered_set/queryable_ordered_set.dart'; - import '../../../game.dart'; import '../../components/mixins/collidable.dart'; import '../../geometry/collision_detection.dart'; mixin HasCollidables on BaseGame { void handleCollidables() { - final qos = components as QueryableOrderedSet; - collisionDetection(qos.query()); + collisionDetection(components.query()); } } diff --git a/packages/flame/lib/src/components/mixins/has_game_ref.dart b/packages/flame/lib/src/components/mixins/has_game_ref.dart index b27fe5466..da19d9930 100644 --- a/packages/flame/lib/src/components/mixins/has_game_ref.dart +++ b/packages/flame/lib/src/components/mixins/has_game_ref.dart @@ -1,7 +1,7 @@ import '../../../components.dart'; -import '../../game/game.dart'; +import '../../../game.dart'; -mixin HasGameRef { +mixin HasGameRef on Component { T? _gameRef; T get gameRef { @@ -17,9 +17,10 @@ mixin HasGameRef { set gameRef(T gameRef) { _gameRef = gameRef; if (this is BaseComponent) { + // TODO(luan) this is wrong, should be done using propagateToChildren (this as BaseComponent) .children - .whereType>() + .query() .forEach((e) => e.gameRef = gameRef); } } diff --git a/packages/flame/lib/src/components/mixins/hoverable.dart b/packages/flame/lib/src/components/mixins/hoverable.dart index 04ad110e0..bde335bd8 100644 --- a/packages/flame/lib/src/components/mixins/hoverable.dart +++ b/packages/flame/lib/src/components/mixins/hoverable.dart @@ -35,7 +35,7 @@ mixin HasHoverableComponents on BaseGame { return true; // always continue } - for (final c in components.toList().reversed) { + for (final c in components.reversed()) { if (c is BaseComponent) { c.propagateToChildren(_mouseMoveHandler); } diff --git a/packages/flame/lib/src/components/mixins/tapable.dart b/packages/flame/lib/src/components/mixins/tapable.dart index 33d71f379..fd70a2bb4 100644 --- a/packages/flame/lib/src/components/mixins/tapable.dart +++ b/packages/flame/lib/src/components/mixins/tapable.dart @@ -49,7 +49,7 @@ mixin Tapable on BaseComponent { mixin HasTapableComponents on BaseGame { void _handleTapEvent(bool Function(Tapable child) tapEventHandler) { - for (final c in components.toList().reversed) { + for (final c in components.reversed()) { var shouldContinue = true; if (c is BaseComponent) { shouldContinue = c.propagateToChildren(tapEventHandler); diff --git a/packages/flame/lib/src/game/base_game.dart b/packages/flame/lib/src/game/base_game.dart index 2307d58a0..f63f7c4b4 100644 --- a/packages/flame/lib/src/game/base_game.dart +++ b/packages/flame/lib/src/game/base_game.dart @@ -1,8 +1,6 @@ import 'dart:ui'; import 'package:meta/meta.dart'; -import 'package:ordered_set/comparing.dart'; -import 'package:ordered_set/ordered_set.dart'; import 'package:ordered_set/queryable_ordered_set.dart'; import '../../components.dart'; @@ -28,19 +26,7 @@ import 'viewport.dart'; /// It is based on the Component system. class BaseGame extends Game with FPSCounter { /// The list of components to be updated and rendered by the base game. - late final OrderedSet components = createOrderedSet(); - - /// Components to be added on the next update. - /// - /// The component list is only changed at the start of each [update] to avoid - /// concurrency issues. - final List _addLater = []; - - /// Components to be removed on the next update. - /// - /// The component list is only changed at the start of each [update] to avoid - /// concurrency issues. - final Set _removeLater = {}; + late final ComponentSet components = createComponentSet(); /// The camera translates the coordinate space after the viewport is applied. final Camera camera = Camera(); @@ -97,14 +83,14 @@ class BaseGame extends Game with FPSCounter { /// /// You can return a specific sub-class of OrderedSet, like /// [QueryableOrderedSet] for example, that we use for Collidables. - OrderedSet createOrderedSet() { - final comparator = Comparing.on((c) => c.priority); + ComponentSet createComponentSet() { + final components = ComponentSet.createDefault( + (c, {BaseGame? gameRef}) => prepare(c), + ); if (this is HasCollidables) { - final qos = QueryableOrderedSet(comparator); - qos.register(); - return qos; + components.register(); } - return OrderedSet(comparator); + return components; } /// This method is called for every component added. @@ -114,6 +100,11 @@ class BaseGame extends Game with FPSCounter { /// By default, this calls the first time resize for every component, so don't forget to call super.preAdd when overriding. @mustCallSuper void prepare(Component c) { + assert( + hasLayout, + '"prepare/add" called before the game is ready. Did you try to access it on the Game constructor? Use the "onLoad" method instead.', + ); + if (c is Collidable) { assert( this is HasCollidables, @@ -144,7 +135,7 @@ class BaseGame extends Game with FPSCounter { } if (c is HasGameRef) { - (c as HasGameRef).gameRef = this; + c.gameRef = this; } // first time resize @@ -156,38 +147,16 @@ class BaseGame extends Game with FPSCounter { /// This methods is an async operation since it await the `onLoad` method of the component. Nevertheless, this method only need to be waited to finish if by some reason, your logic needs to be sure that the component has finished loading, otherwise, this method can be called without waiting for it to finish as the BaseGame already handle the loading of the component. /// /// *Note:* Do not add components on the game constructor. This method can only be called after the game already has its layout set, this can be verified by the [hasLayout] property, to add components upon a game initialization, the [onLoad] method can be used instead. - Future add(Component c) async { - assert( - hasLayout, - '"add" called before the game is ready. Did you try to access it on the Game constructor? Use the "onLoad" method instead.', - ); - prepare(c); - final loadFuture = c.onLoad(); - - if (loadFuture != null) { - await loadFuture; - } - _addLater.add(c); + Future add(Component c) { + return components.addChild(c); } - /// Prepares and registers a list of components to be added on the next game tick - void addAll(Iterable components) { - components.forEach(add); - } - - /// Marks a component to be removed from the components list on the next game tick - void remove(Component c) { - _removeLater.add(c); - } - - /// Marks a list of components to be removed from the components list on the next game tick - void removeAll(Iterable components) { - _removeLater.addAll(components); - } - - /// Marks all existing components to be removed from the components list on the next game tick - void clear() { - _removeLater.addAll(components); + /// Adds a list of components, calling addChild for each one. + /// + /// The returned Future completes once all are loaded and added. + /// Component loading is done in parallel. + Future addAll(List cs) { + return components.addChildren(cs); } /// This implementation of render basically calls [renderComponent] for every component, making sure the canvas is reset for each one. @@ -223,7 +192,7 @@ class BaseGame extends Game with FPSCounter { @override @mustCallSuper void update(double dt) { - _updateComponentList(); + components.updateComponentList(); if (this is HasCollidables) { (this as HasCollidables).handleCollidables(); @@ -233,22 +202,6 @@ class BaseGame extends Game with FPSCounter { camera.update(dt); } - void _updateComponentList() { - _removeLater.addAll(components.where((c) => c.shouldRemove)); - _removeLater.forEach((c) { - c.onRemove(); - components.remove(c); - }); - _removeLater.clear(); - - if (_addLater.isNotEmpty) { - final addNow = _addLater.toList(growable: false); - _addLater.clear(); - components.addAll(addNow); - addNow.forEach((component) => component.onMount()); - } - } - /// This implementation of resize passes the resize call along to every /// component in the list, enabling each one to make their decisions as how to handle the resize. /// diff --git a/packages/flame/test/components/composed_component_test.dart b/packages/flame/test/components/composed_component_test.dart index 25de0a517..c573f6e53 100644 --- a/packages/flame/test/components/composed_component_test.dart +++ b/packages/flame/test/components/composed_component_test.dart @@ -62,6 +62,7 @@ void main() { final child = MyTap(); final wrapper = MyComposed(); wrapper.addChild(child); + wrapper.update(0); // children are only added on the next tick expect(wrapper.containsChild(child), true); }); @@ -69,10 +70,15 @@ void main() { test('removes the child from the component', () { final child = MyTap(); final wrapper = MyComposed(); - wrapper.addChild(child); - expect(true, wrapper.containsChild(child)); - wrapper.removeChild(child); + wrapper.addChild(child); + expect(wrapper.containsChild(child), false); + wrapper.update(0); // children are only added on the next tick + expect(wrapper.containsChild(child), true); + + wrapper.children.remove(child); + expect(wrapper.containsChild(child), true); + wrapper.update(0); // children are only removed on the next tick expect(wrapper.containsChild(child), false); }); @@ -81,8 +87,12 @@ void main() { () async { final child = MyAsyncChild(); final wrapper = MyComposed(); - await wrapper.addChild(child); + final future = wrapper.addChild(child); + expect(wrapper.containsChild(child), false); + await future; + expect(wrapper.containsChild(child), false); + wrapper.update(0); expect(wrapper.containsChild(child), true); }, ); @@ -107,7 +117,7 @@ void main() { final game = MyGame(); final children = List.generate(10, (_) => MyTap()); final wrapper = MyComposed(); - wrapper.addChildren(children); + wrapper.children.addChildren(children); game.onResize(size); game.add(wrapper); diff --git a/packages/flame/test/components/priority_test.dart b/packages/flame/test/components/priority_test.dart index ab03d25f6..21cc4f764 100644 --- a/packages/flame/test/components/priority_test.dart +++ b/packages/flame/test/components/priority_test.dart @@ -58,7 +58,7 @@ void main() { priorityComponents.shuffle(); final game = BaseGame()..onResize(Vector2.zero()); game.add(parentComponent); - parentComponent.addChildren(priorityComponents, gameRef: game); + parentComponent.children.addChildren(priorityComponents, gameRef: game); final children = parentComponent.children; game.update(0); componentsSorted(children); @@ -73,7 +73,7 @@ void main() { priorityComponents.shuffle(); final game = BaseGame()..onResize(Vector2.zero()); game.add(parentComponent); - parentComponent.addChildren(priorityComponents, gameRef: game); + parentComponent.children.addChildren(priorityComponents, gameRef: game); final children = parentComponent.children; game.update(0); componentsSorted(children); @@ -92,7 +92,7 @@ void main() { final game = BaseGame()..onResize(Vector2.zero()); game.add(grandParentComponent); grandParentComponent.addChild(parentComponent, gameRef: game); - parentComponent.addChildren(priorityComponents, gameRef: game); + parentComponent.children.addChildren(priorityComponents, gameRef: game); final children = parentComponent.children; game.update(0); componentsSorted(children); diff --git a/packages/flame/test/game/base_game_test.dart b/packages/flame/test/game/base_game_test.dart index f63fceb3e..fd6a35888 100644 --- a/packages/flame/test/game/base_game_test.dart +++ b/packages/flame/test/game/base_game_test.dart @@ -172,7 +172,7 @@ void main() { // by the function on the component, but the onRemove callback should // only be called once. component.remove(); - game.remove(component); + game.components.remove(component); // The component is not removed from the component list until an update has been performed game.update(0.0); @@ -214,7 +214,7 @@ void main() { game.update(0.0); expect(game.components.length, equals(3)); - game.clear(); + game.components.clear(); // Ensure clear does not remove components directly expect(game.components.length, equals(3)); diff --git a/packages/flame_forge2d/CHANGELOG.md b/packages/flame_forge2d/CHANGELOG.md index ad9aa42e7..a56b496b2 100644 --- a/packages/flame_forge2d/CHANGELOG.md +++ b/packages/flame_forge2d/CHANGELOG.md @@ -1,8 +1,11 @@ # CHANGELOG +## [next] + - Update mechanism by which `BodyComponent`'s are disposed to use the `onRemove` method + ## [0.7.3-releasecandidate.12] - Fix prepareCanvas type error - + ## [0.7.2-releasecandidate.12] - Update to Forge2D 0.7.2 - Update to Flame 1.0.0-releasecandidate.12 diff --git a/packages/flame_forge2d/lib/body_component.dart b/packages/flame_forge2d/lib/body_component.dart index b41e8deb8..729b27551 100644 --- a/packages/flame_forge2d/lib/body_component.dart +++ b/packages/flame_forge2d/lib/body_component.dart @@ -133,4 +133,10 @@ abstract class BodyComponent extends BaseComponent bool containsPoint(Vector2 point) { return body.fixtures.any((fixture) => fixture.testPoint(point)); } + + @override + void onRemove() { + super.onRemove(); + world.destroyBody(body); + } } diff --git a/packages/flame_forge2d/lib/forge2d_game.dart b/packages/flame_forge2d/lib/forge2d_game.dart index 05566fa15..6304e04ff 100644 --- a/packages/flame_forge2d/lib/forge2d_game.dart +++ b/packages/flame_forge2d/lib/forge2d_game.dart @@ -5,7 +5,6 @@ import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:forge2d/forge2d.dart' hide Timer; -import 'body_component.dart'; import 'contact_callbacks.dart'; import 'forge2d_camera.dart'; @@ -54,15 +53,6 @@ class Forge2DGame extends BaseGame { } } - @override - void remove(Component component) { - super.remove(component); - if (component is BodyComponent) { - world.destroyBody(component.body); - component.remove(); - } - } - void addContactCallback(ContactCallback callback) { _contactCallbacks.register(callback); }