The whole signals.lua
file exists only because reading signals via the api is veeeery slow (or at least it was when i wrote that part). And there's quite a bit of reading happening all over the place. Iirc recipe combinator in particular only uses the get_highest
function, which does what it says, but using the lamps as a cache - they have conditions such that unless the highest signal changes to something other than it was last time, it will do with a simple disabled
check on the control behaviors of the lamps, instead of actually reading the signals. More or less, i don't quite remember the exact details.
The metatable is mostly just a shortcut - __index
is called when a non-existent key is accessed on the table with this metatable and whatever it returns will be returned in place of the non-existent value. If you were to write the table access operator as a function it would look something like this:
function table_access_operator(table, key)
local raw_value = rawget(table, key)
if raw_value ~= nil then
return raw_value
end
local metatable = getmetatable(table)
local __index = metatable and metatable.__index
if type(__index) == 'function' then
return __index(table, key)
end
-- __index can also be a table, but that's not relevant here.
return nil
end
So really, the only parts of the code that do "metatable voodoo" is when you read something from a table you got from global.signals.cache
(basically the things returned from _M.cache.get
), and even then, it only does it the first time, since the __index
function assigns the result back to the table, meaning on following access of the same key it will actually be stored in the table itself and the metatable doesn't come into play at all.
To sum it up, the metatable is used for lazy initialization of the cache lamps. This can be done without the metatable, but you would have to manually check if the particular lamp exists (and create it if not) before using it, in a similar manner to what __index
does now. The only other metatable-related thing is the usage of rawget
, which is just like the table access operator, but it ignores metatables.
In the case of get_highest
, there's three lamps in use as that was the minimum to make the cache work: highest
, highest_present
and highest_count
. In the function you can see three parts: Everything above local highest = nil
is the cache check, the local and the loop bellow is the computation in case of a miss and the rest is setting up the cache for the new value. The update_count
parameter is just a small optimization for the cases where you don't care about the value of the signal, just that it is highest.
With all that said though, i have to wonder which part of this is worth porting to 2.0? I was of the impression that pretty much all of what this does is now possible in vanilla. And anything i thought at least a little useful would be a lot better done using those new features instead of the hacks in place currently, which i didn't feel was worth the effort. If you have a compelling argument for something that's not possible (or practical) in vanilla 2.0, i might be open to implementing something myself. The only remotely useful suggestion i got so far was to get the ingredients of a recipe more easily than using an actual assembler, but that didn't seem enough of a problem to me. Granted i haven't really had a need for anything too advanced in this regard in my 2.0 playthrough so far, so maybe im just missing something.