Classes are instantiated as userdata in 1.13

I may oversimplify/skew this too much, as I have just a surface level knowledge of the Lua/SDK. I tried to be dilligent as hard as I could, though :smiling_face_with_tear:

It seems that in 1.13 that classes are instantiated as userdata instead of tables. Is this intentional? I don’t see it documented in the changelog or mentioned anywhere.

This minimal example code

local sprite = playdate.graphics.sprite.new()
print(sprite)

TestSprite = {}
class("TestSprite").extends(playdate.graphics.sprite)

local sprite2 = TestSprite()
print(sprite2)

prints this in 1.12.3

playdate.graphics.sprite: 0x6000008341c0
table: 0x6000008b7280

but this in 1.13:

playdate.graphics.sprite: 0x6000036a1768
userdata: 0x60000368b288

I can still get the playdate.graphics.sprite object if I use a TestSprite.new() call, but the docs point toward instantiation via the func call, and I cannot pass the params for .init() that way.


For me the issue manifests when adding items into table — SDK’s utility method table.indexOfElement() does not work with userdata. And unfortunately, that is the way Noble Engine manages sprites inside Scenes (as an insert check and also when removing a specific sprite from a scene).

I see few possible workarounds but as this change is not documented, I want to ask first whether it is an intended change in behavior, or whether there’s some omission in docs or elsewhere. Thanks for any info!

EDIT: Just adding the info about the firmware/build — the behavior on 1.13 device/simulator is the same, regardless if the game was built with 1.13 or 1.12.3 SDK.

1 Like

this game has same issue Mbg1101 added MAZE to Playdate - itch.io

Oof. I never noticed that indexOfElement throws an error on userdatas. Before 1.13, every sprite was a weird amalgam of a table, a userdata, and the low-level struct so that they could act like both tables and userdatas, but I finally cleaned up that code to make them proper userdatas with a storage table and __index/__newindex to handle setting and getting. So that's what's behind the table->userdata change. And making that work with indexOfElement is super simple, we just use the same code we're using for tables. After that change, the above maze game works fine. I've got that change in and I'm working this weekend on all the other game breaking regressions, shooting for a 1.13.1 release out ASAP.

3 Likes

Amazing, thanks! That should 100 % fix my problems, too.

1 Like

Hi dave, hopefully you're actually not working over the weekend <3

Yikes! That's kind of a big change, I've been extending Sprites (because they were tables) in a way that needs to be re-written now since they're strictly userdata.

Any chance this change can be documented in the future in the changelog? I went through the list before updating, not realizing this change.

Cheers,
Carl

Oops. I thought it was in there but maybe under a non-obvious title like "made userdata types consistent" but you're right, there's nothing at all about userdata. :frowning: That's my bad, I absolutely should have called out such a big change. What doesn't work for you now that sprites are userdata? You can still set and get on them like tables, and you can use metatables with them in the same way afaik.. Hopefully whatever's not working right will be as easy to fix as table.indexOfElement(). The old weird table/userdata amalgam kept causing lots of problems under the hood, so I still think it's the right thing to do. I just wish I'd gotten around to it before the public release.

1 Like

Thanks for the quick reply!

I totally understand the change (it's cleaner and no longer an amalgam of things as you said). I'm all for it hahaha :slightly_smiling_face:

I reverted the SDK on my end, I'll tackle this change eventually. Again, it's just a side effect of how I did things on my end (that's my bad). I extended classes using metatable and adding new fields directly on the instance. You're totally right, I think I can solve the issue just using the __newindex as mentioned previously, so thank you for that.

I'll update this post in the future when I do to confirm my solution.

Cheers,
Carl

I finally got around to testing the 1.13.1-beta2 on my games. To answer this question, a check like type(variable) == 'table' no longer works. In one of my games, I have a variable that can either be a boolean, nil, or a sprite, depending on some other state, and was checking the type to determine the action to take. Yes, this was definitely poor practice on my end, but I can't be the only one out there relying on a type()=='table' check on a potentially multi-typed variable. (Another potential example is iterating through a queue with multiple types of objects, and performing different actions depending on type.) Fortunately the fix on my end is super easy, but it will still require all players update to a new build for it to continue working.