diff --git a/mods/CORE/mcl_init/API.md b/mods/CORE/mcl_init/API.md new file mode 100644 index 000000000..e30ea0793 --- /dev/null +++ b/mods/CORE/mcl_init/API.md @@ -0,0 +1,62 @@ +# mcl_init + +Initialization of VoxeLibre, in particular some shared variables and utility functions exposed via `mcl_vars`. + +## `get_node_name` + +This is an interim API while we still support Luanti versions that do not expose `core.get_node_raw`. +We would like to use that function because it generates fewer Lua tables, and hence causes less garbage collection, +yielding better performance. The current `get_node_name` API is a middle-ground that covers many use cases +of that API, while having little overhead over the old `core.get_node`, nor the new `core.get_node_raw` API then. + +- `mcl_vars.get_node_name(pos)` returns the node *name*, param1 and param2 at position `pos`. + +- `mcl_vars.get_node_name_raw(x, y, z)` returns the node *name*, param1 and param2 at position (x,y,z). + +- `mcl_vars.get_node_raw(x, y, z)` returns the *content ID*, param1 and param2 at position (x,y,z). + +Which version to use: + +1. if you are working with content ids (integers), use `get_node_raw`. +2. if you work with node names, and vectors, use `get_node_name`. +3. if you work with node names and integer coordinate loops, use `get_node_name_raw`. +4. if you need dense access on a larger volume, use a Lua Voxel Manipulator. + +Overhead: + +On current Luanti, without trusted mods, all functions use `get_node`, and the performance will be similar to +using `get_node`. + +When `core.get_node_raw` becomes a public API, or when the trusted mod hack is enabled, the first two perform similar +to using `core.get_node_raw` followed by an content ID to node name lookup (which is supposedly a simple array access). +While the function `get_node_raw` becomes an alias for `core.get_node_raw`. + + +## Optimized LuaJIT parameters + +As of 2024, the default LuaJIT parameters are not well tuned, *depending on the version you use*. + +According to <https://luajit.org/running.html>, standard LuaJIT uses: + +| maxtrace | 1000 | Max. number of traces in the cache | +| maxrecord | 4000 | Max. number of recorded IR instructions | +| maxirconst | 500 | Max. number of IR constants of a trace | +| minstitch | 0 | Min. # of IR ins for a stitched trace. | +| maxmcode | 512 | Max. total size of all machine code areas in KBytes | + +However, the openresty branch uses more sane defaults for a long time <https://github.com/openresty/luajit2/blob/v2.1-agentzh/src/lj_jit.h#L117>: + +| maxtrace | 8000 | Max. number of traces in the cache | +| maxrecord | 16000 | Max. number of recorded IR instructions | +| maxirconst | 500 | Max. number of IR constants of a trace | +| minstitch | 3 | Min. # of IR ins for a stitched trace. | +| maxmcode | 40960 | Max. total size of all machine code areas in KBytes | + +Mineclonia contributor "halon" investigated this, and +increasing these values appears to be beneficial for improving the performance of Luanti, +although users of openresty (e.g., on Debian GNU/Linux) may not notice a difference. + +TODO: every few years, the situation should be re-assessed. For example, Luanti upstream might set improved values. +Unfortunately, it does not appear that we can query the values, only set them. + + diff --git a/mods/CORE/mcl_init/get_node_name.lua b/mods/CORE/mcl_init/get_node_name.lua new file mode 100644 index 000000000..352daff27 --- /dev/null +++ b/mods/CORE/mcl_init/get_node_name.lua @@ -0,0 +1,66 @@ +-- NOTE: As of Luanti 5.9 and 5.10, the function `core.get_node_raw` is NOT exposed. +-- However, via `secure.trusted_mods=vl_trusted` it is possible to expose and use it. +-- If <https://github.com/minetest/minetest/issues/15317> is merged, it may become public API. +-- For more details, see the vl_trusted module. +-- We do not call into vl_trusted directly, but it set `core.get_node_raw` if loaded first. +-- Load order hence is important, and this mod should depend on vl_trusted. + +local core_get_node = core.get_node +local core_get_node_raw = core.get_node_raw +local core_get_name_from_content_id = core.get_name_from_content_id +local core_get_content_id = core.get_content_id + +--- Get the node name, param and param2 using `core.get_node_raw` if available, fall back to regular get_node otherwise. +--- +--- @param pos vector: position +--- @return (string, number, number): node name, param1 and param2 +function mcl_vars.get_node_name(pos) -- Fallback version + local node = core_get_node(pos) + return node.name, node.param1, node.param2 +end +-- optimized version +if core_get_node_raw then + mcl_vars.get_node_name = function(pos) + local content, param1, param2, pos_ok = core_get_node_raw(pos.x, pos.y, pos.z) + if not pos_ok then return "ignore", 0, 0 end + return core_get_name_from_content_id(content), param1, param2 + end +end + +--- Get the node name, param and param2 using `core.get_node_raw` if available fall back to regular get_node otherwise. +--- Note: up to Luanti 5.10 at least, this will create a new vector. If you already have a vector, prefer `get_node_name`. +--- +--- @param x number: coordinate +--- @param y number: coordinate +--- @param z number: coordinate +--- @return (string, number, number): node name, param1, param2 +function mcl_vars.get_node_name_raw(x, y, z) -- Fallback version + local node = core_get_node({x=x,y=y,z=z}) -- raw table, not need for vector + return node.name, node.param1, node.param2 +end +-- optimized version +if core_get_node_raw then + mcl_vars.get_node_name_raw = function(x, y, z) + local content, param1, param2, pos_ok = core_get_node_raw(x, y, z) + if not pos_ok then return "ignore", 0, 0 end + return core_get_name_from_content_id(content), param1, param2 + end +end + +--- Get the node name, param and param2 using `core.get_node_raw` if available, fall back to regular get_node otherwise. +--- Note: up to Luanti 5.10 at least, this involves an unnecessary roundtrip via the node name. +--- If you use the node name anyway, prefer `get_node_name_raw` or `get_node_name`. +--- +--- @param x number: coordinate +--- @param y number: coordinate +--- @param z number: coordinate +--- @return (number, number, number, boolean): node content id, param1, param2, pos_ok +function mcl_vars.get_node_raw(x, y, z) -- Fallback + local node = core_get_node({x=x,y=y,z=z}) -- raw table, not need for vector + return core_get_content_id(node.name), node.param1, node.param2, node.name ~= "ignore" +end +-- optimized version +if core_get_node_raw then + mcl_vars.get_node_raw = core_get_node_raw +end + diff --git a/mods/CORE/mcl_init/init.lua b/mods/CORE/mcl_init/init.lua index aae6b090e..b87ac2639 100644 --- a/mods/CORE/mcl_init/init.lua +++ b/mods/CORE/mcl_init/init.lua @@ -1,5 +1,6 @@ -- Some global variables (don't overwrite them!) mcl_vars = {} +local modpath = core.get_modpath(core.get_current_modname()) minetest.log("action", "World seed = " .. minetest.get_mapgen_setting("seed")) @@ -22,7 +23,7 @@ if not map_version then core.set_mapgen_setting("vl_world_version", map_version, true) end mcl_vars.map_version = map_version -- make available -core.log("action", "Voxelibre mapgen version = "..map_version) +core.log("action", "VoxeLibre mapgen version = "..map_version) mcl_vars.redstone_tick = 0.1 @@ -216,6 +217,7 @@ minetest.craftitemdef_default.stack_max = 64 -- Set random seed for all other mods (Remember to make sure no other mod calls this function) math.randomseed(os.time()) +---DEPRECATED. If you need to ensure the area is emerged, use LVM. ---"Trivial" (actually NOT) function to just read the node and some stuff to not just return "ignore", like mt 5.4 does. ---@param pos Vector Position, if it's wrong, `{name="error"}` node will return. ---@param force? boolean Optional (default: `false`), Do the maximum to still read the node within us_timeout. @@ -252,3 +254,6 @@ function mcl_vars.get_node(pos, force, us_timeout) -- it still can return "ignore", LOL, even if force = true, but only after time out end +dofile(modpath.."/tune_jit.lua") +dofile(modpath.."/get_node_name.lua") + diff --git a/mods/CORE/mcl_init/mod.conf b/mods/CORE/mcl_init/mod.conf index 4ce7b394d..355eb6048 100644 --- a/mods/CORE/mcl_init/mod.conf +++ b/mods/CORE/mcl_init/mod.conf @@ -1,3 +1,4 @@ name = mcl_init author = Wuzzy description = Initialization mod of VoxeLibre. Defines some common shared variables and sets up initial default settings which have to be set at the beginning. +optional_depends = vl_trusted diff --git a/mods/CORE/mcl_init/tune_jit.lua b/mods/CORE/mcl_init/tune_jit.lua new file mode 100644 index 000000000..1809ca3ba --- /dev/null +++ b/mods/CORE/mcl_init/tune_jit.lua @@ -0,0 +1,23 @@ +--- This code is largely based on the work by halon for mineclonia, but encapsulated differently +local luajit_present = core.global_exists("jit") + +-- Increased limits for the JIT, as of 2024 +-- TODO: re-assess this every year or so, as either upstream Luanti may +-- eventually increase these limits itself, or the luajit libraries increase +-- their parameters - e.g., the openresty version already has increase limits +-- because apparently we can not query the JIT parameters +if luajit_present then + local status, opt = jit.status() + if not status then + core.log("warning", "[mcl_init] LuaJIT appears to be available, but turned off. This will result in degraded performance.") + end + jit.opt.start( + "maxtrace=24000", + "maxrecord=32000", + "minstitch=3", + "maxmcode=163840" + ) + core.log("action", "[mcl_init] increased LuaJIT parameters. LuaJIT version: "..jit.version.." with flags "..tostring(opt)) +else + core.log("warning", "[mcl_init] LuaJIT not detected - it is strongly recommended to build luanti with LuaJIT for performance reasons!") +end diff --git a/mods/CORE/vl_trusted/API.md b/mods/CORE/vl_trusted/API.md new file mode 100644 index 000000000..58a8b5f00 --- /dev/null +++ b/mods/CORE/vl_trusted/API.md @@ -0,0 +1,22 @@ +# vl_trusted + +This module does not provide a public API. + +The optimized function calls are only available via existing APIs. + +We currently implement one feature that require trusted access: + +## Access to `core.get_node_raw` + +The `core.get_node_raw` function has been added to Luanti in version 5.9.0, but as of 5.10 is not a public API, +although we have asked for this to be made public: <https://github.com/minetest/minetest/issues/15317> + +This function is beneficial as it does not create a table for the return, which reduces the amount of garbage collection necessary, +in particular as LuaJIT's allocation sinking does not appear to eliminate these, unfortunately. + +For compatibility, we expose this with slightly different semantics, which are a tradeoff between using the new API when available +(or exposed via this trusted module), and having to be able to fall back to the regular `core.get_node` call. + +TODO: when the minimum version of Luanti has a public version of the API, these wrappers should likely be removed and the unmodified +`core.get_node_raw` should be used where possible. + diff --git a/mods/CORE/vl_trusted/init.lua b/mods/CORE/vl_trusted/init.lua new file mode 100644 index 000000000..e8fd95dbd --- /dev/null +++ b/mods/CORE/vl_trusted/init.lua @@ -0,0 +1,30 @@ +--- Make `core.get_node_raw` public. It's safe to use and stable. +-- +-- This code is based on the work by halon for mineclonia, but encapsulated differently +-- Check if `core.get_node_raw` is public by now. +if not core.get_node_raw then + -- try to un-hide the function + local ie = core.request_insecure_environment() + if not ie then + core.log("action", "[vl_trusted] cannot unhide get_node_raw, please add vl_trusted to secure.trusted_mods to improve performance (optional).") + elseif not ie.debug or not ie.debug.getupvalue then + core.log("warning", "[vl_trusted] debug.getupvalue is not available, unhiding does not work. Version: "..dump(core.get_version(),"")) + else + for i=1,5 do -- will not be five levels deep + local name, upvalue = ie.debug.getupvalue(core.get_node, i) + if not name then break end + if name == "get_node_raw" then + core.get_node_raw = upvalue + break + end + end + if core.get_node_raw then + core.log("action", "[vl_trusted] get_node_raw unhiding successful.") + else + core.log("warning", "[vl_trusted] get_node_raw unhiding NOT successful. Version: "..dump(core.get_version(),"")) + end + end +else + core.log("verbose", "[vl_trusted] get_node_raw available without workaround.") +end + diff --git a/mods/CORE/vl_trusted/mod.conf b/mods/CORE/vl_trusted/mod.conf new file mode 100644 index 000000000..50f4ddb83 --- /dev/null +++ b/mods/CORE/vl_trusted/mod.conf @@ -0,0 +1,3 @@ +name = vl_trusted +author = kno10 +description = Optional - access to some protected Luanti functionality for increased performance