Corona/LUA Runtime - Great optimization potential?

Yeah I didn’t appear to have issues with multiple calls until the calls were happening more frequently such as every 2 seconds. Certainly everything is multiplied with multiple skeletons.

Also there is no resource drain if you setup a test condition that plays animations within onComplete e.g.

state.onComplete = function (trackIndex, loop)
    state:addAnimationByName( data )
end

The drain only occurs when you call the animation function.

Anyway, here’s my Spine library, SpineTrigger, collected with an example project:

https://dl.orangedox.com/fwtujtJVvpG2Ey8npr/spineTrigger.zip

This one creates 6 dragons and sets their animations off. When they each trigger onComplete, they call a function which changes the name of the animation and sends it back into the animation function.

There is a performance function which displays framerate and luaMem. You’ll see this quickly degrade from 60fps down to 1 fps. The memory continues to balloon. So something is compounding somewhere each time the animation function is called. I would have blamed the global nature of Spine but it might be something more or less straight forward…

Steal away. I’ll steel myself for the feedback :slight_smile:

I have not enough time to share my SpineWrapper with you, as it fullfills the very specific needs of my project.

But I’d like to share my optimized corona spine runtime with you:

http://torben-ratzlaff.de/stuff/spine%20remastered%20vs001.zip

Just call the spine librabry with require(“spine.spine”)

@kilopop

Concerning the memory issues, it seems to be the fault of your wrapper.

If you call

state:setAnimation(...)

directly inside the “dragon” function the main lua, (just add the need parameters to the event)

there is no memory leak and the framerate stays at 60.

I believe the reason is the “updateState” function.

As calling timers and Runtimes in a quasi recursive function is not a good idea in most cases.

Hope I could spare some time in the future to optimize further.

@torbenratzlaff  Thanks for sharing. I’ll have a look at this today. I’m assuming you would be okay with sharing these updates on gitHub somewhere?

@kilopop, yep, what @torbenratzlaff said… You are creating a new infinite timer every time you call updateState() and only removing it once… I did a quick hack to get it working…

 if not self.removeEL then \_gal.timerStash[name] = timer.performWithDelay( 1, updateState, -1 ) self.removeEL = updateState --allow instance to keep reference to this for pause/ resume purposes --Runtime:addEventListener("enterFrame", updateState) print("Added Timer") end

Replace what you have around line 380 with this, and a new timer will only get created when removeEL doesn’t exist. FYI, I don’t know if I’m a fan of the timers so I’d go back to your enterFrame events. :slight_smile:

I use one enterframe per spine animation, but I’ve thought about optimizing the wrapper to have a single enterFrame() event so things like frame delta only need to be calculated once. That may help your performance too.

@no2games

Yeah, sure I’m ok with sharing it on github.

But I would like to do that myself in the next couple of days.

Sounds good. I’ll hold on to my revisions and submit them when you post.

Thanks!

Hey guys - many thanks for taking a look at the memory issues of my module. @no2games your amendment works a treat. It wasn’t enough that the timer was referred to by the same name on each recursion so that’s where I went amiss.

@torbenratzlaff - I’ll test run your localised version of spine as soon as i get the chance. Have you seen about getting it accepted into the official runtimes? I would suggest contacting Esoteric directly through their forums. Otherwise as soon as those runtimes are updated, all your ammendments will need to be made again…

Hey guys,

sorry it took me so long to upload to github.

Here’s the link of the fork.

https://github.com/torbenratzlaff/spine-runtimes

Would be great if you contribute your changes/feedback there :slight_smile:

Looks good. I’m on vacation for a couple weeks, but I’ll try to get my wrapper updated for this and add the pre-calculated sin/cos/rad functions as soon as I can. Nice work.

Would be great for the performance to implement the lookup table.

But I wonder, if slow rotation would look a bit strange, because you round the angle to the next integer.

Soo, anything new on this front?

for what it’s worth…, just be sure to benchmark any proposed trig lookup on an actual device, cuz it often doesn’t pay off.  math.cos (et al) are native calls, whereas a table lookup needs:  conversion of the angle to index, a proper modulo handling negative values, and finally the table indexing itself.  and all that interpreted indexing code often works out to be slower than just calling math.cos directly!  so you may lose accuracy AND performance.

I did update Bone.lua to use a single lookup table, but I agree since Corona itself is capped at 30 or 60FPS unless you have performance issues on a device there’s no reason to implement it. And you do see “clicking” into angles with slow rotations of big objects.

Here’s a snippet of the lines I added. It’s pretty easy to comment/uncomment to see it both ways.

I really haven’t had a chance to evaluate your fork of the spine runtimes, because I’m working on a game release right now and we’re trying to lock things down and I don’t want to introduce any new code. I will take a peek the second things slow down.

local Bone = {} local lookup = {} for i = 0, 359 do lookup[i] = math.sin( math.pi / 180 \* i ) end function Bone.new (data, skeleton, parent) if not data then error("data cannot be nil", 2) end if not skeleton then error("skeleton cannot be nil", 2) end local self = { data = data, -- continued -- self.worldScaleY = self.scaleY self.worldRotation = self.rotationIK self.worldFlipX = skeletonFlipX ~= self.flipX self.worldFlipY = skeletonFlipY ~= self.flipY end --local radians = math.rad(self.worldRotation) --local cos = math.cos(radians) --local sin = math.sin(radians) local angle = (self.worldRotation - self.worldRotation % 1) % 360 local cos = lookup[(angle+90) % 360] local sin = lookup[angle]

Searched the web for some validation and found a few (not Corona) test that other developers shared.

The conclusion seems to be, that it depends which framework you use and how it handles the math calls.

But the difference is so small in comparison to other optimizations (e.g. localising) that it isn’t worth the effort and downsides to use a lookup table instead of a localized math function.

@no2games

did you upload your spine wrapper to github ? 
i need for a good spine wrapper 

Regards 
Jens

@blomjens1 Not pushed it github yet, but it’s pretty much the code I posted on the first page of this thread…

ok thanks!

The latest spine runtimes can be found on Esoteric Software’s gitHub: https://github.com/EsotericSoftware/spine-runtimes 

Yeah, that’s the version I was referring to.

Spine is entirely global as well as you can see in spine-corona/spine.lua…

Yeah, been experiencing very sluggish behaviour when using more than one skeleton - especially as new animations are being called when onComplete is triggered by a previous animation (or any other event start, onEnd, events). After continually doing this, frame rate drops right down to 2, lua memory is bulked. Upon changing the scene, the frame rate stays low and the lua memory still holds the memory it picked up from the spine animation scene. Haven’t found this on a looping animation, only one that plays a new animation after an event…

Perhaps a table somewhere is just collecting animation data and storing it globally? Any ideas?

Torbenratzlaff - be good to know how you get on. In the meantime, I’ll start bug busting further…

So far I’m done with the following:

  • adding metatables wherever possible and necessary

  • localizing global variables and function

  • localizing variables in time critical processes (e.g. loops, runtime)

  • removing unnecessary code (e.g. mesh transformation)

  • wrinting a wrapper, so skeletonData, spritesheets and others have to be only created once

  • creating a new skeleton from preexisting skeletonData with a single line of code

Now I ask myself, what to do with those changes. Is there a spine “official” in the Corona forums? Or should I make a pull request to the above mentioned GitHub project?

@kilopop

Concerning the memory leak you mentioned. Could you go a little bit more into detail how to reproduce that bug?