dep-ygopro-core/interpreter.cpp
2025-05-30 23:09:29 +02:00

715 lines
24 KiB
C++

/*
* Copyright (c) 2010-2015, Argon Sun (Fluorohydride)
* Copyright (c) 2016-2025, Edoardo Lolletti (edo9300) <edoardo762@gmail.com>
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <cstring> //std::memcpy
#include <utility> //std::exchange
#include <vector>
#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<lua_State*>(state);
auto pduel = lua_get<duel*>(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;
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;
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);
}
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);
}
// 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_obj**>(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_obj**>(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<LuaParam::CARD>(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_obj**>(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<uint32_t>(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<const char*>(param.ptr));
break;
case LuaParam::BOOLEAN:
lua_pushboolean(L, static_cast<bool>(param.integer));
break;
case LuaParam::CARD:
case LuaParam::EFFECT:
case LuaParam::GROUP:
pushobject(L, static_cast<lua_obj*>(param.ptr));
break;
case LuaParam::FUNCTION:
pushobject(L, static_cast<int32_t>(param.integer));
break;
case LuaParam::INDEX: {
auto index = static_cast<int32_t>(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<lua_Integer>(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<lua_Integer>& 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<bool>(current_state, index);
else
return_value = lua_get<lua_Integer, 0>(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<bool>(current_state, -1);
else
result = lua_get<lua_Integer, 0>(current_state, -1);
lua_pop(current_state, 1);
return result;
}
bool interpreter::get_function_value(int32_t function, uint32_t param_count, std::vector<lua_Integer>& 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<bool>(current_state, index);
else
return_value = lua_get<lua_Integer, 0>(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_Integer>(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_obj**>(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)
obj->pduel->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<duel*>(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;
}
}