Log In  

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.

P#79549 2020-07-18 11:12

:: Stompy

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.

P#79552 2020-07-18 11:22

This is a great trick. Thank you!

P#79601 2020-07-19 11:27
:: dredds

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?

P#79606 2020-07-19 14:42
:: Stompy

Yes I use an external editor.

P#79607 2020-07-19 15:32

> 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

P#90877 2021-04-21 09:06

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
P#94906 2021-07-15 12:21 ( Edited 2021-07-17 12:03)

[Please log in to post a comment]

Follow Lexaloffle:        
Generated 2021-10-24 13:00:04 | 0.011s | Q:19