7.5 KiB
The funccapture
helper allows you to capture a regular Lua function and use it for the init
and update
functions for simulations. It also captures upvalues used by this function allowing you to use functions and values from outside the init
and update
functions. In other words, you can use libraries and reuse your functions for multiple simulations.
local func_capture = require("__simhelper__.funccapture")
-- ...
{
type = "tips-and-tricks-item",
name = "nixie-tubes",
tag = "[entity=nixie-tube]",
category = "nixie-tubes",
is_title = true,
order = "zz-00",
dependencies = {"circuit-network"},
simulation = {
save = "__nixie-tubes__/simulations/nixiesim.zip",
init = func_capture.capture(function()
local bp="0eNq1lVtugzAQRfcy36aKzZu/dBtVhIA47UhgkDFRoogFdCHdWFdSG9QEKYRH1fwgbMbncsejmQukecMriUJBdAHMSlFD9HaBGt9Fkps9da44RICKF0BAJIVZCTwht1STcmgJoNjzE0S03RHgQqFC3lO6xTkWTZFyqQPGzhOoylofKYVR0xjLeXEJnPULa1tyB2HLIO4kxF4GYZMQZxnEnoS4yyCbG4SAviUlyzxO+UdyxFKaoAxl1qCK9bf99eQBZa3iu7s8olSN3rlK9xHWFnp4rRJTDxajju8EtucEZruoEpkoowbfn1/Q9rGCZ0atNnhqHpLvh3ePekXtdteOmfeWmaeTGfRHIFZdJHk+VVs6oWOwYA3MnoGFa2BsBkY3a2h0jkbX0DZD2vPq7/VZ9cce1B+9NZNfWUsLpSg6obtEsPk8HDBXXD5oojPGG+OaMttxPT8Ih311hVf6yKv9R6/0f71u77z+zajb+dQTp5tM0WCQETjqv+qcsECXUMh8FgaB5+v28QOiY0wL"
game.tick_paused = false
game.camera_alt_info = true
local result = {game.surfaces[1].create_entities_from_blueprint_string{
string = bp,
position = {0, 0},
}}
remote.call("nixie-tubes", "RebuildNixies")
end),
update = func_capture.capture(function()
end),
}
},
Quriks and Pitfalls
Captured current State
When capturing, it captures the current state of every upvalue and every upvalue of upvalues, and so on. This means that any changes to the values that it captured after they got captured will not have effect on the captured function.
Custom Lua Globals
It doesn't capture the global environment _ENV
(so global variables). When the captured function is loaded and run in the simulation it will be in a new Lua state with a fresh _ENV
. That means it is recommended to not use non default globals, but instead put them into a local and capture those as upvalues, that way they will be captured.
However if any of the captured functions use globals where you cannot change them to "not use custom globals" you will have to restore the global yourself.
For example here we have to manually restore the global util
because some_library_function
is using it as a global.
-- some_lib.lua
require("util")
local function some_library_function(arg)
-- using 'util' as a global
return util.copy(arg)
end
return {
some_library_function = some_library_function,
}
-- somewhere in data stage where you define your simulations
local func_capture = require("__simhelper__.funccapture")
local some_lib = require("some_lib")
local util = require("util") -- put 'util' into a local
-- ...
{ -- in your simulation definition
init = func_capture.capture(function()
-- have to restore the global 'util' using the
-- captured local 'util' as an upvalue in order
-- for the library function to have access to 'util'
_ENV.util = util
local my_data = {}
local data_copy = some_lib.some_library_function(my_data)
end),
}
But again, if nothing that gets captured is using non default globals you simply don't have to worry about this.
Custom Captures and Restorers
In some weird cases it can happen that it captures an upvalue that was meant to be a global. For example:
local settings = settings
-- ...
{
init = func_capture.capture(function()
local my_setting_value = settings.global["my_setting"].value
-- use my_setting_value
end),
}
This ends up capturing the data stage settings
table as an upvalue, and restores it in control stage, which means it would only have startup
settings.
In this case there are 3 ways to solve this issue:
- Just don't do that. Don't put something you don't want to capture in a local and then capture it as an upvalue.
- Similar to 1, use
_ENV.settings
instead - If that's not possible, you can define custom restorers like this:
local settings = settings
-- ...
{
init = func_capture.capture(function()
local my_setting_value = settings.global["my_setting"].value
-- use my_setting_value
end, { -- a list of restorers
{
upvalue_name = "settings",
restore_as_global = {"settings"},
}
}),
}
Any upvalue with the name settings
will then be restored as defined by restore_as_global
instead, and doesn't get captured.
restore_as_global
takes an array of keys (strings, numbers or booleans) to index into _ENV
with. Examples:
{"settings"}
: will restore as_ENV["settings"]
{"foo", "bar"}
: will resoter as_ENV["foo"]["bar"]
Performance with huge tables in _ENV
While it doesn't capture anything in _ENV
, it does have to look through _ENV
to find all C functions it can restore in the new Lua state later down the road. C functions being all the library functions, for example all the functions in table
.
In order to find all of those functions it loops through the entire _ENV
table. However there can be some gigantic tables in there that don't actually contain any of the C functions it's looking for, which is a waste of performance. For example the data
table is huge and doesn't contain any C functions, therefore it should be ignored, which it is already by default so don't worry about that, it's just an example. However if you have your own huge table in _ENV
be sure to ignore_table_in_env(tab)
it:
local func_capture = require("__simhelper__.funccapture")
my_huge_table = {
-- ...
}
func_capture.ignore_table_in_env(my_huge_table)
And by "huge" I mean like several thousands of key value pairs, including nested ones.
Make sure to ignore it immediately after adding it to _ENV
. It technicaly doesn't have to be immediate, it's just easier that way to avoid any capture()
calls in between adding it to _ENV
and ignoring it, since this search happens as part of capture()
if there are any C function upvalues, (and the mapping is cached, so it only happens once).
For completeness sake there is also un_ignore_table_in_env(tab)
which will undo the registration of tab
but even if you don't deregister it, it won't keep the table alive forever because it's stored in a weak table.
Control stage and storing in global
Simply put, if you try to use funccapture
to store functions in global
you will find out why it is disallowed by the base game to store functions. So don't do it.
A few more details to make sure you understand: When restoring a captured function it restores everything that was captured as a copy of what it captured (except for C function, those are restored as a reference to somewhere in _ENV
), which means any reference to your file level locals, libraries, functions, just anything that was captured will be duplicated, no longer referring to the same thing as the rest of your code base. If you've ever accidently required the same file using different strings you'll know what kind of weird issues you can get by doing that.
Aside from that, if you wanted to update anything that is used by your captured function you'd have to somehow figure out which captured functions have a captured copy of it (through a chain of upvalues), and then somehow update that to be the new thing. I don't even know how you'd do that.
So yea, safe yourself some sanity and just don't.