Hey everyone. I recently released Goober's in the Mix. It was my first bigger pico game that actually required me to optimize for tokens. I wrote a lot of pseudo object oriented code with classes and inheritance. I found myself with a lot of code that looked like this:
local thing = class{ update = function(self) -- cache a bunch of properties local x, y, dx, dy, health, frozen, invuln = self.x, self.y, self.dx, self.dy, self.health, self.frozen ... game logic in here -- resync all those properties on the instance self.x, self.y, self.dx, self.dy, self.health, self.frozen, self.invuln = x, y, dx, dy, health, frozen end } |
All that caching and syncing saves tokens instead of referencing self in the logic, but we can save more.
Lua has a feature called environments that lets you alter how global variables are looked up. So I altered my initial class declaration to look something like:
_G = _ENV -- just a new name to distinguish this as the original global scope _G.__index = _G -- if _G is used as a metatable, it will look itself for unfound properties local class do local mt = setmetatable({ update = noop, -- defaults draw = noop, -- defaults init = noop, -- defaults __call = function(self, o) -- takes an table of nondefault values o = o or {} -- empty table if none given setmetatable(o, self) -- each instance sets itself as the new prototype self.__index = self -- looks in itself for unfound properties self.__call = getmetatable(self).__call -- sugar to allow calling class like a function o:init(self) -- call an init/constructor function if given return o -- return the new instance end }, _G) -- the class mt is the global scope !IMPORTANT -- if you don't keep this reference to global scope you're going to run into trouble later [1] mt.__index = mt -- this class mt indexes itself if child doesn't have a property class = setmetatable({}, mt) -- set the classes metatable end |
Most of this is pretty standard for classes in Lua. But the magic comes in with that global reference. This allows us to use environments without breakings everything.
dog = class{ x = 10, update = function(self) local _ENV = self -- set self as the new environment if (x < 127) x += 1 -- we can now reference self.x as just x. end } |
Things to note:
_ENV is where globals are looked up. Like how globals are found on the Window object in client-side javascript. Normally when you reference a global `z` it's the same as referencing `_G.z` or `_ENV.z` in Lua. So when you change the local value of `_ENV` within that scope, it will reference what ever you set as the value instead.
This means that things previously available in the global scope may not be there. Like the pico api functions. `print`, `rectfill`, `time` etc wouldn't be available. This is why we added that reference to `_G` in our lookup chain.
This also means you can have weird variable shadowing issues when you think you're altering a global but really creating a new property on your class instance or vice versa. You can use that `_G` reference or `self` reference directly if you know you're shadowing an existing variable in a parent scope.
When I first refactored to add this technique i saved something like 1.5k+ tokens removing all those caching variable and the syncing back to the instance values.
Hope this saves you all a bunch of tokens and continues to move the meta forward! Happy to answer any questions or make revisions as needed.
There's a more thorough environments tutorial on the Lua Users Wiki, if you don't want to RTFM or needed more than my brief explanation.
How do you refer to _ENV and _G (e.g. in capitals) from within Pico-8? Or do you write this in an external editor?
> How do you refer to _ENV and _G (e.g. in capitals) from within Pico-8?
press ctrl-P to enter "puny text mode" and type "_ENV"; these puny characters will be saved as uppercase
A really neat trick which saves a ton of tokens and makes the code look much cleaner. Thank you!
I am using a json parser for most of my tables (https://gist.github.com/tylerneylon/59f4bcf316be525b30ab) and I'm having trouble implementing your method with this. I can't find a way to add the "class" reference when creating the table ( thing=class{...} ). Any idea how I could do that or if I could add that afterwards somehow?
Edit
I have found a way of handling this, I'm now doing this:
player=class{} player_data=json_parse('{"x":1,"y":2",speed":1}') for k,v in pairs(player_data) do player[k] = v end |
[Please log in to post a comment]