/* * Copyright (c) 2010-2015, Argon Sun (Fluorohydride) * Copyright (c) 2016-2026, Edoardo Lolletti (edo9300) * * SPDX-License-Identifier: AGPL-3.0-or-later */ #include //std::memcpy #include //std::exchange #include #include "duel.h" #include "card.h" #include "effect.h" #include "field.h" #include "interpreter.h" #include "lua_obj.h" #include "group.h" #include "scriptlib.h" using namespace scriptlib; // This function will be used by a lua library built with api check #ifdef __GNUC__ [[gnu::used]] #endif void ocgcore_lua_api_check(void* state, const char* error_message) { auto L = static_cast(state); auto pduel = lua_get(L); pduel->handle_message(error_message, OCG_LOG_TYPE_ERROR); } // Explicitly check for stack unwinding to be performed when lua errors are raised. // This required as otherwise when errors are raised, c++ classes will not be // properly cleaned and memory corruption issues will arise. // For this we need lua to be built as c++ so that internally it'll use try/catch instead // of setjmp/longjmp. // A particular case is MSVC crt which in some versions also performs stack unwinding via the use // of structured exceptions, this check function will also account for that, and in that // case the core will still be accepted. static bool check_lua_stack_unwinding(lua_State* L) { uint32_t flag = 0xDEADBEEF; std::memcpy(lua_getextraspace(L), &flag, sizeof(flag)); lua_pushcfunction(L, [](lua_State* L)->int { struct Guard { lua_State* state; ~Guard() { uint32_t new_flag = 0xFFFFFFFF; std::memcpy(lua_getextraspace(state), &new_flag, sizeof(new_flag)); } } _{ L }; luaL_error(L, ""); return 0; }); if(lua_pcall(L, 0, 0, 0) != LUA_OK) { lua_pop(L, 1); } std::memcpy(&flag, lua_getextraspace(L), sizeof(flag)); return flag == 0xFFFFFFFF; } interpreter::interpreter(duel* pd, const OCG_DuelOptions& options, bool& valid_lua_lib): coroutines(256), deleted(pd) { lua_state = luaL_newstate(); if(!check_lua_stack_unwinding(lua_state)) { valid_lua_lib = false; pd->handle_message("The lua library linked with this ocgcore does not support c++'s stack unwinding", OCG_LOG_TYPE_ERROR); return; } current_state = lua_state; pduel = pd; no_action = 0; call_depth = 0; std::memcpy(lua_getextraspace(lua_state), &pd, sizeof(duel*)); // Open basic and used functionality auto open_lib = [L=lua_state](const char* libname, lua_CFunction openf) { ensure_luaL_stack(luaL_requiref, L, libname, openf, 1); lua_pop(L, 1); }; open_lib("_G", luaopen_base); open_lib(LUA_STRLIBNAME, luaopen_string); open_lib(LUA_TABLIBNAME, luaopen_table); open_lib(LUA_MATHLIBNAME, luaopen_math); if(options.enableUnsafeLibraries != 0) open_lib(LUA_IOLIBNAME, luaopen_io); auto nil_out = [&](const char* name) { lua_pushnil(lua_state); lua_setglobal(lua_state, name); }; // Remove "dangerous" functions nil_out("collectgarbage"); if(options.enableUnsafeLibraries == 0) { nil_out("dofile"); nil_out("loadfile"); } { /* Creates a table and sets a metatable to it making a table with weak keys, which will then be used in place of the LUA_REGISTRYINDEX table to keep track of groups when they are not owned by the core */ luaL_checkstack(lua_state, 4, nullptr); lua_newtable(lua_state); lua_newtable(lua_state); lua_pushliteral(lua_state, "__mode"); lua_pushliteral(lua_state, "v"); lua_rawset(lua_state, -3); // metatable._mode='v' lua_setmetatable(lua_state, -2); weak_lua_references = ensure_luaL_stack(luaL_ref, lua_state, LUA_REGISTRYINDEX); } { lua_newtable(lua_state); loaded_scripts_table = ensure_luaL_stack(luaL_ref, lua_state, LUA_REGISTRYINDEX); } // Open all card scripting libs scriptlib::push_card_lib(lua_state); scriptlib::push_effect_lib(lua_state); scriptlib::push_group_lib(lua_state); scriptlib::push_duel_lib(lua_state); scriptlib::push_debug_lib(lua_state); } interpreter::~interpreter() { lua_close(lua_state); } //creates a pointer to a lua_obj in the lua stack static inline lua_obj** create_object(lua_State* L) { #if LUA_VERSION_NUM <= 503 return static_cast(lua_newuserdata(L, sizeof(lua_obj*))); #else // in lua 5.4 and later, userdata can have an arbitrary number of // user values, including 0, which make the userdata use less memory return static_cast(lua_newuserdatauv(L, sizeof(lua_obj*), 0)); #endif } void interpreter::register_card(card* pcard) { //create a card in by userdata luaL_checkstack(lua_state, 1, nullptr); luaL_checkstack(current_state, 1, nullptr); lua_obj** ppcard = create_object(lua_state); *ppcard = pcard; pcard->ref_handle = ensure_luaL_stack(luaL_ref, lua_state, LUA_REGISTRYINDEX); //creates the pointer in the main state, then after taking a reference //pushes it in the stack of the current_state, as load_card_script push the metatable there lua_rawgeti(current_state, LUA_REGISTRYINDEX, pcard->ref_handle); //load script if(pcard->data.alias && (pcard->data.alias < pcard->data.code + 10) && (pcard->data.code < pcard->data.alias + 10)) load_card_script(pcard->data.alias); else load_card_script(pcard->data.code); //set metatable of pointer to base script lua_setmetatable(current_state, -2); lua_pop(current_state, 1); //Initial if(pcard->data.code) { const bool forced = !(pcard->data.type & TYPE_NORMAL) || (pcard->data.type & TYPE_PENDULUM); pcard->set_status(STATUS_INITIALIZING, TRUE); add_param(pcard); call_card_function(pcard, "initial_effect", 1, 0, forced); pcard->set_status(STATUS_INITIALIZING, FALSE); } pcard->cardid = pduel->game_field->infos.card_id++; } static inline void remove_object(lua_State* L, lua_obj* obj, lua_obj* replacement) { if(!obj || !obj->ref_handle) return; lua_rawgeti(L, LUA_REGISTRYINDEX, obj->ref_handle); lua_obj** lobj = static_cast(lua_touserdata(L, -1)); if(lobj) *lobj = replacement; lua_pop(L, 1); ensure_luaL_stack(luaL_unref, L, LUA_REGISTRYINDEX, obj->ref_handle); obj->ref_handle = 0; } void interpreter::register_effect(effect* peffect) { register_obj(peffect, "Effect", false); } void interpreter::unregister_effect(effect* peffect) { if (!peffect) return; if(peffect->condition) ensure_luaL_stack(luaL_unref, lua_state, LUA_REGISTRYINDEX, peffect->condition); if(peffect->cost) ensure_luaL_stack(luaL_unref, lua_state, LUA_REGISTRYINDEX, peffect->cost); if(peffect->target) ensure_luaL_stack(luaL_unref, lua_state, LUA_REGISTRYINDEX, peffect->target); if(peffect->operation) ensure_luaL_stack(luaL_unref, lua_state, LUA_REGISTRYINDEX, peffect->operation); if(peffect->value && peffect->is_flag(EFFECT_FLAG_FUNC_VALUE)) ensure_luaL_stack(luaL_unref, lua_state, LUA_REGISTRYINDEX, peffect->value); if(peffect->label_object) ensure_luaL_stack(luaL_unref, lua_state, LUA_REGISTRYINDEX, peffect->label_object); remove_object(lua_state, peffect, &deleted); } void interpreter::register_group(group* pgroup) { register_obj(pgroup, "Group", true); } void interpreter::register_obj(lua_obj* obj, const char* tablename, bool weak) { if(!obj) return; if(weak) { luaL_checkstack(lua_state, 1, nullptr); lua_rawgeti(lua_state, LUA_REGISTRYINDEX, weak_lua_references); } luaL_checkstack(lua_state, 3, nullptr); lua_obj** pobj = create_object(lua_state); *pobj = obj; //set metatable current lua object lua_getglobal(lua_state, tablename); lua_setmetatable(lua_state, -2); //pops the lua object from the stack and takes a reference of it if(weak) { obj->weak_ref_handle = ensure_luaL_stack(luaL_ref, lua_state, -2); lua_pop(lua_state, 1); } else { obj->ref_handle = ensure_luaL_stack(luaL_ref, lua_state, LUA_REGISTRYINDEX); } } void interpreter::collect(bool full) { lua_gc(current_state, full ? LUA_GCCOLLECT : LUA_GCSTEP, 0); } bool interpreter::load_script(const char* buffer, int len, const char* script_name) { if(!buffer) return false; ++no_action; if(ensure_luaL_stack(luaL_loadbuffer, current_state, buffer, len, script_name) != LUA_OK || lua_pcall(current_state, 0, 0, 0) != LUA_OK) { pduel->handle_message(lua_get_string_or_empty(current_state, -1), OCG_LOG_TYPE_ERROR); lua_pop(current_state, 1); --no_action; return false; } --no_action; return true; } //Load a card script (if not already loaded) and push the corresponding cXXXXXX table on the stack, +1 -0 bool interpreter::load_card_script(uint32_t code) { char code_buf[32]; const char* class_name = format_to(code_buf, "c%u", code); luaL_checkstack(current_state, 1, nullptr); //puts cXXXXX on the stack lua_getglobal(current_state, class_name); //+1 if(!lua_isnil(current_state, -1)) return true; //if script is not loaded, create and load it lua_pop(current_state, 1); //-1 luaL_checkstack(current_state, 5, nullptr); { //self_code=code lua_pushinteger(current_state, code); //+1 lua_setglobal(current_state, "self_code"); //-1 } //create a table & set metatable { lua_createtable(current_state, 0, 0); //+1 { lua_pushstring(current_state, "__tostring"); //+1 { //pushes Debug.CardStringWrapper to the stack lua_getglobal(current_state, "Debug"); //+1 lua_pushstring(current_state, "CardToStringWrapper"); //+1 lua_rawget(current_state, -2); //-1, +1 lua_remove(current_state, -2); //-1 } //cXXXXX.__tostring=Debug.CardToStringWrapper lua_rawset(current_state, -3); //-2 } //cXXXXX={} lua_setglobal(current_state, class_name); //-1 } //push cXXXXX on the stack another time lua_getglobal(current_state, class_name); //+1 { //setmetatable(cXXXXX, Card) lua_getglobal(current_state, "Card"); //+1 lua_setmetatable(current_state, -2); //-1 } { //rawset(cXXXXX,"__index",cXXXXX) lua_pushstring(current_state, "__index"); //+1 lua_pushvalue(current_state, -2); //+1 lua_rawset(current_state, -3); //-2 } { //self_table=cXXXX lua_getglobal(current_state, class_name); //+1 lua_setglobal(current_state, "self_table"); //-1 } const auto res = pduel->read_script(format_to(code_buf, "c%u.lua", code)); { //self_table=nil lua_pushnil(current_state); //+1 lua_setglobal(current_state, "self_table"); //-1 } { //self_code=nil lua_pushnil(current_state); //+1 lua_setglobal(current_state, "self_code"); //-1 } return res; } void interpreter::push_param(lua_State* L, bool is_coroutine) { int32_t pushed = 0; luaL_checkstack(L, static_cast(params.size()), nullptr); for(const auto& [param, type] : params) { switch(type) { case LuaParam::INT: lua_pushinteger(L, param.integer); break; case LuaParam::STRING: lua_pushstring(L, static_cast(param.ptr)); break; case LuaParam::BOOLEAN: lua_pushboolean(L, static_cast(param.integer)); break; case LuaParam::CARD: case LuaParam::EFFECT: case LuaParam::GROUP: pushobject(L, static_cast(param.ptr)); break; case LuaParam::FUNCTION: pushobject(L, static_cast(param.integer)); break; case LuaParam::INDEX: { auto index = static_cast(param.integer); if(index > 0) lua_pushvalue(L, index); else if(is_coroutine) { //copy value from current_state to new stack lua_pushvalue(current_state, index); lua_xmove(current_state, L, 1); } else { //the calling function is pushed before the params, so the actual index is: index - pushed -1 lua_pushvalue(L, index - pushed - 1); } break; } case LuaParam::DELETED: unreachable(); } ++pushed; } params.clear(); } inline int interpreter::call_lua(lua_State* L, int nargs, int nresults) { ++no_action; ++call_depth; /* Push the error handler function, when called, it will have a single argument passed to it, consisting of the error object, since we do nothing with it, return it directly. It's actually faster and simpler to push the light C function each time than to store it in the global registry and retrieve it every call. */ luaL_checkstack(L, 1, nullptr); lua_pushcfunction(L, [](lua_State* L) -> int32_t { interpreter::print_stacktrace(L); return 1; }); /* The stack contains nargs on top, and the function below them. We put the error handler below the function object, save its new absolute index, and pass it to lua. */ lua_insert(L, -(nargs + 2)); auto error_handler_index = lua_absindex(L, -(nargs + 2)); auto ret = lua_pcall(L, nargs, nresults, error_handler_index); // We need to remove the error handler from the stack to clean things up. lua_remove(L, error_handler_index); --no_action; --call_depth; if(call_depth == 0) { pduel->restore_assumes(); } return ret; } inline bool interpreter::ret_fail(const char* message) { interpreter::print_stacktrace(current_state); pduel->handle_message(message, OCG_LOG_TYPE_ERROR); params.clear(); return false; } inline bool interpreter::ret_fail(const char* message, bool error) { if(error) { interpreter::print_stacktrace(current_state); pduel->handle_message(message, OCG_LOG_TYPE_ERROR); } params.clear(); return false; } bool interpreter::call_function(int param_count, int ret_count) { push_param(current_state); auto ret = true; if(call_lua(current_state, param_count, ret_count) != LUA_OK) { pduel->handle_message(lua_get_string_or_empty(current_state, -1), OCG_LOG_TYPE_ERROR); lua_pop(current_state, 1); ret = false; } return ret; } bool interpreter::call_function(int32_t function, uint32_t param_count, int32_t ret_count) { if (!function) return ret_fail(R"("CallFunction": attempt to call a null function.)"); if (param_count != params.size()) return ret_fail(format(R"("CallFunction": incorrect parameter count (%u expected, %zu pushed))", param_count, params.size())); luaL_checkstack(current_state, 1, nullptr); pushobject(current_state, function); if (!lua_isfunction(current_state, -1)) return ret_fail(R"("CallFunction": attempt to call an error function)"); return call_function(param_count, ret_count); } bool interpreter::call_card_function(card* pcard, const char* function_name, uint32_t param_count, int32_t ret_count, bool forced) { if (param_count != params.size()) return ret_fail(format(R"("CallCardFunction"(c%u.%s): incorrect parameter count)", pcard->data.code, function_name)); luaL_checkstack(current_state, 1, nullptr); pushobject(current_state, pcard); lua_getfield(current_state, -1, function_name); if (!lua_isfunction(current_state, -1)) { lua_pop(current_state, 2); return ret_fail(format(R"("CallCardFunction"(c%u.%s): attempt to call an error function)", pcard->data.code, function_name), forced); } lua_remove(current_state, -2); return call_function(param_count, ret_count); } bool interpreter::call_code_function(uint32_t code, const char* function_name, uint32_t param_count, int32_t ret_count) { if (param_count != params.size()) return ret_fail(R"("CallCodeFunction": incorrect parameter count)"); load_card_script(code); lua_getfield(current_state, -1, function_name); if (!lua_isfunction(current_state, -1)) { lua_pop(current_state, 2); return ret_fail(R"("CallCodeFunction": attempt to call an error function)"); } lua_remove(current_state, -2); return call_function(param_count, ret_count); } bool interpreter::check_condition(int32_t function, uint32_t param_count) { if(!function) { params.clear(); return true; } if(!call_function(function, param_count, 1)) return false; bool result = lua_toboolean(current_state, -1); lua_pop(current_state, 1); return result; } static inline void push_range_of_values(lua_State* L, int32_t idx, int32_t amount) { idx = lua_absindex(L, idx); for(int i = 0; i < amount; ++i) lua_pushvalue(L, idx + i); } bool interpreter::check_matching(card* pcard, int32_t findex, int32_t extraargs) { luaL_checkstack(current_state, extraargs + 2, nullptr); lua_pushvalue(current_state, findex); pushobject(current_state, pcard); push_range_of_values(current_state, -(extraargs + 2), extraargs); auto result = false; if(call_lua(current_state, 1 + extraargs, 1) != LUA_OK) { pduel->handle_message(lua_get_string_or_empty(current_state, -1), OCG_LOG_TYPE_ERROR); } else result = lua_toboolean(current_state, -1); lua_pop(current_state, 1); return result; } bool interpreter::check_matching_table(card* pcard, int32_t findex, int32_t table_index) { if(!findex || !lua_istable(current_state, table_index)) return true; luaL_checkstack(current_state, 2, nullptr); lua_pushvalue(current_state, findex); pushobject(current_state, pcard); int extraargs = pushExpandedTable(current_state, table_index); auto result = false; if(call_lua(current_state, 1 + extraargs, 1) != LUA_OK) { pduel->handle_message(lua_get_string_or_empty(current_state, -1), OCG_LOG_TYPE_ERROR); } else result = lua_toboolean(current_state, -1); lua_pop(current_state, 1); return result; } lua_Integer interpreter::get_operation_value(card* pcard, int32_t findex, int32_t extraargs) { if(!findex || lua_isnoneornil(current_state, findex)) return 0; luaL_checkstack(current_state, extraargs + 2, nullptr); lua_pushvalue(current_state, findex); pushobject(current_state, pcard); push_range_of_values(current_state, -(extraargs + 2), extraargs); lua_Integer result = 0; if(call_lua(current_state, 1 + extraargs, 1) != LUA_OK) { pduel->handle_message(lua_get_string_or_empty(current_state, -1), OCG_LOG_TYPE_ERROR); } else result = lua_get(current_state, -1); lua_pop(current_state, 1); return result; } bool interpreter::get_operation_value(card* pcard, int32_t findex, int32_t extraargs, std::vector& result) { if(!findex || lua_isnoneornil(current_state, findex)) return false; luaL_checkstack(current_state, extraargs + 2, nullptr); lua_pushvalue(current_state, findex); int32_t stack_top = lua_gettop(current_state); pushobject(current_state, pcard); push_range_of_values(current_state, -(extraargs + 2), extraargs); auto ret = call_lua(current_state, extraargs, LUA_MULTRET) == LUA_OK; if(!ret) { pduel->handle_message(lua_get_string_or_empty(current_state, -1), OCG_LOG_TYPE_ERROR); lua_pop(current_state, 1); } else { int32_t stack_newtop = lua_gettop(current_state); for(int32_t index = stack_top + 1; index <= stack_newtop; ++index) { lua_Integer return_value = 0; if(lua_isboolean(current_state, index)) return_value = lua_get(current_state, index); else return_value = lua_get(current_state, index); result.push_back(return_value); } lua_settop(current_state, stack_top); ret = true; } return ret; } lua_Integer interpreter::get_function_value(int32_t function, uint32_t param_count) { if(!function) { params.clear(); return 0; } if(!call_function(function, param_count, 1)) return 0; lua_Integer result = 0; if(lua_isboolean(current_state, -1)) result = lua_get(current_state, -1); else result = lua_get(current_state, -1); lua_pop(current_state, 1); return result; } bool interpreter::get_function_value(int32_t function, uint32_t param_count, std::vector& result) { if(!function) { params.clear(); return false; } const int32_t stack_top = lua_gettop(current_state); if(!call_function(function, param_count, LUA_MULTRET)) return false; const int32_t stack_newtop = lua_gettop(current_state); for(int32_t index = stack_top + 1; index <= stack_newtop; ++index) { lua_Integer return_value = 0; if(lua_isboolean(current_state, index)) return_value = lua_get(current_state, index); else return_value = lua_get(current_state, index); result.push_back(return_value); } //pops all the results from the stack (lua_pop(current_state, stack_newtop - stack_top)) lua_settop(current_state, stack_top); return true; } namespace { #if LUA_VERSION_NUM <= 503 int lua_resume(lua_State* L, lua_State* from, int nargs, int* nresults) { auto ret = lua_resume(L, from, nargs); *nresults = lua_gettop(L); return ret; } #endif auto resume_coroutine(lua_State* L, lua_State* from, int nargs) { int result, nresults; result = lua_resume(L, from, nargs, &nresults); return std::make_pair(result, nresults); } } int32_t interpreter::call_coroutine(int32_t function, uint32_t param_count, lua_Integer* yield_value, uint16_t step) { auto ret_error = [&](const char* message) { interpreter::print_stacktrace(current_state); pduel->handle_message(message, OCG_LOG_TYPE_ERROR); params.clear(); return COROUTINE_ERROR; }; if(yield_value) *yield_value = 0; if (!function) return ret_error(R"("CallCoroutine": attempt to call a null function)"); if (param_count != params.size()) return ret_error(R"("CallCoroutine": incorrect parameter count)"); auto it = coroutines.find(function); lua_State* rthread; if (it == coroutines.end()) { rthread = lua_newthread(lua_state); const auto threadref = ensure_luaL_stack(luaL_ref, lua_state, LUA_REGISTRYINDEX); pushobject(rthread, function); if(!lua_isfunction(rthread, -1)) { ensure_luaL_stack(luaL_unref, lua_state, LUA_REGISTRYINDEX, threadref); return ret_error(R"("CallCoroutine": attempt to call an error function)"); } ++call_depth; auto ret = coroutines.emplace(function, std::make_pair(rthread, threadref)); it = ret.first; } else { if(step == 0) { auto ref = it->second.second; coroutines.erase(it); ensure_luaL_stack(luaL_unref, lua_state, LUA_REGISTRYINDEX, ref); --call_depth; if(call_depth == 0) { pduel->restore_assumes(); } return ret_error("recursive event trigger detected."); } rthread = it->second.first; } push_param(rthread, true); auto prev_state = std::exchange(current_state, rthread); auto [result, nresults] = resume_coroutine(current_state, prev_state, param_count); current_state = prev_state; if(result == LUA_YIELD) return COROUTINE_YIELD; if(result != LUA_OK) { print_stacktrace(rthread); pduel->handle_message(lua_get_string_or_empty(rthread, -1), OCG_LOG_TYPE_ERROR); } else if(yield_value) { if(nresults == 0) *yield_value = 0; else if(lua_isboolean(rthread, -1)) *yield_value = lua_toboolean(rthread, -1); else *yield_value = static_cast(lua_tointeger(rthread, -1)); } auto ref = it->second.second; coroutines.erase(it); ensure_luaL_stack(luaL_unref, lua_state, LUA_REGISTRYINDEX, ref); --call_depth; if(call_depth == 0) { pduel->restore_assumes(); } return (result == LUA_OK) ? COROUTINE_FINISH : COROUTINE_ERROR; } int32_t interpreter::clone_lua_ref(int32_t lua_ref) { lua_rawgeti(current_state, LUA_REGISTRYINDEX, lua_ref); return ensure_luaL_stack(luaL_ref, current_state, LUA_REGISTRYINDEX); } int32_t interpreter::strong_from_weak_ref(int32_t weak_lua_ref) { push_weak_ref(current_state, weak_lua_ref); return ensure_luaL_stack(luaL_ref, current_state, LUA_REGISTRYINDEX); } void interpreter::push_weak_ref(lua_State* L, int32_t weak_lua_ref) { luaL_checkstack(L, 2, nullptr); lua_rawgeti(L, LUA_REGISTRYINDEX, weak_lua_references); lua_rawgeti(L, -1, weak_lua_ref); lua_remove(L, -2); } void* interpreter::get_ref_object(int32_t ref_handler) { if(ref_handler == 0) return nullptr; lua_rawgeti(current_state, LUA_REGISTRYINDEX, ref_handler); auto obj = static_cast(lua_touserdata(current_state, -1)); void* p = obj ? *obj : nullptr; lua_pop(current_state, 1); return p; } //Convert a pointer to a lua value, +1 -0 void interpreter::pushobject(lua_State* L, lua_obj* obj) { if(!obj || (obj->ref_handle == 0 && obj->weak_ref_handle == 0)) lua_pushnil(L); else if(obj->ref_handle == 0) lua_get(L)->lua->push_weak_ref(L, obj->weak_ref_handle); else lua_rawgeti(L, LUA_REGISTRYINDEX, obj->ref_handle); } void interpreter::pushobject(lua_State* L, int32_t lua_ptr) { if(!lua_ptr) lua_pushnil(L); else lua_rawgeti(L, LUA_REGISTRYINDEX, lua_ptr); } //Push all the elements of the table to the stack, +len(table) -0 int interpreter::pushExpandedTable(lua_State* L, int32_t table_index) { int extraargs = 0; lua_table_iterate(L, table_index, [&extraargs, &L] { luaL_checkstack(L, 1, nullptr); lua_pushvalue(L, -1); lua_insert(L, -3); ++extraargs; }); return extraargs; } int32_t interpreter::get_function_handle(lua_State* L, int32_t index) { lua_pushvalue(L, index); int32_t ref = ensure_luaL_stack(luaL_ref, L, LUA_REGISTRYINDEX); return ref; } void interpreter::print_stacktrace(lua_State* L) { const auto pduel = lua_get(L); ensure_luaL_stack(luaL_traceback, L, L, nullptr, 1); auto len = lua_rawlen(L, -1); /*checks for an empty stack*/ if(len > sizeof("stack traceback:")) pduel->handle_message(lua_get_string_or_empty(L, -1), OCG_LOG_TYPE_FOR_DEBUG); lua_pop(L, 1); } void lua_obj::incr_ref() { if(ref_handle == 0) { ref_handle = pduel->lua->strong_from_weak_ref(weak_ref_handle); } ++num_ref; } void lua_obj::decr_ref() { if(--num_ref == 0) { auto* L = pduel->lua->current_state; /* "ensure_luaL_stack" is not used because that would raise a lua error TODO: what to do here? assert if this fails? */ lua_checkstack(L, 5); luaL_unref(L, LUA_REGISTRYINDEX, ref_handle); ref_handle = 0; } }