From 6a1059b0a6e381020cdaa7a96ceecbcaa45b9a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20L=C3=B6hnn?= Date: Tue, 23 Jan 2024 09:55:05 +0100 Subject: [PATCH] fix: CameraComponent no longer throws Concurrent modification on stop (#2997) When camera is following a component and trying to follow another target I got an exception: ``` dart:core Iterable.forEach package:flame/src/camera/camera_component.dart 326:25 CameraComponent.stop package:flame/src/camera/camera_component.dart 309:5 CameraComponent.follow test/camera/camera_component_test.dart 69:14 main.. package:flame_test/src/test_flame_game.dart 80:21 testWithGame. Concurrent modification during iteration: _Set len:0. ``` Copying viewfinder children before iterating through it and removing child from parent does the trick, as we are not iterating through the same list as we are removing items from, but rather a copy of it. I tried to use camera from `game` provided in the test but it is AFAIK not mounted and will hence not queue modifications (adds and removes). Hence I create a new camera and mount it. --- .github/.cspell/gamedev_dictionary.txt | 1 + .../flame/lib/src/camera/camera_component.dart | 2 +- .../flame/test/camera/camera_component_test.dart | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/.cspell/gamedev_dictionary.txt b/.github/.cspell/gamedev_dictionary.txt index 642d8737ef9..df93f810d8d 100644 --- a/.github/.cspell/gamedev_dictionary.txt +++ b/.github/.cspell/gamedev_dictionary.txt @@ -46,6 +46,7 @@ raytracing # rendering techniques that calculates light rays as straight lines rects # plural of rect respawned # past tense of respawn respawn # when the player character dies and is brought back after some time and penalties +retarget # to direct (something) toward a different target RGBA # red green blue alpha RGBO # red green blue opacity rrect # rounded rect diff --git a/packages/flame/lib/src/camera/camera_component.dart b/packages/flame/lib/src/camera/camera_component.dart index 76f63a657f9..a75dbaf9d23 100644 --- a/packages/flame/lib/src/camera/camera_component.dart +++ b/packages/flame/lib/src/camera/camera_component.dart @@ -323,7 +323,7 @@ class CameraComponent extends Component { /// Removes all movement effects or behaviors from the viewfinder. void stop() { - viewfinder.children.forEach((child) { + viewfinder.children.toList().forEach((child) { if (child is FollowBehavior || child is MoveEffect) { child.removeFromParent(); } diff --git a/packages/flame/test/camera/camera_component_test.dart b/packages/flame/test/camera/camera_component_test.dart index 2694a3b6ec4..efbdbbfd040 100644 --- a/packages/flame/test/camera/camera_component_test.dart +++ b/packages/flame/test/camera/camera_component_test.dart @@ -60,6 +60,20 @@ void main() { } }); + testWithFlameGame('camera should be able to retarget follow', (game) async { + // Creating new camera as the one included with game is not mounted and + // will therefore not be queued. + final camera = CameraComponent(world: game.world)..addToParent(game); + final player = PositionComponent()..addToParent(game.world); + final player2 = PositionComponent()..addToParent(game.world); + camera.follow(player); + camera.follow(player2); + await game.ready(); + + expect(camera.viewfinder.children.length, 1); + expect(camera.viewfinder.children.first, isA()); + }); + testWithFlameGame('follow with snap', (game) async { final world = World()..addToParent(game); final player = PositionComponent()