• Runtimes
  • RESOLVED [Corona] Locating error, sim/device inconsistent

Related Discussions
...

I am having some issues with animations not running and with runtime errors on devices. I'll try to detail the issues and provide appropriate code. Maybe I am overlooking something simple that someone more familiar with spine will recognize.

Running in the Corona simulator, most animations run fine, including unit idle animations, etc. The only visible issue is that some attack animations simply do not play at all. There is inconsistency in this as the attack will play for one unit but not the other, even though they are loading the same spine json and visual assets. There are NO errors logged in the console or any indication that there is a problem. This is also a recent issue as at some point, all animations were running fine.

When running on android devices, there are many issues with the animations. Idle animations are seemingly random, some animate some don't. There is no consistency as to which work based on unit types, positions, etc even between turns. Some attachments do not appear at all or disappear after a non-idle animation. The non-animating attacks from the sim still do not animate. Reading the logs show this runtime error being spammed to the console:

I/Corona (20725): Runtime error
I/Corona (20725):
I/Corona (20725): stack traceback:
I/Corona (20725): [C]: in function 'removeSelf'
I/Corona (20725): ...-Brawl(default)\Monster-Brawl\spine-corona\spine.lua:110: in function 'updateWorldTransform'
I/Corona (20725): ...onster-Brawl(default)\Monster-Brawl\modules\unit.lua:264: in function 'updateAnim'
I/Corona (20725): ...Monster-Brawl(default)\Monster-Brawl\gamemanager.lua:671: in function <...Monster-Brawl(default)\Monster-Brawl\gamemanager.lua:665>
I/Corona (20725): ?: in function <?:218>

The code traces back to this line in spine.lua:

if image and image.attachment ~= attachment then 

---

 Attachment image has changed.
	if self:modifyImage(image, attachment) then
		image.lastR, image.lastA = nil, nil
		image.attachment = attachment
	else 

---

 If not modified, remove the image and it will be recreated.
		image:removeSelf()      

---

 This is line 110************
		images[slot] = nil
		image = nil
	end
end

My enterframe code calculates delta once, then calls this function for each existing unit:

function unit:updateAnim(delta)
    self.sprite.animState:update(delta)
    self.sprite.animState:apply(self.sprite)
    self.sprite:updateWorldTransform()
end

Here is how the spines are being created within the unit module:

function unit.new(unitType, iff, position, hp, cd, eq)     
    
local newUnit = {} createUnitData(newUnit, unitType, iff, position, hp, eq) newUnit.sprite = createUnitSpine(newUnit) return setmetatable(newUnit, unit_mt) end

The createUnitSpine function is local to the module. The relevant code portions look like:

local function createUnitSpine(newUnit)
    
local spine = require('spine-corona.spine') local spinePath = 'assets/visual/animations/'..newUnit.model.spine..'/' local skinPath = spinePath..newUnit.model.skin..'/' local jsonSpine = spine.SkeletonJson.new() jsonSpine.scale = 1 local skeletonData = jsonSpine:readSkeletonDataFile(spinePath..newUnit.model.spine..'.json') local sprite = spine.Skeleton.new(skeletonData) function sprite:createImage(attachment) return display.newImageRect(skinPath..attachment.name..'.png', attachment.width, attachment.height) end sprite:setToSetupPose()

--- AnimationStateData defines crossfade durations between animations sprite.animStateData = spine.AnimationStateData.new(skeletonData)
--- Set up to create transitions between ALL animations in table below local animStates = {'idle','hurt','select','dead'} local animTransTime = 0.3 --- common length for all transitions for i=1,#animStates do for j=1,#animStates do sprite.animStateData:setMix(animStates[i], animStates[j], animTransTime) end sprite.animStateData:setMix(animStates[i], 'attack1', animTransTime) sprite.animStateData:setMix(animStates[i], 'attack2', animTransTime) sprite.animStateData:setMix(animStates[i], 'attack3', animTransTime) sprite.animStateData:setMix(animStates[i], 'attack4', animTransTime) end
--- AnimationState keeps queue of animations and applies via crossfading sprite.animState = spine.AnimationState.new(sprite.animStateData) sprite.animState:setAnimationByName(0,'idle', true) return sprite end

Does anything look out of sorts? Why am I not seeing any runtime errors in the simulator? Any help or advice is appreciated. Thanks.

Nothing stands out as being a problem in the code you posted. AnimationStateData has a defaultMix field so you don't have to set the mix for all combinations. How are you changing animations? Note you can use a single SkeletonData with many Skeleton instances (assumign of course the skeletons use the same JSON). This avoids loading the same data multiple times.

The logged error seems to be in C code, inside image:removeSelf(). The Corona guys might have some insight as to why this happens. I would expect this function to never crash, so I guess there is a bug in their stuff. 🙁 We might be able to work around the state that causes the function to crash.

Maybe the image has already been removed? I don't see how this should happen though. Maybe a parent of the image has been removed? Maybe the device is out of memory? This probably wouldn't explain the simulator behavior, if is the same issue.

The best way to continue is to figure out the simplest example program that shows the issue. Maybe you can start by modifying the spineboy spine-corona example to use your skeleton and then try adding from there until you get the bad behavior. Eg, use newImageRect, animation changes, add multiple skeletons, etc one at a time.

It's strange the simulator has a problem. It could be a different problem, or maybe its the same Corona bug manifesting differently? I don't mean to keep blaming them, it's just how it seems. To debug this further it's probably best to add logging. Eg, log the attachment for a slot where the attachment isn't appearing correctly to see if the slot actually has an attachment. If the slot has the correct attachment and it just isn't being drawn, add logging for when that slot is drawn to see if the image is being created, etc.

Nate,
Thanks so much for your response! Here are some answers to your suggestions:

Nate wrote

AnimationStateData has a defaultMix field so you don't have to set the mix for all combinations. How are you changing animations?

Good to note the default mix. I assume you are talking about this one here:

	local self = {
		animationToMixTime = {},
		skeletonData = skeletonData,
		defaultMix = 0}

However, I am not currently mixing all animations. I've found that if the attack animation includes a projectile, mixing from the attack back to idle will mess up the projectile's animation. It may actually be possible to correct this by using tracks, but this is the solution for now.

Animations change using statements similar to the initial one in the new function:

    self.owner.sprite.animState:setAnimationByName(0,self.anim, false)
    self.owner.sprite.animState:addAnimationByName(0,'idle', true)
Nate wrote

Note you can use a single SkeletonData with many Skeleton instances (assumign of course the skeletons use the same JSON). This avoids loading the same data multiple times.

Units are unique per player, so there will only ever be 0-2 in any game. However, if I preloaded all skeletonDatas into a table ahead of time, this could optimize opening and closing the game module.

Nate wrote

The logged error seems to be in C code, inside image:removeSelf(). The Corona guys might have some insight as to why this happens. I would expect this function to never crash, so I guess there is a bug in their stuff. 🙁 We might be able to work around the state that causes the function to crash.

I will definitely post this to the Corona forum as well.

Nate wrote

Maybe the image has already been removed? I don't see how this should happen though. Maybe a parent of the image has been removed?

To me, this is the most obvious offense, but I also do not see how it would occur. All manipulation to the skeleton is done through the runtime, I am not attempting anything programmatically. If this is the issue, it would explain sim/device inconsistency.

Nate wrote

Maybe the device is out of memory?

I doubt this. Like you said, the sim mostly works fine. Running a memory monitor shows low memory use and no leaks associated with the animations. On the device, memory use continues to rise, even just sitting and having the idle animations playing (I'm assuming this is due to the continuous runtime errors).

Nate wrote

The best way to continue is to figure out the simplest example program that shows the issue.

I did write a basic animation viewer so we could view our animations without having to actually play the game. I am hoping to use that as a base for debugging this isse. However, I was converting it to use the new spine runtime and had an issue locating a name field. With the last runtime I was using:

self.animState.animation.name

This would access the name of the animation currently playing. It now gives me 'animation' as nil. Where can I find this name?

Nate wrote

It's strange the simulator has a problem. It could be a different problem, or maybe its the same Corona bug manifesting differently?

From my experience with the simulator, I would guess it's the same issue. The simulator (on Windows at least) is much more forgiving of warnings/errors than devices. Our tutorial scene worked perfect on the sim, but was ruined on devices because we had one image with .PNG instead of .png. Using remove methods on non-existing Corona objects doesn't typically give an error, so it could be that the asset is previously removed as mentioned before.

Nate wrote

To debug this further it's probably best to add logging. Eg, log the attachment for a slot where the attachment isn't appearing correctly to see if the slot actually has an attachment. If the slot has the correct attachment and it just isn't being drawn, add logging for when that slot is drawn to see if the image is being created, etc.

Good thoughts, I will work on this. Again, thanks for your help.

joe-crg wrote

I've found that if the attack animation includes a projectile, mixing from the attack back to idle will mess up the projectile's animation. It may actually be possible to correct this by using tracks, but this is the solution for now.

Once cause might be that Spine rotates a bone in whichever direction is the shortest rotation. Maybe the ending position of your attack animation makes the mix to the idle animation choose the wrong rotation direction for some bones, which is unlikely to look good. It's kind of sucky, but you could use an animation for this transition instead of mixing.

I was converting it to use the new spine runtime and had an issue locating a name field. With the last runtime I was using:

self.animState.animation.name

This would access the name of the animation currently playing. It now gives me 'animation' as nil. Where can I find this name?

It's slightly different with tracks:

animState:getCurrent(trackIndex).animation.name

But be careful, getCurrent can return nil if no animations are currently playing on that track!

Let me know if I can be of any help figuring out the issue. Good luck!

An update:
Building the "goblins" example to the device worked with no issues. It runs fine and the log is empty. However, when I replace the goblin with my own character (making only minimal changes, such as filepaths, etc) I again get the runtime error for removeSelf @line 110. This error is still present on device logs only, and not in the simulator. If so, does that mean the problem could be in our actual animations? Could this be a side effect of non-uniform scaling? If this is the direction I should be looking, I may ask you to take a look at our skeleton jsons and/or speak with our animator. Thanks.

Non-uniform scaling would rendering incorrectly, it wouldn't cause any errors.

I would guess it has something to do with attachment change keys. The goblin has attachment keys to show/hide the eyes, maybe your animation excersizes something the goblins example doesn't.

Could you reduce the complexity of your skeleton to further narrow it down? Eg, remove all animations that aren't involved in the reproduction steps and see if it still occurs. Next you could binary search the keys for the problematic animation(s): remove half of the keys to determine which half has keys that cause the problem, then remove half of those keys, etc until you can see what exactly causes it, then we can investigate what code changes we might make to avoid the crash.

I can take a look at your project or JSON, though needing to deploy to a device to see the error makes it harder for me to try.

Okay, I found the issue. The device was not locating certain attachment images. The device is case sensitive, but the simulator is not, which is why it was working on one, but not the other.

Our team works remotely using both Dropbox and Github. The animations are completed in Spine on the dropbox side and work fine using correct filenames. However, when pushing the new animations using Git, if the asset already exists in the repository, it uses the pre-existing case designation of the file. (For example, if mouth.png was changed to Mouth.png, it will not recognize the difference and continue using mouth). For this reason, some of the image filenames used in the build were incorrect case and so the display objects could not be created. When removeSelf was called on the non-existing objects, it gave the runtime error at line 110.

object:removeSelf() does not check for nil, but display.remove() does. From the Corona API:

display.remove( object )


---

 is a faster way of doing this:
if object ~= nil then
    object:removeSelf()
end

Using this in the runtime could prevent the error, however leaving the error in may alert developers when problems are actually occuring.

In any case, thanks for your support Nate. Keep up the good work! :clap:

Glad you figured it out! Pretty nasty problem.

I'll change to display.remove, it's better than a misleading crash and if it's faster it's better.

If spine.Skeleton:createImage returns nil it should print "Error creating image: xxx", so I don't think the image is nil. Maybe Corona returns a bad object from display.newImage or display.newImageRect? :o