From 711f6963d4d61d2bcc740349c5331be8987d7b31 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 19 Apr 2026 19:36:36 +0300 Subject: [PATCH 01/11] add VC_HEADLESS constant --- src/logic/scripting/lua/lua_engine.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 5edb24fd9..bfca17328 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -144,6 +144,9 @@ void lua::initialize(const EnginePaths& paths, const CoreParameters& params) { ); lua::pushstring(main_thread, params.scriptFile.stem().u8string()); lua::setglobal(main_thread, "__VC_SCRIPT_NAME"); + + lua::pushboolean(main_thread, params.headless); + lua::setglobal(main_thread, "VC_HEADLESS"); } void lua::finalize() { From fc80feeb840f4a37111edec934d6e56ea2c84ca4 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 19 Apr 2026 19:36:48 +0300 Subject: [PATCH 02/11] test removing client-side logic in headless (core:drop component) --- res/content/base/scripts/components/drop.lua | 90 +++++++++++--------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index 2b798a386..c3d629828 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -2,7 +2,6 @@ local tsf = entity.transform local body = entity.rigidbody local rig = entity.skeleton -inair = true target = -1 timer = 0.3 @@ -20,40 +19,59 @@ if SAVED_DATA.item then dropitem.data = SAVED_DATA.data end -local DROP_SCALE = 0.3 -local scale = {1, 1, 1} -local rotation = mat4.rotate({ - math.random(), math.random(), math.random() -}, 360) - function on_save() SAVED_DATA.item = item.name(dropitem.id) SAVED_DATA.count = dropitem.count SAVED_DATA.data = dropitem.data end -do -- setup visuals - local matrix = mat4.idt() - rig:set_model(0, item.model_name(dropitem.id)) - local bodysize = math.min(scale[1], scale[2], scale[3]) * DROP_SCALE - body:set_size({scale[1] * DROP_SCALE, bodysize, scale[3] * DROP_SCALE}) - mat4.mul(matrix, rotation, matrix) - mat4.scale(matrix, scale, matrix) - rig:set_matrix(0, matrix) +if not VC_HEADLESS then + local SCALE = 0.3 + local scale = {1, 1, 1} + local rotation = mat4.rotate({ + math.random(), math.random(), math.random() + }, 360) + + do + local matrix = mat4.idt() + rig:set_model(0, item.model_name(dropitem.id)) + local bodysize = math.min(scale[1], scale[2], scale[3]) * SCALE + body:set_size({scale[1] * SCALE, bodysize, scale[3] * SCALE}) + mat4.mul(matrix, rotation, matrix) + mat4.scale(matrix, scale, matrix) + rig:set_matrix(0, matrix) + end + + local inair = true + + function on_grounded() + local matrix = mat4.idt() + mat4.rotate(matrix, {0, 1, 0}, math.random() * 360, matrix) + mat4.rotate(matrix, {1, 0, 0}, 90, matrix) + mat4.scale(matrix, scale, matrix) + rig:set_matrix(0, matrix) + inair = false + end + + function on_fall() + inair = true + end + + function on_render() + if inair then + local dt = time.delta(); + + mat4.rotate(rotation, {0, 1, 0}, 240*dt, rotation) + mat4.rotate(rotation, {0, 0, 1}, 240*dt, rotation) + + local matrix = mat4.idt() + mat4.mul(matrix, rotation, matrix) + mat4.scale(matrix, scale, matrix) + rig:set_matrix(0, matrix) + end + end end -function on_grounded(force) - local matrix = mat4.idt() - mat4.rotate(matrix, {0, 1, 0}, math.random() * 360, matrix) - mat4.rotate(matrix, {1, 0, 0}, 90, matrix) - mat4.scale(matrix, scale, matrix) - rig:set_matrix(0, matrix) - inair = false -end - -function on_fall() - inair = true -end function on_sensor_enter(index, oid) local other = entities.get(oid) @@ -85,7 +103,9 @@ function on_sensor_enter(index, oid) if timer < 0.0 and index == 0 then entity:despawn() inventory.add(player.get_inventory(pid), dropitem.id, dropitem.count, dropitem.data) - audio.play_sound_2d("events/pickup", 0.5, 0.8 + math.random() * 0.4, "regular") + if not VC_HEADLESS then + audio.play_sound_2d("events/pickup", 0.5, 0.8 + math.random() * 0.4, "regular") + end end if index == 1 then target = oid @@ -98,20 +118,6 @@ function on_sensor_exit(index, oid) end end -function on_render() - if inair then - local dt = time.delta(); - - mat4.rotate(rotation, {0, 1, 0}, 240*dt, rotation) - mat4.rotate(rotation, {0, 0, 1}, 240*dt, rotation) - - local matrix = mat4.idt() - mat4.mul(matrix, rotation, matrix) - mat4.scale(matrix, scale, matrix) - rig:set_matrix(0, matrix) - end -end - function on_update(tps) timer = timer - 1.0/tps From 2afadb4ae6b5b3159a5ba4130e93dc910cf73b80 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 19 Apr 2026 21:06:29 +0300 Subject: [PATCH 03/11] test removing client-side logic in headless (base pack scripts/components) --- res/content/base/scripts/components/drop.lua | 9 ++---- .../base/scripts/components/falling_block.lua | 4 +-- .../scripts/components/player_animator.lua | 2 -- res/content/base/scripts/grass_block.lua | 32 +++++++++++-------- res/content/base/scripts/world.lua | 2 +- res/scripts/components/player.lua | 2 +- 6 files changed, 26 insertions(+), 25 deletions(-) diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index c3d629828..f8ca6c9c7 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -26,7 +26,6 @@ function on_save() end if not VC_HEADLESS then - local SCALE = 0.3 local scale = {1, 1, 1} local rotation = mat4.rotate({ math.random(), math.random(), math.random() @@ -35,8 +34,8 @@ if not VC_HEADLESS then do local matrix = mat4.idt() rig:set_model(0, item.model_name(dropitem.id)) - local bodysize = math.min(scale[1], scale[2], scale[3]) * SCALE - body:set_size({scale[1] * SCALE, bodysize, scale[3] * SCALE}) + local bodysize = math.min(scale[1], scale[2], scale[3]) + body:set_size({scale[1], bodysize, scale[3]}) mat4.mul(matrix, rotation, matrix) mat4.scale(matrix, scale, matrix) rig:set_matrix(0, matrix) @@ -103,9 +102,7 @@ function on_sensor_enter(index, oid) if timer < 0.0 and index == 0 then entity:despawn() inventory.add(player.get_inventory(pid), dropitem.id, dropitem.count, dropitem.data) - if not VC_HEADLESS then - audio.play_sound_2d("events/pickup", 0.5, 0.8 + math.random() * 0.4, "regular") - end + audio.play_sound_2d("events/pickup", 0.5, 0.8 + math.random() * 0.4, "regular") end if index == 1 then target = oid diff --git a/res/content/base/scripts/components/falling_block.lua b/res/content/base/scripts/components/falling_block.lua index b6bbe4656..9bb02e25a 100644 --- a/res/content/base/scripts/components/falling_block.lua +++ b/res/content/base/scripts/components/falling_block.lua @@ -1,6 +1,5 @@ local tsf = entity.transform local body = entity.rigidbody -local rig = entity.skeleton local blockid = ARGS.block local blockstates = ARGS.states or 0 @@ -12,7 +11,8 @@ else SAVED_DATA.states = blockstates end -do -- setup visuals +if not VC_HEADLESS then-- setup visuals + local rig = entity.skeleton local id = block.index(blockid) local rotation = block.decompose_state(blockstates)[1] local textures = block.get_textures(id) diff --git a/res/content/base/scripts/components/player_animator.lua b/res/content/base/scripts/components/player_animator.lua index f82927ccd..6bd118906 100644 --- a/res/content/base/scripts/components/player_animator.lua +++ b/res/content/base/scripts/components/player_animator.lua @@ -1,5 +1,3 @@ -local tsf = entity.transform -local body = entity.rigidbody local rig = entity.skeleton local mob = entity:require_component("core:mob") diff --git a/res/content/base/scripts/grass_block.lua b/res/content/base/scripts/grass_block.lua index e58597cc4..fb7482c20 100644 --- a/res/content/base/scripts/grass_block.lua +++ b/res/content/base/scripts/grass_block.lua @@ -1,20 +1,26 @@ +local offsets = {} +for lx=-1,1 do + for ly=-1,1 do + for lz=-1,1 do + offsets[(lx * 3 + ly) * 3 + lz] = {lx, ly, lz} + end + end +end + function on_random_update(x, y, z) local dirtid = block.index('base:dirt'); if block.is_solid_at(x, y+1, z) then block.set(x, y, z, dirtid, 0) - else - local grassblockid = block.index('base:grass_block') - for lx=-1,1 do - for ly=-1,1 do - for lz=-1,1 do - if block.get(x + lx, y + ly, z + lz) == dirtid then - if not block.is_solid_at(x + lx, y + ly + 1, z + lz) then - block.set(x + lx, y + ly, z + lz, grassblockid, 0) - return - end - end - end - end + return + end + local grassblockid = block.index('base:grass_block') + for _, offset in ipairs(offsets) do + local nx, ny, nz = x + offset[1], y + offset[2], z + offset[3] + + if block.get(nx, ny, nz) == dirtid and + not block.is_solid_at(nx, ny + 1, nz) then + block.set(nx, ny, nz, grassblockid, 0) + return end end end diff --git a/res/content/base/scripts/world.lua b/res/content/base/scripts/world.lua index 7f468c291..9698eef54 100644 --- a/res/content/base/scripts/world.lua +++ b/res/content/base/scripts/world.lua @@ -1,5 +1,5 @@ function on_block_broken(id, x, y, z, playerid) - if gfx then + if not VC_HEADLESS then local size = {block.get_size(id)} gfx.particles.emit({ x + size[1] * 0.5, diff --git a/res/scripts/components/player.lua b/res/scripts/components/player.lua index 49a03377f..96b92fe81 100644 --- a/res/scripts/components/player.lua +++ b/res/scripts/components/player.lua @@ -80,7 +80,7 @@ function on_physics_update(delta) local front = mat4.mul(rot, {0, 0, -1}) local pos = tsf:get_pos() - if hud and pid == hud.get_player() then + if not VC_HEADLESS and pid == hud.get_player() then process_player_inputs(pid, rot, delta) end mob.look_at(vec3.add(pos, front)) From dcaf87988bfac2b622eba4003ff7c52b41088601 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 19 Apr 2026 22:04:40 +0300 Subject: [PATCH 04/11] fix components parent environment --- src/content/ContentPack.hpp | 8 ++++++++ src/logic/scripting/scripting_entities.cpp | 14 +++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index 06a8cf4dc..37dbe6b1b 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -114,6 +114,14 @@ struct ContentPack { default: return ""; } } + + static std::string_view getPrefix(std::string_view path) { + size_t pos = path.find(':'); + if (pos == std::string::npos) { + return ""; + } + return path.substr(0, pos); + } }; struct WorldFuncsSet { diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index 32a396b25..7e286c713 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -1,5 +1,6 @@ #include "scripting.hpp" +#include "debug/Logger.hpp" #include "lua/lua_engine.hpp" #include "objects/Entities.hpp" #include "objects/EntityDef.hpp" @@ -11,6 +12,7 @@ using namespace scripting; +static debug::Logger logger("scripting-entities"); static inline const std::string STDCOMP = "stdcomp"; [[nodiscard]] static scriptenv create_component_environment( @@ -61,9 +63,19 @@ static void create_component( const dv::value& args, const dv::value& saved ) { + auto parentEnv = get_root_environment(); + auto prefix = ContentPack::getPrefix(component.name); + const auto& content = *scripting::content; + if (auto runtime = content.getPackRuntime(std::string(prefix))) { + parentEnv = runtime->getEnvironment(); + } else { + logger.warning() << "pack environment is not available for " + << component.name; + } + logger.debug() << "creating instance of component " << component.name; lua::pushvalue(L, entityIdx); auto compenv = create_component_environment( - get_root_environment(), -1, component.name + std::move(parentEnv), -1, component.name ); lua::get_from(L, lua::CHUNKS_TABLE, component.name, true); lua::pushenv(L, *compenv); From 84a47b92987278e5f5771aad0452c82de47bf855 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 19 Apr 2026 22:53:34 +0300 Subject: [PATCH 05/11] cleanup --- src/content/ContentPack.cpp | 161 +++++++++++---------- src/content/ContentPack.hpp | 1 - src/logic/scripting/scripting_entities.cpp | 34 +++-- 3 files changed, 106 insertions(+), 90 deletions(-) diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index d2ffdc9e5..ec7168a28 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -47,10 +47,6 @@ io::path ContentPack::getContentFile() const { return folder / CONTENT_FILENAME; } -bool ContentPack::is_pack(const io::path& folder) { - return io::is_regular_file(folder / PACKAGE_FILENAME); -} - std::optional ContentPack::loadStats() const { auto contentFile = getContentFile(); if (!io::exists(contentFile)) { @@ -85,6 +81,9 @@ static void checkContentPackId(const std::string& id, const io::path& folder) { ); } } + if (id == "none") { + throw contentpack_error(id, folder, "content-pack id is not specified"); + } if (std::find( ContentPack::RESERVED_NAMES.begin(), ContentPack::RESERVED_NAMES.end(), @@ -94,10 +93,71 @@ static void checkContentPackId(const std::string& id, const io::path& folder) { } } +static DependencyPack parse_dependency(std::string depName) { + auto level = DependencyLevel::REQUIRED; + switch (depName.at(0)) { + case '!': + depName = depName.substr(1); + break; + case '?': + depName = depName.substr(1); + level = DependencyLevel::OPTIONAL; + break; + case '~': + depName = depName.substr(1); + level = DependencyLevel::WEAK; + break; + } + + std::string depVer = "*"; + std::string depVerOperator = "="; + + size_t versionPos = depName.rfind("@"); + if (versionPos != std::string::npos) { + depVer = depName.substr(versionPos + 1); + depName = depName.substr(0, versionPos); + + if (depVer.size() >= 2) { + std::string op = depVer.substr(0, 2); + std::uint8_t op_size = 0; + + // Two symbol operators + if (op == ">=" || op == "<=") { + op_size = 2; + depVerOperator = op; + } + + // One symbol operators + else { + op = depVer.substr(0, 1); + + if (op == ">" || op == "<") { + op_size = 1; + depVerOperator = op; + } + } + + depVer = depVer.substr(op_size); + } else { + if (depVer == ">" || depVer == "<"){ + depVer = "*"; + } + } + } + + VersionOperator versionOperator; + if (VersionOperatorMeta.getItem(depVerOperator, versionOperator)) { + return DependencyPack{level, depName, depVer, versionOperator}; + } else { + throw std::runtime_error("invalid version operator"); + } +} + ContentPack ContentPack::read(const io::path& folder) { auto root = io::read_json(folder / PACKAGE_FILENAME); ContentPack pack; root.at("id").get(pack.id); + checkContentPackId(pack.id, folder); root.at("title").get(pack.title); root.at("version").get(pack.version); if (root.has("creators")) { @@ -115,81 +175,26 @@ ContentPack ContentPack::read(const io::path& folder) { root.at("source").get(pack.source); pack.folder = folder; + auto dependenciesList = root.at("dependencies"); + if (!dependenciesList) { + return pack; + } + if (auto found = root.at("dependencies")) { const auto& dependencies = *found; for (const auto& elem : dependencies) { std::string depName = elem.asString(); - auto level = DependencyLevel::REQUIRED; - switch (depName.at(0)) { - case '!': - depName = depName.substr(1); - break; - case '?': - depName = depName.substr(1); - level = DependencyLevel::OPTIONAL; - break; - case '~': - depName = depName.substr(1); - level = DependencyLevel::WEAK; - break; - } - - std::string depVer = "*"; - std::string depVerOperator = "="; - - size_t versionPos = depName.rfind("@"); - if (versionPos != std::string::npos) { - depVer = depName.substr(versionPos + 1); - depName = depName.substr(0, versionPos); - - if (depVer.size() >= 2) { - std::string op = depVer.substr(0, 2); - std::uint8_t op_size = 0; - - // Two symbol operators - if (op == ">=" || op == "<=") { - op_size = 2; - depVerOperator = op; - } - - // One symbol operators - else { - op = depVer.substr(0, 1); - - if (op == ">" || op == "<") { - op_size = 1; - depVerOperator = op; - } - } - - depVer = depVer.substr(op_size); - } else { - if (depVer == ">" || depVer == "<"){ - depVer = "*"; - } - } - } - - VersionOperator versionOperator; - if (VersionOperatorMeta.getItem(depVerOperator, versionOperator)) { - pack.dependencies.push_back( - {level, depName, depVer, versionOperator} - ); - } else { + try { + pack.dependencies.push_back(parse_dependency(std::move(depName))); + } catch (const std::runtime_error& err) { throw contentpack_error( - pack.id, folder, "invalid version operator" + pack.id, + folder, + "dependency parsing error: " + std::string(err.what()) ); } } } - - if (pack.id == "none") { - throw contentpack_error( - pack.id, folder, "content-pack id is not specified" - ); - } - checkContentPackId(pack.id, folder); - return pack; } @@ -200,15 +205,21 @@ void ContentPack::scanFolder( return; } for (const auto& packFolder : io::directory_iterator(folder)) { - if (!io::is_directory(packFolder)) continue; - if (!is_pack(packFolder)) continue; + if (!io::is_directory(packFolder)) { + continue; + } + auto packageFile = packFolder / PACKAGE_FILENAME; + if (!io::is_regular_file(packageFile)) { + continue; + } try { packs.push_back(read(packFolder)); } catch (const contentpack_error& err) { - std::cerr << "package.json error at " << err.getFolder().string(); - std::cerr << ": " << err.what() << std::endl; + logger.warning() << "package.json error at " + << err.getFolder().string() << ": " << err.what(); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; + logger.error() << "reading " << packageFile.string() + << " error: " << err.what(); } } } diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index 37dbe6b1b..cd5f93dbc 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -85,7 +85,6 @@ struct ContentPack { static inline const io::path GENERATORS_FOLDER = "generators"; static const std::vector RESERVED_NAMES; - static bool is_pack(const io::path& folder); static ContentPack read(const io::path& folder); static void scanFolder( diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index 7e286c713..656cbbba6 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -56,6 +56,24 @@ dv::value scripting::get_component_value( return nullptr; } +static void init_component_events( + lua::State* L, const scriptenv& env, UserComponent& component +) { + lua::pushenv(L, *env); + auto& funcsset = component.funcsset; + funcsset.on_grounded = lua::hasfield(L, "on_grounded"); + funcsset.on_fall = lua::hasfield(L, "on_fall"); + funcsset.on_despawn = lua::hasfield(L, "on_despawn"); + funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter"); + funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit"); + funcsset.on_save = lua::hasfield(L, "on_save"); + funcsset.on_aim_on = lua::hasfield(L, "on_aim_on"); + funcsset.on_aim_off = lua::hasfield(L, "on_aim_off"); + funcsset.on_attacked = lua::hasfield(L, "on_attacked"); + funcsset.on_used = lua::hasfield(L, "on_used"); + lua::pop(L); +} + static void create_component( lua::State* L, int entityIdx, @@ -72,7 +90,6 @@ static void create_component( logger.warning() << "pack environment is not available for " << component.name; } - logger.debug() << "creating instance of component " << component.name; lua::pushvalue(L, entityIdx); auto compenv = create_component_environment( std::move(parentEnv), -1, component.name @@ -109,19 +126,8 @@ static void create_component( lua::setfenv(L); lua::call_nothrow(L, 0, 0); - lua::pushenv(L, *compenv); - auto& funcsset = component.funcsset; - funcsset.on_grounded = lua::hasfield(L, "on_grounded"); - funcsset.on_fall = lua::hasfield(L, "on_fall"); - funcsset.on_despawn = lua::hasfield(L, "on_despawn"); - funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter"); - funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit"); - funcsset.on_save = lua::hasfield(L, "on_save"); - funcsset.on_aim_on = lua::hasfield(L, "on_aim_on"); - funcsset.on_aim_off = lua::hasfield(L, "on_aim_off"); - funcsset.on_attacked = lua::hasfield(L, "on_attacked"); - funcsset.on_used = lua::hasfield(L, "on_used"); - lua::pop(L, 2); + init_component_events(L, compenv, component); + lua::pop(L); component.env = compenv; } From 66413531ff734cb2dde6fd6e037cae2bf67a12e3 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 19 Apr 2026 23:41:11 +0300 Subject: [PATCH 06/11] move $L, $C tables to registry --- src/logic/scripting/lua/lua_engine.cpp | 4 +- src/logic/scripting/lua/lua_util.cpp | 16 +-- src/logic/scripting/lua/lua_util.hpp | 110 +++++++++++++++------ src/logic/scripting/scripting.cpp | 2 +- src/logic/scripting/scripting_entities.cpp | 2 +- 5 files changed, 94 insertions(+), 40 deletions(-) diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index bfca17328..c27f31293 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -123,10 +123,10 @@ void lua::init_state(State* L, StateType stateType) { setglobal(L, env_name(0)); createtable(L, 0, 0); - setglobal(L, LAMBDAS_TABLE); + setregistry(L, LAMBDAS_TABLE); createtable(L, 0, 0); - setglobal(L, CHUNKS_TABLE); + setregistry(L, CHUNKS_TABLE); initialize_libs_extends(L); diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index 6fdbe91ea..a563239a7 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -220,7 +220,7 @@ void lua::dump_stack(State* L) { static std::shared_ptr create_lambda_handler(State* L) { auto ptr = reinterpret_cast(topointer(L, -1)); auto name = util::mangleid(ptr); - requireglobal(L, LAMBDAS_TABLE); + reguireregistry(L, LAMBDAS_TABLE); pushvalue(L, -2); setfield(L, name); pop(L, 2); @@ -229,7 +229,7 @@ static std::shared_ptr create_lambda_handler(State* L) { new std::string(name), [=](std::string* name) { auto L = lua::get_main_state(); - requireglobal(L, LAMBDAS_TABLE); + reguireregistry(L, LAMBDAS_TABLE); pushnil(L); setfield(L, *name); pop(L); @@ -242,8 +242,9 @@ runnable lua::create_runnable(State* L) { auto funcptr = create_lambda_handler(L); return [=]() { auto L = lua::get_main_state(); - if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) + if (!get_from_registry(L, LAMBDAS_TABLE, *funcptr, false)) { return; + } call_nothrow(L, 0, 0); pop(L); }; @@ -252,8 +253,9 @@ runnable lua::create_runnable(State* L) { KeyCallback lua::create_simple_handler(State* L) { auto funcptr = create_lambda_handler(L); return [=]() -> bool { - if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) + if (!get_from_registry(L, LAMBDAS_TABLE, *funcptr, false)) { return false; + } int top = gettop(L) - 1; if (call_nothrow(L, 0)) { int nres = gettop(L) - top; @@ -271,8 +273,9 @@ KeyCallback lua::create_simple_handler(State* L) { scripting::common_func lua::create_lambda(State* L) { auto funcptr = create_lambda_handler(L); return [=](const std::vector& args) -> dv::value { - if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) + if (!get_from_registry(L, LAMBDAS_TABLE, *funcptr, false)) { return nullptr; + } int top = gettop(L) - 1; for (const auto& arg : args) { pushvalue(L, arg); @@ -294,8 +297,9 @@ scripting::common_func lua::create_lambda(State* L) { scripting::common_func lua::create_lambda_nothrow(State* L) { auto funcptr = create_lambda_handler(L); return [=](const std::vector& args) -> dv::value { - if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) + if (!get_from_registry(L, LAMBDAS_TABLE, *funcptr, false)) { return nullptr; + } int top = gettop(L) - 1; for (const auto& arg : args) { pushvalue(L, arg); diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index f87853256..c7fa60cca 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -20,15 +20,27 @@ namespace lua { std::string env_name(int env); void dump_stack(lua::State*); - inline bool getglobal(lua::State* L, const std::string& name) { - lua_getglobal(L, name.c_str()); - if (isnil(L, -1)) { + inline bool isnoneornil(lua::State* L, int idx) { + return lua_isnoneornil(L, idx); + } + + inline bool getfield(lua::State* L, const std::string& name, int idx = -1) { + lua_getfield(L, idx, name.c_str()); + if (isnoneornil(L, -1)) { pop(L); return false; } return true; } + inline bool getglobal(lua::State* L, const std::string& name) { + return getfield(L, name, LUA_GLOBALSINDEX); + } + + inline bool getregistry(lua::State* L, const std::string& name) { + return getfield(L, name, LUA_REGISTRYINDEX); + } + inline int requireglobal(lua::State* L, const std::string& name) { if (getglobal(L, name)) { return 1; @@ -37,6 +49,14 @@ namespace lua { } } + inline int reguireregistry(lua::State* L, const std::string& name) { + if (getregistry(L, name)) { + return 1; + } else { + throw std::runtime_error("registry entry " + name + " not found"); + } + } + inline bool hasglobal(lua::State* L, const std::string& name) { lua_getglobal(L, name.c_str()); if (isnil(L, -1)) { @@ -203,9 +223,6 @@ namespace lua { inline int pushglobals(lua::State* L) { return pushvalue(L, LUA_GLOBALSINDEX); } - inline bool isnoneornil(lua::State* L, int idx) { - return lua_isnoneornil(L, idx); - } inline bool isboolean(lua::State* L, int idx) { return lua_isboolean(L, idx); } @@ -267,6 +284,9 @@ namespace lua { inline void setglobal(lua::State* L, const std::string& name) { lua_setglobal(L, name.c_str()); } + inline void setregistry(lua::State* L, const std::string& key) { + lua_setfield(L, LUA_REGISTRYINDEX, key.c_str()); + } template inline T* touserdata(lua::State* L, int idx) { if (void* rawptr = lua_touserdata(L, idx)) { @@ -461,15 +481,6 @@ namespace lua { [[nodiscard]] dv::value tovalue(lua::State*, int idx); - inline bool getfield(lua::State* L, const std::string& name, int idx = -1) { - lua_getfield(L, idx, name.c_str()); - if (isnoneornil(L, -1)) { - pop(L); - return false; - } - return true; - } - inline int requirefield( lua::State* L, const std::string& name, int idx = -1 ) { @@ -551,7 +562,46 @@ namespace lua { setfield(L, name); pop(L, 2); } else { - throw std::runtime_error("table " + tableName + " not found"); + throw std::runtime_error("global table " + tableName + " not found"); + } + } + + inline void store_in_registry( + lua::State* L, const std::string& tableName, const std::string& name + ) { + if (getregistry(L, tableName)) { + pushvalue(L, -2); + setfield(L, name); + pop(L, 2); + } else { + throw std::runtime_error("table " + tableName + " not found in registry"); + } + } + + inline int get_from( + lua::State* L, + const std::string& tableName, + const std::string& name, + bool required, + int idx, + std::string_view context + ) { + if (getfield(L, tableName, idx)) { + if (getfield(L, name)) { + return 1; + } else if (required) { + pop(L); + throw std::runtime_error( + std::string(context) + " table " + tableName + + " has no member " + name + ); + } + pop(L); + return 0; + } else { + throw std::runtime_error( + std::string(context) + " table " + tableName + " not found" + ); } } @@ -561,20 +611,20 @@ namespace lua { const std::string& name, bool required = false ) { - if (getglobal(L, tableName)) { - if (getfield(L, name)) { - return 1; - } else if (required) { - pop(L); - throw std::runtime_error( - "table " + tableName + " has no member " + name - ); - } - pop(L); - return 0; - } else { - throw std::runtime_error("table " + tableName + " not found"); - } + return get_from( + L, tableName, name, required, LUA_GLOBALSINDEX, "global" + ); + } + + inline int get_from_registry( + lua::State* L, + const std::string& tableName, + const std::string& name, + bool required = false + ) { + return get_from( + L, tableName, name, required, LUA_REGISTRYINDEX, "registry" + ); } int call(lua::State*, int argc, int nresults = -1); diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 341470c17..bf9227390 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -742,7 +742,7 @@ void scripting::load_entity_component( std::string src = io::read_string(file); logger.info() << "script (component) " << file.string(); lua::loadbuffer(L, *env, src, fileName); - lua::store_in(L, lua::CHUNKS_TABLE, name); + lua::store_in_registry(L, lua::CHUNKS_TABLE, name); } void scripting::load_world_script( diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index 656cbbba6..375b382a9 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -94,7 +94,7 @@ static void create_component( auto compenv = create_component_environment( std::move(parentEnv), -1, component.name ); - lua::get_from(L, lua::CHUNKS_TABLE, component.name, true); + lua::get_from_registry(L, lua::CHUNKS_TABLE, component.name, true); lua::pushenv(L, *compenv); if (args != nullptr) { From 678e5db03d1822581457f4d48fae1fa880aa2a6f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 26 Apr 2026 22:39:41 +0300 Subject: [PATCH 07/11] add vc.is_headless, vc.is_client --- res/content/base/scripts/components/drop.lua | 2 +- .../base/scripts/components/falling_block.lua | 2 +- res/content/base/scripts/world.lua | 2 +- res/scripts/components/player.lua | 2 +- res/scripts/stdmin.lua | 13 +++++++++++++ src/logic/scripting/lua/lua_engine.cpp | 4 ++-- 6 files changed, 19 insertions(+), 6 deletions(-) diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index f8ca6c9c7..39bd03ceb 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -25,7 +25,7 @@ function on_save() SAVED_DATA.data = dropitem.data end -if not VC_HEADLESS then +if vc.is_client() then local scale = {1, 1, 1} local rotation = mat4.rotate({ math.random(), math.random(), math.random() diff --git a/res/content/base/scripts/components/falling_block.lua b/res/content/base/scripts/components/falling_block.lua index 9bb02e25a..01c5bc5f2 100644 --- a/res/content/base/scripts/components/falling_block.lua +++ b/res/content/base/scripts/components/falling_block.lua @@ -11,7 +11,7 @@ else SAVED_DATA.states = blockstates end -if not VC_HEADLESS then-- setup visuals +if vc.is_client() then-- setup visuals local rig = entity.skeleton local id = block.index(blockid) local rotation = block.decompose_state(blockstates)[1] diff --git a/res/content/base/scripts/world.lua b/res/content/base/scripts/world.lua index 9698eef54..419dcc769 100644 --- a/res/content/base/scripts/world.lua +++ b/res/content/base/scripts/world.lua @@ -1,5 +1,5 @@ function on_block_broken(id, x, y, z, playerid) - if not VC_HEADLESS then + if vc.is_client() then local size = {block.get_size(id)} gfx.particles.emit({ x + size[1] * 0.5, diff --git a/res/scripts/components/player.lua b/res/scripts/components/player.lua index f033a2998..4f2ec53a8 100644 --- a/res/scripts/components/player.lua +++ b/res/scripts/components/player.lua @@ -80,7 +80,7 @@ function on_physics_update(delta) local front = mat4.mul(rot, {0, 0, -1}) local pos = tsf:get_pos() - if not VC_HEADLESS and pid == hud.get_player() then + if vc.is_client() and pid == hud.get_player() then process_player_inputs(pid, rot, delta) end mob.look_at(vec3.add(pos, front)) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 4ae34b2fd..be437513e 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -1,5 +1,18 @@ +local _vc_headless = __VC_HEADLESS +__VC_HEADLESS = nil + +vc = { + is_headless = function() + return _vc_headless + end, + is_client = function () + return not _vc_headless + end +} + local _ffi = ffi local _debug_getinfo = debug.getinfo +local _crc32 = crc32 function crc32(bytes, chksum) chksum = chksum or 0 diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index c27f31293..07fdfe8d2 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -95,7 +95,7 @@ static void create_libs(State* L, StateType stateType) { } addfunc(L, "print", lua::wrap); - addfunc(L, "_crc32", lua::wrap); + addfunc(L, "crc32", lua::wrap); } void lua::init_state(State* L, StateType stateType) { @@ -146,7 +146,7 @@ void lua::initialize(const EnginePaths& paths, const CoreParameters& params) { lua::setglobal(main_thread, "__VC_SCRIPT_NAME"); lua::pushboolean(main_thread, params.headless); - lua::setglobal(main_thread, "VC_HEADLESS"); + lua::setglobal(main_thread, "__VC_HEADLESS"); } void lua::finalize() { From ab179881e8458f763887a0bef865338430267b2d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 26 Apr 2026 23:30:11 +0300 Subject: [PATCH 08/11] remove __vc_app global variable --- res/modules/internal/gui_util.lua | 4 +- res/scripts/hud.lua | 36 ------------- res/scripts/stdlib.lua | 73 +++++++++++++++++++------- res/scripts/stdmin.lua | 3 +- src/logic/scripting/lua/lua_engine.cpp | 11 ++-- 5 files changed, 63 insertions(+), 64 deletions(-) diff --git a/res/modules/internal/gui_util.lua b/res/modules/internal/gui_util.lua index 0f53faaba..0ec47291e 100644 --- a/res/modules/internal/gui_util.lua +++ b/res/modules/internal/gui_util.lua @@ -24,7 +24,7 @@ function gui_util.parse_query(query) end --- @param query string page query string -function gui_util.load_page(query) +function gui_util.load_page(app, query) local name, args = gui_util.parse_query(query) for i = #gui_util.local_dispatchers, 1, -1 do local newname, newargs = gui_util.local_dispatchers[i](name, args) @@ -34,7 +34,7 @@ function gui_util.load_page(query) local filename = file.find(string.format("layouts/pages/%s.xml", name)) if filename then name = file.prefix(filename)..":pages/"..name - gui.load_document(filename, name, args, { app = __vc_app }) + gui.load_document(filename, name, args, { app = app }) return name end end diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index 322f03db5..562fe19d1 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -1,37 +1,3 @@ -local function configure_SSAO() - -- Temporary using slot to configure built-in SSAO effect - local slot = gfx.posteffects.index("core:ssao") - gfx.posteffects.set_effect(slot, "ssao") - - -- Generating random SSAO samples - local buffer = Bytearray(0) - for i = 0, 63 do - local x = math.random() * 2.0 - 1.0 - local y = math.random() * 2.0 - 1.0 - local z = math.random() * 2.0 - local len = math.sqrt(x * x + y * y + z * z) - if len > 0 then - x = x / len - y = y / len - z = z / len - end - Bytearray.append(buffer, byteutil.pack("fff", x, y, z)) - end - gfx.posteffects.set_array(slot, "u_ssaoSamples", Bytearray_as_string(buffer)) - - local function update_ssao_quality(value) - value = math.min(value, 3) - gfx.posteffects.set_params(slot, { - u_kernelSize = value * 16, - u_radius = 0.4 / value, - u_bias = 0.006 / value / value, - }) - end - events.on("core:setting.graphics.ssao.set", update_ssao_quality) - - update_ssao_quality(__vc_app.get_setting("graphics.ssao")) -end - local function update_hand() local skeleton = gfx.skeletons local pid = hud.get_player() @@ -123,8 +89,6 @@ function on_hud_open() end end) - configure_SSAO() - hud.default_hand_controller = update_hand end diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 900b71b2e..3a7ecf02d 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -1,4 +1,5 @@ -local enable_experimental = __vc_app.get_setting("debug.enable-experimental") +local __app = __vc_app +local enable_experimental = __app.get_setting("debug.enable-experimental") ------------------------------------------------ ------ Extended kit of standard functions ------ @@ -119,12 +120,7 @@ local function complete_app_lib(app) end end -if app then - complete_app_lib(app) -elseif __vc_app then - complete_app_lib(__vc_app) -end - +complete_app_lib(__app) require "core:internal/maths_inline" require "core:internal/debugging" require "core:internal/audio_input" @@ -175,18 +171,18 @@ function start_coroutine(chunk, name) local co = coroutine.create(function() local status, error = xpcall(chunk, function(err) local fullmsg = "error: "..string.match(err, ": (.+)").."\n"..debug.traceback() - - if hud then - gui.alert(fullmsg, function() - if world.is_open() then - __vc_app.close_world() - else - __vc_app.reset_content() - menu:reset() - menu.page = "main" - end - end) + if vc.is_headless() then + return fullmsg end + gui.alert(fullmsg, function() + if world.is_open() then + __app.close_world() + else + __app.reset_content() + menu:reset() + menu.page = "main" + end + end) return fullmsg end) if not status then @@ -204,7 +200,7 @@ function __vc_start_app_script(path, name) if chunk == nil then error(err) end - local script_env = setmetatable({app = app or __vc_app}, {__index=_G}) + local script_env = setmetatable({app = __app}, {__index=_G}) chunk = setfenv(chunk, script_env) if name then start_coroutine(chunk, name) @@ -222,7 +218,7 @@ gui_util = require "core:internal/gui_util" Document = gui_util.Document Element = gui_util.Element RadioGroup = gui_util.RadioGroup -__vc_page_loader = gui_util.load_page +__vc_page_loader = function(...) return gui_util.load_page(__app, ...) end function __vc_get_document_node(docname, nodeid) return Element.new(docname, nodeid) @@ -384,6 +380,40 @@ math.randomseed(time.uptime() * 1536227939) rules = require "core:internal/rules" local _rules = rules +local function configure_SSAO() + -- Temporary using slot to configure built-in SSAO effect + local slot = gfx.posteffects.index("core:ssao") + gfx.posteffects.set_effect(slot, "ssao") + + -- Generating random SSAO samples + local buffer = Bytearray(0) + for i = 0, 63 do + local x = math.random() * 2.0 - 1.0 + local y = math.random() * 2.0 - 1.0 + local z = math.random() * 2.0 + local len = math.sqrt(x * x + y * y + z * z) + if len > 0 then + x = x / len + y = y / len + z = z / len + end + Bytearray.append(buffer, byteutil.pack("fff", x, y, z)) + end + gfx.posteffects.set_array(slot, "u_ssaoSamples", Bytearray_as_string(buffer)) + + local function update_ssao_quality(value) + value = math.min(value, 3) + gfx.posteffects.set_params(slot, { + u_kernelSize = value * 16, + u_radius = 0.4 / value, + u_bias = 0.006 / value / value, + }) + end + events.on("core:setting.graphics.ssao.set", update_ssao_quality) + + update_ssao_quality(__app.get_setting("graphics.ssao")) +end + function __vc_on_hud_open() local _hud_is_content_access = hud._is_content_access local _hud_set_content_access = hud._set_content_access @@ -446,6 +476,8 @@ function __vc_on_hud_open() end end) hud.open_permanent("core:ingame_chat") + + configure_SSAO() end local Schedule = require "core:schedule" @@ -590,4 +622,5 @@ end require "core:internal/deprecated" ffi = nil +__vc_app = nil __vc_lock_internal_modules() diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index be437513e..fa1ec0422 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -11,7 +11,6 @@ vc = { } local _ffi = ffi -local _debug_getinfo = debug.getinfo local _crc32 = crc32 function crc32(bytes, chksum) @@ -116,6 +115,8 @@ end ---------------------------------------------- +local _debug_getinfo = debug.getinfo + function debug.count_frames() local frames = 1 while true do diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 07fdfe8d2..5dfc8ebd1 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -64,12 +64,13 @@ static void create_libs(State* L, StateType stateType) { openlib(L, "vec4", vec4lib); openlib(L, "yaml", yamllib); + openlib(L, "__vc_app", applib); + lua::getglobal(L, "__vc_app"); + lua::setregistry(L, "app"); + if (stateType == StateType::SCRIPT) { - openlib(L, "app", applib); - lua::getglobal(L, "app"); - lua::setglobal(L, "__vc_app"); - } else if (stateType == StateType::BASE) { - openlib(L, "__vc_app", applib); + lua::getregistry(L, "app"); + lua::setglobal(L, "app"); } if (stateType == StateType::BASE || stateType == StateType::SCRIPT) { openlib(L, "assets", assetslib); From 629f672d38fff9ca8b730fb7d24863dffb847836 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 27 Apr 2026 00:24:25 +0300 Subject: [PATCH 09/11] cleanup internal functions --- res/scripts/stdlib.lua | 24 ++++++++++++++++-------- res/scripts/stdmin.lua | 18 ++++++++++++------ src/logic/scripting/lua/lua_engine.cpp | 2 ++ src/logic/scripting/lua/lua_util.cpp | 6 +++--- src/logic/scripting/lua/lua_util.hpp | 3 ++- src/logic/scripting/scripting.cpp | 8 ++------ 6 files changed, 37 insertions(+), 24 deletions(-) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 3a7ecf02d..0e72ad86d 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -540,20 +540,27 @@ function __vc_on_world_save() file.write(RULES_FILE, toml.tostring(rule_values)) end +local __close_all_descriptors = file.__close_all_descriptors +local __gui_util_reset_local = gui_util.__reset_local +local __stdcomp_reset = stdcomp.__reset +file.__close_all_descriptors = nil +gui_util.__reset_local = nil +stdcomp.reset = nil + function __vc_on_world_quit() _rules.clear() - gui_util:__reset_local() - stdcomp.__reset() - file.__close_all_descriptors() + __gui_util_reset_local() + __stdcomp_reset() + __close_all_descriptors() end -local __post_runnables = {} - local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer audio.__reset_fetch_buffer = nil core.get_core_token = audio.input.__get_core_token -local function __vc__process_post_runnables() +local __post_runnables = {} + +local function __process_post_runnables() if #__post_runnables > 0 then for _, func in ipairs(__post_runnables) do local status, result = xpcall(func, __vc__error) @@ -587,9 +594,9 @@ local function __vc__process_post_runnables() end end -function __process_post_runnables() +function __vc__process_post_runnables() __vc__is_post_runnable = true - local success, err = pcall(__vc__process_post_runnables) + local success, err = pcall(__process_post_runnables) if not success then debug.error("an error ocurred while processing post-runnables: ".. err) end @@ -624,3 +631,4 @@ require "core:internal/deprecated" ffi = nil __vc_app = nil __vc_lock_internal_modules() +__vc_lock_internal_modules = nil diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index fa1ec0422..70e27f561 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -192,7 +192,7 @@ function reload_module(name) end end -local internal_locked = false +local __internal_locked = false -- Load script with caching -- @@ -202,9 +202,12 @@ local internal_locked = false -- nocache - ignore cached script, load anyway function __load_script(path, nocache, env) local packname, filename = parse_path(path) + local is_internal = (packname == "res" or packname == "core") + and filename:find("modules/internal") == 1 - if internal_locked and (packname == "res" or packname == "core") - and filename:starts_with("modules/internal") then + nocache = nocache or is_internal + + if is_internal and __internal_locked then error("access to core:internal modules outside of [core]") end @@ -232,16 +235,19 @@ function __load_script(path, nocache, env) end function __vc_lock_internal_modules() - internal_locked = true + __internal_locked = true end +local __pack_envs = __vc__pack_envs +__vc__pack_envs = nil + function require(path) if not string.find(path, ':') then local prefix, _ = parse_path(_debug_getinfo(2).source) return require(prefix .. ':' .. path) end local prefix, file = parse_path(path) - local env = __vc__pack_envs[prefix] + local env = __pack_envs[prefix] return __load_script(prefix .. ":modules/" .. file .. ".lua", nil, env) end @@ -262,7 +268,7 @@ function __scripts_cleanup(non_reset_packs) __cached_scripts[k] = nil package.loaded[k] = nil end - __vc__pack_envs[packname] = nil + __pack_envs[packname] = nil ::continue:: end end diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 5dfc8ebd1..1536cb41f 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -113,7 +113,9 @@ void lua::init_state(State* L, StateType stateType) { setglobal(L, "io"); createtable(L, 0, 0); + pushvalue(L, -1); setglobal(L, "__vc__pack_envs"); + setregistry(L, lua::PACK_ENVS_TABLE); const char* removed_os[] { "execute", "exit", "remove", "rename", "setlocale", "tmpname", nullptr}; diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index a563239a7..51bfb4d65 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -220,7 +220,7 @@ void lua::dump_stack(State* L) { static std::shared_ptr create_lambda_handler(State* L) { auto ptr = reinterpret_cast(topointer(L, -1)); auto name = util::mangleid(ptr); - reguireregistry(L, LAMBDAS_TABLE); + requireregistry(L, LAMBDAS_TABLE); pushvalue(L, -2); setfield(L, name); pop(L, 2); @@ -229,7 +229,7 @@ static std::shared_ptr create_lambda_handler(State* L) { new std::string(name), [=](std::string* name) { auto L = lua::get_main_state(); - reguireregistry(L, LAMBDAS_TABLE); + requireregistry(L, LAMBDAS_TABLE); pushnil(L); setfield(L, *name); pop(L); @@ -341,7 +341,7 @@ int lua::create_environment(State* L, int parent) { } int lua::restore_pack_environment(lua::State* L, const std::string& packid) { - if(!lua::getglobal(L, "__vc__pack_envs")) { + if(!lua::requireregistry(L, lua::PACK_ENVS_TABLE)) { return -1; } int id = nextEnvironment++; diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index c7fa60cca..d6a45fecd 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -14,6 +14,7 @@ namespace lua { inline std::string LAMBDAS_TABLE = "$L"; // lambdas storage inline std::string CHUNKS_TABLE = "$C"; // precompiled lua chunks + inline std::string PACK_ENVS_TABLE = "$P"; extern std::unordered_map usertypeNames; int userdata_destructor(lua::State* L); @@ -49,7 +50,7 @@ namespace lua { } } - inline int reguireregistry(lua::State* L, const std::string& name) { + inline int requireregistry(lua::State* L, const std::string& name) { if (getregistry(L, name)) { return 1; } else { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index bf9227390..806f92c24 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -188,11 +188,7 @@ std::unique_ptr scripting::start_app_script(const io::path& script) { lua::pushstring(L, pack.id); lua::setfield(L, "PACK_ID"); - if(!lua::getglobal(L, "__vc__pack_envs")) { - lua::createtable(L, 0, 0); - lua::setglobal(L, "__vc__pack_envs"); - lua::pushvalue(L, -1); - } + lua::requireregistry(L, lua::PACK_ENVS_TABLE); lua::pushenv(L, id); lua::setfield(L, pack.id); lua::pop(L); @@ -248,7 +244,7 @@ std::unique_ptr scripting::start_app_script(const io::path& script) { void scripting::process_post_runnables() { auto L = lua::get_main_state(); - if (lua::getglobal(L, "__process_post_runnables")) { + if (lua::getglobal(L, "__vc__process_post_runnables")) { lua::call_nothrow(L, 0, 0); } } From ad1d7477adeb618f5e08b482b2b1f33c1b9d41ba Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 27 Apr 2026 02:16:24 +0300 Subject: [PATCH 10/11] cleanup global environment (_ENV* variables) --- src/logic/scripting/lua/libs/libgui.cpp | 5 +-- src/logic/scripting/lua/lua_engine.cpp | 8 +++-- src/logic/scripting/lua/lua_util.cpp | 32 ++++++++++------- src/logic/scripting/lua/lua_util.hpp | 46 +++++++++++++++---------- 4 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 382113171..0e4645adc 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -1010,8 +1010,9 @@ static int l_gui_get_env(lua::State* L) { "document '" + std::string(name) + "' not found" ); } - lua::getglobal(L, lua::env_name(*doc->getEnvironment())); - return 1; + return lua::getregistry( + L, lua::ENVS_TABLE, lua::env_name(*doc->getEnvironment()) + ); } static int l_gui_str(lua::State* L) { diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 1536cb41f..0143f6a9e 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -122,15 +122,17 @@ void lua::init_state(State* L, StateType stateType) { remove_lib_funcs(L, "os", removed_os); create_libs(L, stateType); - pushglobals(L); - setglobal(L, env_name(0)); - createtable(L, 0, 0); setregistry(L, LAMBDAS_TABLE); createtable(L, 0, 0); setregistry(L, CHUNKS_TABLE); + createtable(L, 0, 0); + pushglobals(L); + setfield(L, env_name(0)); + setregistry(L, ENVS_TABLE); + initialize_libs_extends(L); newusertype(L); diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index 51bfb4d65..ded9cc79b 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -8,7 +8,7 @@ using namespace lua; -static int nextEnvironment = 1; +static int next_environment = 1; std::unordered_map lua::usertypeNames; @@ -20,7 +20,7 @@ int lua::userdata_destructor(lua::State* L) { } std::string lua::env_name(int env) { - return "_ENV" + util::mangleid(env); + return std::to_string(env); } int lua::pushvalue(State* L, const dv::value& value) { @@ -317,8 +317,16 @@ scripting::common_func lua::create_lambda_nothrow(State* L) { }; } +static void store_env(lua::State* L, int id) { + requireregistry(L, ENVS_TABLE); + pushvalue(L, -2); + lua_remove(L, -3); + setfield(L, env_name(id)); + pop(L); +} + int lua::create_environment(State* L, int parent) { - int id = nextEnvironment++; + int id = next_environment++; // local env = {} createtable(L, 0, 1); @@ -335,24 +343,22 @@ int lua::create_environment(State* L, int parent) { setfield(L, "__index"); setmetatable(L); - // envname = env - setglobal(L, env_name(id)); + store_env(L, id); return id; } int lua::restore_pack_environment(lua::State* L, const std::string& packid) { - if(!lua::requireregistry(L, lua::PACK_ENVS_TABLE)) { + if(!requireregistry(L, PACK_ENVS_TABLE)) { return -1; } - int id = nextEnvironment++; + int id = next_environment++; - if (lua::getfield(L, packid)) { - // envname = env - setglobal(L, env_name(id)); - lua::pop(L); + if (getfield(L, packid)) { + store_env(L, id); + pop(L); return id; } - lua::pop(L); + pop(L); return -1; } @@ -361,5 +367,5 @@ void lua::remove_environment(State* L, int id) { return; } pushnil(L); - setglobal(L, env_name(id)); + store_env(L, id); } diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index d6a45fecd..9ae1e48f6 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -15,6 +15,7 @@ namespace lua { inline std::string LAMBDAS_TABLE = "$L"; // lambdas storage inline std::string CHUNKS_TABLE = "$C"; // precompiled lua chunks inline std::string PACK_ENVS_TABLE = "$P"; + inline std::string ENVS_TABLE = "$E"; extern std::unordered_map usertypeNames; int userdata_destructor(lua::State* L); @@ -58,6 +59,16 @@ namespace lua { } } + inline bool getregistry(lua::State* L, const std::string& name, const std::string& key) { + requireregistry(L, name); + if (getfield(L, key)) { + lua_remove(L, -2); + return true; + } + pop(L); + return false; + } + inline bool hasglobal(lua::State* L, const std::string& name) { lua_getglobal(L, name.c_str()); if (isnil(L, -1)) { @@ -550,7 +561,7 @@ namespace lua { if (luaL_loadbuffer(L, src.c_str(), src.length(), file.c_str())) { throw luaerror(tostring(L, -1)); } - if (env && getglobal(L, env_name(env))) { + if (env && getregistry(L, ENVS_TABLE, env_name(env))) { lua_setfenv(L, -2); } } @@ -658,10 +669,7 @@ namespace lua { scripting::common_func create_lambda_nothrow(lua::State*); inline int pushenv(lua::State* L, int env) { - if (getglobal(L, env_name(env))) { - return 1; - } - return 0; + return getregistry(L, ENVS_TABLE, env_name(env)); } int create_environment(lua::State*, int parent); int restore_pack_environment(lua::State*, const std::string& packid); @@ -790,14 +798,14 @@ namespace lua { inline void read_bytes_from_table( lua::State* L, int tableIndex, std::vector& bytes ) { - if (!lua::istable(L, tableIndex)) { + if (!istable(L, tableIndex)) { throw std::runtime_error("table expected"); } else { - size_t size = lua::objlen(L, tableIndex); + size_t size = objlen(L, tableIndex); for (size_t i = 0; i < size; i++) { - lua::rawgeti(L, i + 1, tableIndex); - const int byte = lua::tointeger(L, -1); - lua::pop(L); + rawgeti(L, i + 1, tableIndex); + const int byte = tointeger(L, -1); + pop(L); if (byte < 0 || byte > 255) { throw std::runtime_error( "invalid byte '" + std::to_string(byte) + "'" @@ -809,11 +817,11 @@ namespace lua { } inline int create_bytearray(lua::State* L, const void* bytes, size_t size) { - lua::requireglobal(L, "Bytearray_construct"); - lua::pushlstring( + requireglobal(L, "Bytearray_construct"); + pushlstring( L, std::string_view(reinterpret_cast(bytes), size) ); - return lua::call(L, 1, 1); + return call(L, 1, 1); } inline int create_bytearray(lua::State* L, const std::vector& bytes) { @@ -821,12 +829,12 @@ namespace lua { } inline std::string_view bytearray_as_string(lua::State* L, int idx) { - lua::pushvalue(L, idx); - lua::requireglobal(L, "Bytearray_as_string"); - lua::pushvalue(L, -2); - lua::call(L, 1, 1); - auto view = lua::tolstring(L, -1); - lua::pop(L, 2); + pushvalue(L, idx); + requireglobal(L, "Bytearray_as_string"); + pushvalue(L, -2); + call(L, 1, 1); + auto view = tolstring(L, -1); + pop(L, 2); return view; } } From bc7d3abeade838a781813924882e98e7d8ebc759 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 27 Apr 2026 02:16:46 +0300 Subject: [PATCH 11/11] add lua panic handler --- src/logic/scripting/lua/lua_engine.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 0143f6a9e..bd2f0359b 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -99,7 +99,15 @@ static void create_libs(State* L, StateType stateType) { addfunc(L, "crc32", lua::wrap); } +static int l_panic_handler(lua::State* L) { + logger.error() << "PANIC: unprotected error in call to Lua API: " << lua::tostring(L, -1); + logger.flush(); + abort(); +} + void lua::init_state(State* L, StateType stateType) { + lua_atpanic(L, l_panic_handler); + // Allowed standard libraries luaL_openlibs(L);