Merge pull request #832 from MihailRis/client-server-logic-split

Client server logic split
This commit is contained in:
MihailRis 2026-04-29 00:28:02 +03:00 • committed by GitHub
commit 0ca5b7bb4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 466 additions and 316 deletions

View file

@ -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,58 @@ 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 vc.is_client() then
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])
body:set_size({scale[1], bodysize, scale[3]})
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)
@ -98,20 +115,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

View file

@ -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 vc.is_client() 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)

View file

@ -1,5 +1,3 @@
local tsf = entity.transform
local body = entity.rigidbody
local rig = entity.skeleton
local mob = entity:require_component("core:mob")

View file

@ -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

View file

@ -1,5 +1,5 @@
function on_block_broken(id, x, y, z, playerid)
if gfx then
if vc.is_client() then
local size = {block.get_size(id)}
gfx.particles.emit({
x + size[1] * 0.5,

View file

@ -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

View file

@ -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 vc.is_client() and pid == hud.get_player() then
process_player_inputs(pid, rot, delta)
end
mob.look_at(vec3.add(pos, front))

View file

@ -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

View file

@ -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"
@ -508,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)
@ -555,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
@ -590,4 +629,6 @@ end
require "core:internal/deprecated"
ffi = nil
__vc_app = nil
__vc_lock_internal_modules()
__vc_lock_internal_modules = nil

View file

@ -1,5 +1,17 @@
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
@ -103,6 +115,8 @@ end
----------------------------------------------
local _debug_getinfo = debug.getinfo
function debug.count_frames()
local frames = 1
while true do
@ -178,7 +192,7 @@ function reload_module(name)
end
end
local internal_locked = false
local __internal_locked = false
-- Load script with caching
--
@ -188,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
@ -218,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
@ -248,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

View file

@ -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<ContentPackStats> 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();
}
}
}

View file

@ -85,7 +85,6 @@ struct ContentPack {
static inline const io::path GENERATORS_FOLDER = "generators";
static const std::vector<std::string> RESERVED_NAMES;
static bool is_pack(const io::path& folder);
static ContentPack read(const io::path& folder);
static void scanFolder(
@ -114,6 +113,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 {

View file

@ -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) {

View file

@ -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);
@ -95,10 +96,18 @@ static void create_libs(State* L, StateType stateType) {
}
addfunc(L, "print", lua::wrap<l_print>);
addfunc(L, "_crc32", lua::wrap<l_crc32>);
addfunc(L, "crc32", lua::wrap<l_crc32>);
}
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);
@ -112,21 +121,25 @@ 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};
remove_lib_funcs(L, "os", removed_os);
create_libs(L, stateType);
createtable(L, 0, 0);
setregistry(L, LAMBDAS_TABLE);
createtable(L, 0, 0);
setregistry(L, CHUNKS_TABLE);
createtable(L, 0, 0);
pushglobals(L);
setglobal(L, env_name(0));
createtable(L, 0, 0);
setglobal(L, LAMBDAS_TABLE);
createtable(L, 0, 0);
setglobal(L, CHUNKS_TABLE);
setfield(L, env_name(0));
setregistry(L, ENVS_TABLE);
initialize_libs_extends(L);
@ -144,6 +157,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() {

View file

@ -8,7 +8,7 @@
using namespace lua;
static int nextEnvironment = 1;
static int next_environment = 1;
std::unordered_map<std::type_index, std::string> 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) {
@ -220,7 +220,7 @@ void lua::dump_stack(State* L) {
static std::shared_ptr<std::string> create_lambda_handler(State* L) {
auto ptr = reinterpret_cast<ptrdiff_t>(topointer(L, -1));
auto name = util::mangleid(ptr);
requireglobal(L, LAMBDAS_TABLE);
requireregistry(L, LAMBDAS_TABLE);
pushvalue(L, -2);
setfield(L, name);
pop(L, 2);
@ -229,7 +229,7 @@ static std::shared_ptr<std::string> create_lambda_handler(State* L) {
new std::string(name),
[=](std::string* name) {
auto L = lua::get_main_state();
requireglobal(L, LAMBDAS_TABLE);
requireregistry(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<dv::value>& 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<dv::value>& 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);
@ -313,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);
@ -331,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::getglobal(L, "__vc__pack_envs")) {
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;
}
@ -357,5 +367,5 @@ void lua::remove_environment(State* L, int id) {
return;
}
pushnil(L);
setglobal(L, env_name(id));
store_env(L, id);
}

View file

@ -14,21 +14,35 @@
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<std::type_index, std::string> usertypeNames;
int userdata_destructor(lua::State* L);
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 +51,24 @@ namespace lua {
}
}
inline int requireregistry(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 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)) {
@ -203,9 +235,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 +296,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 <class T>
inline T* touserdata(lua::State* L, int idx) {
if (void* rawptr = lua_touserdata(L, idx)) {
@ -461,15 +493,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
) {
@ -538,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);
}
}
@ -551,7 +574,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 +623,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);
@ -607,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);
@ -739,14 +798,14 @@ namespace lua {
inline void read_bytes_from_table(
lua::State* L, int tableIndex, std::vector<ubyte>& 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) + "'"
@ -758,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<const char*>(bytes), size)
);
return lua::call(L, 1, 1);
return call(L, 1, 1);
}
inline int create_bytearray(lua::State* L, const std::vector<ubyte>& bytes) {
@ -770,15 +829,15 @@ namespace lua {
}
inline std::string_view bytearray_as_string(lua::State* L, int idx) {
if (lua::type(L, idx) == LUA_TSTRING) {
return lua::tolstring(L, idx);
if (type(L, idx) == LUA_TSTRING) {
return tolstring(L, 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;
}
}

View file

@ -188,11 +188,7 @@ std::unique_ptr<Process> 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<Process> 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);
}
}
@ -742,7 +738,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(

View file

@ -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(
@ -54,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,
@ -61,11 +81,20 @@ 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;
}
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::get_from_registry(L, lua::CHUNKS_TABLE, component.name, true);
lua::pushenv(L, *compenv);
if (args != nullptr) {
@ -97,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;
}