#include "luaState.h" #include "lua/lua.h" #include #include #include #include #include #include #include #include std::vector>>> gdLibraries = std::vector>>>(); // Destructor: meant to cleanup loaded libraries once the target instance reach EOLife. // Maybe move lua_close(lState) here? LuaState::~LuaState() { for (int i = 0; i < gdLibraries.size(); i++) { // First find the right luaState instance. if (std::get<0>(gdLibraries[i]) == this) { // Then remove all loaded libraries for the current instance. gdLibraries.erase(gdLibraries.begin() + i); } } } void LuaState::setState(lua_State *state, LuaAPI *api, bool bindAPI) { this->L = state; if (!bindAPI) { return; } // push our custom print function so by default it prints to the GDConsole. lua_register(L, "print", luaPrint); // saving the object into registry lua_pushstring(L, "__LAPI__"); lua_pushlightuserdata(L, api); lua_rawset(L, LUA_REGISTRYINDEX); // Creating basic types metatables and saving them in registry createVector2Metatable(); // "mt_Vector2" createVector3Metatable(); // "mt_Vector3" createColorMetatable(); // "mt_Color" createRect2Metatable(); // "mt_Rect2" createPlaneMetatable(); // "mt_Plane" createSignalMetatable(); // "mt_Signal" createObjectMetatable(); // "mt_Object" createCallableMetatable(); // "mt_Callable" createCallableExtraMetatable(); // "mt_CallableExtra" // Exposing basic types constructors exposeConstructors(); } lua_State *LuaState::getState() const { return L; } // Binds lua libraries with the lua state Ref LuaState::bindLibraries(TypedArray libs) { for (int i = 0; i < libs.size(); i++) { if (!loadLuaLibrary(L, libs[i])) { return LuaError::newError(vformat("Library \"%s\" does not exist.", libs[i]), LuaError::ERR_RUNTIME); } if (libs[i] == "base") { lua_register(L, "print", luaPrint); } } return nullptr; } void LuaState::setHook(Callable hook, int mask, int count) { if (hook.is_null()) { lua_sethook(L, nullptr, 0, 0); return; } lua_pushstring(L, "__HOOK"); pushVariant(hook); lua_settable(L, LUA_REGISTRYINDEX); lua_sethook(L, luaHook, mask, count); } void LuaState::indexForReading(String name) { #ifndef LAPI_GDEXTENSION Vector strs = name.split("."); #else PackedStringArray strs = name.split("."); #endif for (String str : strs) { if (lua_type(L, -1) != LUA_TTABLE) { lua_pop(L, 1); lua_pushnil(L); break; } lua_getfield(L, -1, str.utf8().get_data()); lua_remove(L, -2); } } String LuaState::indexForWriting(String name) { #ifndef LAPI_GDEXTENSION Vector strs = name.split("."); #else PackedStringArray strs = name.split("."); #endif String last = strs[strs.size() - 1]; strs.remove_at(strs.size() - 1); for (String str : strs) { if (lua_type(L, -1) != LUA_TTABLE) { lua_pop(L, 1); lua_pushnil(L); break; } lua_getfield(L, -1, str.utf8().get_data()); lua_remove(L, -2); } return last; } // Returns true if a lua function exists with the given name bool LuaState::luaFunctionExists(String functionName) { #ifndef LAPI_LUAJIT lua_pushglobaltable(L); #else lua_pushvalue(L, LUA_GLOBALSINDEX); #endif indexForReading(functionName); // LuaJIT does not return a type here int type = lua_type(L, -1); lua_pop(L, 1); return type == LUA_TFUNCTION; } // get a value at the given index and return as a variant Variant LuaState::getVar(int index) const { return getVariant(L, index); } // Pull a global variant from Lua to GDScript Variant LuaState::pullVariant(String name) { #ifndef LAPI_LUAJIT lua_pushglobaltable(L); #else lua_pushvalue(L, LUA_GLOBALSINDEX); #endif indexForReading(name); Variant val = getVar(-1); lua_pop(L, 1); return val; } Variant LuaState::getRegistryValue(String name) { lua_pushvalue(L, LUA_REGISTRYINDEX); indexForReading(name); Variant val = getVar(-1); lua_pop(L, 1); return val; } Ref LuaState::setRegistryValue(String name, Variant var) { lua_pushvalue(L, LUA_REGISTRYINDEX); String field = indexForWriting(name); if (lua_isnil(L, -1)) { lua_pop(L, 1); return LuaError::newError("cannot index nil with string", LuaError::ERR_RUNTIME); // Make it look natural. } Ref err = pushVariant(var); if (err.is_null()) { lua_setfield(L, -2, field.utf8().get_data()); lua_pop(L, 1); return nullptr; } lua_pop(L, 1); return err; } // call a Lua function from GDScript Variant LuaState::callFunction(String functionName, Array args) { // push the error handler on to the stack lua_pushcfunction(L, luaErrorHandler); #ifndef LAPI_LUAJIT lua_pushglobaltable(L); #else lua_pushvalue(L, LUA_GLOBALSINDEX); #endif indexForReading(functionName); // push args for (int i = 0; i < args.size(); ++i) { pushVariant(args[i]); } // error handlers index is -2 - args.size() int ret = lua_pcall(L, args.size(), 1, -2 - args.size()); if (ret != LUA_OK) { return handleError(ret); } Variant toReturn = getVar(-1); // get return value lua_pop(L, 1); // pop err handler return toReturn; } // Push a GD Variant to the lua stack and returns a error if the type is not supported Ref LuaState::pushVariant(Variant var) const { return LuaState::pushVariant(L, var); } // Call pushVariant() and set it to a global name Ref LuaState::pushGlobalVariant(String name, Variant var) { #ifndef LAPI_LUAJIT lua_pushglobaltable(L); #else lua_pushvalue(L, LUA_GLOBALSINDEX); #endif String field = indexForWriting(name); if (lua_isnil(L, -1)) { lua_pop(L, 1); return LuaError::newError("cannot index nil with string", LuaError::ERR_RUNTIME); // Make it look natural. } Ref err = pushVariant(var); if (err.is_null()) { lua_setfield(L, -2, field.utf8().get_data()); lua_pop(L, 1); return nullptr; } lua_pop(L, 1); return err; } Ref LuaState::handleError(int lua_error) const { return LuaState::handleError(L, lua_error); } // -------------- // STATIC METHODS // -------------- LuaAPI *LuaState::getAPI(lua_State *state) { lua_pushstring(state, "__LAPI__"); lua_rawget(state, LUA_REGISTRYINDEX); LuaAPI *api = (LuaAPI *)lua_touserdata(state, -1); lua_pop(state, 1); return api; } // Push a GD Variant to the lua stack and returns a error if the type is not supported Ref LuaState::pushVariant(lua_State *state, Variant var) { switch (var.get_type()) { case Variant::Type::NIL: lua_pushnil(state); break; case Variant::Type::STRING: lua_pushstring(state, (var.operator String()).utf8().get_data()); break; case Variant::Type::INT: lua_pushinteger(state, (int64_t)var); break; case Variant::Type::FLOAT: lua_pushnumber(state, var.operator double()); break; case Variant::Type::BOOL: lua_pushboolean(state, (bool)var); break; case Variant::Type::PACKED_BYTE_ARRAY: case Variant::Type::PACKED_INT64_ARRAY: case Variant::Type::PACKED_INT32_ARRAY: case Variant::Type::PACKED_STRING_ARRAY: case Variant::Type::PACKED_FLOAT64_ARRAY: case Variant::Type::PACKED_FLOAT32_ARRAY: case Variant::Type::PACKED_VECTOR2_ARRAY: case Variant::Type::PACKED_VECTOR3_ARRAY: case Variant::Type::PACKED_COLOR_ARRAY: case Variant::Type::ARRAY: { Array array = var.operator Array(); lua_createtable(state, 0, array.size()); for (int i = 0; i < array.size(); i++) { Variant key = i + 1; Variant value = array[i]; Ref err = pushVariant(state, key); if (!err.is_null()) { return err; } err = pushVariant(state, value); if (!err.is_null()) { return err; } lua_settable(state, -3); } break; } case Variant::Type::DICTIONARY: { Dictionary dict = var.operator Dictionary(); lua_createtable(state, 0, dict.size()); Array keys = dict.keys(); for (int i = 0; i < dict.size(); i++) { Variant key = keys[i]; Variant value = dict[key]; Ref err = pushVariant(state, key); if (!err.is_null()) { return err; } err = pushVariant(state, value); if (!err.is_null()) { return err; } lua_settable(state, -3); } break; } case Variant::Type::VECTOR2: { Variant *userdata = (Variant *)lua_newuserdata(state, sizeof(Variant)); memnew_placement(userdata, Variant(var)); luaL_setmetatable(state, "mt_Vector2"); break; } case Variant::Type::VECTOR3: { Variant *userdata = (Variant *)lua_newuserdata(state, sizeof(Variant)); memnew_placement(userdata, Variant(var)); luaL_setmetatable(state, "mt_Vector3"); break; } case Variant::Type::COLOR: { Variant *userdata = (Variant *)lua_newuserdata(state, sizeof(Variant)); memnew_placement(userdata, Variant(var)); luaL_setmetatable(state, "mt_Color"); break; } case Variant::Type::RECT2: { Variant *userdata = (Variant *)lua_newuserdata(state, sizeof(Variant)); memnew_placement(userdata, Variant(var)); luaL_setmetatable(state, "mt_Rect2"); break; } case Variant::Type::PLANE: { Variant *userdata = (Variant *)lua_newuserdata(state, sizeof(Variant)); memnew_placement(userdata, Variant(var)); luaL_setmetatable(state, "mt_Plane"); break; } case Variant::Type::SIGNAL: { Variant *userdata = (Variant *)lua_newuserdata(state, sizeof(Variant)); memnew_placement(userdata, Variant(var)); luaL_setmetatable(state, "mt_Signal"); break; } case Variant::Type::OBJECT: { if (var.operator Object *() == nullptr) { lua_pushnil(state); break; } // If the type being pushed is a lua error, Raise an error #ifndef LAPI_GDEXTENSION if (Ref err = Object::cast_to(var.operator Object *()); !err.is_null()) { #else // blame this on https://github.com/godotengine/godot-cpp/issues/995 if (Ref err = dynamic_cast(var.operator Object *()); !err.is_null()) { #endif lua_pushstring(state, err->getMessage().utf8().get_data()); lua_error(state); break; } // If the type being pushed is a tuple, push its content instead. #ifndef LAPI_GDEXTENSION if (Ref tuple = Object::cast_to(var.operator Object *()); tuple.is_valid()) { #else // blame this on https://github.com/godotengine/godot-cpp/issues/995 if (Ref tuple = dynamic_cast(var.operator Object *()); tuple.is_valid()) { #endif for (int i = 0; i < tuple->size(); i++) { Variant value = tuple->get(i); pushVariant(state, value); } break; } // If the type being pushed is a thread, push a LUA_TTHREAD state. #ifndef LAPI_GDEXTENSION if (Ref thread = Object::cast_to(var.operator Object *()); thread.is_valid()) { #else // blame this on https://github.com/godotengine/godot-cpp/issues/995 if (Ref thread = dynamic_cast(var.operator Object *()); thread.is_valid()) { #endif return LuaError::newError("pushing threads is currently not supported.", LuaError::ERR_TYPE); break; } // If the type being pushed is a thread, push a LUA_TTHREAD state. #ifndef LAPI_GDEXTENSION if (Ref funcRef = Object::cast_to(var.operator Object *()); funcRef.is_valid()) { #else // blame this on https://github.com/godotengine/godot-cpp/issues/995 if (Ref funcRef = dynamic_cast(var.operator Object *()); funcRef.is_valid()) { #endif lua_rawgeti(state, LUA_REGISTRYINDEX, funcRef->getRef()); if (funcRef->getLuaState() != state) { lua_xmove(funcRef->getLuaState(), state, 1); } break; } // If the type being pushed is a LuaCallableExtra. use mt_CallableExtra instead #ifndef LAPI_GDEXTENSION if (Ref func = Object::cast_to(var.operator Object *()); func.is_valid()) { #else // blame this on https://github.com/godotengine/godot-cpp/issues/995 if (Ref func = dynamic_cast(var.operator Object *()); func.is_valid()) { #endif Variant *userdata = (Variant *)lua_newuserdata(state, sizeof(Variant)); memnew_placement(userdata, Variant(var)); luaL_setmetatable(state, "mt_CallableExtra"); break; } Variant *userdata = (Variant *)lua_newuserdata(state, sizeof(Variant)); memnew_placement(userdata, Variant(var)); luaL_setmetatable(state, "mt_Object"); break; } case Variant::Type::CALLABLE: { Callable callable = var.operator Callable(); if (!callable.is_valid() || callable.is_null()) { lua_pushnil(state); break; } #ifndef LAPI_GDEXTENSION if (callable.is_custom()) { CallableCustom *custom = callable.get_custom(); LuaCallable *luaCallable = dynamic_cast(custom); if (luaCallable != nullptr) { lua_rawgeti(state, LUA_REGISTRYINDEX, luaCallable->getFuncRef()); if (luaCallable->getLuaState() != state) { lua_xmove(luaCallable->getLuaState(), state, 1); } break; } // A work around to preserve ref count of CallableCustoms Ref callableCustom; callableCustom.instantiate(); callableCustom->setInfo(callable, 0, false, false); LuaState::pushVariant(state, callableCustom); break; } #endif Variant *userdata = (Variant *)lua_newuserdata(state, sizeof(Variant)); memnew_placement(userdata, Variant(var)); luaL_setmetatable(state, "mt_Callable"); break; } default: lua_pushnil(state); return LuaError::newError(vformat("can't pass Variants of type \"%s\" to Lua.", Variant::get_type_name(var.get_type())), LuaError::ERR_TYPE); } return nullptr; } Ref LuaState::pushMember(lua_State *state, String name, Variant var) { Ref err = pushVariant(state, name); if (!err.is_null()) { return err; } bool pushed = false; if (var.get_type() == Variant::Type::ARRAY) { Array arr = var; if (arr.size() == 2 && arr[0].get_type() == Variant::Type::STRING) { /* If the variant is a 2-element array with the first element being a string Create a new Lua table and push it onto the stack */ lua_newtable(state); /* Recursively push the nested member */ err = pushMember(state, arr[0], arr[1]); if (!err.is_null()) { return err; } /* Set the table as a named member. */ lua_settable(state, -3); pushed = true; } } /* If not a sub table (Array of Array of size 2 with name first), push as a standard named variant. */ if (!pushed) { err = pushVariant(state, var); if (!err.is_null()) { return err; } lua_settable(state, -3); } return nullptr; } Ref LuaState::pushModule(lua_State *state, Array arr) { lua_newtable(state); for (int i = 0; i < arr.size(); i++) { Variant value = arr[i]; if (value.get_type() != Variant::Type::ARRAY) { return LuaError::newError(("modules must be an array of array [[\"member_name\", member]]."), LuaError::ERR_RUNTIME); } Array member = value; if (member.size() != 2) { return LuaError::newError(("modules must be an array of array [[\"member_name\", member]]."), LuaError::ERR_RUNTIME); } Variant name = member[0]; Variant body = member[1]; /* Or it is any kind of variant. */ if (name.get_type() != Variant::Type::STRING) { return LuaError::newError(("modules must be an array of array [[\"member_name\", member]]."), LuaError::ERR_RUNTIME); } Ref err = pushMember(state, name, body); if (!err.is_null()) { return err; } } return nullptr; } // Function opening all godot made lua libraries. int open_gd_library(lua_State *L) { const char *libname = luaL_checkstring(L, 1); for (int i = 0; i < gdLibraries.size(); i++) { // First find the right luaState instance. if (std::get<1>(gdLibraries[i]) == L) { // Then find a library having the same name. for (int j = 0; j < std::get<2>(gdLibraries[i]).size(); j++) { if (strcmp(std::get<2>(gdLibraries[i])[j].first.utf8().get_data(), libname) == 0) { // Then push its methods. std::get<0>(gdLibraries[i])->pushModule(L, std::get<2>(gdLibraries[i])[j].second); return 1; } } } } // If the library was not found, generate an error message with the library name. return luaL_error(L, "Failed to open Lua library: \'%s\', not found.", libname); } // Register library into gdLibraries for the current instance of luaState. Ref LuaState::bindGDLibrary(String name, Array arr) { int idx = -1; for (int i = 0; i < gdLibraries.size(); i++) { if (std::get<1>(gdLibraries[i]) == L) { idx = i; break; } } // If no library have been registered for the current luaState. if (idx == -1) { gdLibraries.push_back(std::make_tuple(this, L, std::vector>{ std::make_pair(name, arr) })); } // If there is already one or more library registered for the current state. else { for (int i = 0; i < std::get<2>(gdLibraries[idx]).size(); i++) { // If a library is existing with the same name. if (std::get<2>(gdLibraries[idx])[i].first == name) { // Let's erase it. std::get<2>(gdLibraries[idx]).erase(std::get<2>(gdLibraries[idx]).begin() + i); break; } } std::get<2>(gdLibraries[idx]).push_back(std::make_pair(name, arr)); } // Reference our lib to be required. luaL_requiref(L, name.utf8().get_data(), open_gd_library, true); return nullptr; } // gets a variant at a given index Variant LuaState::getVariant(lua_State *state, int index) { Variant result; int type = lua_type(state, index); switch (type) { case LUA_TSTRING: { String utf8_str; utf8_str.parse_utf8(lua_tostring(state, index)); result = utf8_str; break; } case LUA_TNUMBER: result = lua_tonumber(state, index); break; case LUA_TBOOLEAN: result = (bool)lua_toboolean(state, index); break; case LUA_TUSERDATA: result = *(Variant *)lua_touserdata(state, index); break; case LUA_TTABLE: { #ifndef LAPI_LUAJIT lua_len(state, index); #else lua_objlen(state, index); #endif int len = lua_tointeger(state, -1); lua_pop(state, 1); // len should be 0 if the type is table and not a array if (len) { Array array; for (int i = 1; i <= len; i++) { #ifndef LAPI_LUAJIT lua_geti(state, index, i); #else lua_rawgeti(state, index, i); #endif array.push_back(getVariant(state, -1)); lua_pop(state, 1); } result = array; break; } lua_pushnil(state); /* first key */ Dictionary dict; while (lua_next(state, (index < 0) ? (index - 1) : (index)) != 0) { Variant key = getVariant(state, -2); Variant value = getVariant(state, -1); dict[key] = value; lua_pop(state, 1); } result = dict; break; } case LUA_TFUNCTION: { Ref api = getAPI(state); // Put function on the top of the stack and get a ref to it. This will create a copy of the function. lua_pushvalue(state, index); if (api->getUseCallables()) { LuaCallable *callable = memnew(LuaCallable(api, luaL_ref(state, LUA_REGISTRYINDEX), state)); result = Callable(callable); } else { Ref funcRef; funcRef.instantiate(); funcRef->setRef(luaL_ref(state, LUA_REGISTRYINDEX)); funcRef->setLuaState(state); result = funcRef; } break; } case LUA_TTHREAD: { lua_State *tState = lua_tothread(state, index); Ref thread; thread.instantiate(); thread->bindExisting(getAPI(state), tState); result = thread; break; } case LUA_TNIL: { break; } default: result = LuaError::newError(vformat("Unsupported lua type '%d' in LuaState::getVariant", type), LuaError::ERR_RUNTIME); } return result; } // Assumes there is a error in the top of the stack. Pops it. Ref LuaState::handleError(lua_State *state, int lua_error) { String msg; switch (lua_error) { case LUA_ERRRUN: { String utf8_str; utf8_str.parse_utf8(lua_tostring(state, -1)); msg += "[LUA_ERRRUN - runtime error ]\n"; msg += utf8_str; msg += "\n"; lua_pop(state, 1); break; } case LUA_ERRSYNTAX: { String utf8_str; utf8_str.parse_utf8(lua_tostring(state, -1)); msg += "[LUA_ERRSYNTAX - syntax error ]\n"; msg += utf8_str; msg += "\n"; lua_pop(state, 1); break; } case LUA_ERRMEM: { msg += "[LUA_ERRMEM - memory allocation error ]\n"; break; } case LUA_ERRERR: { msg += "[LUA_ERRERR - error while calling LuaState::luaErrorHandler ] please report this issue: https://github.com/WeaselGames/lua/issues/new\n"; break; } case LUA_ERRFILE: { msg += "[LUA_ERRFILE - error while opening file]\n"; break; } default: break; } return LuaError::newError(msg, static_cast(lua_error)); } #ifndef LAPI_GDEXTENSION // for handling callable errors. Ref LuaState::handleError(const StringName &func, Callable::CallError error, const Variant **p_arguments, int argc) { switch (error.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { return LuaError::newError( vformat("Error calling function: %s - Invalid type for argument %s, expected %s but is %s.", String(func), itos(error.argument + 1), // lua indexes by 1 so this should be more correct Variant::get_type_name(Variant::Type(error.expected)), Variant::get_type_name(p_arguments[error.argument]->get_type())), LuaError::ERR_RUNTIME); } case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: { return LuaError::newError( vformat("Error calling function: %s - Too many arguments, expected %d but got %d.", String(func), argc), LuaError::ERR_RUNTIME); } case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: { return LuaError::newError( vformat("Error calling function: %s - Too few arguments, expected %d but got $d.", String(func), error.argument, argc), LuaError::ERR_RUNTIME); } case Callable::CallError::CALL_ERROR_INVALID_METHOD: { return LuaError::newError( vformat("Error calling function: %s - Method is invalid.", String(func)), LuaError::ERR_RUNTIME); } case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: { return LuaError::newError( vformat("Error calling function: %s - Instance is null.", String(func)), LuaError::ERR_RUNTIME); } default: return nullptr; } } #else Ref LuaState::handleError(const StringName &func, GDExtensionCallError error, const Variant **p_arguments, int argc) { switch (error.error) { case GDEXTENSION_CALL_ERROR_INVALID_ARGUMENT: { return LuaError::newError( vformat("Error calling function: %s - Invalid type for argument %s, expected %s but is %s.", String(func), itos(error.argument + 1), // lua indexes by 1 so this should be more correct Variant::get_type_name(Variant::Type(error.expected)), Variant::get_type_name(p_arguments[error.argument]->get_type())), LuaError::ERR_RUNTIME); } case GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS: { return LuaError::newError( vformat("Error calling function: %s - Too many arguments, expected %d but got %d.", String(func), argc), LuaError::ERR_RUNTIME); } case GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS: { return LuaError::newError( vformat("Error calling function: %s - Too few arguments, expected %d but got $d.", String(func), error.argument, argc), LuaError::ERR_RUNTIME); } case GDEXTENSION_CALL_ERROR_INVALID_METHOD: { return LuaError::newError( vformat("Error calling function: %s - Method is invalid.", String(func)), LuaError::ERR_RUNTIME); } case GDEXTENSION_CALL_ERROR_INSTANCE_IS_NULL: { return LuaError::newError( vformat("Error calling function: %s - Instance is null.", String(func)), LuaError::ERR_RUNTIME); } default: return nullptr; } } #endif // ------------- // Lua functions // ------------- // Lua error handler, when a error occurs it appends the stacktrace to the error message int LuaState::luaErrorHandler(lua_State *state) { const char *msg = lua_tostring(state, -1); luaL_traceback(state, state, msg, 2); lua_remove(state, -2); return 1; } // Change lua's print function to print to the Godot console by default int LuaState::luaPrint(lua_State *state) { int args = lua_gettop(state); String final_string; for (int n = 1; n <= args; ++n) { String it_string; switch (lua_type(state, n)) { case LUA_TUSERDATA: { Variant var = *(Variant *)lua_touserdata(state, n); it_string = var.operator String(); break; } case LUA_TBOOLEAN: { it_string = lua_toboolean(state, n) ? "true" : "false"; break; } default: { it_string.parse_utf8(lua_tostring(state, n)); break; } } final_string += it_string; if (n < args) { final_string += ", "; } } print_line(final_string); return 0; } #ifndef LAPI_GDEXTENSION #include // Used as the __call metamethod for mt_Callable. // All exposed gdscript functions are called vis this method. int LuaState::luaCallableCall(lua_State *state) { int argc = lua_gettop(state) - 1; // We subtract 1 because the callable its self will be counted Callable callable = (Callable)LuaState::getVariant(state, 1); Array args; args.resize(argc); Vector mem_args; mem_args.resize(argc); int index = 2; // we start at 2, 1 is the callable for (int i = 0; i < argc; i++) { args[i] = LuaState::getVariant(state, index++); if (args[i].get_type() != Variant::Type::OBJECT) { if (Ref err = Object::cast_to(args[i].operator Object *()); !err.is_null()) { lua_pushstring(state, err->getMessage().utf8().get_data()); lua_error(state); return 0; } } mem_args.write[i] = &args[i]; } const Variant **p_args = (const Variant **)mem_args.ptr(); Variant returned; Callable::CallError error; callable.callp(p_args, argc, returned, error); if (error.error != error.CALL_OK) { Ref err = LuaState::handleError(callable.get_method(), error, p_args, argc); lua_pushstring(state, err->getMessage().utf8().get_data()); lua_error(state); return 0; } // await was called, so yield if (returned.is_ref_counted() && (returned.operator Object *())->get_class_name() == "GDScriptFunctionState") { return lua_yield(state, lua_gettop(state)); } Ref err = LuaState::pushVariant(state, returned); if (!err.is_null()) { lua_pushstring(state, err->getMessage().utf8().get_data()); lua_error(state); return 0; } if (returned.get_type() != Variant::Type::OBJECT) { return 1; } if (LuaTuple *tuple = Object::cast_to(returned.operator Object *()); tuple != nullptr) { return tuple->size(); } return 1; } #else int LuaState::luaCallableCall(lua_State *state) { int argc = lua_gettop(state) - 1; // We subtract 1 because the callable its self will be counted Callable callable = (Callable)LuaState::getVariant(state, 1); Array args; int index = 2; // we start at 2, 1 is the callable for (int i = 0; i < argc; i++) { Variant var = LuaState::getVariant(state, index++); if (var.get_type() == Variant::Type::OBJECT) { if (Ref err = dynamic_cast(var.operator Object *()); !err.is_null()) { lua_pushstring(state, err->getMessage().utf8().get_data()); lua_error(state); return 0; } } args.append(var); } Variant returned = callable.callv(args); // await was called, so yield if (returned.get_type() == Variant::OBJECT && (returned.operator Object *())->get_class() == "GDScriptFunctionState") { return lua_yield(state, lua_gettop(state)); } Ref err = LuaState::pushVariant(state, returned); if (!err.is_null()) { lua_pushstring(state, err->getMessage().utf8().get_data()); lua_error(state); return 0; } if (returned.get_type() != Variant::Type::OBJECT) return 1; if (LuaTuple *tuple = dynamic_cast(returned.operator Object *()); tuple != nullptr) return tuple->size(); return 1; } #endif // This function is invoked whenever a function is called on one of the userdata types // excluding mt_Callable or mt_Object if __index is overwritten int LuaState::luaUserdataFuncCall(lua_State *state) { Variant *obj = (Variant *)lua_touserdata(state, lua_upvalueindex(1)); String fName = LuaState::getVariant(state, lua_upvalueindex(2)); int argc = lua_gettop(state); Array args; args.resize(argc); Vector mem_args; mem_args.resize(argc); for (int i = 0; i < argc; i++) { args[i] = LuaState::getVariant(state, i + 1); mem_args.write[i] = &args[i]; } const Variant **p_args = (const Variant **)mem_args.ptr(); Variant returned; #ifndef LAPI_GDEXTENSION Callable::CallError error; obj->callp(fName.utf8().get_data(), p_args, argc, returned, error); if (error.error != error.CALL_OK) { Ref err = LuaState::handleError(fName, error, p_args, argc); lua_pushstring(state, err->getMessage().utf8().get_data()); lua_error(state); return 0; } #else GDExtensionCallError error; obj->callp(fName.utf8().get_data(), p_args, argc, returned, error); if (error.error != GDEXTENSION_CALL_OK) { Ref err = LuaState::handleError(fName, error, p_args, argc); lua_pushstring(state, err->getMessage().utf8().get_data()); lua_error(state); return 0; } #endif LuaState::pushVariant(state, returned); if (returned.get_type() != Variant::Type::OBJECT) { return 1; } #ifndef LAPI_GDEXTENSION if (LuaTuple *tuple = Object::cast_to(returned.operator Object *()); tuple != nullptr) { #else // blame this on https://github.com/godotengine/godot-cpp/issues/995 if (LuaTuple *tuple = dynamic_cast(returned.operator Object *()); tuple != nullptr) { #endif return tuple->size(); } return 1; } void LuaState::luaHook(lua_State *state, lua_Debug *ar) { lua_pushstring(state, "__HOOK"); lua_rawget(state, LUA_REGISTRYINDEX); Callable hook = LuaState::getVariant(state, -1); lua_pop(state, 1); if (hook.is_null()) { return; } Array args; args.append(Ref(getAPI(state))); args.append(ar->event); args.append(ar->currentline); #ifndef LAPI_GDEXTENSION const int argc = 3; const Variant *p_args[argc]; for (int i = 0; i < argc; i++) { p_args[i] = &args[i]; } Variant returned; Callable::CallError error; hook.callp(p_args, argc, returned, error); if (error.error != error.CALL_OK) { Ref err = LuaState::handleError(hook.get_method(), error, p_args, argc); lua_pushstring(state, err->getMessage().utf8().get_data()); lua_error(state); return; } if (returned.get_type() == Variant::NIL) { return; } #else Variant returned = hook.callv(args); #endif Ref err = LuaState::pushVariant(state, returned); if (!err.is_null()) { lua_pushstring(state, err->getMessage().utf8().get_data()); lua_error(state); } }