flixel-addons
flixel-addons copied to clipboard
FlxSpine leaks memory
After instantiation any FlxSpine object will keep its allocated memory even after being removed from the FlxState, destroyed and having its reference nullified.
Steps to reproduce:
- Download the last version of the FlxSpine demo from github: FlxSpine Demo
- Add the following code snippet to
MenuState.updatemethod:
if (FlxG.keys.justPressed.D)
{
if (spineSprite != null) {
remove(spineSprite);
spineSprite.destroy();
spineSprite = null;
openfl.system.System.gc();
}
}
- Compile and run the demo. I'm almost sure this happens with any OpenFL version (next and legacy), but here i'm using
Lime 2.6.8andOpenFL 3.3.8running this from command line:lime test windows -Dnext - Using Windows Task Manager, monitor the amount of memory the application is using. Press
Dto destroy the spineSprite and free it's memory - you'll notice there will be no change in the amount of memory the application is using.
If you really wanna be thorough you can modify MenuState even further to see how much memory gets allocated when spineSprite comes into play. To do that simply comment lines 17 through 19 of the class and add another snippet to the update method:
if (FlxG.keys.justPressed.C) {
spineSprite = new SpineBoyTest(FlxSpine.readSkeletonData("spineboy", "spineboy", "assets", 0.6), 300, 440);
spineSprite.antialiasing = true;
add(spineSprite);
}
This way you can see how much memory the application consumes before instantiation, after instantiation (by pressing C) and after destruction (by pressing D).
I figure this might be a problem with the spinehaxe library, but i wanted to create the issue here since Flixel's community is much more active.
Using FlxSpine like this will allocate more memory very quickly. In my case i'm using ~10 FlxSpine instances per level of the game (each level a FlxState). After switching states at the end of the level there'll be around 20MB of memory which never gets collected. Playing for some time will cause slowdowns, graphics not being rendered (they become invisible, which makes me think the leak is in video memory (?)) and then a crash.
I really need all the help i can get with this (and as soon as possible), so i'll gladly pay a bounty (the amount can be discussed privately) to anybody who can solve this problem, including making pull requests to spinehaxe if they are necessary.
I'm also pinging people who might be interested in this issue: @Beeblerox @bendmorris
It looks like there may be a leak somewhere in SkeletonJson.readSkeletonData. I can reproduce this using spinehaxe + HaxePunk on a Mac and I'm still looking for the specific issue. For now depending on your use case, a workaround might be to cache the results of readSkeletonData in a static variable and only call readSkeletonData once for each kind of sprite. (Probably a good idea to just do this anyway, since the result doesn't change.) When doing this, I no longer see the leak.
If that's indeed the case, it should be fairly easy to cache the results of readSkeletonData in FlxSpine.readSkeletonData, and keep a map of loaded skeleton data. That would be a good change at the engine level, but we should still investigate the cause of the leak.
Unfortunately simply caching the skeletonData won't be enough for me. I need a proper way to completely free the memory spine is using.
I have different game states with different spine animations which play depending on what the player does. If all of these animations are cached together the game will simply crash on mobile (some of the animations are full screen "cutscenes" which are shown during game over, etc).
Btw i'm already caching what i can (i.e. spine animations which will be played on all levels) but i still need a way to clear everything after an intro or game over scene.
Makes sense. For cases with a lot of different animations caching would be a bad idea, so I wouldn't make that change at the engine level.
I'm going to keep digging into this tonight. My best guess right now is that this is a hxcpp GC issue, so you might want to see if Hugh has any insight. I haven't seen anything in the code that would be holding onto references.
One thing that seems very suspicious to me and could / should be changed to be something better is that RegionAttachment implements Dynamic<Dynamic>.
This is actually used in HaxeFlixel by creating a dynamic field named wrapperStrip to store the reference to a FlxStrip inside the RegionAttachment. This can be seen in FlxSpine.renderWithTriangles, lines 230-240:
if (region.wrapperStrip != null)
{
wrapper = cast region.wrapperStrip;
}
else
{
var atlasRegion:AtlasRegion = cast region.rendererObject;
var bitmapData:BitmapData = cast(atlasRegion.page.rendererObject, BitmapData);
wrapper = new FlxStrip(0, 0, bitmapData);
region.wrapperStrip = wrapper;
}
When trying to test hxcpp collection using the HXCPP_GC_MOVING flag the application crashes
on this line:
bool tmp13 = (region->__Field(HX_HCSTRING("wrapperStrip","\x85","\xc0","\xdb","\x32"), hx::paccDynamic ) != null()); HX_STACK_VAR(tmp13,"tmp13");
Which translates to FlxSpine.renderWithTriangles, line 276. I think that instead of using this dynamic field we could implement better HaxeFlixel support inside spinehaxe using the same structure it already has for OpenFL (see spinehaxe OpenFL support).
ok, will look into it tomorrow evening
What is your concern with using implements Dynamic<Dynamic>? Is this a bug in hxcpp? Just to be clear, removing it does not seem to affect the leak.
I'm not sure to be honest - i think simply implementing Dynamic<Dynamic> is not a problem, but i wonder if using it to set dynamic fields like the wrapperStrip in FlxSpine and never destroying and setting it to null could make it never be collected by the gc.
I'm not entirely sure if the leak is caused by spinehaxe or FlxSpine. There seems to be some problems with hxcpp garbage collector as well. I created some dispose functions in my fork of spinehaxe to try and free any possible references that could possibly be forgotten. This plus using a different version of hxcpp (with the HXCPP_GC_MOVING flag) reduced the leak to a much more manageable level.
I'll leave some notes here to help anyone else trying to troubleshoot this:
- I can reproduce this issue using spinehaxe with my own engine. This suggests that the issue is not specific to FlxSpine. The class I'm using is mostly based on this: https://github.com/bendmorris/SpinePunk/blob/master/src/spinepunk/SpinePunk.hx
- I'm not using any of the dynamic RegionAttachment fields, so that's probably not the issue (or not the only one.) I also removed
implements Dynamic<Dynamic>completely for one test and it didn't make a difference. - I updated hxcpp due to reports of a memory leak (https://github.com/HaxeFoundation/hxcpp/issues/315) and it seems like the leak may be less severe but is still present.