From 3a24a51d108b1138ac3dd735956f4276f16b2974 Mon Sep 17 00:00:00 2001 From: DevKage <33748002+ufrshubham@users.noreply.github.com> Date: Sun, 25 Feb 2024 16:32:20 +0530 Subject: [PATCH] fix: Check for removing state while adding a child (#3050) Adding a child component to a parent which is in `removing` state, caused the lifecycle processing to go into a cyclic dependency when the parent is re-added. It happens because, while processing the lifecycle events, child's add event causes itself and the parent to get added to the blocked list. As a result of this, when the parent's add event is processed next, it gets skipped due to being in the blocked list. This PR makes sure that the child does not get enqueued when parent is about to be removed. --- .../lib/src/components/core/component.dart | 2 +- .../flame/test/components/component_test.dart | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/flame/lib/src/components/core/component.dart b/packages/flame/lib/src/components/core/component.dart index 0097139dea6..d16ddf6a0b2 100644 --- a/packages/flame/lib/src/components/core/component.dart +++ b/packages/flame/lib/src/components/core/component.dart @@ -594,7 +594,7 @@ class Component { _clearRemovingBit(); } game.enqueueMove(child, this); - } else if (isMounted && !child.isMounted) { + } else if (isMounted && !isRemoving && !child.isMounted) { child._parent = this; game.enqueueAdd(child, this); } else { diff --git a/packages/flame/test/components/component_test.dart b/packages/flame/test/components/component_test.dart index 3dd96e61cbc..1eb61d6aa81 100644 --- a/packages/flame/test/components/component_test.dart +++ b/packages/flame/test/components/component_test.dart @@ -556,6 +556,27 @@ void main() { expect(wrapper.contains(child), true); }, ); + + testWithFlameGame('when parent is in removing state', (game) async { + final parent = Component(); + final child = Component(); + + await game.add(parent); + await game.ready(); + + // Remove the parent and add the child in the same tick. + parent.removeFromParent(); + await parent.add(child); + + // Timeout is added because processLifecycleEvents of ComponentTreeRoot + // gets blocked in such cases. + expect(game.ready().timeout(const Duration(seconds: 2)), completes); + + // Adding the parent again should eventually mount the child as well. + await game.add(parent); + await game.ready(); + expect(child.isMounted, true); + }); }); group('Removing components', () {