Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Prevent onRemove/onDetach being called for initial Gesture Detector addition #2653

Merged
merged 25 commits into from
Aug 20, 2023

Conversation

munsterlander
Copy link
Contributor

@munsterlander munsterlander commented Aug 18, 2023

Description

This fixes: #2647
Reverts: #2602
OBE: #2650

To be clear, the memory leak issue is not addressed because it is not a Flame issue. Flame properly calls the onRemove event and if it is needed to pass that on to children, users should do as documented: https://docs.flame-engine.org/latest/flame/game.html#lifecycle.

So if you are using GameWidget.controlled, add a tappable component and then remove the game, the lifecycle looks like:

flutter: onLoad
flutter: mount
flutter: onMount
flutter: attach
flutter: add has_tappable_components
flutter: refreshWidget 
// Remove Game
flutter: detach
flutter: removeGameStateListener
flutter: onRemove
flutter: remove

If you do not use GameWidget.controlled, add a tappable component and then remove the game, the lifecycle looks like:

flutter: onLoad
flutter: mount
flutter: onMount
flutter: attach
flutter: add has_tappable_components
flutter: refreshWidget
// Remove Game
flutter: detach
flutter: removeGameStateListener
flutter: onRemove
flutter: remove

If you do not use GameWidget.controlled, do not add a tappable component and then remove the game, the lifecycle looks like:

flutter: onLoadFuture
flutter: mount
flutter: onMount
flutter: attach
// Remove Game
flutter: detach
flutter: removeGameStateListener
flutter: onRemove
flutter: remove

Previously, the lifecycle would look like:

flutter: onLoad
flutter: mount
flutter: onMount
flutter: attach
flutter: add has_tappable_components
flutter: refreshWidget 
flutter: detach // These unnecessary calls have been eliminated
flutter: removeGameStateListener // These unnecessary calls have been eliminated
flutter: onRemove // These unnecessary calls have been eliminated
flutter: attach
// Remove Game
flutter: detach
flutter: removeGameStateListener
flutter: onRemove
flutter: remove

I have updated the below diagram for those who may need it in the future:

Flame and Flutter Events Fixed

Checklist

  • I have followed the Contributor Guide when preparing my PR.
  • I have updated/added tests for ALL new/updated/fixed functionality.
  • I have updated/added relevant documentation in docs and added dartdoc comments with ///.
  • I have updated/added relevant examples in examples or docs.

Breaking Change?

  • Yes, this PR is a breaking change.
  • No, this PR is not a breaking change.

Related Issues

@munsterlander munsterlander changed the title bug: Fix event lifecycle for gesture detectors bug: Fixes event lifecycle for gesture detectors Aug 18, 2023
@munsterlander munsterlander changed the title bug: Fixes event lifecycle for gesture detectors fix: Fixes event lifecycle for gesture detectors Aug 18, 2023
@munsterlander munsterlander changed the title fix: Fixes event lifecycle for gesture detectors fix: Prevent onRemove being called for initial Gesture Detector addition Aug 18, 2023
@spydon
Copy link
Member

spydon commented Aug 18, 2023

I think this can be done in a simpler manner by using the refreshWidget method that is passed in as a callback to GestureDetectorBuilder in Game and add a flag that says that it is an internal refresh and then when that flag is set just skip calling the methods that you are skipping now too:

  @internal
  bool isInternalRefresh = false;

  @internal
  void refreshWidget() {
    isInternalRefresh = true;
    gameStateListeners.forEach((callback) => callback());
    isInternalRefresh = false;
  }

What do you think?

flutter: onLoad
flutter: mount
flutter: onMount
flutter: attach
flutter: add has_tappable_components
flutter: refreshWidget
flutter: detach // These unnecessary calls have been eliminated
flutter: removeGameStateListener // These unnecessary calls have been eliminated
flutter: onRemove // These unnecessary calls have been eliminated
flutter: attach

Shouldn't the last attach call here also be removed?

@munsterlander
Copy link
Contributor Author

munsterlander commented Aug 18, 2023

