diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a8233cc..8f294495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog ## [Unreleased](https://github.com/gilzoide/lua-gdextension/compare/0.7.0...HEAD) +### Changed +- Opening `GODOT_CLASSES` now registers all classes at once instead of setting up a lazy getter in `_G`'s metatable +- Opening `GODOT_SINGLETONS` now registers all singletons at once instead of setting up a lazy getter in `_G`'s metatable ## [0.7.0](https://github.com/gilzoide/lua-gdextension/releases/tag/0.7.0) diff --git a/doc_classes/LuaState.xml b/doc_classes/LuaState.xml index 7c82b737..abd94944 100644 --- a/doc_classes/LuaState.xml +++ b/doc_classes/LuaState.xml @@ -293,7 +293,7 @@ [gdscript] var lua_state = LuaState.new() lua_state.open_libraries(LuaState.Library.LUA_BASE | LuaState.Library.GODOT_CLASSES) - var vec3 = lua_state.do_string(""" + var node = lua_state.do_string(""" -- Create a Node in Lua local node = Node:new() return node diff --git a/src/LuaState.cpp b/src/LuaState.cpp index 33331b08..94764c0d 100644 --- a/src/LuaState.cpp +++ b/src/LuaState.cpp @@ -25,7 +25,6 @@ #include "LuaTable.hpp" #include "LuaThread.hpp" #include "luaopen/godot.hpp" -#include "utils/_G_metatable.hpp" #include "utils/convert_godot_lua.hpp" #include "utils/module_names.hpp" @@ -75,7 +74,6 @@ LuaState::LuaState() : lua_state(lua_panic_handler, lua_alloc) #endif { - setup_G_metatable(lua_state); #ifdef HAVE_LUA_WARN lua_setwarnf(lua_state, lua_warn_handler, this); #endif @@ -153,12 +151,12 @@ void LuaState::open_libraries(BitField libraries) { if (libraries.has_flag(GODOT_UTILITY_FUNCTIONS)) { lua_state.require(module_names::utility_functions, &luaopen_godot_utility_functions, false); } - if (libraries.has_flag(GODOT_SINGLETONS)) { - lua_state.require(module_names::singleton_access, &luaopen_godot_singleton_access, false); - } if (libraries.has_flag(GODOT_CLASSES)) { lua_state.require(module_names::classes, &luaopen_godot_classes, false); } + if (libraries.has_flag(GODOT_SINGLETONS)) { + lua_state.require(module_names::singleton_access, &luaopen_godot_singleton_access, false); + } if (libraries.has_flag(GODOT_ENUMS)) { lua_state.require(module_names::enums, &luaopen_godot_enums, false); } diff --git a/src/luaopen/classes.cpp b/src/luaopen/classes.cpp index 9fc94125..9396c93e 100644 --- a/src/luaopen/classes.cpp +++ b/src/luaopen/classes.cpp @@ -19,28 +19,23 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - +#include "../script-language/LuaScriptLanguage.hpp" #include "../utils/Class.hpp" -#include "../utils/module_names.hpp" -#include "../utils/convert_godot_std.hpp" -#include -#include +#include using namespace luagdextension; extern "C" int luaopen_godot_classes(lua_State *L) { sol::state_view state = L; - state.registry()[module_names::classes] = true; - sol::table global_class_paths = state.registry().create_named("_GDEXTENSION_GLOBAL_CLASS_PATHS"); Class::register_usertype(state); - - TypedArray global_class_list = ProjectSettings::get_singleton()->get_global_class_list(); - for (int64_t i = 0; i < global_class_list.size(); ++i) { - Dictionary type_info = global_class_list[i]; - global_class_paths[to_std_string(type_info["class"])] = to_std_string(type_info["path"]); + ClassDBSingleton *classdb = ClassDBSingleton::get_singleton(); + for (auto&& class_name : classdb->get_class_list()) { + state.set(class_name.ascii().get_data(), Class(class_name)); } + LuaScriptLanguage::get_singleton()->register_global_classes(L); + return 0; } diff --git a/src/luaopen/godot.cpp b/src/luaopen/godot.cpp index f3f249f2..6c182be6 100644 --- a/src/luaopen/godot.cpp +++ b/src/luaopen/godot.cpp @@ -33,8 +33,8 @@ extern "C" int luaopen_godot(lua_State *L) { state.require(module_names::variant, &luaopen_godot_variant, false); state.require(module_names::utility_functions, &luaopen_godot_utility_functions, false); - state.require(module_names::singleton_access, &luaopen_godot_singleton_access, false); state.require(module_names::classes, &luaopen_godot_classes, false); + state.require(module_names::singleton_access, &luaopen_godot_singleton_access, false); state.require(module_names::enums, &luaopen_godot_enums, false); state.require(module_names::local_paths, &luaopen_godot_local_paths, false); diff --git a/src/luaopen/singleton_access.cpp b/src/luaopen/singleton_access.cpp index 02aff10b..5f127405 100644 --- a/src/luaopen/singleton_access.cpp +++ b/src/luaopen/singleton_access.cpp @@ -19,17 +19,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +#include "../script-language/LuaScriptLanguage.hpp" -#include "../utils/module_names.hpp" - -#include +#include +using namespace godot; using namespace luagdextension; extern "C" int luaopen_godot_singleton_access(lua_State *L) { - sol::state_view state = L; + sol::state_view state(L); + Engine *engine = Engine::get_singleton(); + for (auto&& singleton_name : engine->get_singleton_list()) { + state.set(singleton_name.ascii().get_data(), engine->get_singleton(singleton_name)); + } - state.registry()[module_names::singleton_access] = true; + LuaScriptLanguage::get_singleton()->register_named_globals(L); return 0; } diff --git a/src/script-language/LuaScriptLanguage.cpp b/src/script-language/LuaScriptLanguage.cpp index d6567533..b9ed1bab 100644 --- a/src/script-language/LuaScriptLanguage.cpp +++ b/src/script-language/LuaScriptLanguage.cpp @@ -30,7 +30,9 @@ #include "../LuaTable.hpp" #include "../LuaState.hpp" #include "../generated/lua_script_globals.h" +#include "../utils/convert_godot_lua.hpp" #include "../utils/project_settings.hpp" +#include "../utils/stack_top_resetter.hpp" #include #include @@ -67,6 +69,10 @@ void LuaScriptLanguage::_init() { // Additional globals defined in Lua code lua_state->do_string(lua_script_globals); + + // Now, after everything is setup, we can load global classes (which might be LuaScripts) + global_class_list = project_settings->get_global_class_list(); + register_global_classes(lua_state->get_lua_state()); } String LuaScriptLanguage::_get_type() const { @@ -421,8 +427,29 @@ PackedStringArray LuaScriptLanguage::get_lua_member_keywords() const { ); } -const Dictionary& LuaScriptLanguage::get_named_globals() const { - return named_globals; +void LuaScriptLanguage::register_named_globals(lua_State *L) const { + StackTopResetter resettop(L); + lua_pushglobaltable(L); + Array keys = named_globals.keys(); + for (int64_t i = 0, count = keys.size(); i < count; ++i) { + Variant key = keys[i]; + lua_push(L, key); + lua_push(L, named_globals[key]); + lua_rawset(L, -3); + } +} + +void LuaScriptLanguage::register_global_classes(lua_State *L) const { + StackTopResetter resettop(L); + ResourceLoader *resource_loader = ResourceLoader::get_singleton(); + lua_pushglobaltable(L); + for (int64_t i = 0, count = global_class_list.size(); i < count; ++i) { + Dictionary type_info = global_class_list[i]; + Ref script = resource_loader->load(type_info["path"]); + lua_push(L, type_info["class"]); + lua_push(L, script); + lua_rawset(L, -3); + } } LuaState *LuaScriptLanguage::get_lua_state() { diff --git a/src/script-language/LuaScriptLanguage.hpp b/src/script-language/LuaScriptLanguage.hpp index a2ba9283..03cebfe7 100644 --- a/src/script-language/LuaScriptLanguage.hpp +++ b/src/script-language/LuaScriptLanguage.hpp @@ -98,7 +98,9 @@ class LuaScriptLanguage : public ScriptLanguageExtension { PackedStringArray get_lua_keywords() const; PackedStringArray get_lua_member_keywords() const; - const Dictionary& get_named_globals() const; + + void register_named_globals(lua_State *L) const; + void register_global_classes(lua_State *L) const; LuaState *get_lua_state(); LuaParser *get_lua_parser() const; @@ -113,6 +115,7 @@ class LuaScriptLanguage : public ScriptLanguageExtension { Ref lua_state; Ref lua_parser; Dictionary named_globals; + TypedArray global_class_list; private: static LuaScriptLanguage *instance; diff --git a/src/script-language/LuaScriptMethod.cpp b/src/script-language/LuaScriptMethod.cpp index 404eae86..d94fe848 100644 --- a/src/script-language/LuaScriptMethod.cpp +++ b/src/script-language/LuaScriptMethod.cpp @@ -22,23 +22,24 @@ #include "LuaScriptMethod.hpp" #include "../LuaDebug.hpp" +#include "../LuaFunction.hpp" #include "../utils/stack_top_checker.hpp" namespace luagdextension { LuaScriptMethod::LuaScriptMethod(const StringName& name, sol::protected_function method) : name(name) - , method(LuaObject::wrap_object(method)) + , method(method) { } bool LuaScriptMethod::is_valid() const { - return method.is_valid(); + return method.valid(); } int LuaScriptMethod::get_line_defined() const { #ifdef DEBUG_ENABLED - return method->get_debug_info()->get_line_defined(); + return LuaObject::wrap_object(method)->get_debug_info()->get_line_defined(); #else return -1; #endif @@ -46,7 +47,7 @@ int LuaScriptMethod::get_line_defined() const { Variant LuaScriptMethod::get_argument_count() const { #if defined(DEBUG_ENABLED) && LUA_VERSION_NUM >= 502 - return method->get_debug_info()->get_nparams(); + return LuaObject::wrap_object(method)->get_debug_info()->get_nparams(); #else return {}; #endif @@ -57,11 +58,11 @@ MethodInfo LuaScriptMethod::to_method_info() const { mi.name = name; #if defined(DEBUG_ENABLED) && LUA_VERSION_NUM >= 502 - sol::state_view state = method->get_function().lua_state(); + sol::state_view state = LuaObject::wrap_object(method)->get_function().lua_state(); StackTopChecker topcheck(state); - auto debug_info = method->get_debug_info(); - auto methodpop = sol::stack::push_pop(state, method->get_function()); + auto debug_info = LuaObject::wrap_object(method)->get_debug_info(); + auto methodpop = sol::stack::push_pop(state, method); for (int i = 0; i < debug_info->get_nparams(); i++) { String arg_name = lua_getlocal(state, nullptr, i + 1); if (i == 0 && arg_name == "self") { diff --git a/src/script-language/LuaScriptMethod.hpp b/src/script-language/LuaScriptMethod.hpp index 03409d42..bf3c2d23 100644 --- a/src/script-language/LuaScriptMethod.hpp +++ b/src/script-language/LuaScriptMethod.hpp @@ -25,8 +25,6 @@ #include #include -#include "../LuaFunction.hpp" - typedef struct lua_State lua_State; using namespace godot; @@ -35,7 +33,7 @@ namespace luagdextension { struct LuaScriptMethod { StringName name; - Ref method; + sol::protected_function method; LuaScriptMethod() = default; LuaScriptMethod(const StringName& name, sol::protected_function method); diff --git a/src/utils/_G_metatable.cpp b/src/utils/_G_metatable.cpp deleted file mode 100644 index 12cae5d0..00000000 --- a/src/utils/_G_metatable.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (C) 2026 Gil Barbosa Reis. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the “Software”), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is furnished to do - * so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include "_G_metatable.hpp" - -#include "Class.hpp" -#include "convert_godot_lua.hpp" -#include "module_names.hpp" -#include "../script-language/LuaScriptLanguage.hpp" - -#include -#include -#include - -using namespace godot; - -namespace luagdextension { - -sol::object __index(sol::this_state state, sol::global_table _G, sol::stack_object key) { - static Engine *engine = Engine::get_singleton(); - static ResourceLoader *resource_loader = ResourceLoader::get_singleton(); - - if (key.get_type() != sol::type::string) { - return sol::nil; - } - - auto registry = sol::state_view(state).registry(); - if (registry.get_or(module_names::singleton_access, false)) { - auto class_name = key.as(); - if (engine->has_singleton(class_name)) { - Variant singleton = engine->get_singleton(class_name); - return _G[key] = to_lua(state, singleton); - } - else if (Variant named_global = LuaScriptLanguage::get_singleton()->get_named_globals().get(class_name, nullptr); named_global.get_type() != Variant::NIL) { - return _G[key] = to_lua(state, named_global); - } - } - if (registry.get_or(module_names::classes, false)) { - StringName class_name = key.as(); - if (ClassDB::class_exists(class_name)) { - Class cls(class_name); - return _G[key] = sol::make_object(state, cls); - } - else if (const char *global_class_path = registry.get("_GDEXTENSION_GLOBAL_CLASS_PATHS").get_or(key, (const char *) nullptr)) { - Ref res = resource_loader->load(global_class_path); - return _G[key] = to_lua(state, res); - } - } - return sol::nil; -} - -void setup_G_metatable(sol::state_view& state) { - state.globals()[sol::metatable_key] = state.create_table_with( - sol::meta_function::index, &__index - ); -} - -} - diff --git a/src/utils/_G_metatable.hpp b/src/utils/_G_metatable.hpp deleted file mode 100644 index 22e0f009..00000000 --- a/src/utils/_G_metatable.hpp +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (C) 2026 Gil Barbosa Reis. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the “Software”), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is furnished to do - * so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef __UTILS_G_METATABLE_HPP__ -#define __UTILS_G_METATABLE_HPP__ - -#include - -namespace luagdextension { - -void setup_G_metatable(sol::state_view& state); - -} - -#endif // __UTILS_G_METATABLE_HPP__ diff --git a/test/test_entrypoint.gd b/test/test_entrypoint.gd index 96f04096..3bcd8ee0 100644 --- a/test/test_entrypoint.gd +++ b/test/test_entrypoint.gd @@ -4,7 +4,7 @@ const LUA_TEST_DIR = "res://lua_tests" const GDSCRIPT_TEST_DIR = "res://gdscript_tests" func _process(_delta): - var all_success = true + var error_count = 0 print("Starting Lua GDExtension tests (runtime: ", LuaState.get_lua_runtime(), ")") for lua_script in DirAccess.get_files_at(LUA_TEST_DIR): @@ -16,7 +16,7 @@ func _process(_delta): var file_name = str(LUA_TEST_DIR, "/", lua_script) var result = lua_state.do_file(file_name) if result is LuaError: - all_success = false + error_count += 1 print("! ", lua_script) push_error(result.message) else: @@ -38,11 +38,12 @@ func _process(_delta): obj._setup() # actual test if not obj.call(method_name): - all_success = false + error_count += 1 printerr(" ! ", method_name) else: print(" ✓ ", method_name) if obj is Node: obj.queue_free() - quit(0 if all_success else -1) + print("\nFailed tests: ", error_count) + quit(0 if error_count == 0 else -1)