StarlingGraphics / Starling-Extension-Graphics

flash.display.Graphics style extension for the Starling Flash GPU rendering framework
https://github.com/StarlingGraphics/Starling-Extension-Graphics/wiki
MIT License
285 stars 88 forks source link

memory leak found in Graphics #58

Closed haskasu closed 10 years ago

haskasu commented 11 years ago

whenever it goes _currentFill=null; it causes the memory leak because the _currentFill is not disposed.

It can be reproduced by calling shape.graphics.beginFill() shape.graphics.endFill() in an onEnterFrame function.

my current fix is to remove _currentFill=null in the endFill function and do if(_currentFill) { _currentFill.dispose(); _currentFill = null} before every _currentFill = new FIll(). and _currentFill = null;

haskasu commented 11 years ago

and in Graphics.clear() if ( child is Graphic ) {...} should be placed before child.dispose(); because child.dispose() does cleanup the material without disposeTextures.

IonSwitz commented 11 years ago

Nice catch. Thank you, @haskasu

Edit: That child.dispose should be moved after the if ( child is Graphic ) makes better sense from a programming design perspective as well. A disposed object shouldn't really be used after dispose is called. :)

Edit Again: Wouldnt this update to endFill solve the problem in the first post?

if ( _currentFill != null ) { _currentFill.dispose(); _currentFill = null; }

I will dig some more

haskasu commented 11 years ago

i tried to do the same in endFill, but apparently, something somewhere is referencing the _currentFill, so it crashes if we dispose _currentFillt in endFill().

IonSwitz commented 11 years ago

yeah, I just noticed. The fill is (of course) used in render(), so we can't do that.

The only solution then would be to do what you said, dispose the old _currentFill before every new allocation of _currentFill

IonSwitz commented 11 years ago

I want to solve this by adding the protected method:

protected function createCurrentFill() : void { if ( _currentFill != null ) { _currentFill.dispose(); } _currentFill = new Fill(); }

and replacing every instance of _currentFill = new Fill(); with this method.

This way, we solve the dispose problem AND we allow for future overrides, in case someone wants to extend the class.

What do you think?

haskasu commented 11 years ago

sound good! there is only one thing left. the graphics doesn't have a dispose() function. i guess we can dispose the _currentFill, when Shape.dispose()

IonSwitz commented 11 years ago

I think the design should be like this:

override dispose on Shape. and add dispose on Graphics.

in the dispose method on Shape, we also dispose of the Graphics, and, in that chain, we null out the current fill, just like mentioned above.

It makes more sense to me to have the Shape and the Graphics act as "one unit". Only disposing of the Graphics class makes for a broken Shape, basically, since the Graphics instance is created in the constructor of Shape.

So, I suggest adding this to Shape:

override public function dispose() : void { _graphics.dispose(); super.dispose(); }

and then a dispose method on Graphics that does:

public function dispose() : void { clearCurrentFill();

clear();

}

clearCurrentFill is designed like this:

protected function clearCurrentFill() : void { if ( _currentFill != null ) { _currentFill.dispose(); }
}

and, finally, createCurrentFill() looks like this:

protected function createCurrentFill() : void { clearCurrentFill(); _currentFill = new Fill(); }

I think this covers all the bases we need, and it makes the Shape class disposable in the way you would expect it to behave.

IonSwitz commented 11 years ago

If you want to, @hakasu, please have a look at this code check in, if you can. I haven't created a pull request and a merge to the StarlingGraphics branch, so please check iit out in my IonSwitz playground. :)

haskasu commented 11 years ago

this fix looks good i suggest in the function clearCurrentFill() : we set the _currentFill=null so it wont get disposed twice for some reason.

IonSwitz commented 11 years ago

OMG, ofcourse. Haha. I copied the code from the code I initially had in createCurrentFill(), where the next line would be _currentFill = new Fill();

so , in that case the clearing was pointless. But you are absolutely right. :)

haskasu commented 11 years ago

just checked the diff, it looks nice.

in Shape.dispose(), maybe you want to do if(_graphics) { _graphics.dispose(); _graphics = null } the shape would be unreferenced so it may not matter that much. just the styling~

IonSwitz commented 11 years ago

I agree. I figured the _graphics has a 1-to-1 lifespan as the Shape, since it is created in the constructor, but still, you have a point. I will add that.

Ofcourse, we cant protect ourselves completely. The Shape exposes the Graphics class, and anyone with a destructive bent can call dispose on that object from outside of the Shape. We can't solve all scenarios (we can, but it takes too much work), but rather have to expect reasonable usage of the classes.

IonSwitz commented 11 years ago

I have checked in and merged this Pull Request to the main branch. They look solid and reasonable, and I have a hard time to see that anyone would object. The way the implementation works now seems very backwards compatible, and I really hope this will not break anything for anyone.

@haskasu , could you please do some verification on your own, so that it is not just me checking on this? :)

IonSwitz commented 11 years ago

Shoot.

In this thread, there is a similar discussion about resource leaks.

https://github.com/StarlingGraphics/Starling-Extension-Graphics/issues/32

Not sure if the fixes above breaks the behavior mentioned in issue 32. We really should put together a suite of tests beyond the test samples

IonSwitz commented 11 years ago

Yeah, this is a problem. The dispose method kills the material, just as @jonathanrpace mentioned in the other thread.

The solution, the way I see it, is to add a shape.graphics.releaseMaterial() method that sets the material to null on the _currentFill and _currentStroke

This should be called before calling dispose() if you dont want to kill the material you've set.

Or we go back to the way it used to be, with the leak in place. Gods, I feel dumb now.

IonSwitz commented 11 years ago

@jonathanrpace @robsilv I think I have screwed up, and I am unable to undo the mess. I have tried to revert the master branch to the 22nd of September, since the stuff I did from 24th of September and onwards potentially breaks backwards compatability, However, I fail miserably.

robsilv commented 11 years ago

Hi @IonSwitz, don't worry! Everything is stored in Git so we haven't lost anything. I'd much rather there were skilled people frequently updating the library and making the odd mistake than have no one working on it! ;-) If you're having trouble wrangling with Git, have you tried simply browsing the code from the previous commits, then copying and pasting the old classes?

Note how there's a "browse code" button on each commit in the interface. If you update the classes to be how they were before, then create a new commit with these changes, that should work!

IonSwitz commented 11 years ago

@robsilv @haskasu I have "undone", by overwriting the code to the former functionality now. I believe we still have to address the resource leakage, but this way breaks backwards compatibility, from what I can see. ( See discussion in issue #32 )

Setting the lineMaterial, etc, should not be considered as handing over ownership of the material resource to the Shape, so the resources needs to be tracked separately. If we leak internal objects this way, we need to find a solution that works better without destroying external, non-owned resources.

jonathanrpace commented 10 years ago

Leak fixed https://github.com/StarlingGraphics/Starling-Extension-Graphics/commit/853c9410d2bd3314678f979012e847acca7fc320

Note: This is quite a big refactor. Resource management is now much more sane, however this has come at the expense of beginBitmapFill() - use beginTextureFill() instead.

Allowing the API to create textures for you is just asking for abuse. I'm guessing everyone here is already very protective over how and when they create their Textures anyway, so won't mind this slight inconvenience.