@spydon This was my original plan of attack and I even went a step further by trying to identify the callback to check and see if it was a gesture detector, but the callback name is an internal id to Flutter (or maybe I don't know enough to get a simpleName or such).

refreshWidget is also called by the overlay manager and the mouseCursor, so not knowing the full lifecycle of those items, I didn't want to do a blanket override. I also wanted to do refreshWidget with an optional named parameter, but didn't know how to pass true for an invocation such as: late final GestureDetectorBuilder gestureDetectors = GestureDetectorBuilder(refreshWidget)..initializeGestures(this);

I am open to this way so any pointers are greatly appreciated!

As far as the last attach, I thought about that but with what I implemented, GRB was being set to null, so then it wouldn't be available to read the flag in onAttach. Having said that, if we can set the flag in Game and base it off of the refreshWidget, this can be overcome.

Edit: And I would say, I was originally looking at stopping the events in the GRB, but learned that is not a smart approach, so technically, there should be no reason why the flag cannot exist in Game as all detectors have a game reference.

Edit2: I have implemented a version of your suggestion and tested everything to confirm it still works as anticipated. All tests pass and the event lifecycle now removes the extra onAttach call. Its marked internal and can be overridden by passing a parameter in future additions to Flame. I have updated the description and the diagram.

@munsterlander munsterlander changed the title fix: Prevent onRemove being called for initial Gesture Detector addition fix: Prevent onRemove/onDetach being called for initial Gesture Detector addition Aug 19, 2023
packages/flame/lib/src/game/game.dart Outdated Show resolved Hide resolved
packages/flame/lib/src/game/game.dart Outdated Show resolved Hide resolved
@munsterlander
Copy link
Contributor Author

Analyzer failed after direct committing your changes from GitHub. When pulled, its passing local. Let me update branch and see what happens.

@spydon
Copy link
Member

spydon commented Aug 20, 2023

Analyzer failed after direct committing your changes from GitHub. When pulled, its passing local. Let me update branch and see what happens.

How strange, the warning wasn't even about any code that you have touched. 🤷‍♂️

@munsterlander
Copy link
Contributor Author

munsterlander commented Aug 20, 2023

Analyzer failed after direct committing your changes from GitHub. When pulled, its passing local. Let me update branch and see what happens.

How strange, the warning wasn't even about any code that you have touched. 🤷‍♂️

Yeah, I pulled the last updates down, but I can't figure out what is going on. I can't see the logs or I don't know how to see the Analyzer logs in GitHub. What am I missing on this?

OH! The little triangle. Ok, I see the issue. So with your suggested name change, it just needed to be updated in the other file.

@spydon
Copy link
Member

spydon commented Aug 20, 2023

OH! The little triangle. Ok, I see the issue. So with your suggested name change, it just needed to be updated in the other file.

Yeah that was one thing, but there also was a warning about a file in Jenny. Let's see if that warning pops up again now.

…lame-engine#2659)

This adds, imo, the missing piece in the event lifecycle based on all the other lifecycle work I have been doing.

Currently, when dispose is called in game_widget.dart, it calls disposeCurrentGame which has the call for onRemove, at issue though is, disposeCurrentGame is also called by didUpdateWidget if the oldWidget.game != widget.game. It may not be needed, but I feel like because FlameGame is inherently a stateless set of widgets (children of the LeafRenderObjectWidget), the hook for onRemove is muddled with what is NOT actually a dispose event. So my thought is, introduce the onDispose method to game which gets called by making disposeCurrentGame({isDispose = false}) then in the actual overridden dispose method, pass that as true, so we can then call currentGame.onDispose() after the call to currentGame.onRemove().
@munsterlander
Copy link
Contributor Author

class _RecordingDialogueViewAsMixin extends _SomeOtherBaseClass
    with DialogueView {
  _RecordingDialogueViewAsMixin([this.waitDuration = Duration.zero]);
  final List<String> events = [];
  final Duration waitDuration;

This is the warning.

@spydon spydon merged commit d172146 into flame-engine:main Aug 20, 2023
6 checks passed
@munsterlander munsterlander deleted the flameLifecycleFixes branch August 20, 2023 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The onRemove of game is be called when game is running.
2 participants