So after completing the (very helpful) Squash/Pong tutorial in the first pico-8 zine, I decided to go about setting up a class-based object-oriented framework for implementing the same game - one where relevant data is contained within an object, and the object has callable methods that let the object act on its own data rather than other members reaching in and changing values. The _update() and _draw() functions in turn just call _update() and _draw() methods contained inside game objects.
Here's what I came up with: http://hastebin.com/agasogijet.lua
The code at the top is mine, and the section at the bottom contains the original code (more or less, I made a couple of changes).
It's good if you follow the tutorial from the zine (https://sectordub.itch.io/pico-8-fanzine-1), but if you don't, you should be able to paste the code into the pico-8 editor and run it. The health meter sprites won't show up, and you won't get any sound, but everything else will work.
DISCUSSION:
This turned out to be more complicated than I expected, entirely because I couldn't decide how I was going to implement classes. Even though this example only instantiates each class once (singletons), I wanted to have classes for future projects where I would want to be able to generalize across several instances.
The common way of implementing classes in Lua is similar to what I'm used to with JavaScript - prototype inheritance, where a method lookup on an object falls back to another object which represents the class. In this case our "objects" are actually "tables," but it's the same principle. I ended up writing this helper for creating a class in Lua:
-- this is possible in standard lua (try it here: [http://www.lua.org/cgi-bin/demo](http://www.lua.org/cgi-bin/demo)) -- class helper (include this once, use it for all your classes) function class (init) local c = {} c.__index = c function c.init (...) local self = setmetatable({},c) init(self,...) return self end return c end -- end class helper -- actual class definition local someclass = class(function (self, name) self.name = name end) function someclass:setname (name) self.name = name end -- end class definition local someclassinstance = someclass.init("monkey") print(someclassinstance.name) -- "monkey" someclassinstance:setname("banana") print(someclassinstance.name) -- "banana" |
However this requires the "setmetatable()" function which is part of the Lua standard library, which we don't have. As far as I know there isn't another way to pull off prototype-based classes, so I gave up on that. lua-users.org/wiki/ObjectOrientationTutorial
The other accepted method (also discussed in the link above) is closure-based classes, which is also something people do in JavaScript (if they're confused). No need to use a helper function - we can just declare our object and its methods inside a function and return the result - it ends up being a lot like classes in Java and other object-oriented languages. Each instantiated class has its own set of methods rather than a shared set, so the memory cost can be substantially higher for many instances.
-- this works in pico-8! try it. -- class definition function someclass (name) local self = {} self.name = name function self.setname (name) self.name = name end return self end -- end class definition local someclassinstance = someclass("monkey") print(someclassinstance.name) -- "monkey" someclassinstance.setname("banana") print(someclassinstance.name) -- "banana" |
However, that's good enough, if a bit memory inefficient, and what we've got to work with right now. It's what I use throughout the Squash/Pong code I've shared.
P.S. I also want to acknowledge this discussion which suggests copying objects and mutating them to simulate classes. https://www.lexaloffle.com/bbs/?tid=2951
While some consider that a viable option (and it can get the job done), for me it slightly defeats the point of class-based programming.
jihem - thank you for sharing! I'm just learning Lua so I hadn't considered something like that. If I understand correctly, you're mimicking the behavior of the normal setmetatable by hard-binding the metatable's methods the inheritor?
The part I really don't think I understand is on line 14 - how you you could have "self" be its own metatable, rather than some other table representing the class of which "self" is a member.
> you're mimicking the behavior of the normal setmetatable by hard-binding the metatable's methods the inheritor?
Yes
>The part I really don't think I understand is on line 14...
At this step, self has been overwritten by the result of the call of setmetatable. In the following line, the SELF before and after the equal sign aren't the same. Before, it's the result of the function setmetatable. After, as argument, it's relative to P.
SELF = SETMETATABLE(O,SELF)
In hope this could be usefull.
Have a good day :-)
I'm very interested in this, and commenting to bump/save until I have a chance to check it out (as well as jihem's too). As a JavaScript/PHP programmer myself, I've definitely been missing the OO features (and have also been using tables as JS-like objects). Being able to create new instances of objects is something that's been on my "most wanted" list for P8 ever since I started (something like new(<obj>) would be great).
That's really nice. I've been missing OO design in PICO-8 as well (mainly a java and C++ developer). I'll definitely be giving this a try, thanks!
@Scathe - The "new" keyword is very specific to C++-descended languages and I don't see how it could be easily hacked into Lua, but what I showed in the first code excerpt isn't too far off from what you can do with JavaScript. With "setmetatable()" or a stand-in like jihem provided (I still want to mess around with this a bit myself), you can create prototype-based classes that way.
Tables and closures are heap-allocated even if they're never referenced outside of their local scope, so creating many tables or closures every frame can quickly raise the cart's reported memory usage.
The garbage collector will kick in above a certain threshold (in my testing it wakes up near 200kb), but it seems to take time to work, and it's possible that very busy object creation would go over the 512k limit before the collector can catch up.
In many cases your objects will be long-lived and created infrequently; but for short-lived objects like particles that are spawned every frame, it's better to keep a pool of tables around and reuse them when they die, rather than creating new tables for each object. (Unfortunately, closures aren't as reusable.)
For the same reasons, tables are costly to use for local variables and for passing around value types like vectors. It's better to stick with separate scalar variables in local contexts, and for functions that return multiple values it's better to return them as a tuple instead of wrapped in a table. For when nothing but a table will do, consider creating the table once at global scope and emptying it before/after use within the function (ew, but there we are).
As a practical example: I was creating a table at the start of _draw and sorting my game entities into it as a draw list. Even though the table was local to _draw it outlived the function scope, so pico8's memory filled up with frame after frame of these temporary tables until the garbage collector caught up. Replacing it with a global table that I emptied at the end of the function eliminated the memory spikes with no measurable performance cost. (Even better would be to sort my persistent list of entities in place of course, but that wasn't possible here.)
I understand that but why in the world would you be creating new objects every single frame? Create consistent objects at the start of a scene - and in terms of frequently created items, I couldn't imagine using class-based objects for a multitude of particles without non-random unique behavior. It would make sense, like you say, to keep an array of tables holding coordinates and possibly other data for each particle, and to re-use when possible. It could still be appropriate to house this array in some other instanced object if that makes sense within the taxonomy of your game.
I imagine class instances to be most useful for things like players, enemies, and other entitles that exist throughout the duration of a scene (or until killed).
Quite! My post wasn't meant as an admonition to you personally, but just as general observations on memory usage in pico-8. (I dumped my thoughts in here when I probably should have made a new thread; reading back, I can see it sounded like I was addressing your posts specifically.)
My broader point was that table creation of any kind during a frame is kinda fraught, even in seemingly innocuous cases that don't 'smell like' heavyweight objects. For instance, a physics library that passes vectors around as [x,y] tables will have higher, spikier memory usage than one that sticks to scalar parameters and tuple return values. A lot of patterns that seem natural in a language with classes or structs can be potentially ruinous in pico-8.
(FWIW: at the moment I'm dabbling with a component system where particles are rendered and simulated using the same logic as longer-lived game entities to allow richer behaviour. So far, keeping a reusable pool of tables around for structures of any lifetime, long or short, has kept memory usage flat.)
Yeah that makes sense. In the stuff I'm writing, all tables are declared globally and initialized in _init. I haven't dealt with particles yet but it makes sense to have a reusable pool.
[Please log in to post a comment]