Skip to content

router and forge2d integration #3712

@teamboot88

Description

@teamboot88

What happened?

Hello,
I'm encountering an issue when using RouterComponent with WorldRoute in a flame_forge2d project.
Specifically, when transitioning between routes (e.g., from a WorldRoute to an OverlayRoute using replace),
I get the following assertion error:

The following assertion was thrown during a scheduler callback:
'package:forge2d/src/dynamics/world.dart': Failed assertion: line 148 pos 12: 'bodies.isNotEmpty': is not true.
When the exception was thrown, this was the stack:
#2 World.destroyBody (package:forge2d/src/dynamics/world.dart:148:12)
#3 Forge2DWorld.destroyBody (package:flame_forge2d/forge2d_world.dart:31:18)
#4 BodyComponent.onRemove (package:flame_forge2d/body_component.dart:217:11)

context

My game uses RouterComponent to manage navigation between different screens, including a start screen (OverlayRoute), a main game world (WorldRoute), a dungeon world (WorldRoute), and a config dialog (OverlayRoute). Below is an example of my router setup:

RouterComponent(
  routes: {
    RouteType.start.name: OverlayRoute((context, _) {
      return StartScreenWidget(game: game);
    }),
    RouteType.main.name: WorldRoute(
      () => MainWorld(),
      maintainState: false,
    ),
    RouteType.dungeon.name: WorldRoute(
      () => DungeonWorld(),
      maintainState: false,
    ),
    RouteType.configDialog.name: OverlayRoute((context, _) {
      return ConfigDialogWidget(game: game);
    }),
  },
  initialRoute: RouteType.start.name,
);

The error occurs when replacing a WorldRoute (e.g., main or dungeon) with another route (e.g., start) using router.replaceNamed. It appears that BodyComponent instances added to the current WorldRoute are not removed immediately. Instead, their removal seems to be queued via root.enqueueRemove(), and their onRemove method (which calls world.destroyBody(body)) is invoked after the router has already switched to a new World.

Temporary Workaround

As a temporary fix, I explicitly remove all BodyComponent instances before the route transition and added a 1-second delay to ensure the components are fully removed:

void clearGameBodies() {
  final components = game.world.children.whereType<BodyComponent>().toList();
  for (final component in components) {
    component.removeFromParent();
  }
}

Future<void> pushReplacementOverlay(String routeName) async {
  clearGameBodies();

  await Future.delayed(Duration(seconds: 1)); // Wait for removal

  router.replaceNamed(routeName); // OK
}

This workaround prevents the assertion error, but it feels like a suboptimal solution due to the arbitrary delay.

  • Is the combination of RouterComponent and flame_forge2d with WorldRoute inherently problematic due to the timing of World replacement and BodyComponent removal?
  • What is the recommended approach to handle route transitions with WorldRoute to avoid this assertion error? Should I manually clean up bodies in the World before switching routes, or is there a better way to synchronize BodyComponent removal with World transitions?

What do you expect?

I would ideally expect route transitions in flame_forge2d using WorldRoute to work seamlessly without requiring special handling. Specifically, it would be great if switching between routes (e.g., from a WorldRoute to another route) automatically manages the cleanup of BodyComponent instances and their associated Body objects in the Forge2DWorld, preventing assertion errors like assert(bodies.isNotEmpty) without manual intervention.

How can we reproduce this?

No response

What steps should take to fix this?

No response

Do have an example of where the bug occurs?

No response

Relevant log output

Execute in a terminal and put output into the code block below

Output of: flutter doctor -v

Affected platforms

Android

Other information

No response

Are you interested in working on a PR for this?

  • I want to work on this

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions