Does anyone know what causes: "error in __gc metamethod"?

,

Platform: Mac

I'm seeing the following error in my console (in yellow text):

error in

__gc metamethod
(
lua_release called on object with retainCount == 0
)

I vaguely understand what that means, but I have no idea why I'm getting it. I am able to track down exactly when/where it's happening in my code, but I guess I don't understand what is causing it.

I have a general "bitmap sprite" class that extends playdate.graphics.sprite:

Bsprite = {}
class('Bsprite').extends(GFX.sprite)

And then I have an "asteroid" class that extends THAT:

Asteroid = {}
class('Asteroid').extends(Bsprite)

And the error occurs every time I spawn a new asteroid from within that asteroid (the asteroid breaks in two - the original updates to the smaller size, and an additional smaller one is also created):

local a2 = getAsteroid()
a2:add()

The "getAsteroid" method just returns either an existing asteroid object from a pool or a new one if the pool is out of asteroids. The error only occurs when the getAsteroid returns a new, fresh instance of an Asteroid object, AND specifically on the "a2:add()" <- if I comment this line out, I no longer get the error. So I can instantiate a new Asteroid, but as soon as I add it to the display list, I see the above error.

Any ideas?

1 Like

Hello! I've been trying a few things but haven't been able to reproduce this error. What do you mean when you say you're creating a new asteroid from within another?

One thing to look out for is that you're not modifying your sprites from within a collisionResponse() callback, but that's just a wild guess :slight_smile:

If you have example code that can demonstrate the problem, that would be extremely helpful. If you have code that you don't want to share publicly you can email me: dan@panic.com

Dan, thanks for the reply!

I'll send you all of my source code if I can't figure this out after a couple of attempts. Don't want you to have to wade through the hundreds of line of code, to figure out what I was thinking, if you don't have to.

Regarding your comment about "not modifying your sprites from within a collisionResponse() callback", I guess I'm not sure...

The collision response is explicitly in the Bsprite object:

function Bsprite:collisionResponse(other)
    return "overlap"
end

And when anything (bullet, player, saucer) collides (all using moveWithCollisions) with an Asteroid (which extends Bsprite), it calls on the "boom" method:

function Asteroid:boom(other)
    ...

    -- shrink and set the current asteroid
    self:setAsteroid(self.size + 1, _x, _y, _dx, _dy)

    -- and spawn another asteroid
    local a2 = getAsteroid()
    a2:setAsteroid(self.size, _x//1, _y//1, _dx + ( ( 2 * RANDOM() ) - 1 ), _dy + ( ( 2 * RANDOM() ) - 1 ))
    a2:add()

    ...
end

Does that count as modifying the sprite from within a collisionResponse()?

If so, I suppose I can send the current Asteroid back to the object pool and grab another one. However, I was trying to keep the number of objects/sprites to a minimum by simply shrinking the current one and spawning one additional one (asteroid breaks into two smaller ones - each asteroid has 3 "states" - large, medium and small).

Hmm. This inspired me to do some more testing. If a saucer or the player hits an asteroid (and calls the Asteroid:boom method), I don't get the error. However, if a bullet hits an asteroid (coming from either a saucer or the player), calls Asteroid:boom, and a new Asteroid is spawned (because there aren't any left in the object pool), that's when I get the error.

AND...problem solved. I was returning the bullet to its object pool and THEN calling Asteroid:boom, passing self to the function.

local x,y,c,n = self:moveWithCollisions(self.x + self.dx, self.y + self.dy)
	
for i = 1,n do
    local other = c[i].other
		
    if other.type == 'asteroid' then
        other:boom(self) -- <- moved this call here

        if self.source == 'ship' then
            updateScore(ptsRock[other.size])
            player:returnBullet(self)
        else
            saucer:returnBullet(self)
        end

        -- other:boom(self) <- this was being called here
    end

...

end

:man_facepalming:

Thanks for rubber-ducking this with me.

1 Like

Hey I'm just happy it didn't end up being ga bug on our end! Thanks for the follow-up :slight_smile:

@dan Well, not so fast. I am still seeing that error intermittently. And since it's not consistent, it makes me wonder if it's a matter of some code racing and beating some other code? Not sure. But I'll send you my files if you care to play around with it. If you play it long enough with the console open - and it seems to happen more often when there are a bunch of asteroids on the screen and you shoot a bunch in succession - although I'm not entirely sure what recreates it, as it's not consistent - you should eventually see these errors:

Curious if you ever managed to narrow down this issue?

I just ran into it myself, tried to do a git bisect to figure out where it was introduced, and traced it back all the way to where I had any sort of gameplay implemented, despite me never having seen this error before. It's definitely triggered by a Sprite being :add()ed or :remove()d, but other than that it appears very random.

Then I rebooted my system and I no longer see the message anymore (it was pretty consistent too in some specific spots, and played for a long time to make sure it didn't happen sporadically anymore).

Wondering if this could simply be a Simulator bug? I'm not sure how it works under the hood but I can imagine it ties into the GC in some way to be able to debug the memory usage.

1 Like

been having the same issue, just trying to remove a sprite using sprite:remove(). it seem to work correctly, but I still get the error.

I had this same issue and was able to determine that the "error in __gc metamethod" was caused by my code to disable collisions on a sprite [with setCollisionsEnabled(false) ] when that same sprite was in the collision table created from either moveWithCollisions or checkCollisions. To Dan's point above, I was "modifying a sprite from within a collisionResponse()". Now, instead, I just set a custom sprite.collide data element to true/false to determine when I need to ignore collisions.

For what it's worth, I ran into this problem and found a different answer. Full disclosure, I've been working with lua for about week, but I have experience writing C for embedded devices.

Anyway, I was doing moveWithCollisions() and had a for loop that stepped through the elements returned. I was assigning a variable to each element to operate on them, but I wasn't assigning the variable as "local."

Adding "local" resolved the issue completely.

I suspect I had some global temporary variables floating around which was angering the GC.