diff --git a/3rdParty/sol2/CMakeLists.txt b/3rdParty/sol2/CMakeLists.txt new file mode 100644 index 00000000000..390dd7c01c8 --- /dev/null +++ b/3rdParty/sol2/CMakeLists.txt @@ -0,0 +1,13 @@ +include(functions/FetchContent_MakeAvailableExcludeFromAll) + +set(SOL2_ENABLE_INSTALL OFF) + +include(FetchContent) +FetchContent_Declare(sol2 + URL https://github.com/ThePhD/sol2/archive/9c882a28fdb6f4ad79a53a4191b43ce48a661175.tar.gz + URL_HASH MD5=2637c3fcdcce3ff34b36437c1d3b99d1 +) +FetchContent_MakeAvailableExcludeFromAll(sol2) + +target_include_directories(sol2 SYSTEM BEFORE INTERFACE ${CMAKE_CURRENT_LIST_DIR}/sol_config) +target_compile_definitions(sol2 INTERFACE -DSOL_NO_EXCEPTIONS) diff --git a/3rdParty/sol2/sol_config/sol/config.hpp b/3rdParty/sol2/sol_config/sol/config.hpp new file mode 100644 index 00000000000..89feb676c74 --- /dev/null +++ b/3rdParty/sol2/sol_config/sol/config.hpp @@ -0,0 +1,8 @@ +#pragma once + +#define SOL_SAFE_USERTYPE 1 +#define SOL_SAFE_REFERENCES 1 +#define SOL_SAFE_FUNCTION_CALLS 1 +#define SOL_SAFE_FUNCTION 1 +#define SOL_NO_NIL 0 +#define SOL_IN_DEBUG_DETECTED 0 diff --git a/3rdParty/sol2/sol_config/sol/debug.hpp b/3rdParty/sol2/sol_config/sol/debug.hpp new file mode 100644 index 00000000000..14b46a89d98 --- /dev/null +++ b/3rdParty/sol2/sol_config/sol/debug.hpp @@ -0,0 +1,36 @@ +#pragma once + +// sol2 uses std::cout for debug logging by default. +// We want to use SDL logging instead for better compatibility. + +#include +#include + +#include + +namespace devilutionx { +void Sol2DebugPrintStack(lua_State *L); +void Sol2DebugPrintSection(const std::string &message, lua_State *L); +} // namespace devilutionx + +namespace sol::detail::debug { + +inline std::string dump_types(lua_State *L) { + std::string visual; + std::size_t size = lua_gettop(L) + 1; + for (std::size_t i = 1; i < size; ++i) { + if (i != 1) { + visual += " | "; + } + visual += type_name(L, stack::get(L, static_cast(i))); + } + return visual; +} + +inline void print_stack(lua_State *L) { ::devilutionx::Sol2DebugPrintStack(L); } + +inline void print_section(const std::string &message, lua_State *L) { + ::devilutionx::Sol2DebugPrintSection(message, L); +} + +} // namespace sol::detail::debug diff --git a/CMake/Dependencies.cmake b/CMake/Dependencies.cmake index 9e6894cccfc..99af8005d2c 100644 --- a/CMake/Dependencies.cmake +++ b/CMake/Dependencies.cmake @@ -27,8 +27,6 @@ endif() find_package(Lua 5.4 QUIET) if(LUA_FOUND) message("-- Found Lua ${LUA_VERSION_STRING}") - # No clear way to statically link from the system lib - set(DEVILUTIONX_STATIC_LUA OFF) else() if(NOT DEFINED DEVILUTIONX_SYSTEM_LUA) message("-- Suitable system Lua package not found, will use Lua from source") @@ -48,6 +46,8 @@ else() include_directories(${LUA_INCLUDE_DIR}) endif() +add_subdirectory(3rdParty/sol2) + if(SCREEN_READER_INTEGRATION) if(WIN32) add_subdirectory(3rdParty/tolk) diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 5e42d82ee65..a88004e2407 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -288,7 +288,7 @@ if(DISCORD_INTEGRATION) target_link_libraries(libdevilutionx PRIVATE discord discord_game_sdk) endif() -target_link_libraries(libdevilutionx PRIVATE ${LUA_LIBRARIES}) +target_link_libraries(libdevilutionx PRIVATE ${LUA_LIBRARIES} sol2::sol2) if(SCREEN_READER_INTEGRATION AND WIN32) target_compile_definitions(libdevilutionx PRIVATE Tolk) diff --git a/Source/utils/lua.cpp b/Source/utils/lua.cpp index fb887c2a1eb..859dbb08a0d 100644 --- a/Source/utils/lua.cpp +++ b/Source/utils/lua.cpp @@ -1,22 +1,20 @@ #include "utils/lua.hpp" +#include #include -extern "C" { -#include "lauxlib.h" -#include "lua.h" -#include "lualib.h" -} +#include #include "engine/assets.hpp" #include "plrmsg.h" #include "utils/console.h" +#include "utils/log.hpp" namespace devilution { namespace { -lua_State *LuaState; +std::optional luaState; int LuaPrint(lua_State *state) { @@ -41,6 +39,19 @@ int LuaPlayerMessage(lua_State *state) return 0; } +bool CheckResult(sol::protected_function_result result) +{ + const bool valid = result.valid(); + if (!valid) { + if (result.get_type() == sol::type::string) { + LogError("Lua error: {}", result.get()); + } else { + LogError("Unknown Lua error"); + } + } + return valid; +} + void RunScript(std::string_view path) { AssetRef ref = FindAsset(path); @@ -48,7 +59,7 @@ void RunScript(std::string_view path) return; const size_t size = ref.size(); - std::unique_ptr luaScript { new char[size + 1] }; + std::unique_ptr luaScript { new char[size] }; AssetHandle handle = OpenAsset(std::move(ref)); if (!handle.ok()) @@ -57,52 +68,53 @@ void RunScript(std::string_view path) if (size > 0 && !handle.read(luaScript.get(), size)) return; - luaScript[size] = '\0'; // Terminate string + const std::string_view luaScriptStr(luaScript.get(), size); + CheckResult(luaState->safe_script(luaScriptStr)); +} - int status = luaL_loadstring(LuaState, luaScript.get()); - if (status == LUA_OK) - status = lua_pcall(LuaState, 0, 0, 0); - if (status != LUA_OK) - SDL_Log("%s", lua_tostring(LuaState, -1)); +void LuaPanic(sol::optional maybe_msg) +{ + LogError("Lua is in a panic state and will now abort() the application:\n", + maybe_msg.value_or("unknown error")); } } // namespace +void Sol2DebugPrintStack(lua_State *L) +{ + LogDebug("{}", sol::detail::debug::dump_types(L)); +} + +void Sol2DebugPrintSection(const std::string &message, lua_State *L) +{ + LogDebug("-- {} -- [ {} ]", message, sol::detail::debug::dump_types(L)); +} + void LuaInitialize() { - LuaState = luaL_newstate(); - - // Load libraries - luaL_requiref(LuaState, LUA_GNAME, luaopen_base, 1); - lua_pop(LuaState, 1); - luaL_requiref(LuaState, LUA_LOADLIBNAME, luaopen_package, 1); - lua_pop(LuaState, 1); - luaL_requiref(LuaState, LUA_COLIBNAME, luaopen_coroutine, 1); - lua_pop(LuaState, 1); - luaL_requiref(LuaState, LUA_TABLIBNAME, luaopen_table, 1); - lua_pop(LuaState, 1); - luaL_requiref(LuaState, LUA_STRLIBNAME, luaopen_string, 1); - lua_pop(LuaState, 1); - luaL_requiref(LuaState, LUA_MATHLIBNAME, luaopen_math, 1); - lua_pop(LuaState, 1); - luaL_requiref(LuaState, LUA_UTF8LIBNAME, luaopen_utf8, 1); - lua_pop(LuaState, 1); + luaState.emplace(sol::c_call); + sol::state &lua = *luaState; + lua.open_libraries( + sol::lib::base, + sol::lib::package, + sol::lib::coroutine, + sol::lib::table, + sol::lib::string, + sol::lib::math, + sol::lib::utf8); #ifdef _DEBUG - luaL_requiref(LuaState, LUA_DBLIBNAME, luaopen_debug, 1); - lua_pop(LuaState, 1); + lua.open_libraries(sol::lib::debug); #endif // Registering globals - lua_register(LuaState, "print", LuaPrint); - lua_pushstring(LuaState, LUA_VERSION); - lua_setglobal(LuaState, "_VERSION"); + lua["print"] = LuaPrint; + lua["_VERSION"] = LUA_VERSION; // Registering devilutionx object table - lua_newtable(LuaState); - lua_pushcfunction(LuaState, LuaPlayerMessage); - lua_setfield(LuaState, -2, "message"); - lua_setglobal(LuaState, "devilutionx"); + sol::table devilutionx(lua, sol::create); + devilutionx["message"] = LuaPlayerMessage; + lua["devilutionx"] = devilutionx; RunScript("lua/init.lua"); RunScript("lua/user.lua"); @@ -112,36 +124,29 @@ void LuaInitialize() void LuaShutdown() { - if (LuaState == nullptr) - return; - - lua_close(LuaState); + luaState = std::nullopt; } -void LuaEvent(std::string name) +void LuaEvent(std::string_view name) { - lua_getglobal(LuaState, "Events"); - if (!lua_istable(LuaState, -1)) { - lua_pop(LuaState, 1); - SDL_Log("Events table missing!"); + sol::state &lua = *luaState; + const sol::object events = lua["Events"]; + if (!events.is()) { + LogError("Events table missing!"); return; } - lua_getfield(LuaState, -1, name.c_str()); - if (!lua_istable(LuaState, -1)) { - lua_pop(LuaState, 2); - SDL_Log("Events.%s event not registered", name.c_str()); + const sol::object event = events.as()[name]; + if (!event.is()) { + LogError("Events.{} event not registered", name); return; } - lua_getfield(LuaState, -1, "Trigger"); - if (!lua_isfunction(LuaState, -1)) { - lua_pop(LuaState, 3); - SDL_Log("Events.%s.Trigger is not a function", name.c_str()); + const sol::object trigger = event.as()["Trigger"]; + if (!trigger.is()) { + LogError("Events.{}.Trigger is not a function", name); return; } - if (lua_pcall(LuaState, 0, 0, 0) != LUA_OK) { - SDL_Log("%s", lua_tostring(LuaState, -1)); - } - lua_pop(LuaState, 2); + const sol::protected_function fn = trigger.as(); + CheckResult(fn()); } } // namespace devilution diff --git a/Source/utils/lua.hpp b/Source/utils/lua.hpp index a66a5251f85..141271d2f7c 100644 --- a/Source/utils/lua.hpp +++ b/Source/utils/lua.hpp @@ -1,11 +1,11 @@ #pragma once -#include +#include namespace devilution { void LuaInitialize(); void LuaShutdown(); -void LuaEvent(std::string name); +void LuaEvent(std::string_view name); } // namespace devilution