Open imaNNeo opened 1 year ago
I would say that this is up to the user to handle.
Because we can't know whether the game is going to be re-used later or not, and then the state shouldn't be changed. For example a game might not only contain of a FlameGame
class that is always active throughout the life of a game session, there might for example be other Flutter screens in a game and then the game shouldn't remove all components when you navigate away from a screen showing the GameWidget
.
I agree with @spydon , we can't know, and it should be up to the user, just like flutter's classes like animation controller, text editing controller and other controllers, wher you need to call dispose yourself
But I wonder if we could have a dispose method on game? That would instead of going through the component lifecycle, just iterate over the components and their remove method
But I wonder if we could have a dispose method on game? That would instead of going through the component lifecycle, just iterate over the components and their remove method
That could be a good idea! I don't see any harm in doing it through the lifecycle though.
The only concern Thani have doing in the life cycle is that it is an async op right? Maybe if the game is "gced" before everything is disposed we could still end up with resources not being disposed?
I am just speculating at this point, so if that is not a possibility then yeah, we could do through the lifecycle
But I wonder if we could have a dispose method on game? That would instead of going through the component lifecycle, just iterate over the components and their remove method
It makes sense. Calling dispose()
is like a convention in the Flutter world.
The only concern Thani have doing in the life cycle is that it is an async op right? Maybe if the game is "gced" before everything is disposed we could still end up with resources not being disposed?
Why is it an async operation?
As I see both removeAll(children)
and processPendingLifecycleEvents()
are void functions.
You are correct, it is not async, I am confusing with the game.ready method that we using in testing.
So maybe have a dispose method just to follow flutter standards, call remove of their children and dispose all the images cached that it may contain would be enough I guess
This would imply another lifecycle method on components since component removal is not necessarily the same as disposal. Components can be removed due to game logic, but a game could only be disposed of due to the widget lifecycle.
So based on my recent pr and discovery / charting of the life cycle, I also feel into the trap of thinking onRemove was not properly being called only to later realize that FlameGame is a component and should be treated like other components, meaning just because the game has been removed from the flutter widget tree, does not mean that the actual game state should be wiped by default. Counter to this enhancement was the bug I was fixing, in that they wanted to attach / detach the game and come back to where it was left. So implementing this enhancement would break that work flow.
I do think that for GameRenderBox, there could be a passed super parameter to dispose all children and process life cycle events. That would then solve both cases and allow the user the option of what to do. Thoughts?
I still think of it like this: https://github.com/flame-engine/flame/issues/2403#issuecomment-1466052287
We could create a dispose method, but there is no natural place to call it automatically from, so the user would have to trigger it manually anyways and then they can just as well create that dispose method themselves.
What we could have in such a pre-created dispose method is emptying the caches though.
Was really surprised that such functionally was not implemented, as game objects can be changed frequently. Thankfully it seems that all flame objects are components, and it was not so hard to implement dispose function recursively:
extension ComponentExt on Component {
void dispose() {
for (final c in children) {
c.dispose();
c.onRemove();
}
}
}
Current bug behavior
When the FlameGame gets disposed (page closes), it should remove all components of itself (call
onRemove()
on all the sub-components to let them clear their resources) First of all, when we close the game page (which contains a game), it does not automatically call remove on the child objects, to do that we need to write the below code manually:This
onRemove()
method on the game that extendsFlameGame
gets called when the page closes, but when we callremoveAll(children);
, it adds the components to thelifecycle._removals
queue. But it never processes this queue. Because GameWidget is removed from the widget tree and there is no other tick to calllifecycle.processQueues()
. So it leads to a memory leak in some components such asFlameBlocListenable
becauseonRemove
will never be called and subscription leaks.Expected behavior
I expect that FlameGame call
removeAll(children);
under the hood and process the removal queue before the game disposes to allow all sub-components to free their resources in theonRemove()
function.Steps to reproduce
I wanted to put a Zapp link, but it seems Zapp is down at this moment, so there is a main.dart file here. You just need to add these dependencies to run the reproducible code:
Steps:
HomePage
opensPlay
button on the HomePage, thenGamePage
opens.LeakingCounterComponent.onNewState(1)
prints in the screenMyGame.onRemove()
is called, butLeakingCounterComponent.onRemove()
didn't call, so it leaked through it's subscription.Play
andplus
button againLeakingCounterComponent.onNewState(2)
printed 2 times (because there is a leak)Play
andplus
button againLeakingCounterComponent.onNewState(3)
printed 3 times (because there are two leaks)As a workaround, I wrote the below code:
So with this code,
LeakingCounterComponent.onRemove()
gets called everytime we close the game pageFlutter doctor output