From 56507903711c65fa0ca1bf5af65dc6b10ef035e0 Mon Sep 17 00:00:00 2001 From: Jason Leon Date: Sun, 20 Sep 2026 23:13:31 +0800 Subject: [PATCH] Try to convert sol2 to a simpler glue --- .github/workflows/build-packages.yml | 2 + CMakeLists.txt.in | 93 +- LuaGlue/CMakeLists.txt | 40 + LuaGlue/LICENSE | 21 + LuaGlue/README.md | 74 + LuaGlue/README_zh_CN.md | 43 + LuaGlue/cmake/FindLua.cmake | 53 + LuaGlue/cmake/LuaGlueLua.cmake | 14 + LuaGlue/include/LuaGlue/Api.hpp | 13 + LuaGlue/include/LuaGlue/Binding.hpp | 555 ++++++++ LuaGlue/include/LuaGlue/Codec.hpp | 667 +++++++++ LuaGlue/include/LuaGlue/Lifecycle.hpp | 118 ++ LuaGlue/include/LuaGlue/LuaGlue.hpp | 8 + LuaGlue/include/LuaGlue/Native.hpp | 122 ++ LuaGlue/include/LuaGlue/Value.hpp | 507 +++++++ LuaGlue/include/LuaGlue/Value.inl | 308 ++++ LuaGlue/src/Binding.cpp | 416 ++++++ LuaGlue/src/BindingImpl.hpp | 9 + LuaGlue/src/Lifecycle.cpp | 1245 +++++++++++++++++ LuaGlue/src/Native.cpp | 752 ++++++++++ LuaGlue/src/NativeImpl.hpp | 57 + LuaGlue/src/Value.cpp | 546 ++++++++ LuaSF.cpp.in | 25 +- LuaSF.hpp.in | 2 + README.md | 29 +- README_zh_CN.md | 27 +- analyze_types.py | 2 +- build.bat | 26 +- build.sh | 29 +- cmake/LuaSFTargets.cmake | 21 +- cmake/sol/pr1606.patch | 37 - collect_result.bat | 51 +- collect_result.sh | 39 +- include/LuaCallbackCodec.hpp | 190 +-- include/LuaStateLifecycle.hpp | 82 +- include/bind_ClassSupport.hpp | 8 +- include/bind_Drawable.hpp | 14 +- include/bind_Event.hpp | 2 +- include/bind_Handle.hpp | 2 +- include/lua_stub.hpp | 779 ++++++----- include/luasf_glue.hpp | 97 ++ include/luasf_sol.hpp | 387 ----- include/utils.hpp | 219 +-- include/utils.inl | 1148 +++++++-------- init.bat | 26 - init.sh | 30 +- pack_result.bat | 97 +- pack_result.sh | 115 +- src/LuaCallbackCodec.cpp | 907 ++++++------ src/LuaStateLifecycle.cpp | 944 +------------ src/bind_ClassSupport.cpp | 21 +- src/bind_Drawable.cpp | 71 +- src/bind_Event.cpp | 1171 ++++++++-------- src/bind_Handle.cpp | 52 +- tools/binding_config.py | 60 +- tools/extract_sfml_api.py | 9 +- tools/generate_build_files.py | 77 +- ..._bindings.py => generate_glue_bindings.py} | 374 ++--- tools/output_files.py | 22 + tools/replace_model.py | 16 +- versions.conf | 1 - 61 files changed, 8689 insertions(+), 4183 deletions(-) create mode 100644 LuaGlue/CMakeLists.txt create mode 100644 LuaGlue/LICENSE create mode 100644 LuaGlue/README.md create mode 100644 LuaGlue/README_zh_CN.md create mode 100644 LuaGlue/cmake/FindLua.cmake create mode 100644 LuaGlue/cmake/LuaGlueLua.cmake create mode 100644 LuaGlue/include/LuaGlue/Api.hpp create mode 100644 LuaGlue/include/LuaGlue/Binding.hpp create mode 100644 LuaGlue/include/LuaGlue/Codec.hpp create mode 100644 LuaGlue/include/LuaGlue/Lifecycle.hpp create mode 100644 LuaGlue/include/LuaGlue/LuaGlue.hpp create mode 100644 LuaGlue/include/LuaGlue/Native.hpp create mode 100644 LuaGlue/include/LuaGlue/Value.hpp create mode 100644 LuaGlue/include/LuaGlue/Value.inl create mode 100644 LuaGlue/src/Binding.cpp create mode 100644 LuaGlue/src/BindingImpl.hpp create mode 100644 LuaGlue/src/Lifecycle.cpp create mode 100644 LuaGlue/src/Native.cpp create mode 100644 LuaGlue/src/NativeImpl.hpp create mode 100644 LuaGlue/src/Value.cpp delete mode 100644 cmake/sol/pr1606.patch create mode 100644 include/luasf_glue.hpp delete mode 100644 include/luasf_sol.hpp rename tools/{generate_sol2_bindings.py => generate_glue_bindings.py} (92%) create mode 100644 tools/output_files.py diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index e403699..290d597 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -4,6 +4,7 @@ run-name: Build LuaSF packages for ${{ github.ref_name }} @ ${{ github.sha }} on: workflow_call: + workflow_dispatch: permissions: contents: read @@ -144,6 +145,7 @@ jobs: $requiredRuntimeDlls = @( 'LuaSF.dll', + 'LuaGlue.dll', 'lua.dll', 'msvcp140.dll', 'msvcp140_atomic_wait.dll', diff --git a/CMakeLists.txt.in b/CMakeLists.txt.in index d8534e7..cb25534 100644 --- a/CMakeLists.txt.in +++ b/CMakeLists.txt.in @@ -21,7 +21,7 @@ set(LUASF_EMBEDDED_ARCHIVE_DIR "${{LUASF_BINARY_ROOT}}/lib/$/embedded") set(LUASF_SFML_ROOT "" CACHE PATH "External SFML source project used by LuaSF") set(LUASF_LUA_ROOT "" CACHE PATH "External Lua source directory containing src/lua.h") -set(LUASF_SOL2_ROOT "" CACHE PATH "External sol2 source directory containing include/sol2/sol.hpp") +set(LUASF_GLUE_ROOT "${{LUASF_ROOT}}/../LuaGlue" CACHE PATH "LuaGlue source project") if(NOT LUASF_SFML_ROOT) message(FATAL_ERROR "LUASF_SFML_ROOT is required; point it at an SFML source project.") endif() @@ -31,9 +31,6 @@ endif() if(NOT EXISTS "${{LUASF_LUA_ROOT}}/src/lua.h") message(FATAL_ERROR "Lua source directory was not found: ${{LUASF_LUA_ROOT}}") endif() -if(NOT EXISTS "${{LUASF_SOL2_ROOT}}/include/sol2/sol.hpp") - message(FATAL_ERROR "sol2 source directory was not found: ${{LUASF_SOL2_ROOT}}") -endif() option(LUASF_BUILD_SHARED_SFML "Build SFML as shared libraries for LuaSF" ON) option(LUASF_GENERATE_LUA_STUB "Generate Lua language-server stub from the built LuaSF module" ON) @@ -122,6 +119,8 @@ if(NOT TARGET ${{LUASF_LUA_TARGET}}) C_STANDARD_REQUIRED ON OUTPUT_NAME "lua" RUNTIME_OUTPUT_DIRECTORY "${{LUASF_BINARY_ROOT}}/bin" + BUILD_WITH_INSTALL_RPATH FALSE + BUILD_RPATH "$;$;$" LIBRARY_OUTPUT_DIRECTORY "${{LUASF_BINARY_ROOT}}/bin" ARCHIVE_OUTPUT_DIRECTORY "${{LUASF_BINARY_ROOT}}/lib" ) @@ -169,56 +168,18 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_CXX_COMPILER_ID STREQUAL "Apple endif() endif() -set(LUASF_SOL_COMPAT_INCLUDE "${{LUASF_BINARY_ROOT}}/generated_include") -file(MAKE_DIRECTORY "${{LUASF_SOL_COMPAT_INCLUDE}}/sol") -configure_file( - "${{LUASF_SOL2_ROOT}}/include/sol2/config.hpp" - "${{LUASF_SOL_COMPAT_INCLUDE}}/sol/config.hpp" - COPYONLY -) -configure_file( - "${{LUASF_SOL2_ROOT}}/include/sol2/forward.hpp" - "${{LUASF_SOL_COMPAT_INCLUDE}}/sol/forward.hpp" - COPYONLY -) -configure_file( - "${{LUASF_SOL2_ROOT}}/include/sol2/sol.hpp" - "${{LUASF_SOL_COMPAT_INCLUDE}}/sol/sol.hpp" - COPYONLY -) -file(CONFIGURE - OUTPUT "${{LUASF_SOL_COMPAT_INCLUDE}}/lua.h" - CONTENT "#include \"@LUASF_LUA_ROOT@/src/lua.h\" -#undef LUA_VERSION_NUM -#define LUA_VERSION_NUM 504 -#undef lua_newstate -#define lua_newstate(f, ud) lua_newstate((f), (ud), 0u) -" - @ONLY - NEWLINE_STYLE UNIX -) -file(CONFIGURE - OUTPUT "${{LUASF_SOL_COMPAT_INCLUDE}}/lauxlib.h" - CONTENT "#include \"@LUASF_LUA_ROOT@/src/lauxlib.h\" -#undef LUA_VERSION_NUM -#define LUA_VERSION_NUM 504 -#undef lua_newstate -#define lua_newstate(f, ud) lua_newstate((f), (ud), 0u) -" - @ONLY - NEWLINE_STYLE UNIX -) -file(CONFIGURE - OUTPUT "${{LUASF_SOL_COMPAT_INCLUDE}}/lualib.h" - CONTENT "#include \"@LUASF_LUA_ROOT@/src/lualib.h\" -#undef LUA_VERSION_NUM -#define LUA_VERSION_NUM 504 -#undef lua_newstate -#define lua_newstate(f, ud) lua_newstate((f), (ud), 0u) -" - @ONLY - NEWLINE_STYLE UNIX -) +if(NOT TARGET LuaGlue::LuaGlue) + if(NOT EXISTS "${{LUASF_GLUE_ROOT}}/CMakeLists.txt") + message(FATAL_ERROR "LuaGlue source project was not found: ${{LUASF_GLUE_ROOT}}") + endif() + set(LUAGLUE_LUA_TARGET "${{LUASF_LUA_TARGET}}") + if(LUASF_LIBRARY_TYPE STREQUAL "SHARED") + set(LUAGLUE_BUILD_SHARED ON) + else() + set(LUAGLUE_BUILD_SHARED OFF) + endif() + add_subdirectory("${{LUASF_GLUE_ROOT}}" "${{LUASF_BINARY_ROOT}}/LuaGlue") +endif() set(LUASF_BINDING_HEADERS {binding_headers} @@ -243,20 +204,14 @@ function(luasf_configure_binding_target target_name output_dir archive_dir) target_include_directories("${{_luasf_binding_objects}}" PRIVATE - "${{LUASF_SOL_COMPAT_INCLUDE}}" "${{LUASF_ROOT}}/include" "${{LUASF_SFML_ROOT}}/include" "${{LUASF_LUA_ROOT}}/src" - "${{LUASF_SOL2_ROOT}}/include" ) + target_link_libraries("${{_luasf_binding_objects}}" PRIVATE LuaGlue::LuaGlue) + target_compile_definitions("${{_luasf_binding_objects}}" PRIVATE - $<$:SOL_ALL_SAFETIES_ON=1> - SOL_PRINT_ERRORS=0 - SOL_SAFE_NUMERICS=1 - SOL_USE_INTEROP=1 - SOL_DEFAULT_AUTOMAGICAL_USERTYPES=0 - SOL_USERTYPE_TYPE_BINDING_INFO=0 LUASF_BUILD_DLL=1 ) @@ -279,9 +234,10 @@ function(luasf_configure_binding_target target_name output_dir archive_dir) target_include_directories("${{target_name}}" PUBLIC "$" + "$" ) - target_link_libraries("${{target_name}}" PRIVATE + target_link_libraries("${{target_name}}" PUBLIC LuaGlue::LuaGlue PRIVATE ${{LUASF_LUA_TARGET}} sfml-system sfml-window @@ -329,6 +285,7 @@ function(luasf_copy_target_runtime_files target_name include_lua_runtime) list(APPEND _luasf_runtime_files "$") endif() foreach(_luasf_runtime_target IN ITEMS + LuaGlue::LuaGlue sfml-system sfml-window sfml-graphics @@ -363,6 +320,7 @@ function(luasf_copy_runtime_files target_name) "$" ) foreach(_luasf_runtime_target IN ITEMS + LuaGlue::LuaGlue sfml-system sfml-window sfml-graphics @@ -410,6 +368,7 @@ function(luasf_add_lua_stub target_name) ) target_link_libraries(${{target_name}}_lua_stub_dump PRIVATE "{target_name}_bindings" + LuaGlue::LuaGlue ${{LUASF_LUA_TARGET}} sfml-system sfml-window @@ -419,6 +378,8 @@ function(luasf_add_lua_stub target_name) ) set_target_properties(${{target_name}}_lua_stub_dump PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${{LUASF_BINARY_ROOT}}/bin" + BUILD_WITH_INSTALL_RPATH FALSE + BUILD_RPATH "$;$;$" ) set(_luasf_stub_args "${{LUASF_LUA_STUB_OUTPUT}}") @@ -427,6 +388,7 @@ function(luasf_add_lua_stub target_name) add_custom_command(TARGET ${{target_name}}_lua_stub_dump POST_BUILD COMMAND "${{CMAKE_COMMAND}}" -E copy_if_different "$" + "$" "$" "$" "$" @@ -443,14 +405,14 @@ function(luasf_add_lua_stub target_name) if(APPLE) set(_luasf_stub_command "${{CMAKE_COMMAND}}" -E env - "DYLD_LIBRARY_PATH=$:$:$ENV{{DYLD_LIBRARY_PATH}}" + "DYLD_LIBRARY_PATH=$:$:$:$ENV{{DYLD_LIBRARY_PATH}}" "$" ${{_luasf_stub_args}} ) else() set(_luasf_stub_command "${{CMAKE_COMMAND}}" -E env - "LD_LIBRARY_PATH=$:$:$ENV{{LD_LIBRARY_PATH}}" + "LD_LIBRARY_PATH=$:$:$:$ENV{{LD_LIBRARY_PATH}}" "$" ${{_luasf_stub_args}} ) @@ -459,6 +421,7 @@ function(luasf_add_lua_stub target_name) add_dependencies(${{target_name}}_lua_stub_dump "{target_name}_bindings" + LuaGlue::LuaGlue ${{LUASF_LUA_TARGET}} sfml-system sfml-window diff --git a/LuaGlue/CMakeLists.txt b/LuaGlue/CMakeLists.txt new file mode 100644 index 0000000..d03145a --- /dev/null +++ b/LuaGlue/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.21) +project(LuaGlue VERSION 1.0.0 LANGUAGES CXX) + +include("${CMAKE_CURRENT_LIST_DIR}/cmake/LuaGlueLua.cmake") + +set(_luaglue_shared ON) +if(CMAKE_SYSTEM_NAME MATCHES "^(iOS|Android|OHOS)$") + set(_luaglue_shared OFF) +endif() +option(LUAGLUE_BUILD_SHARED "Build one shared LuaGlue runtime" ${_luaglue_shared}) +if(LUAGLUE_BUILD_SHARED) + set(_luaglue_library_type SHARED) +else() + set(_luaglue_library_type STATIC) +endif() + +add_library(LuaGlue ${_luaglue_library_type} + src/Value.cpp + src/Lifecycle.cpp + src/Native.cpp + src/Binding.cpp) +add_library(LuaGlue::LuaGlue ALIAS LuaGlue) +target_compile_features(LuaGlue PUBLIC cxx_std_20) +target_compile_definitions(LuaGlue PRIVATE LUAGLUE_BUILD) +if(NOT LUAGLUE_BUILD_SHARED) + target_compile_definitions(LuaGlue PUBLIC LUAGLUE_STATIC) +endif() +target_include_directories(LuaGlue PUBLIC + "$" + "$") +target_link_libraries(LuaGlue PUBLIC "${LUAGLUE_LUA_TARGET}") +set_target_properties(LuaGlue PROPERTIES + POSITION_INDEPENDENT_CODE ON + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON) +if(MSVC) + target_compile_options(LuaGlue PRIVATE /GR- /EHsc) +else() + target_compile_options(LuaGlue PRIVATE -fno-rtti) +endif() diff --git a/LuaGlue/LICENSE b/LuaGlue/LICENSE new file mode 100644 index 0000000..2dcf8ee --- /dev/null +++ b/LuaGlue/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Jason Leon + +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. diff --git a/LuaGlue/README.md b/LuaGlue/README.md new file mode 100644 index 0000000..3527ac0 --- /dev/null +++ b/LuaGlue/README.md @@ -0,0 +1,74 @@ +# LuaGlue + +LuaGlue is a C++20 binding runtime for generated Lua 5.5 interfaces. It depends only on Lua, supports builds with RTTI disabled, and keeps type registration, overload dispatch, properties and userdata ownership in the compiled runtime. Small templates adapt each concrete C++ signature. + +[简体中文](README_zh_CN.md) + +## CMake integration + +Keep this directory as a submodule or a source dependency. Provide the existing Lua target before adding it: + +```cmake +set(LUAGLUE_LUA_TARGET MyLuaTarget) +add_subdirectory(third_party/LuaGlue) +target_link_libraries(MyApplication PRIVATE LuaGlue::LuaGlue) +``` + +If `LUAGLUE_LUA_TARGET` is empty, the project's finder locates Lua 5.5 and verifies its header version, including with CMake versions that cannot recognize Lua 5.5's numeric version macros. Set `LUA_INCLUDE_DIR` and `LUA_LIBRARY` explicitly, or supply the installation through `CMAKE_PREFIX_PATH`. LuaGlue does not download Lua. `LUAGLUE_BUILD_SHARED` defaults to `ON` on desktop and `OFF` on iOS, Android and OHOS. All modules in a process must use the same Lua and LuaGlue runtimes. Distribute the shared library with desktop consumers. + +The supported CMake interface is the `LuaGlue::LuaGlue` target created by `add_subdirectory`. An installed `find_package(LuaGlue)` export is not currently provided. + +## Binding a fixed interface + +```cpp +#include +#include +#include +#include +#include +#include + +struct MyClass { + int a{}; + float b{}; + std::string function(double c) { return std::to_string(c); } + inline static std::vector d; + static std::unordered_map StaticFunction(long f) { + return {{static_cast(f), "value"}}; + } +}; + +constexpr std::array docs{"Return the selected entry."}; + +void bindExample(const lua_glue::Table& module) { + auto type = lua_glue::BindClass(module, "MyClass"); + lua_glue::BindConstructor(type); + lua_glue::BindAttr(type, "a", &MyClass::a); + lua_glue::BindAttr(type, "b", &MyClass::b); + lua_glue::BindStaticAttr>(type, "d", &MyClass::d); + lua_glue::BindMethod(type, "function", &MyClass::function); + lua_glue::BindFunction, long>( + type, "StaticFunction", &MyClass::StaticFunction, + lua_glue::Defaults{1L}, docs[0]); +} +``` + +Constructors are available through `MyClass.new(...)`; registering a class alone does not add a constructor. Instance properties access the actual instance, and static properties access the C++ static storage. Const properties are read-only. Container values convert to ordinary Lua tables; assigning the entire property writes a converted container back. + +`BindCallable` and `BindFactory` accept captured or uncaptured lambdas. Register each overload separately under the same Lua name. `BindProperty` takes a getter and an optional setter; `BindMetamethod` takes a Lua metamethod name such as `"__add"`. `BindBase` registers a base-pointer conversion and Lua member inheritance. Use `BindCast` for a native interface conversion that does not expose Lua inheritance; that interface needs no Lua class descriptor. `BindEnum` accepts an initializer list of name/value pairs. + +`Defaults{...}` supplies trailing arguments only. Missing arguments use their defaults; explicit `nil` goes through the parameter codec. Use `DefaultFactory{[] { return expression; }}` for values that must be evaluated for every call. Documentation is a `std::string_view` binding option and can share a source-level `constexpr std::array` with the stub generator. + +Omitted nullable arguments, including `const std::optional&`, receive `nil` when no explicit binding default exists. Missing non-nullable arguments still fail overload matching. + +## Values and lifetime + +`BindStruct` requires an enabled `StructTraits` and adds `copy()`, `deepcopy()`, `__copy` and `__deepcopy`. Derive that trait from `IndependentValue` only after verifying that the complete C++ state, including private members, has independent value semantics. Its shallow and deep operations both use the native copy constructor. Types containing resource pointers, shared ownership or Lua references need a complete custom `DeepCopy` policy or must remain `BindClass` types. + +Values and smart pointers retain ownership. Borrowed pointers and references require their native owner to remain alive; use `ReturnPolicy::ReferenceInternal` to keep the first Lua argument alive with a returned userdata. Instance property getters apply this policy by default. `BindingOptions::keep_alive` expresses further relationships using `0` for the result and `1` for the first Lua argument. `NativePointer`, `NativeSharedOwner` and the native type query APIs provide supported access without exposing userdata layout. + +The host creates and closes the VM. Call `InitializeState` before registration, serialize access with the host execution hooks and `ExecutionScope`, stop callback producers, then call `QuiesceState` and `ShutdownState` before `lua_close`. Registry-backed handles detach during shutdown; they must not be used after shutdown. LuaGlue does not make a Lua state safe for concurrent use by itself. + +`Object`, `Table`, `Function`, `StateView`, `Arguments` and `CallResult` provide registry-backed values, table operations, stack views and protected calls. `CallResult` preserves the position of nil results. Use protected calls and let LuaGlue translate C++ errors at its dispatch boundary; a direct `lua_error` can bypass C++ destructors. + +LuaGlue is distributed under the [MIT License](LICENSE). diff --git a/LuaGlue/README_zh_CN.md b/LuaGlue/README_zh_CN.md new file mode 100644 index 0000000..9073da1 --- /dev/null +++ b/LuaGlue/README_zh_CN.md @@ -0,0 +1,43 @@ +# LuaGlue + +LuaGlue 是面向自动生成接口的 C++20 / Lua 5.5 绑定运行时,只依赖 Lua,支持关闭 RTTI。类型注册、重载分派、属性与 userdata 所有权位于编译后的运行时中,模板只适配确定的 C++ 签名。 + +[English](README.md) + +## CMake 接入 + +可将此目录作为 submodule 或源码依赖,并在添加工程前指定已有的 Lua target: + +```cmake +set(LUAGLUE_LUA_TARGET MyLuaTarget) +add_subdirectory(third_party/LuaGlue) +target_link_libraries(MyApplication PRIVATE LuaGlue::LuaGlue) +``` + +`LUAGLUE_LUA_TARGET` 为空时,项目自带的 finder 查找 Lua 5.5 并校验头文件版本,兼容尚不能识别 Lua 5.5 数字版本宏的 CMake。可显式设置 `LUA_INCLUDE_DIR`、`LUA_LIBRARY`,或通过 `CMAKE_PREFIX_PATH` 指定安装前缀,不会下载 Lua。`LUAGLUE_BUILD_SHARED` 在桌面默认 `ON`,在 iOS、Android、OHOS 默认 `OFF`。同一进程中的模块必须使用同一套 Lua 与 LuaGlue 运行时;桌面分发需携带共享库。 + +当前支持 `add_subdirectory` 创建的 `LuaGlue::LuaGlue` target,尚未提供安装后的 `find_package(LuaGlue)` 导出包。 + +## 固定接口绑定 + +[英文页中的完整示例](README.md#binding-a-fixed-interface) 演示 `BindClass`、`BindConstructor`、`BindAttr`、`BindStaticAttr`、`BindMethod` 与 `BindFunction`。接口显式接收 Lua 名称,成员函数重载应由生成器选择准确的 C++ 签名。 + +构造器通过 `MyClass.new(...)` 调用;只注册类型不会开放构造。实例属性访问实际实例,静态属性访问 C++ 静态存储,const 属性只读。容器按值转换为普通 Lua table;整体属性赋值会转换容器并写回。 + +`BindCallable`、`BindFactory` 接受有捕获与无捕获的 lambda,每个重载独立注册到同一个 Lua 名称。`BindProperty` 接收 getter 与可选的 setter,`BindMetamethod` 接收 `"__add"` 等 Lua 元方法名称。`BindBase` 注册基类指针转换与 Lua 成员继承。只需要原生接口转换时,使用 `BindCast`;它不暴露 Lua 继承,也不要求接口具有 Lua 类型描述。`BindEnum` 接收名称与枚举值的初始化列表。 + +`Defaults{...}` 只表示尾部默认参数。缺少实参时应用默认值,显式 `nil` 仍由参数 codec 处理。需要每次调用重新求值时,使用 `DefaultFactory{[] { return expression; }}`。文档作为 `std::string_view` 绑定选项传入,可与 stub 生成器共用源文件中的 `constexpr std::array`。 + +可空参数(包括 `const std::optional&`)在省略且没有显式绑定默认值时接收 `nil`;缺少不可空参数仍会导致重载匹配失败。 + +## 值与生命周期 + +`BindStruct` 要求启用 `StructTraits`,并添加 `copy()`、`deepcopy()`、`__copy` 与 `__deepcopy`。只有核对包括私有成员在内的完整 C++ 状态具有独立值语义后,才能让 traits 继承 `IndependentValue`;其浅复制与深复制均调用原生拷贝构造。包含资源指针、共享所有权或 Lua 引用的类型,必须提供完整的自定义 `DeepCopy` 策略,或保持为 `BindClass`。 + +值和智能指针保留所有权;借用指针、引用要求原生拥有者继续存活。`ReturnPolicy::ReferenceInternal` 让返回的 userdata 保活第一个 Lua 参数,实例属性 getter 默认应用此策略。`BindingOptions::keep_alive` 可表达更多关系,其中 `0` 为返回值、`1` 为第一个 Lua 参数。原生指针、共享控制块和类型查询通过公开 API 访问,消费者不应解释 userdata 内存布局。 + +VM 由宿主创建和关闭。注册前调用 `InitializeState`,通过宿主 execution hooks 与 `ExecutionScope` 串行访问;停止回调生产者后,在 `lua_close` 之前调用 `QuiesceState`、`ShutdownState`。registry 句柄在 shutdown 时脱离 VM,之后不得继续使用。LuaGlue 不会自动让 Lua state 支持并发访问。 + +`Object`、`Table`、`Function`、`StateView`、`Arguments` 与 `CallResult` 提供 registry 保活值、表操作、栈视图与受保护调用;`CallResult` 保留 nil 返回值的位置。应使用受保护调用,并让 LuaGlue 在分派边界转换 C++ 错误;直接调用 `lua_error` 可能跳过 C++ 析构。 + +LuaGlue 使用 [MIT 许可证](LICENSE)。 diff --git a/LuaGlue/cmake/FindLua.cmake b/LuaGlue/cmake/FindLua.cmake new file mode 100644 index 0000000..5b112ca --- /dev/null +++ b/LuaGlue/cmake/FindLua.cmake @@ -0,0 +1,53 @@ +# Lua 5.5 uses numeric *_N version macros that older CMake finders cannot read. +# Keep discovery local to LuaGlue; existing host targets bypass this module. +find_path(LUA_INCLUDE_DIR NAMES lua.h + HINTS ${Lua_ROOT} ${LUA_ROOT} ENV Lua_ROOT ENV LUA_ROOT ENV LUA_DIR + PATH_SUFFIXES include/lua5.5 include/lua55 include/lua-5.5 include/lua + include src lua5.5 lua55 lua-5.5 lua) +find_library(LUA_LIBRARY NAMES lua5.5 lua55 lua-5.5 lua + HINTS ${Lua_ROOT} ${LUA_ROOT} ENV Lua_ROOT ENV LUA_ROOT ENV LUA_DIR + PATH_SUFFIXES lib lib64) + +unset(LUA_VERSION_STRING) +set(_lua_headers_compatible FALSE) +if(EXISTS "${LUA_INCLUDE_DIR}/lua.h") + file(STRINGS "${LUA_INCLUDE_DIR}/lua.h" _lua_version_lines + REGEX "^[ \t]*#[ \t]*define[ \t]+LUA_VERSION_(MAJOR|MINOR|RELEASE)(_N)?[ \t]+") + foreach(part IN ITEMS MAJOR MINOR RELEASE) + unset(LUA_VERSION_${part}) + string(REGEX MATCH "LUA_VERSION_${part}_N[ \t]+([0-9]+)" match "${_lua_version_lines}") + if(match) + set(LUA_VERSION_${part} "${CMAKE_MATCH_1}") + else() + string(REGEX MATCH "LUA_VERSION_${part}[ \t]+\"([0-9]+)\"" match "${_lua_version_lines}") + if(match) + set(LUA_VERSION_${part} "${CMAKE_MATCH_1}") + endif() + endif() + endforeach() + if(DEFINED LUA_VERSION_MAJOR AND DEFINED LUA_VERSION_MINOR AND DEFINED LUA_VERSION_RELEASE) + set(LUA_VERSION_STRING "${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}.${LUA_VERSION_RELEASE}") + if(LUA_VERSION_MAJOR STREQUAL "5" AND LUA_VERSION_MINOR STREQUAL "5") + set(_lua_headers_compatible TRUE) + endif() + endif() +endif() +set(_lua_library_exists FALSE) +if(EXISTS "${LUA_LIBRARY}" AND NOT IS_DIRECTORY "${LUA_LIBRARY}") + set(_lua_library_exists TRUE) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Lua + REQUIRED_VARS LUA_INCLUDE_DIR LUA_LIBRARY _lua_headers_compatible _lua_library_exists + VERSION_VAR LUA_VERSION_STRING + REASON_FAILURE_MESSAGE "LuaGlue requires Lua 5.5 headers and a matching library. Set LUA_INCLUDE_DIR and LUA_LIBRARY, or add the Lua installation to CMAKE_PREFIX_PATH.") + +if(Lua_FOUND) + set(LUA_INCLUDE_DIRS "${LUA_INCLUDE_DIR}") + set(LUA_LIBRARIES "${LUA_LIBRARY}") + if(UNIX AND NOT APPLE) + list(APPEND LUA_LIBRARIES m ${CMAKE_DL_LIBS}) + endif() +endif() +mark_as_advanced(LUA_INCLUDE_DIR LUA_LIBRARY) diff --git a/LuaGlue/cmake/LuaGlueLua.cmake b/LuaGlue/cmake/LuaGlueLua.cmake new file mode 100644 index 0000000..6186568 --- /dev/null +++ b/LuaGlue/cmake/LuaGlueLua.cmake @@ -0,0 +1,14 @@ +set(LUAGLUE_LUA_TARGET "" CACHE STRING "Existing Lua 5.5 CMake target") +if(NOT LUAGLUE_LUA_TARGET) + set(_luaglue_saved_module_path "${CMAKE_MODULE_PATH}") + list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + find_package(Lua 5.5 EXACT REQUIRED MODULE) + set(CMAKE_MODULE_PATH "${_luaglue_saved_module_path}") + unset(_luaglue_saved_module_path) + add_library(LuaGlueLua INTERFACE) + target_include_directories(LuaGlueLua INTERFACE "${LUA_INCLUDE_DIR}") + target_link_libraries(LuaGlueLua INTERFACE ${LUA_LIBRARIES}) + set(LUAGLUE_LUA_TARGET LuaGlueLua) +elseif(NOT TARGET "${LUAGLUE_LUA_TARGET}") + message(FATAL_ERROR "LUAGLUE_LUA_TARGET must name an existing Lua 5.5 target") +endif() diff --git a/LuaGlue/include/LuaGlue/Api.hpp b/LuaGlue/include/LuaGlue/Api.hpp new file mode 100644 index 0000000..b72eb6b --- /dev/null +++ b/LuaGlue/include/LuaGlue/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#if defined(_WIN32) && !defined(LUAGLUE_STATIC) +#if defined(LUAGLUE_BUILD) +#define LUAGLUE_API __declspec(dllexport) +#else +#define LUAGLUE_API __declspec(dllimport) +#endif +#elif defined(__GNUC__) || defined(__clang__) +#define LUAGLUE_API __attribute__((visibility("default"))) +#else +#define LUAGLUE_API +#endif diff --git a/LuaGlue/include/LuaGlue/Binding.hpp b/LuaGlue/include/LuaGlue/Binding.hpp new file mode 100644 index 0000000..ae6fe33 --- /dev/null +++ b/LuaGlue/include/LuaGlue/Binding.hpp @@ -0,0 +1,555 @@ +#pragma once + +#include "Codec.hpp" + +#include +#include + +namespace lua_glue { + +enum class ReturnPolicy { + Automatic, + Copy, + Move, + Reference, + ReferenceInternal +}; +struct BindingOptions { + ReturnPolicy return_policy = ReturnPolicy::Automatic; + std::vector> keep_alive; +}; + +template +struct DefaultFactory { + F function; +}; +template +DefaultFactory(F) -> DefaultFactory; +template +struct Defaults { + std::tuple values; + explicit Defaults(T... entries) : values(std::move(entries)...) {} +}; +template +Defaults(T...) -> Defaults; + +namespace detail { + +struct ParameterRecord { + std::string typeName; + bool (*check)(lua_State*, int) = nullptr; + int (*rank)(lua_State*, int) = nullptr; +}; +struct DefaultRecord { + std::shared_ptr storage; + int (*push)(lua_State*, const void*) = nullptr; +}; +struct FunctionRecord { + std::string name; + std::string signature; + std::string doc; + std::shared_ptr storage; + int (*invoke)(lua_State*, void*, ReturnPolicy) = nullptr; + std::vector parameters; + std::vector defaults; + BindingOptions options; + bool variadic = false; + int cachedReference = LUA_NOREF; +}; + +LUAGLUE_API void RegisterFunction(const Table&, std::string_view, + std::shared_ptr); +LUAGLUE_API void RegisterProperty(const Table&, std::string_view, + std::shared_ptr, + std::shared_ptr, + bool isStatic); +LUAGLUE_API void RegisterMetamethod(const Table&, std::string_view, + std::shared_ptr); +LUAGLUE_API int PushFunctionValue(lua_State*, std::shared_ptr); + +template +inline constexpr bool Injected = + std::is_same_v, ThisState> || + std::is_same_v, StateView> || + std::is_same_v, Arguments>; + +template +struct CallableSignature : CallableSignature {}; +template +struct CallableSignature { + using Return = R; + using ArgumentsTuple = std::tuple; +}; +template +struct CallableSignature + : CallableSignature {}; +template +struct CallableSignature : CallableSignature {}; +template +struct CallableSignature + : CallableSignature {}; +template +struct CallableSignature + : CallableSignature {}; +template +struct CallableSignature + : CallableSignature {}; + +template +struct ArgumentStorage { + using U = std::remove_cvref_t; + static constexpr bool reference = + std::is_lvalue_reference_v && Codec::native; + using Value = std::conditional_t< + reference, std::reference_wrapper>, U>; + Value value; + ArgumentStorage(lua_State* state, int index) : value(load(state, index)) {} + static Value load(lua_State* state, int index) { + if constexpr (reference) { + return std::ref(lua_glue::Read(state, index)); + } else { + return lua_glue::Read(state, index); + } + } + decltype(auto) get() { + if constexpr (reference) { + return value.get(); + } else if constexpr (std::is_lvalue_reference_v) { + return (value); + } else { + return std::move(value); + } + } +}; +template <> +struct ArgumentStorage { + ThisState value; + ArgumentStorage(lua_State* state, int) : value(state) {} + ThisState get() const { + return value; + } +}; +template <> +struct ArgumentStorage { + StateView value; + ArgumentStorage(lua_State* state, int) : value(state) {} + StateView get() const { + return value; + } +}; +template <> +struct ArgumentStorage { + Arguments value; + ArgumentStorage(lua_State* state, int first) : value(state, first) {} + Arguments get() const { + return value; + } +}; +template <> +struct ArgumentStorage : ArgumentStorage { + using ArgumentStorage::ArgumentStorage; +}; +template <> +struct ArgumentStorage : ArgumentStorage { + using ArgumentStorage::ArgumentStorage; +}; + +template +constexpr int ArgumentIndex() { + return [](std::index_sequence) { + return 1 + + (0 + ... + (Injected> ? 0 : 1)); + }(std::make_index_sequence{}); +} + +template +int PushReturn(lua_State* state, R&& value, ReturnPolicy policy) { + using U = std::remove_cvref_t; + if constexpr (IsTuple::value) { + int count = 0; + std::apply( + [&](auto&&... entries) { + ((count += lua_glue::Push( + state, std::forward(entries))), + ...); + }, + std::forward(value)); + return count; + } else if constexpr (IsReference::value) { + return PushReturn(state, value.get(), policy); + } else if constexpr (std::is_reference_v && Codec::native) { + if (policy == ReturnPolicy::Copy) { + if constexpr (std::is_copy_constructible_v) { + return Codec::Push(state, value); + } else { + throw std::runtime_error( + "copy return policy requires a copyable type"); + } + } + if (policy == ReturnPolicy::Move) { + if constexpr (std::is_constructible_v) { + return Codec::Push(state, std::move(value)); + } else { + throw std::runtime_error( + "move return policy requires a movable type"); + } + } + return lua_glue::Push(state, std::ref(value)); + } else if constexpr (std::is_pointer_v && + !std::is_function_v> && + !std::is_void_v> && + !std::is_same_v< + std::remove_cv_t>, + char>) { + using Element = std::remove_cv_t>; + if (value && policy == ReturnPolicy::Copy) { + if constexpr (std::is_copy_constructible_v) { + return Codec::Push(state, *value); + } else { + throw std::runtime_error( + "copy return policy requires a copyable type"); + } + } + if (value && policy == ReturnPolicy::Move) { + if constexpr (std::is_constructible_v) { + return Codec::Push(state, std::move(*value)); + } else { + throw std::runtime_error( + "move return policy requires a movable type"); + } + } + return lua_glue::Push(state, value); + } else { + return lua_glue::Push(state, std::forward(value)); + } +} + +template +int InvokeCallable(lua_State* state, F& function, ReturnPolicy policy, + std::index_sequence) { + std::tuple>...> arguments{ + ArgumentStorage>( + state, ArgumentIndex())...}; + if constexpr (std::is_void_v) { + std::invoke(function, std::get(arguments).get()...); + return 0; + } else { + decltype(auto) result = + std::invoke(function, std::get(arguments).get()...); + return PushReturn(state, std::forward(result), policy); + } +} + +template +void AddParameter(FunctionRecord& record) { + using U = std::remove_cvref_t; + if constexpr (std::is_same_v) { + record.variadic = true; + } else if constexpr (!Injected) { + std::string name; + if constexpr (std::is_const_v>) { + name += "const "; + } + name += TypeName(); + if constexpr (std::is_lvalue_reference_v) { + name += '&'; + } + if constexpr (std::is_rvalue_reference_v) { + name += "&&"; + } + record.parameters.push_back( + {std::move(name), + [](lua_State* state, int index) { + return lua_glue::Check(state, index); + }, + [](lua_State* state, int index) -> int { + if constexpr (std::is_same_v) { + return 100; + } else if constexpr (std::is_same_v) { + return 20; + } else if constexpr (std::is_integral_v || + std::is_enum_v) { + return lua_isinteger(state, index) ? 0 : 2; + } else if constexpr (std::is_floating_point_v) { + return lua_isinteger(state, index) ? 1 : 0; + } else { + return 0; + } + }}); + } +} + +inline void AddOption(FunctionRecord& record, ReturnPolicy policy) { + record.options.return_policy = policy; +} +inline void AddOption(FunctionRecord& record, BindingOptions options) { + record.options = std::move(options); +} +inline void AddOption(FunctionRecord& record, std::string_view doc) { + record.doc = doc; +} +inline void AddOption(FunctionRecord& record, const char* doc) { + if (doc) { + record.doc = doc; + } +} +template +struct IsDefaultFactory : std::false_type {}; +template +struct IsDefaultFactory> : std::true_type {}; +template +void AddDefault(FunctionRecord& record, V value) { + auto storage = std::make_shared(std::move(value)); + record.defaults.push_back( + {std::move(storage), [](lua_State* state, const void* raw) { + const auto& value = *static_cast(raw); + if constexpr (IsDefaultFactory::value) { + return lua_glue::Push(state, value.function()); + } else { + return lua_glue::Push(state, value); + } + }}); +} +template +void AddOption(FunctionRecord& record, Defaults values) { + std::apply( + [&](auto&&... entries) { + (AddDefault(record, std::move(entries)), ...); + }, + std::move(values.values)); + if (record.defaults.size() > record.parameters.size()) { + throw std::invalid_argument( + "too many default values for binding signature"); + } +} + +template +std::shared_ptr MakeFunction(F&& function, + Options&&... options) { + using Callable = std::decay_t; + using Signature = CallableSignature; + using R = typename Signature::Return; + using Tuple = typename Signature::ArgumentsTuple; + auto record = std::make_shared(); + record->storage = std::make_shared(std::forward(function)); + record->invoke = [](lua_State* state, void* raw, ReturnPolicy policy) { + return InvokeCallable( + state, *static_cast(raw), policy, + std::make_index_sequence>{}); + }; + [&](std::index_sequence) { + (AddParameter>(*record), ...); + }(std::make_index_sequence>{}); + record->signature = "("; + for (const auto& parameter : record->parameters) { + if (record->signature.size() > 1) { + record->signature += ", "; + } + record->signature += parameter.typeName; + } + if (record->variadic) { + record->signature += ", ..."; + } + record->signature += ") -> "; + record->signature += TypeName(); + (AddOption(*record, std::forward(options)), ...); + return record; +} + +template +int PushCallable(lua_State* state, F&& function) { + return PushFunctionValue(state, MakeFunction(std::forward(function))); +} + +} // namespace detail + +template +void BindCallable(const Table& table, std::string_view name, F&& function, + Options&&... options) { + detail::RegisterFunction( + table, name, + detail::MakeFunction(std::forward(function), + std::forward(options)...)); +} + +template +void BindFunction(const Table& table, std::string_view name, F&& function, + Options&&... options) { + BindCallable( + table, name, + [callable = std::forward(function)](A... args) mutable -> R { + return std::invoke(callable, std::forward(args)...); + }, + std::forward(options)...); +} + +template +void BindMethod(const Class& type, std::string_view name, + R (C::*method)(A...), Options&&... options) { + BindCallable( + type, name, + [method](C& self, A... args) -> R { + return std::invoke(method, self, std::forward(args)...); + }, + std::forward(options)...); +} +template +void BindMethod(const Class& type, std::string_view name, + R (C::*method)(A...) const, Options&&... options) { + BindCallable( + type, name, + [method](const C& self, A... args) -> R { + return std::invoke(method, self, std::forward(args)...); + }, + std::forward(options)...); +} +template + requires(!std::is_member_function_pointer_v>) +void BindMethod(const Class& type, std::string_view name, F&& function, + Options&&... options) { + BindCallable(type, name, std::forward(function), + std::forward(options)...); +} + +template +void BindConstructor(const Class& type, Options&&... options) { + BindCallable( + type, "new", + [](A... args) { + return std::make_shared(std::forward(args)...); + }, + std::forward(options)...); +} +template +void BindFactory(const Class& type, F&& factory, Options&&... options) { + BindCallable(type, "new", std::forward(factory), + std::forward(options)...); +} + +template +void BindProperty(const Table& type, std::string_view name, Getter&& getter) { + detail::RegisterProperty( + type, name, + detail::MakeFunction(std::forward(getter), + ReturnPolicy::ReferenceInternal), + {}, false); +} +template +void BindProperty(const Table& type, std::string_view name, Getter&& getter, + Setter&& setter) { + detail::RegisterProperty( + type, name, + detail::MakeFunction(std::forward(getter), + ReturnPolicy::ReferenceInternal), + detail::MakeFunction(std::forward(setter)), false); +} + +template +void BindStaticProperty(const Table& type, std::string_view name, + Getter&& getter) { + detail::RegisterProperty(type, name, + detail::MakeFunction(std::forward(getter)), + {}, true); +} +template +void BindStaticProperty(const Table& type, std::string_view name, + Getter&& getter, Setter&& setter) { + detail::RegisterProperty( + type, name, detail::MakeFunction(std::forward(getter)), + detail::MakeFunction(std::forward(setter)), true); +} + +template +void BindAttr(const Class& type, std::string_view name, Field C::* member) { + auto getter = [member](ThisState state, Object object) -> Object { + StackGuard stack(state.value); + object.push(state.value); + auto* self = static_cast(NativePointer(state, -1, TypeName())); + if (!self) { + throw std::runtime_error("invalid property receiver"); + } + using Value = std::remove_const_t; + if constexpr (Codec::native) { + if (NativeIsConst(state, -1)) { + return MakeObject(state.value, std::cref(self->*member)); + } + return MakeObject(state.value, std::ref(self->*member)); + } else { + return MakeObject(state.value, self->*member); + } + }; + if constexpr (std::is_const_v) { + BindProperty(type, name, std::move(getter)); + } else { + BindProperty(type, name, std::move(getter), + [member](C& self, Field value) { + self.*member = std::move(value); + }); + } +} + +template +void BindStaticAttr(const Table& type, std::string_view name, Field* member) { + using Value = std::remove_const_t; + auto getter = detail::MakeFunction([member]() -> decltype(auto) { + if constexpr (Codec::native) { + return std::ref(*member); + } else { + return Value(*member); + } + }); + std::shared_ptr setter; + if constexpr (!std::is_const_v) { + setter = detail::MakeFunction([member](Value value) { + *member = std::move(value); + }); + } + detail::RegisterProperty(type, name, std::move(getter), std::move(setter), + true); +} + +template +void BindMetamethod(const Table& type, std::string_view name, F&& function, + Options&&... options) { + detail::RegisterMetamethod( + type, name, + detail::MakeFunction(std::forward(function), + std::forward(options)...)); +} + +template +Class BindStruct(const Table& module, std::string_view name) { + static_assert(StructTraits::enabled, + "BindStruct requires a verified complete independent-value " + "or custom deep-copy policy"); + auto type = + Class(detail::RegisterClass(module, name, TypeName(), true)); + BindCallable(type, "copy", [](const T& value) { + return T(value); + }); + BindCallable(type, "__copy", [](const T& value) { + return T(value); + }); + BindCallable(type, "deepcopy", [](const T& value) { + return StructTraits::DeepCopy(value); + }); + BindCallable(type, "__deepcopy", [](const T& value) { + return StructTraits::DeepCopy(value); + }); + return type; +} + +template +Table BindEnum(const Table& module, std::string_view name, + std::initializer_list> values) { + Table result = module.get_or_create(name); + for (const auto& [key, value] : values) { + result.raw_set(key, value); + } + return result; +} + +} // namespace lua_glue diff --git a/LuaGlue/include/LuaGlue/Codec.hpp b/LuaGlue/include/LuaGlue/Codec.hpp new file mode 100644 index 0000000..52488b9 --- /dev/null +++ b/LuaGlue/include/LuaGlue/Codec.hpp @@ -0,0 +1,667 @@ +#pragma once + +#include "Native.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lua_glue { + +namespace detail { +template +int PushCallable(lua_State*, F&&); +LUAGLUE_API int PushString(lua_State*, std::string_view); +template +struct IsReference : std::false_type {}; +template +struct IsReference> : std::true_type {}; +template +struct IsShared : std::false_type {}; +template +struct IsShared> : std::true_type {}; +template +struct IsUnique : std::false_type {}; +template +struct IsUnique> : std::true_type {}; +template +struct IsTuple : std::false_type {}; +template +struct IsTuple> : std::true_type {}; +template +struct IsTuple> : std::true_type {}; +template +inline constexpr bool IsCallable = requires { + &T::operator(); +} || (std::is_pointer_v && std::is_function_v>); +} // namespace detail + +template +struct Codec { + static constexpr bool native = true; + static bool Check(lua_State* state, int index) { + return NativePointer(state, index, TypeName()) != nullptr; + } + static T Read(lua_State* state, int index) { + auto* pointer = + static_cast(NativePointer(state, index, TypeName())); + if (!pointer) { + throw std::runtime_error("expected " + std::string(TypeName())); + } + return T(*pointer); + } + template + static int Push(lua_State* state, V&& value) { + auto owner = std::make_shared(std::forward(value)); + T* pointer = owner.get(); + return detail::PushNative(state, TypeName(), pointer, + std::move(owner), false); + } +}; + +template +struct Codec< + T, std::enable_if_t && !std::is_same_v>> { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + if (lua_type(state, index) != LUA_TNUMBER) { + return false; + } + if (lua_isinteger(state, index)) { + const lua_Integer value = lua_tointeger(state, index); + if constexpr (std::is_signed_v) { + if constexpr (std::numeric_limits::digits >= + std::numeric_limits::digits) { + return true; + } + return value >= static_cast( + std::numeric_limits::min()) && + value <= static_cast( + std::numeric_limits::max()); + } else { + if (value < 0) { + return false; + } + return static_cast(value) <= + std::numeric_limits::max(); + } + } + const lua_Number value = lua_tonumber(state, index); + if (!std::isfinite(value) || std::trunc(value) != value) { + return false; + } + const lua_Number limit = + std::ldexp(lua_Number{1}, std::numeric_limits::digits); + if constexpr (std::is_signed_v) { + return value >= -limit && value < limit; + } else { + return value >= 0 && value < limit; + } + } + static T Read(lua_State* state, int index) { + if (!Check(state, index)) { + throw std::runtime_error("expected a finite, in-range integer"); + } + return lua_isinteger(state, index) + ? static_cast(lua_tointeger(state, index)) + : static_cast(lua_tonumber(state, index)); + } + static int Push(lua_State* state, T value) { + if constexpr (std::is_unsigned_v && + sizeof(T) >= sizeof(lua_Integer)) { + if (value > + static_cast(std::numeric_limits::max())) { + lua_pushnumber(state, static_cast(value)); + return 1; + } + } + lua_pushinteger(state, static_cast(value)); + return 1; + } +}; + +template <> +struct Codec { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return lua_type(state, index) == LUA_TBOOLEAN; + } + static bool Read(lua_State* state, int index) { + if (!Check(state, index)) { + throw std::runtime_error("expected boolean"); + } + return lua_toboolean(state, index) != 0; + } + static int Push(lua_State* state, bool value) { + lua_pushboolean(state, value); + return 1; + } +}; + +template +struct Codec>> { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return lua_type(state, index) == LUA_TNUMBER; + } + static T Read(lua_State* state, int index) { + if (!Check(state, index)) { + throw std::runtime_error("expected number"); + } + return static_cast(lua_tonumber(state, index)); + } + static int Push(lua_State* state, T value) { + lua_pushnumber(state, static_cast(value)); + return 1; + } +}; + +template +struct Codec>> { + static constexpr bool native = false; + using Base = Codec>; + static bool Check(lua_State* state, int index) { + return Base::Check(state, index); + } + static T Read(lua_State* state, int index) { + return static_cast(Base::Read(state, index)); + } + static int Push(lua_State* state, T value) { + return Base::Push(state, static_cast>(value)); + } +}; + +template <> +struct Codec { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return lua_type(state, index) == LUA_TSTRING; + } + static std::string Read(lua_State* state, int index) { + if (!Check(state, index)) { + throw std::runtime_error("expected string"); + } + std::size_t size = 0; + const char* value = lua_tolstring(state, index, &size); + return {value, size}; + } + static int Push(lua_State* state, std::string_view value) { + return detail::PushString(state, value); + } +}; + +template <> +struct Codec { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return Codec::Check(state, index); + } + static std::string_view Read(lua_State* state, int index) { + if (!Check(state, index)) { + throw std::runtime_error("expected string"); + } + std::size_t size = 0; + const char* value = lua_tolstring(state, index, &size); + return {value, size}; + } + static int Push(lua_State* state, std::string_view value) { + return detail::PushString(state, value); + } +}; + +template <> +struct Codec { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return lua_isnoneornil(state, index); + } + static Nil Read(lua_State* state, int index) { + if (!Check(state, index)) { + throw std::runtime_error("expected nil"); + } + return {}; + } + static int Push(lua_State* state, Nil) { + lua_pushnil(state); + return 1; + } +}; +template <> +struct Codec : Codec {}; +template <> +struct Codec { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return Codec::Check(state, index); + } + static std::monostate Read(lua_State* state, int index) { + Codec::Read(state, index); + return {}; + } + static int Push(lua_State* state, std::monostate) { + lua_pushnil(state); + return 1; + } +}; + +template +struct Codec>> { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + if constexpr (std::is_base_of_v) { + return lua_istable(state, index); + } + if constexpr (std::is_base_of_v) { + return lua_isfunction(state, index); + } + return lua_type(state, index) != LUA_TNONE; + } + static T Read(lua_State* state, int index) { + return T(state, index); + } + static int Push(lua_State* state, const T& value) { + return value.push(state); + } +}; + +template <> +struct Codec { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return lua_islightuserdata(state, index); + } + static LightUserdata Read(lua_State* state, int index) { + if (!Check(state, index)) { + throw std::runtime_error("expected light userdata"); + } + return LightUserdata(lua_touserdata(state, index)); + } + static int Push(lua_State* state, LightUserdata value) { + lua_pushlightuserdata(state, value.value); + return 1; + } +}; + +template +struct Codec { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + if (lua_isnil(state, index)) { + return true; + } + if constexpr (std::is_void_v>) { + return lua_isuserdata(state, index); + } else { + return NativePointer(state, index, TypeName()) && + (std::is_const_v || !NativeIsConst(state, index)); + } + } + static T* Read(lua_State* state, int index) { + if (!Check(state, index)) { + throw std::runtime_error("expected " + std::string(TypeName()) + + " pointer"); + } + if (lua_isnil(state, index)) { + return nullptr; + } + if constexpr (std::is_void_v>) { + return static_cast(lua_touserdata(state, index)); + } else { + return static_cast(NativePointer(state, index, TypeName())); + } + } + static int Push(lua_State* state, T* value) { + if constexpr (std::is_void_v>) { + if (value) { + lua_pushlightuserdata( + state, const_cast(static_cast(value))); + } else { + lua_pushnil(state); + } + return 1; + } else { + return detail::PushNative( + state, TypeName(), + const_cast*>(value), {}, + std::is_const_v); + } + } +}; + +template <> +struct Codec { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return lua_isnil(state, index) || lua_type(state, index) == LUA_TSTRING; + } + static const char* Read(lua_State* state, int index) { + if (!Check(state, index)) { + throw std::runtime_error("expected string or nil"); + } + return lua_tostring(state, index); + } + static int Push(lua_State* state, const char* value) { + if (!value) { + lua_pushnil(state); + return 1; + } + return detail::PushString(state, value); + } +}; + +template +struct Codec> { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return lua_isnil(state, index) || + (Codec::Check(state, index) && + NativeSharedOwner(state, index, TypeName()).use_count() != + 0); + } + static std::shared_ptr Read(lua_State* state, int index) { + if (lua_isnil(state, index)) { + return {}; + } + auto owner = NativeSharedOwner(state, index, TypeName()); + T* pointer = Codec::Read(state, index); + if (!owner.use_count()) { + throw std::runtime_error("native value has no shared ownership"); + } + return {std::move(owner), pointer}; + } + static int Push(lua_State* state, const std::shared_ptr& value) { + std::shared_ptr owner( + value, const_cast*>(value.get())); + return detail::PushNative( + state, TypeName(), + const_cast*>(value.get()), std::move(owner), + std::is_const_v); + } +}; + +template +struct Codec> { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return lua_isnil(state, index) || lua_glue::Check(state, index); + } + static std::optional Read(lua_State* state, int index) { + if (lua_isnil(state, index)) { + return {}; + } + return lua_glue::Read(state, index); + } + static int Push(lua_State* state, const std::optional& value) { + if (!value) { + lua_pushnil(state); + return 1; + } + return lua_glue::Push(state, *value); + } +}; + +template +struct Codec> { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + if (!lua_istable(state, index)) { + return false; + } + Table table(state, index); + const auto size = table.size(); + for (std::size_t i = 1; i <= size; ++i) { + if (!table.raw_get(i).template is()) { + return false; + } + } + return true; + } + static std::vector Read(lua_State* state, int index) { + Table table(state, index); + std::vector result; + const auto size = table.size(); + result.reserve(size); + for (std::size_t i = 1; i <= size; ++i) { + result.push_back(table.raw_get(i)); + } + return result; + } + static int Push(lua_State* state, const std::vector& value) { + Table table = + StateView(state).create_table(static_cast(value.size()), 0); + for (std::size_t i = 0; i < value.size(); ++i) { + table.raw_set(i + 1, static_cast(value[i])); + } + return table.push(state); + } +}; + +template +struct Codec> { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return lua_istable(state, index) && lua_rawlen(state, index) == N && + Codec>::Check(state, index); + } + static std::array Read(lua_State* state, int index) { + if (!Check(state, index)) { + throw std::runtime_error("expected fixed-size array"); + } + Table table(state, index); + return [&](std::index_sequence) { + return std::array{table.raw_get(I + 1)...}; + }(std::make_index_sequence{}); + } + static int Push(lua_State* state, const std::array& value) { + Table table = StateView(state).create_table(static_cast(N), 0); + for (std::size_t i = 0; i < N; ++i) { + table.raw_set(i + 1, value[i]); + } + return table.push(state); + } +}; + +namespace detail { +template +struct MapCodec { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + if (!lua_istable(state, index)) { + return false; + } + for (const auto& [key, value] : Table(state, index)) { + if (!key.template is() || + !value.template is()) { + return false; + } + } + return true; + } + static Map Read(lua_State* state, int index) { + Map result; + for (const auto& [key, value] : Table(state, index)) { + result.emplace(key.template as(), + value.template as()); + } + return result; + } + static int Push(lua_State* state, const Map& value) { + Table table = + StateView(state).create_table(0, static_cast(value.size())); + for (const auto& [key, entry] : value) { + table.raw_set(key, entry); + } + return table.push(state); + } +}; +} // namespace detail +template +struct Codec> : detail::MapCodec> {}; +template +struct Codec> + : detail::MapCodec> {}; + +template +struct Codec> { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return (lua_glue::Check(state, index) || ...); + } + template + static std::variant Read(lua_State* state, int index) { + if constexpr (I == sizeof...(T)) { + throw std::runtime_error("no matching variant alternative"); + } else { + using U = std::variant_alternative_t>; + if (lua_glue::Check(state, index)) { + return std::variant(std::in_place_index, + lua_glue::Read(state, index)); + } + return Read(state, index); + } + } + static int Push(lua_State* state, const std::variant& value) { + return std::visit( + [state](const auto& entry) { + return lua_glue::Push(state, entry); + }, + value); + } +}; + +template +struct Codec::value>> { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + if (!lua_istable(state, index)) { + return false; + } + Table table(state, index); + return [&](std::index_sequence) { + return (table.raw_get(I + 1) + .template is>() && + ...); + }(std::make_index_sequence>{}); + } + static T Read(lua_State* state, int index) { + Table table(state, index); + return [&](std::index_sequence) { + return T{table.raw_get>(I + 1)...}; + }(std::make_index_sequence>{}); + } + static int Push(lua_State* state, const T& value) { + Table table = StateView(state).create_table(std::tuple_size_v, 1); + [&](std::index_sequence) { + (table.raw_set(I + 1, std::get(value)), ...); + }(std::make_index_sequence>{}); + table.raw_set("n", std::tuple_size_v); + return table.push(state); + } +}; + +template +struct Codec> { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return lua_isfunction(state, index); + } + static std::function Read(lua_State* state, int index) { + Function function(state, index); + return [function = std::move(function)](A... args) -> R { + CallResult result = function(std::forward(args)...); + if (!result.valid()) { + throw std::runtime_error(result.error()); + } + if constexpr (!std::is_void_v) { + return result.template get(); + } + }; + } + static int Push(lua_State* state, const std::function& value) { + return detail::PushCallable(state, value); + } +}; + +template +bool Check(lua_State* state, int index) { + using U = std::remove_cv_t>; + if constexpr (std::is_reference_v && Codec::native) { + return Codec::Check(state, index) && + (std::is_const_v> || + !NativeIsConst(state, index)); + } else { + return Codec::Check(state, index); + } +} + +template +T Read(lua_State* state, int index) { + using U = std::remove_cv_t>; + if constexpr (std::is_reference_v && Codec::native) { + if (!Check(state, index)) { + throw std::runtime_error("expected " + std::string(TypeName())); + } + return *static_cast*>( + NativePointer(state, index, TypeName())); + } else { + return Codec::Read(state, index); + } +} + +template +int Push(lua_State* state, T&& value) { + using U = std::remove_cv_t>; + if constexpr (std::is_same_v, lua_CFunction>) { + lua_pushcfunction(state, static_cast(value)); + return 1; + } else if constexpr (std::is_function_v) { + return detail::PushCallable(state, &value); + } else if constexpr (std::is_same_v) { + return value.object().push(state); + } else if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + return value.push(state); + } else if constexpr (std::is_same_v) { + lua_pushnil(state); + return 1; + } else if constexpr (std::is_array_v && + std::is_same_v< + std::remove_cv_t>, char>) { + return detail::PushString(state, std::string_view(value)); + } else if constexpr (std::is_same_v) { + return Codec::Push(state, value); + } else if constexpr (detail::IsReference::value) { + using V = std::remove_cvref_t; + if constexpr (Codec::native) { + return Codec::Push(state, &value.get()); + } else { + return lua_glue::Push(state, value.get()); + } + } else if constexpr (detail::IsUnique::value) { + using E = typename U::element_type; + std::shared_ptr owner(std::forward(value)); + return Codec>::Push(state, owner); + } else if constexpr (detail::IsCallable) { + return detail::PushCallable(state, std::forward(value)); + } else { + return Codec::Push(state, std::forward(value)); + } +} + +template +const Container& AsTable(const Container& value) { + return value; +} +template +Container AsTable(Container&& value) { + return std::forward(value); +} + +} // namespace lua_glue diff --git a/LuaGlue/include/LuaGlue/Lifecycle.hpp b/LuaGlue/include/LuaGlue/Lifecycle.hpp new file mode 100644 index 0000000..c23b946 --- /dev/null +++ b/LuaGlue/include/LuaGlue/Lifecycle.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include + +#include +#include +#include + +struct lua_State; + +namespace lua_glue { + +using StateEnterHook = int (*)(lua_State*, void*) noexcept; +using StateTryEnterHook = int (*)(lua_State*, void*) noexcept; +using StateLeaveHook = void (*)(lua_State*, void*) noexcept; +// Reports current-thread gate ownership without blocking, allocating, +// accessing Lua, or re-entering LuaGlue. The context is the execution hook's. +using StateOwnsExecutionHook = int (*)(lua_State*, void*) noexcept; + +LUAGLUE_API int InitializeState(lua_State* state); +LUAGLUE_API int EnterState(lua_State* state); +LUAGLUE_API int TryEnterState(lua_State* state) noexcept; +LUAGLUE_API void LeaveState(lua_State* state) noexcept; +LUAGLUE_API void QuiesceState(lua_State* state) noexcept; +LUAGLUE_API void ShutdownState(lua_State* state); +LUAGLUE_API int SetStateExecutionHooks(lua_State* state, StateEnterHook enter, + StateTryEnterHook tryEnter, + StateLeaveHook leave, void* context); +// Allows internal value access to reuse a currently owned execution gate. +// Replacing execution hooks clears this opt-in. +LUAGLUE_API int SetStateOwnsExecutionHook(lua_State* state, + StateOwnsExecutionHook owns); +LUAGLUE_API int TakeDeferredCallbackError(lua_State* state, char* buffer, + std::size_t capacity); + +struct RegistryReferenceState; +using StateQuiesceCallback = void (*)() noexcept; + +class LUAGLUE_API ExecutionScope { +public: + explicit ExecutionScope(lua_State* state) noexcept; + ~ExecutionScope(); + + ExecutionScope(const ExecutionScope&) = delete; + ExecutionScope& operator=(const ExecutionScope&) = delete; + + [[nodiscard]] bool active() const noexcept; + +private: + lua_State* state_{}; + bool active_{}; +}; + +class LUAGLUE_API TryExecutionScope { +public: + explicit TryExecutionScope(lua_State* state) noexcept; + ~TryExecutionScope(); + + TryExecutionScope(const TryExecutionScope&) = delete; + TryExecutionScope& operator=(const TryExecutionScope&) = delete; + + [[nodiscard]] bool active() const noexcept; + +private: + lua_State* state_{}; + bool active_{}; +}; + +class LUAGLUE_API RegistryReference { +public: + RegistryReference() noexcept = default; + RegistryReference(lua_State* state, int stackIndex); + + [[nodiscard]] lua_State* state() const noexcept; + [[nodiscard]] lua_State* originState() const noexcept; + [[nodiscard]] bool push() const; + [[nodiscard]] bool push(lua_State* target) const; + [[nodiscard]] bool pushUnderExecutionScope() const noexcept; + void deferCallbackError(std::string_view label, + std::string_view message) const noexcept; + [[nodiscard]] bool equals(const RegistryReference& other) const; + explicit operator bool() const noexcept; + +private: + std::shared_ptr reference_; +}; + +namespace detail { + +// Internal, non-escaping access within an enclosing caller's lifetime. +// Public ExecutionScope always acquires its own entry and never borrows. +class LUAGLUE_API AccessScope { +public: + explicit AccessScope(lua_State* state) noexcept; + ~AccessScope(); + AccessScope(const AccessScope&) = delete; + AccessScope& operator=(const AccessScope&) = delete; + [[nodiscard]] bool active() const noexcept; + +private: + lua_State* state_{}; + bool active_{}; + bool borrowed_{}; +}; + +LUAGLUE_API void registerLuaThreadForRegistryReference(lua_State* state); +LUAGLUE_API void retainLuaRegistryReference(const void* owner, + const RegistryReference& reference); +LUAGLUE_API void releaseLuaRegistryReference(const void* owner); +LUAGLUE_API void registerStateQuiesceCallback(lua_State* state, + const void* owner, + StateQuiesceCallback callback); +LUAGLUE_API void unregisterStateQuiesceCallback(lua_State* state, + const void* owner) noexcept; + +} // namespace detail + +} // namespace lua_glue diff --git a/LuaGlue/include/LuaGlue/LuaGlue.hpp b/LuaGlue/include/LuaGlue/LuaGlue.hpp new file mode 100644 index 0000000..dec3602 --- /dev/null +++ b/LuaGlue/include/LuaGlue/LuaGlue.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "Api.hpp" +#include "Lifecycle.hpp" +#include "Value.hpp" +#include "Native.hpp" +#include "Codec.hpp" +#include "Binding.hpp" diff --git a/LuaGlue/include/LuaGlue/Native.hpp b/LuaGlue/include/LuaGlue/Native.hpp new file mode 100644 index 0000000..d224576 --- /dev/null +++ b/LuaGlue/include/LuaGlue/Native.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "Api.hpp" +#include "Value.hpp" + +#include +#include +#include +#include +#include + +namespace lua_glue { + +template +constexpr std::string_view TypeName() noexcept { + using U = std::remove_cv_t>; + if constexpr (!std::is_same_v) { + return TypeName(); + } else { +#if defined(_MSC_VER) + constexpr std::string_view signature = __FUNCSIG__; + constexpr auto start = signature.find("TypeName<") + 9; + constexpr auto stop = signature.rfind(">(void)"); + constexpr auto name = signature.substr(start, stop - start); + if constexpr (name.starts_with("class ")) { + return name.substr(6); + } else if constexpr (name.starts_with("struct ")) { + return name.substr(7); + } else if constexpr (name.starts_with("enum ")) { + return name.substr(5); + } else { + return name; + } +#else + constexpr std::string_view signature = __PRETTY_FUNCTION__; + constexpr auto start = signature.find("T = ") + 4; + constexpr auto stop = signature.find_first_of(";]", start); + return signature.substr(start, stop - start); +#endif + } +} + +using NativeResolver = void* (*)(lua_State*, int, std::string_view); +using SharedOwnerResolver = std::shared_ptr (*)(lua_State*, int, + std::string_view); + +LUAGLUE_API void* NativePointer(lua_State*, int, + std::string_view requestedType = {}); +LUAGLUE_API std::shared_ptr NativeSharedOwner( + lua_State*, int, std::string_view requestedType = {}); +LUAGLUE_API bool NativeIsConst(lua_State*, int); +LUAGLUE_API Table NativeTypeTable(lua_State*, int); +LUAGLUE_API Table NativeTypeTable(lua_State*, std::string_view); +LUAGLUE_API void RegisterExternalResolver(lua_State*, NativeResolver, + SharedOwnerResolver); +LUAGLUE_API void RetainDependency(lua_State*, int dependent, int owner); +LUAGLUE_API void AttachSharedOwner(lua_State*, int, + const std::shared_ptr&); + +namespace detail { +struct TypeRecord; +using NativeCast = void* (*)(void*) noexcept; +LUAGLUE_API Table RegisterClass(const Table&, std::string_view luaName, + std::string_view cppName, bool valueType); +LUAGLUE_API void RegisterBase(const Table&, std::string_view derived, + std::string_view base, NativeCast); +LUAGLUE_API void RegisterCast(const Table&, std::string_view derived, + std::string_view base, NativeCast); +LUAGLUE_API int PushNative(lua_State*, std::string_view cppName, void*, + std::shared_ptr, bool isConst); +LUAGLUE_API TypeRecord* GetTypeRecord(lua_State*, int typeTable); +} // namespace detail + +template +class Class : public Table { +public: + using value_type = T; + using Table::operator=; + explicit Class(const Table& table) : Table(table) {} +}; + +template +Class BindClass(const Table& module, std::string_view name) { + return Class(detail::RegisterClass(module, name, TypeName(), false)); +} + +template +void BindBase(const Class& type) { + static_assert(std::is_base_of_v); + detail::RegisterBase( + type, TypeName(), TypeName(), + [](void* object) noexcept -> void* { + return static_cast(static_cast(object)); + }); +} + +template +void BindCast(const Class& type) { + static_assert(std::is_base_of_v); + detail::RegisterCast( + type, TypeName(), TypeName(), + [](void* object) noexcept -> void* { + return static_cast(static_cast(object)); + }); +} + +template +struct StructTraits { + static constexpr bool enabled = false; +}; + +// Opt in only after the generator or binding author has verified that every +// stored member has independent C++ value semantics, including private state. +template +struct IndependentValue { + static constexpr bool enabled = std::is_copy_constructible_v; + static T DeepCopy(const T& value) { + return T(value); + } +}; + +} // namespace lua_glue diff --git a/LuaGlue/include/LuaGlue/Value.hpp b/LuaGlue/include/LuaGlue/Value.hpp new file mode 100644 index 0000000..d448449 --- /dev/null +++ b/LuaGlue/include/LuaGlue/Value.hpp @@ -0,0 +1,507 @@ +#pragma once + +#include +#include + +extern "C" { +#include +#include +} + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lua_glue { + +template +T Read(lua_State* state, int index); +template +bool Check(lua_State* state, int index); +template +int Push(lua_State* state, T&& value); + +enum class Type : int { + None = LUA_TNONE, + Nil = LUA_TNIL, + Boolean = LUA_TBOOLEAN, + LightUserdata = LUA_TLIGHTUSERDATA, + Number = LUA_TNUMBER, + String = LUA_TSTRING, + Table = LUA_TTABLE, + Function = LUA_TFUNCTION, + Userdata = LUA_TUSERDATA, + Thread = LUA_TTHREAD +}; +LUAGLUE_API const char* TypeName(Type type) noexcept; +inline const char* TypeName(lua_State*, Type type) noexcept { + return TypeName(type); +} +namespace detail { +LUAGLUE_API void ProtectedCallOperation(lua_State* state, + lua_CFunction operation, void* context, + int resultCount = 0); +} +struct Nil {}; +inline constexpr Nil nil{}; +struct LightUserdata { + void* value{}; + explicit LightUserdata(void* pointer = nullptr) noexcept : value(pointer) {} + operator void*() const noexcept { + return value; + } +}; +struct ThisState { + lua_State* value{}; + ThisState(lua_State* state) noexcept : value(state) {} + operator lua_State*() const noexcept { + return value; + } +}; + +class Object; +class Table; +class Function; +class FieldRef; +class CallResult; +class StateView; + +class LUAGLUE_API StackGuard { +public: + explicit StackGuard(lua_State* state) noexcept; + ~StackGuard(); + StackGuard(const StackGuard&) = delete; + StackGuard& operator=(const StackGuard&) = delete; + int top() const noexcept { + return top_; + } + void dismiss() noexcept { + state_ = nullptr; + } + +private: + lua_State* state_{}; + int top_{}; +}; + +class LUAGLUE_API Object { +public: + Object() noexcept = default; + Object(Nil) noexcept {} + Object(lua_State* state, int index); + Object(const FieldRef& field); + lua_State* lua_state() const noexcept { + return reference_.originState(); + } + bool valid() const noexcept; + explicit operator bool() const noexcept { + return valid(); + } + Type get_type() const noexcept; + int push(lua_State* target = nullptr) const; + template + T as() const; + template + T get() const { + return as(); + } + template + bool is() const; + template + FieldRef operator[](Key&& key) const; + Table metatable() const; + void set_metatable(const Table& table) const; + const RegistryReference& reference() const noexcept { + return reference_; + } + friend LUAGLUE_API bool operator==(const Object& left, const Object& right); + friend bool operator==(const Object& left, Nil) noexcept { + return left.get_type() == Type::Nil || left.get_type() == Type::None; + } + friend bool operator==(Nil, const Object& right) noexcept { + return right == nil; + } + +private: + RegistryReference reference_; + // A registry reference retains a value, whose Lua type cannot change. + Type type_{Type::None}; +}; + +class LUAGLUE_API Table : public Object { +public: + Table() noexcept = default; + Table(Nil) noexcept {} + Table(lua_State* state, int index); + Table(const Object& value); + Table(Object&& value); + Table(const FieldRef& field); + template + T get(Key&& key) const; + template + T raw_get(Key&& key) const; + template + void set(Key&& key, Value&& value) const; + template + void raw_set(Key&& key, Value&& value) const; + template + T get_or_create(Key&& key) const; + template + T get_or(Key&& key, T fallback) const; + template + void add(T&& value) const; + template + void set_function(Key&& key, Callable&& callable) const; + std::size_t size() const; + bool empty() const; + class LUAGLUE_API Iterator { + public: + using value_type = std::pair; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + using pointer = const value_type*; + using reference = const value_type&; + Iterator() = default; + explicit Iterator(const Table& table); + reference operator*() const noexcept { + return value_; + } + pointer operator->() const noexcept { + return &value_; + } + Iterator& operator++(); + Iterator operator++(int) { + Iterator previous(*this); + ++*this; + return previous; + } + friend bool operator==(const Iterator& left, const Iterator& right) { + return left.done_ == right.done_ && + (left.done_ || (left.table_ == right.table_ && + left.value_.first == right.value_.first)); + } + + private: + Object table_; + value_type value_; + bool done_{true}; + }; + Iterator begin() const; + Iterator end() const noexcept { + return {}; + } +}; + +class LUAGLUE_API FieldRef { +public: + FieldRef(Object parent, Object key) + : parent_(std::move(parent)), key_(std::move(key)) {} + FieldRef(const FieldRef&) = default; + FieldRef(FieldRef&&) = default; + FieldRef& operator=(const FieldRef& value); + FieldRef& operator=(FieldRef&& value); + template + FieldRef& operator=(T&& value); + Object object() const; + template + T get() const; + template + T get_or(T fallback) const; + template + T get_or_create() const; + template + T as() const { + return get(); + } + template + bool is() const { + return object().template is(); + } + bool valid() const { + return object().valid(); + } + Type get_type() const { + return object().get_type(); + } + template + operator T() const { + return get(); + } + template + FieldRef operator[](Key&& key) const; + lua_State* lua_state() const noexcept { + return parent_.lua_state(); + } + template + CallResult operator()(Args&&... args) const; + template + CallResult call(Args&&... args) const; + +private: + Object parent_; + Object key_; +}; + +class LUAGLUE_API StackValue { +public: + StackValue() noexcept = default; + StackValue(lua_State* state, int index) noexcept + : state_(state), index_(lua_absindex(state, index)) {} + lua_State* lua_state() const noexcept { + return state_; + } + int stack_index() const noexcept { + return index_; + } + int get_index() const noexcept { + return index_; + } + int push(lua_State* target = nullptr) const; + Type get_type() const noexcept { + return state_ ? static_cast(lua_type(state_, index_)) + : Type::None; + } + bool valid() const noexcept { + return get_type() != Type::None && get_type() != Type::Nil; + } + template + T as() const { + return Read(state_, index_); + } + template + T get() const { + return as(); + } + template + bool is() const { + return state_ && Check(state_, index_); + } + template + operator T() const { + return as(); + } + +private: + lua_State* state_{}; + int index_{}; +}; + +class LUAGLUE_API Arguments { +public: + explicit Arguments(lua_State* state, int first = 1, + int count = -1) noexcept; + lua_State* lua_state() const noexcept { + return state_; + } + std::size_t size() const noexcept { + return static_cast(count_); + } + bool empty() const noexcept { + return count_ == 0; + } + int stack_index() const noexcept { + return first_; + } + StackValue operator[](std::size_t index) const; + template + T get(std::size_t index = 0) const { + return (*this)[index].template as(); + } + Arguments slice(std::size_t offset) const; + int push(lua_State* target = nullptr) const; + class Iterator { + public: + using value_type = StackValue; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + Iterator(lua_State* state, int index) : state_(state), index_(index) {} + StackValue operator*() const { + return StackValue(state_, index_); + } + Iterator& operator++() { + ++index_; + return *this; + } + Iterator operator++(int) { + Iterator previous(*this); + ++*this; + return previous; + } + friend bool operator==(const Iterator&, const Iterator&) = default; + + private: + lua_State* state_{}; + int index_{}; + }; + Iterator begin() const { + return Iterator(state_, first_); + } + Iterator end() const { + return Iterator(state_, first_ + count_); + } + +private: + lua_State* state_{}; + int first_{1}; + int count_{}; +}; + +class LUAGLUE_API MultipleResults : public std::vector { +public: + using std::vector::vector; + int push(lua_State* target) const; +}; + +class LUAGLUE_API CallResult { +public: + CallResult() noexcept = default; + explicit CallResult(std::string error) + : error_(std::move(error)), succeeded_(false) {} + explicit CallResult(std::vector values) + : values_(std::move(values)) {} + bool valid() const noexcept { + return succeeded_; + } + explicit operator bool() const noexcept { + return valid(); + } + int return_count() const noexcept { + return static_cast(values_.size()); + } + const std::string& error() const noexcept { + return error_; + } + Type get_type(std::size_t index = 0) const noexcept { + return index < values_.size() ? values_[index].get_type() : Type::None; + } + template + T get(std::size_t index = 0) const; + template + operator T() const { + return get(); + } + const std::vector& values() const noexcept { + return values_; + } + template + CallResult call(Args&&... args) const; + template + CallResult operator()(Args&&... args) const; + +private: + std::vector values_; + std::string error_; + bool succeeded_{true}; +}; + +// Function and metamethod calls cross a Lua protected boundary. C++ callers +// receive a result or exception only after the Lua stack has been restored. +LUAGLUE_API void SetErrorHandler(lua_State* state, lua_CFunction handler); +LUAGLUE_API int ProtectedStackCall(lua_State* state, int argumentCount, + int resultCount = LUA_MULTRET); +LUAGLUE_API CallResult ProtectedCall(lua_State* state, int argumentCount, + int resultCount = LUA_MULTRET); + +class LUAGLUE_API Function : public Object { +public: + Function() noexcept = default; + Function(Nil) noexcept {} + Function(lua_State* state, int index); + Function(const Object& object); + Function(Object&& object); + Function(const FieldRef& field); + template + CallResult call(Args&&... args) const; + template + CallResult operator()(Args&&... args) const; +}; + +class LUAGLUE_API StateView { +public: + explicit StateView(lua_State* state); + StateView(ThisState state) : StateView(static_cast(state)) {} + lua_State* lua_state() const noexcept { + return state_; + } + operator lua_State*() const noexcept { + return state_; + } + Table globals() const; + Table registry() const; + Table create_table(int arraySize = 0, int mapSize = 0) const; + template + Table create_table_with(Values&&... values) const; + template + FieldRef operator[](Key&& key) const; + template + T get(Key&& key) const; + template + void set(Key&& key, Value&& value) const; + template + void set_function(Key&& key, Callable&& callable) const; + CallResult load(std::string_view source, + std::string_view label = "chunk") const; + CallResult script(std::string_view source, + std::string_view label = "chunk") const; + +private: + lua_State* state_{}; +}; + +template +Object MakeObject(lua_State* state, T&& value); +template +Object MakeObject(StateView state, T&& value); +LUAGLUE_API Table GetMetatable(const Object& value); +LUAGLUE_API void SetMetatable(const Object& value, const Table& metatable); +LUAGLUE_API Object GetIndex(const Object& value, const Object& key, + bool raw = false); +LUAGLUE_API void SetIndex(const Object& value, const Object& key, + const Object& entry, bool raw = false); + +namespace detail { +// The caller holds an execution scope and owns the argument stack slots. +// Reads append one result; writes leave the existing slots untouched. +LUAGLUE_API void PushIndex(const Object& value, int keyIndex, bool raw); +LUAGLUE_API void AssignIndex(const Object& value, int keyIndex, int entryIndex, + bool raw); +} // namespace detail + +class LUAGLUE_API PushGuard { +public: + explicit PushGuard(const Object& value); + ~PushGuard(); + PushGuard(const PushGuard&) = delete; + PushGuard& operator=(const PushGuard&) = delete; + int index() const noexcept { + return index_; + } + int get_index() const noexcept { + return index_; + } + +private: + lua_State* state_{}; + int index_{}; +}; +class LUAGLUE_API PopGuard { +public: + PopGuard(lua_State* state, int count) noexcept + : state_(state), count_(count) {} + ~PopGuard(); + PopGuard(const PopGuard&) = delete; + PopGuard& operator=(const PopGuard&) = delete; + +private: + lua_State* state_{}; + int count_{}; +}; + +} // namespace lua_glue + +#include diff --git a/LuaGlue/include/LuaGlue/Value.inl b/LuaGlue/include/LuaGlue/Value.inl new file mode 100644 index 0000000..6a36df2 --- /dev/null +++ b/LuaGlue/include/LuaGlue/Value.inl @@ -0,0 +1,308 @@ +#pragma once + +namespace lua_glue { + +template +T Object::as() const { + lua_State* state = lua_state(); + if (state == nullptr) { + if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) { + return T{}; + } else { + throw std::runtime_error( + "Cannot read an empty or closed Lua value"); + } + } + if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) { + return T(*this); + } else { + detail::AccessScope execution(state); + if (!execution.active()) { + throw std::runtime_error("Lua state is stopping"); + } + StackGuard stack(state); + push(state); + return Read(state, -1); + } +} + +template +bool Object::is() const { + if constexpr (std::is_same_v) { + return get_type() != Type::None; + } else if constexpr (std::is_same_v) { + return get_type() == Type::Table; + } else if constexpr (std::is_same_v) { + return get_type() == Type::Function; + } + lua_State* state = lua_state(); + if (state == nullptr) { + return false; + } + detail::AccessScope execution(state); + if (!execution.active()) { + return false; + } + StackGuard stack(state); + push(state); + return Check(state, -1); +} + +template +Object MakeObject(lua_State* state, T&& value) { + if (state == nullptr) { + throw std::invalid_argument( + "Cannot create a value without a Lua state"); + } + if (InitializeState(state) != 0) { + throw std::runtime_error("Lua state is stopping"); + } + detail::AccessScope execution(state); + if (!execution.active()) { + throw std::runtime_error("Lua state is stopping"); + } + StackGuard stack(state); + const int count = Push(state, std::forward(value)); + if (count != 1) { + throw std::invalid_argument("A single Lua value is required"); + } + return Object(state, -1); +} + +template +Object MakeObject(StateView state, T&& value) { + return MakeObject(state.lua_state(), std::forward(value)); +} + +template +FieldRef Object::operator[](Key&& key) const { + return FieldRef(*this, MakeObject(lua_state(), std::forward(key))); +} + +namespace detail { +template +T ReadIndex(const Object& value, Key&& key, bool raw) { + lua_State* state = value.lua_state(); + detail::AccessScope execution(state); + if (state == nullptr || !execution.active()) { + throw std::runtime_error("Lua state is unavailable or stopping"); + } + StackGuard stack(state); + if (!lua_checkstack(state, 1)) { + throw std::runtime_error("Lua index stack cannot grow"); + } + if (Push(state, std::forward(key)) != 1) { + throw std::invalid_argument("A single Lua value is required"); + } + PushIndex(value, -1, raw); + return Read(state, -1); +} + +template +void WriteIndex(const Object& table, Key&& key, Value&& value, bool raw) { + lua_State* state = table.lua_state(); + detail::AccessScope execution(state); + if (state == nullptr || !execution.active()) { + throw std::runtime_error("Lua state is unavailable or stopping"); + } + StackGuard stack(state); + if (!lua_checkstack(state, 2)) { + throw std::runtime_error("Lua index stack cannot grow"); + } + if (Push(state, std::forward(key)) != 1 || + Push(state, std::forward(value)) != 1) { + throw std::invalid_argument("A single Lua value is required"); + } + AssignIndex(table, -2, -1, raw); +} +} // namespace detail + +template +T Table::get(Key&& key) const { + return detail::ReadIndex(*this, std::forward(key), false); +} + +template +T Table::raw_get(Key&& key) const { + return detail::ReadIndex(*this, std::forward(key), true); +} + +template +void Table::set(Key&& key, Value&& value) const { + detail::WriteIndex(*this, std::forward(key), + std::forward(value), false); +} + +template +void Table::raw_set(Key&& key, Value&& value) const { + detail::WriteIndex(*this, std::forward(key), + std::forward(value), true); +} + +template +T Table::get_or_create(Key&& key) const { + return (*this)[std::forward(key)].template get_or_create(); +} + +template +T Table::get_or(Key&& key, T fallback) const { + return (*this)[std::forward(key)].template get_or( + std::move(fallback)); +} + +template +void Table::add(T&& value) const { + raw_set(size() + 1, std::forward(value)); +} + +template +void Table::set_function(Key&& key, Callable&& callable) const { + set(std::forward(key), std::forward(callable)); +} + +template +FieldRef& FieldRef::operator=(T&& value) { + SetIndex(parent_, key_, MakeObject(lua_state(), std::forward(value))); + return *this; +} + +template +T FieldRef::get() const { + return object().template as(); +} + +template +T FieldRef::get_or(T fallback) const { + Object value = object(); + return value.template is() ? value.template as() + : std::move(fallback); +} + +template +T FieldRef::get_or_create() const { + Object value = object(); + if (value.valid()) { + return value.template as(); + } + static_assert(std::is_same_v, + "Only Lua tables can be created implicitly"); + Table created = StateView(lua_state()).create_table(); + SetIndex(parent_, key_, created); + return created; +} + +template +FieldRef FieldRef::operator[](Key&& key) const { + return object()[std::forward(key)]; +} + +template +CallResult FieldRef::operator()(Args&&... args) const { + return call(std::forward(args)...); +} + +template +CallResult FieldRef::call(Args&&... args) const { + return Function(object()).call(std::forward(args)...); +} + +template +T CallResult::get(std::size_t index) const { + if (!succeeded_) { + throw std::runtime_error(error_); + } + if (index >= values_.size()) { + if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) { + return T{}; + } else { + throw std::out_of_range("Lua result index out of range"); + } + } + return values_[index].template as(); +} + +template +CallResult CallResult::call(Args&&... args) const { + if (!succeeded_) { + return CallResult(error_); + } + return get().call(std::forward(args)...); +} + +template +CallResult CallResult::operator()(Args&&... args) const { + return call(std::forward(args)...); +} + +template +CallResult Function::call(Args&&... args) const { + lua_State* state = lua_state(); + if (state == nullptr) { + return CallResult("Cannot call an empty function or closed Lua state"); + } + detail::AccessScope execution(state); + if (!execution.active()) { + return CallResult("Lua state is stopping"); + } + StackGuard stack(state); + try { + push(state); + int argumentCount = 0; + ((argumentCount += Push(state, std::forward(args))), ...); + return ProtectedCall(state, argumentCount); + } catch (const std::exception& error) { + return CallResult(error.what()); + } catch (...) { + return CallResult("Unknown C++ exception while calling Lua"); + } +} + +template +CallResult Function::operator()(Args&&... args) const { + return call(std::forward(args)...); +} + +namespace detail { +inline void SetInitialTableValues(const Table&) {} +template +void SetInitialTableValues(const Table& table, Key&& key, Value&& value, + Rest&&... rest) { + table.set(std::forward(key), std::forward(value)); + SetInitialTableValues(table, std::forward(rest)...); +} +} // namespace detail + +template +Table StateView::create_table_with(Values&&... values) const { + static_assert(sizeof...(Values) % 2 == 0, + "Table initialization requires key/value pairs"); + Table table = create_table(0, sizeof...(Values) / 2); + detail::SetInitialTableValues(table, std::forward(values)...); + return table; +} + +template +FieldRef StateView::operator[](Key&& key) const { + return globals()[std::forward(key)]; +} + +template +T StateView::get(Key&& key) const { + return globals().template get(std::forward(key)); +} + +template +void StateView::set(Key&& key, Value&& value) const { + globals().set(std::forward(key), std::forward(value)); +} + +template +void StateView::set_function(Key&& key, Callable&& callable) const { + globals().set_function(std::forward(key), + std::forward(callable)); +} + +} // namespace lua_glue diff --git a/LuaGlue/src/Binding.cpp b/LuaGlue/src/Binding.cpp new file mode 100644 index 0000000..efe17da --- /dev/null +++ b/LuaGlue/src/Binding.cpp @@ -0,0 +1,416 @@ +#include "BindingImpl.hpp" +#include "NativeImpl.hpp" + +#include +#include +#include + +namespace { + +constexpr const char* overloadMetatable = "LuaGlue.Overloads.v1"; + +int overloadGc(lua_State* state) noexcept { + auto* set = static_cast( + luaL_testudata(state, 1, overloadMetatable)); + if (set) { + std::destroy_at(set); + } + return 0; +} + +bool acceptsArity(lua_State* state, + const lua_glue::detail::FunctionRecord& record) { + const int provided = lua_gettop(state); + const int full = static_cast(record.parameters.size()); + if (!record.variadic && provided > full) { + return false; + } + const int required = full - static_cast(record.defaults.size()); + if (provided >= required) { + return true; + } + // Lua supplies nil for omitted nullable arguments, including const + // optional references. Explicit binding defaults take precedence below. + if (!lua_checkstack(state, required - provided)) { + throw std::runtime_error("Lua argument stack cannot grow"); + } + lua_glue::StackGuard guard(state); + for (int index = provided; index < required; ++index) { + lua_pushnil(state); + if (!record.parameters[static_cast(index)].check( + state, index + 1)) { + return false; + } + } + return true; +} + +int invoke(lua_State* state, lua_glue::detail::FunctionRecord& record) { + const int provided = lua_gettop(state); + const int full = static_cast(record.parameters.size()); + const int required = full - static_cast(record.defaults.size()); + if (!acceptsArity(state, record)) { + throw std::runtime_error("wrong number of arguments for " + + record.name + record.signature); + } + for (int index = provided; index < full; ++index) { + if (index < required) { + lua_pushnil(state); + continue; + } + const auto& value = + record.defaults[static_cast(index - required)]; + if (value.push(state, value.storage.get()) != 1) { + throw std::runtime_error( + "a binding default must produce one Lua value"); + } + } + const int before = lua_gettop(state); + const int results = record.invoke(state, record.storage.get(), + record.options.return_policy); + if (results < 0 || lua_gettop(state) < before + results) { + throw std::runtime_error("binding returned an invalid result count"); + } + if (record.options.return_policy == + lua_glue::ReturnPolicy::ReferenceInternal && + provided > 0) { + for (int index = 0; index < results; ++index) { + lua_glue::RetainDependency(state, -results + index, 1); + } + } + for (const auto& [dependent, owner] : record.options.keep_alive) { + if (owner < 1 || owner > provided) { + continue; + } + if (dependent == 0) { + for (int index = 0; index < results; ++index) { + lua_glue::RetainDependency(state, -results + index, owner); + } + } else if (dependent > 0 && dependent <= provided) { + lua_glue::RetainDependency(state, dependent, owner); + } + } + return results; +} + +int dispatchImpl(lua_State* state, lua_glue::detail::OverloadSet& set) { + // Lua invokes unary operators with the operand repeated in slot two. + // Fixed C++ signatures receive only the meaningful operand. + if (set.unaryMetamethod && lua_gettop(state) == 2 && + lua_rawequal(state, 1, 2)) { + lua_settop(state, 1); + } + const int count = lua_gettop(state); + lua_glue::detail::FunctionRecord* selected = nullptr; + int best = std::numeric_limits::max(); + for (const auto& candidate : set.functions) { + const int full = static_cast(candidate->parameters.size()); + if (!acceptsArity(state, *candidate)) { + continue; + } + bool matches = true; + int rank = candidate->variadic ? 1000 : 0; + for (int i = 0; i < count && i < full; ++i) { + const auto& parameter = + candidate->parameters[static_cast(i)]; + if (!parameter.check(state, i + 1)) { + matches = false; + break; + } + rank += parameter.rank(state, i + 1); + } + if (matches && rank < best) { + best = rank; + selected = candidate.get(); + } + } + if (selected) { + return invoke(state, *selected); + } + std::string message = "no matching overload for "; + message += set.functions.empty() ? "function" : set.functions.front()->name; + message += "("; + for (int i = 1; i <= count; ++i) { + if (i > 1) { + message += ", "; + } + message += lua_typename(state, lua_type(state, i)); + } + message += "). Candidates:"; + for (const auto& candidate : set.functions) { + message += "\n " + candidate->name + candidate->signature; + } + throw std::runtime_error(message); +} + +int dispatch(lua_State* state) noexcept { + char error[4096]{}; + try { + auto* set = static_cast( + lua_touserdata(state, lua_upvalueindex(1))); + if (!set) { + throw std::runtime_error("invalid LuaGlue function closure"); + } + return dispatchImpl(state, *set); + } catch (const std::exception& exception) { + std::snprintf(error, sizeof(error), "%s", exception.what()); + } catch (...) { + std::snprintf(error, sizeof(error), + "unknown C++ exception in LuaGlue binding"); + } + // Only trivial storage remains when Lua performs its non-local error jump. + lua_pushstring(state, error); + return lua_error(state); +} + +struct FunctionContext { + lua_glue::detail::OverloadSet* set; +}; + +int createFunction(lua_State* state) { + auto* context = static_cast(lua_touserdata(state, 1)); + if (luaL_newmetatable(state, overloadMetatable)) { + lua_pushcfunction(state, overloadGc); + lua_setfield(state, -2, "__gc"); + } + lua_pop(state, 1); + void* memory = + lua_newuserdatauv(state, sizeof(lua_glue::detail::OverloadSet), 0); + std::construct_at(static_cast(memory), + std::move(*context->set)); + luaL_setmetatable(state, overloadMetatable); + lua_pushcclosure(state, dispatch, 1); + return 1; +} + +lua_glue::detail::OverloadSet* getOverloads(lua_State* state, int index) { + if (!lua_iscfunction(state, index) || + lua_tocfunction(state, index) != dispatch) { + return nullptr; + } + if (!lua_getupvalue(state, index, 1)) { + return nullptr; + } + auto* result = static_cast( + luaL_testudata(state, -1, overloadMetatable)); + lua_pop(state, 1); + return result; +} + +void addOverload(lua_glue::detail::OverloadSet& set, + std::shared_ptr record) { + for (auto& existing : set.functions) { + if (existing->signature == record->signature) { + existing = std::move(record); + return; + } + } + set.functions.push_back(std::move(record)); +} + +void installFunction(const lua_glue::Table& table, std::string_view name, + std::shared_ptr record, + bool unaryMetamethod = false) { + lua_State* state = table.lua_state(); + lua_glue::StackGuard guard(state); + table.raw_get(name).push(state); + if (auto* set = getOverloads(state, -1)) { + addOverload(*set, std::move(record)); + set->unaryMetamethod = unaryMetamethod; + return; + } + lua_pop(state, 1); + lua_glue::detail::PushFunctionValue(state, std::move(record)); + getOverloads(state, -1)->unaryMetamethod = unaryMetamethod; + table.raw_set(name, lua_glue::Object(state, -1)); +} + +int pushString(lua_State* state) { + auto* value = static_cast(lua_touserdata(state, 1)); + lua_pushlstring(state, value->data(), value->size()); + return 1; +} + +int referenceFunction(lua_State* state) { + const int reference = luaL_ref(state, LUA_REGISTRYINDEX); + lua_pushinteger(state, reference); + return 1; +} + +int protectedReference(lua_State* state) { + lua_pushcfunction(state, referenceFunction); + lua_insert(state, -2); + if (lua_pcall(state, 1, 1, 0) != LUA_OK) { + const char* message = lua_tostring(state, -1); + std::string error = + message ? message : "could not retain native property"; + lua_pop(state, 1); + throw std::runtime_error(error); + } + const int reference = static_cast(lua_tointeger(state, -1)); + lua_pop(state, 1); + return reference; +} + +} // namespace + +namespace lua_glue::detail { + +int PushString(lua_State* state, std::string_view value) { + ProtectedCallOperation(state, pushString, &value, 1); + return 1; +} + +int PushFunctionValue(lua_State* state, + std::shared_ptr record) { + OverloadSet set; + set.functions.push_back(std::move(record)); + FunctionContext context{&set}; + ProtectedCallOperation(state, createFunction, &context, 1); + return 1; +} + +void PushFunction(lua_State* state, + const std::shared_ptr& record) { + if (record->cachedReference >= 0) { + lua_rawgeti(state, LUA_REGISTRYINDEX, record->cachedReference); + } else { + PushFunctionValue(state, record); + } +} + +int InvokeFunction(lua_State* state, + const std::shared_ptr& record) { + return invoke(state, *record); +} + +void RegisterFunction(const Table& table, std::string_view name, + std::shared_ptr record) { + record->name = name; + installFunction(table, name, std::move(record)); +} + +void RegisterProperty(const Table& table, std::string_view name, + std::shared_ptr getter, + std::shared_ptr setter, bool isStatic) { + lua_State* state = table.lua_state(); + StackGuard guard(state); + table.push(state); + auto* type = GetTypeRecord(state, -1); + if (!type && isStatic) { + type = PreparePropertyScope(table); + } + if (!type) { + throw std::runtime_error( + "properties require a registered LuaGlue type"); + } + getter->name = type->luaName + "." + std::string(name); + if (setter) { + setter->name = getter->name; + } + auto& properties = isStatic ? type->staticProperties : type->properties; + const std::string key(name); + auto found = properties.find(key); + const bool isNew = found == properties.end(); + PropertyRecord previous = isNew ? PropertyRecord{} : found->second; + bool mapChanged = false; + auto release = [state](const std::shared_ptr& function) { + if (function && function->cachedReference >= 0) { + luaL_unref(state, LUA_REGISTRYINDEX, function->cachedReference); + function->cachedReference = LUA_NOREF; + } + }; + try { + auto clone = [state](const Table& original) { + Table result = StateView(state).create_table(); + for (const auto& [entryKey, value] : original) { + result.raw_set(entryKey, value); + } + return result; + }; + Table getters = clone(table.raw_get
("__getters")); + Table setters = clone(table.raw_get
("__setters")); + Table names = clone(table.raw_get
("__nativeProperties")); + PushFunctionValue(state, getter); + getters.raw_set(name, Object(state, -1)); + getter->cachedReference = protectedReference(state); + if (setter) { + PushFunctionValue(state, setter); + setters.raw_set(name, Object(state, -1)); + setter->cachedReference = protectedReference(state); + } else { + setters.raw_set(name, nil); + } + if (isNew && !isStatic) { + names.add(name); + } + Object nameObject = MakeObject(state, name); + if (!lua_checkstack(state, 7)) { + throw std::runtime_error("not enough stack to register property"); + } + // Stage every allocating operation before replacing either native or + // Lua-visible metadata. These existing class-table slots cannot grow. + lua_pushcfunction( + state, +[](lua_State* inner) -> int { + lua_pushvalue(inner, 2); + lua_setfield(inner, 1, "__getters"); + lua_pushvalue(inner, 3); + lua_setfield(inner, 1, "__setters"); + lua_pushvalue(inner, 4); + lua_setfield(inner, 1, "__nativeProperties"); + lua_pushvalue(inner, 5); + lua_pushnil(inner); + lua_rawset(inner, 1); + return 0; + }); + table.push(state); + getters.push(state); + setters.push(state); + names.push(state); + nameObject.push(state); + properties.insert_or_assign(key, PropertyRecord{getter, setter}); + mapChanged = true; + if (lua_pcall(state, 5, 0, 0) != LUA_OK) { + const char* text = lua_tostring(state, -1); + std::string message = text ? text : "could not register property"; + lua_pop(state, 1); + throw std::runtime_error(message); + } + release(previous.getter); + release(previous.setter); + } catch (...) { + if (mapChanged) { + if (isNew) { + properties.erase(key); + } else { + properties.find(key)->second = std::move(previous); + } + } + release(getter); + release(setter); + throw; + } +} + +void RegisterMetamethod(const Table& table, std::string_view name, + std::shared_ptr record) { + lua_State* state = table.lua_state(); + StackGuard guard(state); + table.push(state); + auto* type = GetTypeRecord(state, -1); + if (!type) { + throw std::runtime_error("metamethod requires a registered type"); + } + lua_getfield(state, LUA_REGISTRYINDEX, type->metatableName.c_str()); + Table metatable(state, -1); + record->name = type->luaName + "." + std::string(name); + if (name == "__gc") { + throw std::invalid_argument( + "native userdata destruction is owned by LuaGlue; use an owning " + "pointer deleter"); + } + installFunction(metatable, name, std::move(record), + name == "__len" || name == "__unm" || name == "__bnot"); +} + +} // namespace lua_glue::detail diff --git a/LuaGlue/src/BindingImpl.hpp b/LuaGlue/src/BindingImpl.hpp new file mode 100644 index 0000000..fd8ec03 --- /dev/null +++ b/LuaGlue/src/BindingImpl.hpp @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace lua_glue::detail { +struct OverloadSet { + std::vector> functions; + bool unaryMetamethod = false; +}; +} // namespace lua_glue::detail diff --git a/LuaGlue/src/Lifecycle.cpp b/LuaGlue/src/Lifecycle.cpp new file mode 100644 index 0000000..b2b708c --- /dev/null +++ b/LuaGlue/src/Lifecycle.cpp @@ -0,0 +1,1245 @@ +#include + +extern "C" { +#include +#include +} + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lua_glue::detail_internal { + +enum class LuaStatePhase { + running, + stopping, + stopped, +}; + +struct LuaStateSession; + +} // namespace lua_glue::detail_internal + +namespace lua_glue { + +struct RegistryReferenceState { + RegistryReferenceState( + std::weak_ptr session, + lua_State* state, lua_State* origin) noexcept + : session(std::move(session)), state(state), origin(origin) {} + + ~RegistryReferenceState(); + + std::weak_ptr session; + lua_State* state{}; + lua_State* origin{}; + int reference{LUA_NOREF}; +}; + +} // namespace lua_glue + +namespace lua_glue::detail_internal { + +class DeferredCallbackErrorQueue final { +public: + static constexpr std::size_t capacity = 64; + static constexpr std::size_t messageCapacity = 512; + + DeferredCallbackErrorQueue() noexcept { + for (std::size_t index = 0; index < slots_.size(); ++index) { + slots_[index].sequence.store(index, std::memory_order_relaxed); + } + } + + bool enqueue(std::string_view label, std::string_view message) noexcept { + std::size_t position = enqueuePosition_.load(std::memory_order_relaxed); + Slot* slot = nullptr; + for (;;) { + slot = &slots_[position & (capacity - 1)]; + const std::size_t sequence = + slot->sequence.load(std::memory_order_acquire); + const std::intptr_t difference = + static_cast(sequence) - + static_cast(position); + if (difference == 0) { + if (enqueuePosition_.compare_exchange_weak( + position, position + 1, std::memory_order_relaxed)) { + break; + } + } else if (difference < 0) { + overflowPending_.store(1, std::memory_order_release); + return false; + } else { + position = enqueuePosition_.load(std::memory_order_relaxed); + } + } + + std::size_t offset = 0; + copyPart(slot->message, offset, label); + if (!label.empty() && !message.empty()) { + copyPart(slot->message, offset, ": "); + } + copyPart(slot->message, offset, message); + slot->message[offset] = '\0'; + slot->sequence.store(position + 1, std::memory_order_release); + return true; + } + + bool dequeue(char* buffer, std::size_t bufferCapacity) noexcept { + std::size_t position = dequeuePosition_.load(std::memory_order_relaxed); + Slot* slot = nullptr; + for (;;) { + slot = &slots_[position & (capacity - 1)]; + const std::size_t sequence = + slot->sequence.load(std::memory_order_acquire); + const std::intptr_t difference = + static_cast(sequence) - + static_cast(position + 1); + if (difference == 0) { + if (dequeuePosition_.compare_exchange_weak( + position, position + 1, std::memory_order_relaxed)) { + break; + } + } else if (difference < 0) { + if (overflowPending_.exchange(0, std::memory_order_acq_rel) == + 0) { + return false; + } + copyToBuffer(buffer, bufferCapacity, + "Lua callback error queue overflow"); + return true; + } else { + position = dequeuePosition_.load(std::memory_order_relaxed); + } + } + + copyToBuffer(buffer, bufferCapacity, slot->message.data()); + slot->sequence.store(position + capacity, std::memory_order_release); + return true; + } + +private: + struct Slot { + std::atomic sequence{}; + std::array message{}; + }; + + static void copyPart(std::array& target, + std::size_t& offset, std::string_view value) noexcept { + const std::size_t available = messageCapacity - 1 - offset; + const std::size_t count = std::min(available, value.size()); + if (count != 0) { + std::memcpy(target.data() + offset, value.data(), count); + } + offset += count; + } + + static void copyToBuffer(char* buffer, std::size_t bufferCapacity, + std::string_view value) noexcept { + const std::size_t count = std::min(bufferCapacity - 1, value.size()); + if (count != 0) { + std::memcpy(buffer, value.data(), count); + } + buffer[count] = '\0'; + } + + static_assert((capacity & (capacity - 1)) == 0); + static_assert(std::atomic::is_always_lock_free); + std::array slots_{}; + alignas(64) std::atomic enqueuePosition_{}; + alignas(64) std::atomic dequeuePosition_{}; + std::atomic overflowPending_{}; +}; + +struct LuaStateSession { + explicit LuaStateSession(lua_State* value) noexcept : state(value) {} + + lua_State* state{}; + std::recursive_mutex fallbackExecutionMutex; + std::recursive_mutex metadataMutex; + std::atomic phase{LuaStatePhase::running}; + StateEnterHook enterHook{}; + StateTryEnterHook tryEnterHook{}; + StateLeaveHook leaveHook{}; + void* hookContext{}; + StateOwnsExecutionHook ownsExecutionHook{}; + std::atomic hookGeneration{}; + std::unordered_set registryReferences; + std::unordered_map retainedObjects; + bool quiescing{}; + std::unordered_map quiesceCallbacks; + DeferredCallbackErrorQueue deferredCallbackErrors; +}; + +struct EnteredSession { + lua_State* state{}; + lua_State* requestedState{}; + std::shared_ptr session; + StateLeaveHook leaveHook{}; + void* hookContext{}; + bool usesFallback{}; + StateOwnsExecutionHook ownsExecutionHook{}; + std::uint64_t hookGeneration{}; + bool gateAcquired{}; +}; + +std::mutex sessionsMutex; +std::unordered_map> sessions; +std::unordered_map stateAliases; +class EnteredSessionStack final { +public: + [[nodiscard]] bool empty() const noexcept { + return size_ == 0; + } + [[nodiscard]] std::size_t size() const noexcept { + return size_; + } + [[nodiscard]] constexpr std::size_t capacity() const noexcept { + return entries_.size(); + } + + EnteredSession& back() noexcept { + return entries_[size_ - 1]; + } + + void push_back(EnteredSession value) { + if (size_ == entries_.size()) { + throw std::bad_alloc(); + } + entries_[size_++] = std::move(value); + } + + void pop_back() noexcept { + if (size_ == 0) { + return; + } + entries_[--size_] = {}; + } + +private: + std::array entries_{}; + std::size_t size_{}; +}; + +thread_local EnteredSessionStack enteredSessions; + +const EnteredSession* enteredSessionForState(lua_State* state) noexcept { + if (state == nullptr || enteredSessions.empty()) { + return nullptr; + } + const EnteredSession& entered = enteredSessions.back(); + if ((state != entered.state && state != entered.requestedState) || + entered.session == nullptr || + entered.session->phase.load(std::memory_order_acquire) != + LuaStatePhase::running) { + return nullptr; + } + // Entries exist while an enter hook is still acquiring its gate, and a + // host may temporarily release that gate. Reuse only the VM identity; + // callers must still execute the normal enter hook and phase checks. + return &entered; +} + +lua_State* mainThreadFromRegistry(lua_State* state) noexcept { + if (state == nullptr || lua_checkstack(state, 1) == 0) { + return nullptr; + } + + const int originalTop = lua_gettop(state); + lua_rawgeti(state, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD); + lua_State* mainState = lua_tothread(state, -1); + lua_settop(state, originalTop); + return mainState; +} + +std::shared_ptr findSessionByMainState(lua_State* mainState) { + if (mainState == nullptr) { + return {}; + } + if (const auto* entered = enteredSessionForState(mainState); + entered != nullptr && entered->state == mainState) { + return entered->session; + } + std::scoped_lock lock(sessionsMutex); + const auto item = sessions.find(mainState); + return item == sessions.end() ? std::shared_ptr{} + : item->second; +} + +struct ResolvedSession { + lua_State* mainState{}; + std::shared_ptr session; +}; + +ResolvedSession findResolvedSessionLocked(lua_State* state) { + const auto alias = stateAliases.find(state); + if (alias == stateAliases.end()) { + return {}; + } + const auto session = sessions.find(alias->second); + return session == sessions.end() + ? ResolvedSession{} + : ResolvedSession{alias->second, session->second}; +} + +ResolvedSession findResolvedSession(lua_State* state) noexcept { + if (state == nullptr) { + return {}; + } + if (const auto* entered = enteredSessionForState(state)) { + return {entered->state, entered->session}; + } + try { + std::scoped_lock lock(sessionsMutex); + return findResolvedSessionLocked(state); + } catch (...) { + return {}; + } +} + +int createProtectedRegistryReference(lua_State* state, int index) { + if (lua_checkstack(state, 3) == 0) { + throw std::runtime_error("Lua registry stack cannot grow"); + } + index = lua_absindex(state, index); + lua_pushcfunction(state, [](lua_State* inner) -> int { + lua_settop(inner, 1); + const int reference = luaL_ref(inner, LUA_REGISTRYINDEX); + lua_pushinteger(inner, reference); + return 1; + }); + lua_pushvalue(state, index); + if (lua_pcall(state, 1, 1, 0) != LUA_OK) { + const char* message = lua_tostring(state, -1); + const std::string error = + message ? message : "Unable to retain Lua registry value"; + lua_pop(state, 1); + throw std::runtime_error(error); + } + const int reference = static_cast(lua_tointeger(state, -1)); + lua_pop(state, 1); + return reference; +} + +bool addStateAliasUnderExecution( + lua_State* state, lua_State* mainState, + const std::shared_ptr& session) noexcept { + if (state == nullptr || mainState == nullptr || session == nullptr) { + return false; + } + + int threadReference = LUA_NOREF; + if (state != mainState) { + lua_pushthread(state); + try { + threadReference = createProtectedRegistryReference(state, -1); + } catch (...) { + lua_pop(state, 1); + return false; + } + lua_pop(state, 1); + if (threadReference < 0) { + return false; + } + try { + std::scoped_lock lock(session->metadataMutex); + if (session->state != mainState || + session->phase.load(std::memory_order_acquire) != + LuaStatePhase::running || + !session->registryReferences.insert(threadReference).second) { + luaL_unref(state, LUA_REGISTRYINDEX, threadReference); + return false; + } + } catch (...) { + luaL_unref(state, LUA_REGISTRYINDEX, threadReference); + return false; + } + } + + bool aliasRegistered = false; + try { + std::scoped_lock lock(sessionsMutex); + const auto activeSession = sessions.find(mainState); + if (activeSession != sessions.end() && + activeSession->second == session) { + stateAliases.insert_or_assign(state, mainState); + aliasRegistered = true; + } + } catch (...) {} + if (aliasRegistered) { + return true; + } + + if (threadReference >= 0) { + try { + std::scoped_lock lock(session->metadataMutex); + session->registryReferences.erase(threadReference); + } catch (...) {} + luaL_unref(state, LUA_REGISTRYINDEX, threadReference); + } + return false; +} + +ResolvedSession resolveSession(lua_State* state) noexcept { + return findResolvedSession(state); +} + +ResolvedSession learnRegistryReferenceSessionUnderExecution( + lua_State* state) noexcept { + ResolvedSession resolved = findResolvedSession(state); + if (resolved.session != nullptr) { + return resolved; + } + + // Registry-reference construction receives the currently executing Lua + // thread. The host therefore already owns the VM either through a LuaGlue + // scope or through the same lock installed as its execution hooks. + lua_State* mainState = mainThreadFromRegistry(state); + if (mainState == nullptr) { + return {}; + } + std::shared_ptr session; + if (!enteredSessions.empty()) { + const EnteredSession& entered = enteredSessions.back(); + if (mainState != entered.state) { + return {}; + } + session = entered.session; + } else { + session = findSessionByMainState(mainState); + } + if (session == nullptr || + session->phase.load(std::memory_order_acquire) != + LuaStatePhase::running || + !addStateAliasUnderExecution(state, mainState, session)) { + return {}; + } + return {mainState, std::move(session)}; +} + +ResolvedSession tryResolveSession(lua_State* state) noexcept { + if (state == nullptr) { + return {}; + } + if (const auto* entered = enteredSessionForState(state)) { + return {entered->state, entered->session}; + } + + std::unique_lock lock(sessionsMutex, std::try_to_lock); + if (!lock.owns_lock()) { + return {}; + } + return findResolvedSessionLocked(state); +} + +std::vector> snapshotSessions() { + std::scoped_lock lock(sessionsMutex); + std::vector> result; + result.reserve(sessions.size()); + for (const auto& [state, session] : sessions) { + static_cast(state); + result.push_back(session); + } + return result; +} + +} // namespace lua_glue::detail_internal + +namespace lua_glue { + +RegistryReferenceState::~RegistryReferenceState() { + if (reference < 0) { + return; + } + const std::shared_ptr activeSession = + session.lock(); + if (activeSession == nullptr || + activeSession->phase.load(std::memory_order_acquire) != + detail_internal::LuaStatePhase::running) { + return; + } + detail::AccessScope execution(state); + if (!execution.active()) { + return; + } + std::scoped_lock lock(activeSession->metadataMutex); + if (activeSession->state != state || + activeSession->phase.load(std::memory_order_acquire) != + detail_internal::LuaStatePhase::running) { + return; + } + if (activeSession->registryReferences.erase(reference) != 0) { + luaL_unref(state, LUA_REGISTRYINDEX, reference); + } +} + +detail::AccessScope::AccessScope(lua_State* state) noexcept { + const auto* entered = detail_internal::enteredSessionForState(state); + if (entered != nullptr && entered->gateAcquired && + entered->ownsExecutionHook != nullptr && + entered->session->hookGeneration.load(std::memory_order_acquire) == + entered->hookGeneration && + entered->ownsExecutionHook(entered->state, entered->hookContext) != 0 && + entered->session->hookGeneration.load(std::memory_order_acquire) == + entered->hookGeneration && + entered->session->phase.load(std::memory_order_acquire) == + detail_internal::LuaStatePhase::running) { + state_ = entered->state; + active_ = true; + borrowed_ = true; + return; + } + active_ = EnterState(state) != 0; + if (active_ && !detail_internal::enteredSessions.empty()) { + state_ = detail_internal::enteredSessions.back().state; + } +} + +detail::AccessScope::~AccessScope() { + if (active_ && !borrowed_) { + LeaveState(state_); + } +} + +bool detail::AccessScope::active() const noexcept { + return active_; +} + +ExecutionScope::ExecutionScope(lua_State* state) noexcept + : active_(EnterState(state) != 0) { + if (active_ && !detail_internal::enteredSessions.empty()) { + state_ = detail_internal::enteredSessions.back().state; + } +} + +ExecutionScope::~ExecutionScope() { + if (active_) { + LeaveState(state_); + } +} + +bool ExecutionScope::active() const noexcept { + return active_; +} + +TryExecutionScope::TryExecutionScope(lua_State* state) noexcept + : active_(TryEnterState(state) != 0) { + if (active_ && !detail_internal::enteredSessions.empty()) { + state_ = detail_internal::enteredSessions.back().state; + } +} + +TryExecutionScope::~TryExecutionScope() { + if (active_) { + LeaveState(state_); + } +} + +bool TryExecutionScope::active() const noexcept { + return active_; +} + +RegistryReference::RegistryReference(lua_State* state, int stackIndex) { + if (state == nullptr) { + throw std::invalid_argument("Lua registry reference has no state"); + } + const detail_internal::ResolvedSession resolved = + detail_internal::learnRegistryReferenceSessionUnderExecution(state); + lua_State* mainState = resolved.mainState; + const std::shared_ptr& session = + resolved.session; + if (mainState == nullptr || session == nullptr) { + throw std::logic_error("Lua registry reference has no main state"); + } + detail::AccessScope execution(mainState); + if (!execution.active()) { + throw std::logic_error("Lua state is stopping"); + } + const int absoluteStackIndex = lua_absindex(state, stackIndex); + std::scoped_lock lock(session->metadataMutex); + if (session->state != mainState || + session->phase.load(std::memory_order_acquire) != + detail_internal::LuaStatePhase::running) { + throw std::logic_error("Lua state is stopping"); + } + std::shared_ptr reference = + std::make_shared(session, mainState, state); + reference->reference = detail_internal::createProtectedRegistryReference( + state, absoluteStackIndex); + if (reference->reference >= 0) { + try { + session->registryReferences.insert(reference->reference); + } catch (...) { + luaL_unref(state, LUA_REGISTRYINDEX, reference->reference); + reference->reference = LUA_NOREF; + throw; + } + } + reference_ = std::move(reference); +} + +lua_State* RegistryReference::state() const noexcept { + if (reference_ == nullptr) { + return nullptr; + } + const std::shared_ptr session = + reference_->session.lock(); + if (session == nullptr || session->phase.load(std::memory_order_acquire) != + detail_internal::LuaStatePhase::running) { + return nullptr; + } + return reference_->state; +} + +lua_State* RegistryReference::originState() const noexcept { + return state() != nullptr ? reference_->origin : nullptr; +} + +bool RegistryReference::push() const { + if (reference_ == nullptr) { + return false; + } + const std::shared_ptr session = + reference_->session.lock(); + if (session == nullptr || session->phase.load(std::memory_order_acquire) != + detail_internal::LuaStatePhase::running) { + return false; + } + detail::AccessScope execution(reference_->state); + if (!execution.active()) { + return false; + } + std::scoped_lock lock(session->metadataMutex); + if (session->state != reference_->state || + session->phase.load(std::memory_order_acquire) != + detail_internal::LuaStatePhase::running) { + return false; + } + if (lua_checkstack(reference_->state, 2) == 0) { + return false; + } + if (reference_->reference == LUA_REFNIL) { + lua_pushnil(reference_->state); + } else { + lua_rawgeti(reference_->state, LUA_REGISTRYINDEX, + reference_->reference); + } + return true; +} + +bool RegistryReference::push(lua_State* target) const { + lua_State* source = state(); + if (source == nullptr || target == nullptr) { + return false; + } + // The reference already identifies its main VM and retains its origin + // coroutine through the registered session. Other targets still require + // an alias lookup; the entered session is checked again before pushing. + if (target != source && target != reference_->origin) { + const auto resolved = detail_internal::findResolvedSession(target); + if (resolved.mainState != source) { + throw std::invalid_argument( + "Cannot move a Lua value between independent states"); + } + } + detail::AccessScope execution(source); + if (!execution.active() || !pushUnderExecutionScope()) { + return false; + } + if (source != target) { + if (lua_checkstack(target, 1) == 0) { + lua_pop(source, 1); + throw std::runtime_error("Lua target stack cannot grow"); + } + lua_xmove(source, target, 1); + } + return true; +} + +bool RegistryReference::pushUnderExecutionScope() const noexcept { + if (reference_ == nullptr) { + return false; + } + if (detail_internal::enteredSessions.empty()) { + return false; + } + const std::shared_ptr session = + reference_->session.lock(); + if (session == nullptr || reference_->reference == LUA_NOREF) { + return false; + } + const detail_internal::EnteredSession& entered = + detail_internal::enteredSessions.back(); + if (entered.state != reference_->state || entered.session != session || + session->state != reference_->state || + session->phase.load(std::memory_order_acquire) != + detail_internal::LuaStatePhase::running) { + return false; + } + if (lua_checkstack(reference_->state, 2) == 0) { + return false; + } + if (reference_->reference == LUA_REFNIL) { + lua_pushnil(reference_->state); + } else { + lua_rawgeti(reference_->state, LUA_REGISTRYINDEX, + reference_->reference); + } + return true; +} + +void RegistryReference::deferCallbackError( + std::string_view label, std::string_view message) const noexcept { + if (reference_ == nullptr) { + return; + } + const std::shared_ptr session = + reference_->session.lock(); + if (session != nullptr) { + session->deferredCallbackErrors.enqueue(label, message); + } +} + +bool RegistryReference::equals(const RegistryReference& other) const { + if (reference_ == nullptr || other.reference_ == nullptr || + reference_->state != other.reference_->state) { + return false; + } + const std::shared_ptr session = + reference_->session.lock(); + if (session == nullptr || session != other.reference_->session.lock() || + session->phase.load(std::memory_order_acquire) != + detail_internal::LuaStatePhase::running) { + return false; + } + detail::AccessScope execution(reference_->state); + if (!execution.active()) { + return false; + } + std::scoped_lock lock(session->metadataMutex); + if (session->state != reference_->state || + session->phase.load(std::memory_order_acquire) != + detail_internal::LuaStatePhase::running) { + return false; + } + if (lua_checkstack(reference_->state, 2) == 0) { + return false; + } + if (reference_->reference == LUA_REFNIL) { + lua_pushnil(reference_->state); + } else { + lua_rawgeti(reference_->state, LUA_REGISTRYINDEX, + reference_->reference); + } + if (other.reference_->reference == LUA_REFNIL) { + lua_pushnil(reference_->state); + } else { + lua_rawgeti(reference_->state, LUA_REGISTRYINDEX, + other.reference_->reference); + } + const bool result = lua_rawequal(reference_->state, -2, -1) != 0; + lua_pop(reference_->state, 2); + return result; +} + +RegistryReference::operator bool() const noexcept { + return reference_ != nullptr; +} + +namespace detail { + +void registerLuaThreadForRegistryReference(lua_State* state) { + if (state == nullptr) { + throw std::invalid_argument("Lua registry reference has no state"); + } + const detail_internal::ResolvedSession resolved = + detail_internal::learnRegistryReferenceSessionUnderExecution(state); + if (resolved.session == nullptr) { + throw std::logic_error( + "Lua registry reference requires an entered main state"); + } +} + +void retainLuaRegistryReference(const void* owner, + const RegistryReference& reference) { + if (owner == nullptr || !reference) { + return; + } + lua_State* state = reference.state(); + if (state == nullptr) { + return; + } + const std::shared_ptr session = + detail_internal::findSessionByMainState(state); + if (session == nullptr) { + return; + } + ExecutionScope execution(state); + if (!execution.active()) { + return; + } + std::scoped_lock lock(session->metadataMutex); + if (session->state != state || + session->phase.load(std::memory_order_acquire) != + detail_internal::LuaStatePhase::running) { + return; + } + session->retainedObjects.insert_or_assign(owner, reference); +} + +void releaseLuaRegistryReference(const void* owner) { + if (owner == nullptr) { + return; + } + for (const std::shared_ptr& session : + detail_internal::snapshotSessions()) { + lua_State* state = nullptr; + { + std::scoped_lock lock(session->metadataMutex); + state = session->state; + } + ExecutionScope execution(state); + if (!execution.active()) { + continue; + } + std::scoped_lock lock(session->metadataMutex); + session->retainedObjects.erase(owner); + } +} + +void registerStateQuiesceCallback(lua_State* state, const void* owner, + StateQuiesceCallback callback) { + if (state == nullptr || owner == nullptr || callback == nullptr) { + throw std::invalid_argument("State quiesce callback is incomplete"); + } + const detail_internal::ResolvedSession resolved = + detail_internal::resolveSession(state); + lua_State* mainState = resolved.mainState; + const std::shared_ptr& session = + resolved.session; + if (session == nullptr) { + throw std::logic_error("State quiesce callback has no active state"); + } + ExecutionScope execution(mainState); + if (!execution.active()) { + throw std::logic_error("Lua state is stopping"); + } + std::scoped_lock lock(session->metadataMutex); + if (session->state != mainState || session->quiescing || + session->phase.load(std::memory_order_acquire) != + detail_internal::LuaStatePhase::running) { + throw std::logic_error("Lua state is stopping"); + } + session->quiesceCallbacks.insert_or_assign(owner, callback); +} + +void unregisterStateQuiesceCallback(lua_State* state, + const void* owner) noexcept { + if (state == nullptr || owner == nullptr) { + return; + } + const detail_internal::ResolvedSession resolved = + detail_internal::findResolvedSession(state); + lua_State* mainState = resolved.mainState; + const std::shared_ptr& session = + resolved.session; + if (session == nullptr) { + return; + } + std::scoped_lock lock(session->metadataMutex); + if (session->state == mainState) { + session->quiesceCallbacks.erase(owner); + } +} + +} // namespace detail + +int InitializeState(lua_State* state) { + if (state == nullptr) { + return 1; + } + const lua_glue::detail_internal::ResolvedSession existing = + lua_glue::detail_internal::findResolvedSession(state); + if (existing.session != nullptr) { + return existing.session->phase.load(std::memory_order_acquire) == + lua_glue::detail_internal::LuaStatePhase::running + ? 0 + : 1; + } + + // Initialization is called by the thread currently executing registration, + // before a LuaGlue execution gate necessarily exists. It is the only public + // entry point that learns a VM identity without a pre-existing session. + lua_State* mainState = + lua_glue::detail_internal::mainThreadFromRegistry(state); + if (mainState == nullptr) { + return 1; + } + std::shared_ptr session; + bool createdSession = false; + try { + std::scoped_lock lock(lua_glue::detail_internal::sessionsMutex); + const auto item = lua_glue::detail_internal::sessions.find(mainState); + if (item != lua_glue::detail_internal::sessions.end()) { + if (item->second->phase.load(std::memory_order_acquire) != + lua_glue::detail_internal::LuaStatePhase::running) { + return 1; + } + session = item->second; + } else { + session = + std::make_shared( + mainState); + lua_glue::detail_internal::sessions.emplace(mainState, session); + createdSession = true; + } + try { + lua_glue::detail_internal::stateAliases.insert_or_assign(mainState, + mainState); + } catch (...) { + lua_glue::detail_internal::stateAliases.erase(mainState); + if (createdSession) { + lua_glue::detail_internal::sessions.erase(mainState); + } + throw; + } + } catch (...) { + return 1; + } + + bool initialized = false; + { + lua_glue::ExecutionScope execution(mainState); + if (execution.active()) { + initialized = + state == mainState || + lua_glue::detail_internal::addStateAliasUnderExecution( + state, mainState, session); + } + } + if (!initialized && createdSession) { + ShutdownState(mainState); + } + return initialized ? 0 : 1; +} + +int SetStateExecutionHooks(lua_State* state, StateEnterHook enterHook, + StateTryEnterHook tryEnterHook, + StateLeaveHook leaveHook, void* context) { + if (state == nullptr || + (enterHook == nullptr) != (tryEnterHook == nullptr) || + (enterHook == nullptr) != (leaveHook == nullptr)) { + return 1; + } + const lua_glue::detail_internal::ResolvedSession resolved = + lua_glue::detail_internal::resolveSession(state); + lua_State* mainState = resolved.mainState; + const std::shared_ptr& session = + resolved.session; + if (session == nullptr) { + return 1; + } + lua_glue::ExecutionScope execution(mainState); + if (!execution.active()) { + return 1; + } + std::scoped_lock lock(session->metadataMutex); + if (session->state != mainState || + session->phase.load(std::memory_order_acquire) != + lua_glue::detail_internal::LuaStatePhase::running) { + return 1; + } + session->hookGeneration.fetch_add(1, std::memory_order_acq_rel); + session->ownsExecutionHook = nullptr; + session->enterHook = enterHook; + session->tryEnterHook = tryEnterHook; + session->leaveHook = leaveHook; + session->hookContext = context; + return 0; +} + +int SetStateOwnsExecutionHook(lua_State* state, StateOwnsExecutionHook owns) { + const detail_internal::ResolvedSession resolved = + detail_internal::resolveSession(state); + const auto& session = resolved.session; + if (session == nullptr) { + return 1; + } + ExecutionScope execution(resolved.mainState); + if (!execution.active()) { + return 1; + } + std::scoped_lock lock(session->metadataMutex); + if (session->state != resolved.mainState || + session->phase.load(std::memory_order_acquire) != + detail_internal::LuaStatePhase::running || + (owns != nullptr && session->enterHook == nullptr)) { + return 1; + } + session->hookGeneration.fetch_add(1, std::memory_order_acq_rel); + session->ownsExecutionHook = owns; + return 0; +} + +int EnterState(lua_State* state) { + const lua_glue::detail_internal::ResolvedSession resolved = + lua_glue::detail_internal::resolveSession(state); + lua_State* mainState = resolved.mainState; + const std::shared_ptr& session = + resolved.session; + if (session == nullptr) { + return 0; + } + StateEnterHook enterHook = nullptr; + StateLeaveHook leaveHook = nullptr; + void* hookContext = nullptr; + StateOwnsExecutionHook ownsExecutionHook = nullptr; + std::uint64_t hookGeneration = 0; + { + std::scoped_lock lock(session->metadataMutex); + if (session->state != mainState || + session->phase.load(std::memory_order_acquire) != + lua_glue::detail_internal::LuaStatePhase::running) { + return 0; + } + enterHook = session->enterHook; + leaveHook = session->leaveHook; + hookContext = session->hookContext; + ownsExecutionHook = session->ownsExecutionHook; + hookGeneration = + session->hookGeneration.load(std::memory_order_acquire); + } + try { + lua_glue::detail_internal::enteredSessions.push_back( + {mainState, state, session, leaveHook, hookContext, + enterHook == nullptr, ownsExecutionHook, hookGeneration, false}); + } catch (const std::bad_alloc&) { + return 0; + } + if (enterHook != nullptr) { + if (enterHook(mainState, hookContext) == 0) { + lua_glue::detail_internal::enteredSessions.pop_back(); + return 0; + } + } else { + session->fallbackExecutionMutex.lock(); + } + { + std::scoped_lock lock(session->metadataMutex); + if (session->state == mainState && + session->phase.load(std::memory_order_acquire) == + lua_glue::detail_internal::LuaStatePhase::running) { + lua_glue::detail_internal::enteredSessions.back().gateAcquired = + true; + return 1; + } + } + lua_glue::detail_internal::EnteredSession entered = + std::move(lua_glue::detail_internal::enteredSessions.back()); + lua_glue::detail_internal::enteredSessions.pop_back(); + if (entered.usesFallback) { + session->fallbackExecutionMutex.unlock(); + } else { + entered.leaveHook(mainState, entered.hookContext); + } + return 0; +} + +int TryEnterState(lua_State* state) noexcept { + try { + const lua_glue::detail_internal::ResolvedSession resolved = + lua_glue::detail_internal::tryResolveSession(state); + lua_State* mainState = resolved.mainState; + const std::shared_ptr& + session = resolved.session; + if (session == nullptr) { + return 0; + } + StateTryEnterHook tryEnterHook = nullptr; + StateLeaveHook leaveHook = nullptr; + void* hookContext = nullptr; + StateOwnsExecutionHook ownsExecutionHook = nullptr; + std::uint64_t hookGeneration = 0; + { + std::unique_lock lock(session->metadataMutex, std::try_to_lock); + if (!lock.owns_lock()) { + return 0; + } + if (session->state != mainState || + session->phase.load(std::memory_order_acquire) != + lua_glue::detail_internal::LuaStatePhase::running) { + return 0; + } + tryEnterHook = session->tryEnterHook; + leaveHook = session->leaveHook; + hookContext = session->hookContext; + ownsExecutionHook = session->ownsExecutionHook; + hookGeneration = + session->hookGeneration.load(std::memory_order_acquire); + } + if (lua_glue::detail_internal::enteredSessions.capacity() == + lua_glue::detail_internal::enteredSessions.size()) { + return 0; + } + lua_glue::detail_internal::enteredSessions.push_back( + {mainState, state, session, leaveHook, hookContext, + tryEnterHook == nullptr, ownsExecutionHook, hookGeneration, + false}); + if (tryEnterHook != nullptr) { + if (tryEnterHook(mainState, hookContext) == 0) { + lua_glue::detail_internal::enteredSessions.pop_back(); + return 0; + } + } else { + bool locked = false; + try { + locked = session->fallbackExecutionMutex.try_lock(); + } catch (...) { + lua_glue::detail_internal::enteredSessions.pop_back(); + return 0; + } + if (!locked) { + lua_glue::detail_internal::enteredSessions.pop_back(); + return 0; + } + } + if (session->phase.load(std::memory_order_acquire) == + lua_glue::detail_internal::LuaStatePhase::running) { + lua_glue::detail_internal::enteredSessions.back().gateAcquired = + true; + return 1; + } + lua_glue::detail_internal::EnteredSession entered = + std::move(lua_glue::detail_internal::enteredSessions.back()); + lua_glue::detail_internal::enteredSessions.pop_back(); + if (entered.usesFallback) { + session->fallbackExecutionMutex.unlock(); + } else { + entered.leaveHook(mainState, entered.hookContext); + } + return 0; + } catch (...) { + return 0; + } +} + +void LeaveState(lua_State* state) noexcept { + if (lua_glue::detail_internal::enteredSessions.empty()) { + return; + } + const lua_glue::detail_internal::EnteredSession& active = + lua_glue::detail_internal::enteredSessions.back(); + if (state != active.state && state != active.requestedState) { + return; + } + lua_State* mainState = active.state; + lua_glue::detail_internal::EnteredSession entered = + std::move(lua_glue::detail_internal::enteredSessions.back()); + lua_glue::detail_internal::enteredSessions.pop_back(); + if (entered.usesFallback) { + entered.session->fallbackExecutionMutex.unlock(); + } else { + entered.leaveHook(mainState, entered.hookContext); + } +} + +int TakeDeferredCallbackError(lua_State* state, char* buffer, + std::size_t capacity) { + if (state == nullptr || buffer == nullptr || capacity == 0) { + return 0; + } + const lua_glue::detail_internal::ResolvedSession resolved = + lua_glue::detail_internal::resolveSession(state); + const std::shared_ptr& session = + resolved.session; + if (session == nullptr) { + return 0; + } + return session->deferredCallbackErrors.dequeue(buffer, capacity) ? 1 : 0; +} + +void QuiesceState(lua_State* state) noexcept { + const lua_glue::detail_internal::ResolvedSession resolved = + lua_glue::detail_internal::findResolvedSession(state); + lua_State* mainState = resolved.mainState; + const std::shared_ptr& session = + resolved.session; + if (session == nullptr) { + return; + } + std::unordered_map callbacks; + { + std::scoped_lock lock(session->metadataMutex); + if (session->state != mainState || session->quiescing) { + return; + } + session->quiescing = true; + callbacks.swap(session->quiesceCallbacks); + } + for (const auto& [owner, callback] : callbacks) { + static_cast(owner); + callback(); + } +} + +void ShutdownState(lua_State* state) { + const lua_glue::detail_internal::ResolvedSession resolved = + lua_glue::detail_internal::resolveSession(state); + lua_State* mainState = resolved.mainState; + const std::shared_ptr& session = + resolved.session; + if (session == nullptr) { + return; + } + QuiesceState(mainState); + lua_glue::ExecutionScope execution(mainState); + if (!execution.active()) { + return; + } + lua_glue::detail_internal::LuaStatePhase expected = + lua_glue::detail_internal::LuaStatePhase::running; + if (!session->phase.compare_exchange_strong( + expected, lua_glue::detail_internal::LuaStatePhase::stopping, + std::memory_order_acq_rel, std::memory_order_acquire)) { + return; + } + { + std::scoped_lock lock(session->metadataMutex); + session->retainedObjects.clear(); + for (const int reference : session->registryReferences) { + luaL_unref(mainState, LUA_REGISTRYINDEX, reference); + } + session->registryReferences.clear(); + session->state = nullptr; + session->phase.store(lua_glue::detail_internal::LuaStatePhase::stopped, + std::memory_order_release); + } + std::scoped_lock lock(lua_glue::detail_internal::sessionsMutex); + const auto item = lua_glue::detail_internal::sessions.find(mainState); + if (item != lua_glue::detail_internal::sessions.end() && + item->second == session) { + for (auto alias = lua_glue::detail_internal::stateAliases.begin(); + alias != lua_glue::detail_internal::stateAliases.end();) { + if (alias->second == mainState) { + alias = lua_glue::detail_internal::stateAliases.erase(alias); + } else { + ++alias; + } + } + lua_glue::detail_internal::sessions.erase(item); + } +} + +} // namespace lua_glue diff --git a/LuaGlue/src/Native.cpp b/LuaGlue/src/Native.cpp new file mode 100644 index 0000000..033d8bf --- /dev/null +++ b/LuaGlue/src/Native.cpp @@ -0,0 +1,752 @@ +#include "NativeImpl.hpp" + +#include +#include + +extern "C" { +#include +#include +} + +#include +#include +#include + +namespace { + +constexpr const char* typesKey = "LuaGlue.Types.v1"; +char nativeTypeKey; +char externalResolversKey; +constexpr const char* recordMetatable = "LuaGlue.TypeRecord.v1"; + +lua_glue::detail::TypeRecord* closureType(lua_State* state) { + return static_cast( + lua_touserdata(state, lua_upvalueindex(1))); +} + +lua_glue::detail::PropertyRecord* findProperty( + lua_glue::detail::TypeRecord* type, std::string_view key, bool isStatic) { + auto& properties = isStatic ? type->staticProperties : type->properties; + auto found = properties.find(std::string(key)); + if (found != properties.end()) { + return &found->second; + } + for (auto& base : type->bases) { + if (!base.visible || !base.type) { + continue; + } + if (auto* property = findProperty(base.type, key, isStatic)) { + return property; + } + } + return nullptr; +} + +bool pushMember(lua_State* state, lua_glue::detail::TypeRecord* type, + int keyIndex) { + keyIndex = lua_absindex(state, keyIndex); + lua_getfield(state, LUA_REGISTRYINDEX, type->classRegistryName.c_str()); + lua_pushvalue(state, keyIndex); + lua_rawget(state, -2); + lua_remove(state, -2); + if (!lua_isnil(state, -1)) { + return true; + } + lua_pop(state, 1); + for (auto& base : type->bases) { + if (!base.visible || !base.type) { + continue; + } + if (pushMember(state, base.type, keyIndex)) { + return true; + } + } + return false; +} + +int nativeIndex(lua_State* state) { + auto* type = closureType(state); + const char* key = + lua_type(state, 2) == LUA_TSTRING ? lua_tostring(state, 2) : nullptr; + auto* property = key ? findProperty(type, key, false) : nullptr; + if (property && property->getter) { + lua_glue::detail::PushFunction(state, property->getter); + lua_pushvalue(state, 1); + lua_call(state, 1, 1); + return 1; + } + if (pushMember(state, type, 2)) { + return 1; + } + // Per-instance dependency storage is separate from bound C++ fields. + // Unknown reads deliberately remain nil, matching ordinary Lua table + // lookup. + lua_pushnil(state); + return 1; +} + +int nativeNewIndex(lua_State* state) { + auto* type = closureType(state); + const char* key = + lua_type(state, 2) == LUA_TSTRING ? lua_tostring(state, 2) : nullptr; + auto* property = key ? findProperty(type, key, false) : nullptr; + if (!property || !property->setter) { + return luaL_error(state, "%s.%s is not a writable instance property", + type->luaName.c_str(), key ? key : "?"); + } + if (lua_glue::NativeIsConst(state, 1)) { + return luaL_error(state, "cannot modify a const %s", + type->luaName.c_str()); + } + lua_glue::detail::PushFunction(state, property->setter); + lua_pushvalue(state, 1); + lua_pushvalue(state, 3); + lua_call(state, 2, 0); + return 0; +} + +int classIndex(lua_State* state) { + auto* type = closureType(state); + const char* key = + lua_type(state, 2) == LUA_TSTRING ? lua_tostring(state, 2) : nullptr; + auto* property = key ? findProperty(type, key, true) : nullptr; + if (property && property->getter) { + lua_glue::detail::PushFunction(state, property->getter); + lua_call(state, 0, 1); + return 1; + } + for (auto& base : type->bases) { + if (!base.visible || !base.type) { + continue; + } + if (pushMember(state, base.type, 2)) { + return 1; + } + } + if (lua_getmetatable(state, 1)) { + lua_getfield(state, -1, "__LuaGlueFallbackIndex"); + if (lua_isfunction(state, -1)) { + lua_pushvalue(state, 1); + lua_pushvalue(state, 2); + lua_call(state, 2, 1); + return 1; + } + if (lua_istable(state, -1)) { + lua_pushvalue(state, 2); + lua_gettable(state, -2); + return 1; + } + } + lua_pushnil(state); + return 1; +} + +int classNewIndex(lua_State* state) { + auto* type = closureType(state); + const char* key = + lua_type(state, 2) == LUA_TSTRING ? lua_tostring(state, 2) : nullptr; + auto* property = key ? findProperty(type, key, true) : nullptr; + if (!property) { + if (lua_getmetatable(state, 1)) { + lua_getfield(state, -1, "__LuaGlueFallbackNewIndex"); + if (lua_isfunction(state, -1)) { + lua_pushvalue(state, 1); + lua_pushvalue(state, 2); + lua_pushvalue(state, 3); + lua_call(state, 3, 0); + return 0; + } + if (lua_istable(state, -1)) { + lua_pushvalue(state, 2); + lua_pushvalue(state, 3); + lua_settable(state, -3); + return 0; + } + } + lua_pushvalue(state, 2); + lua_pushvalue(state, 3); + lua_rawset(state, 1); + return 0; + } + if (!property->setter) { + return luaL_error(state, "%s.%s is read-only", type->luaName.c_str(), + key); + } + lua_glue::detail::PushFunction(state, property->setter); + lua_pushvalue(state, 3); + lua_call(state, 1, 0); + return 0; +} + +int nativeGc(lua_State* state) noexcept { + auto* storage = lua_glue::detail::GetNativeStorage(state, 1); + if (storage) { + storage->magic = 0; + std::destroy_at(storage); + } + return 0; +} + +int typeGc(lua_State* state) noexcept { + auto* record = static_cast( + luaL_testudata(state, 1, recordMetatable)); + if (record) { + for (auto* properties : + {&record->properties, &record->staticProperties}) { + for (auto& [name, property] : *properties) { + for (const auto& function : + {property.getter, property.setter}) { + if (function && function->cachedReference >= 0) { + luaL_unref(state, LUA_REGISTRYINDEX, + function->cachedReference); + function->cachedReference = LUA_NOREF; + } + } + } + } + std::destroy_at(record); + } + return 0; +} + +int typeIs(lua_State* state) { + auto* type = closureType(state); + lua_pushboolean( + state, lua_glue::NativePointer(state, 1, type->cppName) != nullptr); + return 1; +} + +template +int protectedEntry(lua_State* state) noexcept { + char error[2048]{}; + try { + return Function(state); + } catch (const std::exception& exception) { + std::snprintf(error, sizeof(error), "%s", exception.what()); + } catch (...) { + std::snprintf(error, sizeof(error), + "unknown native type operation error"); + } + lua_pushstring(state, error); + return lua_error(state); +} + +void pushTypeFunction(lua_State* state, lua_glue::detail::TypeRecord* type, + lua_CFunction function) { + lua_pushlightuserdata(state, type); + lua_pushcclosure(state, function, 1); +} + +struct RegisterContext { + lua_glue::detail::TypeRecord* record; + bool transferred = false; +}; + +int createType(lua_State* state) { + auto* context = static_cast(lua_touserdata(state, 1)); + lua_getfield(state, LUA_REGISTRYINDEX, typesKey); + if (lua_isnil(state, -1)) { + lua_pop(state, 1); + lua_newtable(state); + lua_pushvalue(state, -1); + lua_setfield(state, LUA_REGISTRYINDEX, typesKey); + } + const int types = lua_absindex(state, -1); + if (luaL_newmetatable(state, recordMetatable)) { + lua_pushcfunction(state, typeGc); + lua_setfield(state, -2, "__gc"); + } + const int recordMeta = lua_absindex(state, -1); + // All non-trivial initialization occurs before any further allocating Lua + // operation. The type registry owns the record for the rest of this VM. + void* memory = + lua_newuserdatauv(state, sizeof(lua_glue::detail::TypeRecord), 0); + auto* record = + std::construct_at(static_cast(memory), + std::move(*context->record)); + context->record = record; + context->transferred = true; + lua_pushvalue(state, recordMeta); + lua_setmetatable(state, -2); + lua_setfield(state, types, record->cppName.c_str()); + + lua_newtable(state); + const int typeTable = lua_absindex(state, -1); + lua_pushvalue(state, typeTable); + lua_setfield(state, LUA_REGISTRYINDEX, record->classRegistryName.c_str()); + lua_pushboolean(state, record->valueType); + lua_setfield(state, typeTable, "__valueType"); + for (const char* name : + {"__getters", "__setters", "__nativeProperties", "__nativeBases"}) { + lua_newtable(state); + lua_setfield(state, typeTable, name); + } + lua_newtable(state); + const int typeInfo = lua_absindex(state, -1); + lua_pushlstring(state, record->cppName.data(), record->cppName.size()); + lua_setfield(state, typeInfo, "name"); + pushTypeFunction(state, record, protectedEntry); + lua_setfield(state, typeInfo, "is"); + lua_pushvalue(state, typeInfo); + lua_setfield(state, typeTable, "__type"); + + lua_newtable(state); + lua_pushlightuserdata(state, record); + lua_setfield(state, -2, "__LuaGlueType"); + lua_pushlightuserdata(state, record); + lua_rawsetp(state, -2, &nativeTypeKey); + lua_pushvalue(state, typeInfo); + lua_setfield(state, -2, "__type"); + pushTypeFunction(state, record, protectedEntry); + lua_setfield(state, -2, "__index"); + pushTypeFunction(state, record, protectedEntry); + lua_setfield(state, -2, "__newindex"); + lua_setmetatable(state, typeTable); + + luaL_newmetatable(state, record->metatableName.c_str()); + lua_pushlightuserdata(state, record); + lua_setfield(state, -2, "__LuaGlueType"); + lua_pushlightuserdata(state, record); + lua_rawsetp(state, -2, &nativeTypeKey); + lua_pushboolean(state, true); + lua_setfield(state, -2, "__LuaGlueNative"); + lua_pushvalue(state, typeInfo); + lua_setfield(state, -2, "__type"); + lua_pushcfunction(state, nativeGc); + lua_setfield(state, -2, "__gc"); + pushTypeFunction(state, record, protectedEntry); + lua_setfield(state, -2, "__index"); + pushTypeFunction(state, record, protectedEntry); + lua_setfield(state, -2, "__newindex"); + lua_pushlstring(state, record->luaName.data(), record->luaName.size()); + lua_setfield(state, -2, "__name"); + lua_pushvalue(state, typeTable); + record->ready = true; + return 1; +} + +struct PushContext { + lua_glue::detail::TypeRecord* type; + void* pointer; + std::shared_ptr* owner; + bool isConst; +}; + +int createPropertyScope(lua_State* state) { + auto* context = static_cast(lua_touserdata(state, 1)); + if (luaL_newmetatable(state, recordMetatable)) { + lua_pushcfunction(state, typeGc); + lua_setfield(state, -2, "__gc"); + } + const int recordMeta = lua_absindex(state, -1); + lua_newtable(state); + const int metatable = lua_absindex(state, -1); + if (lua_getmetatable(state, 2)) { + const int old = lua_absindex(state, -1); + lua_pushnil(state); + while (lua_next(state, old)) { + lua_pushvalue(state, -2); + lua_pushvalue(state, -2); + lua_rawset(state, metatable); + lua_pop(state, 1); + } + lua_getfield(state, old, "__index"); + lua_setfield(state, metatable, "__LuaGlueFallbackIndex"); + lua_getfield(state, old, "__newindex"); + lua_setfield(state, metatable, "__LuaGlueFallbackNewIndex"); + lua_pop(state, 1); + } + void* memory = + lua_newuserdatauv(state, sizeof(lua_glue::detail::TypeRecord), 0); + lua_pushvalue(state, recordMeta); + lua_setmetatable(state, -2); + auto* record = + std::construct_at(static_cast(memory), + std::move(*context->record)); + context->record = record; + context->transferred = true; + record->ready = true; + const int recordIndex = lua_absindex(state, -1); + lua_pushvalue(state, recordIndex); + lua_setfield(state, metatable, "__LuaGlueScope"); + lua_pushlightuserdata(state, record); + lua_setfield(state, metatable, "__LuaGlueType"); + lua_pushlightuserdata(state, record); + lua_rawsetp(state, metatable, &nativeTypeKey); + lua_pushvalue(state, recordIndex); + lua_pushcclosure(state, protectedEntry, 1); + lua_setfield(state, metatable, "__index"); + lua_pushvalue(state, recordIndex); + lua_pushcclosure(state, protectedEntry, 1); + lua_setfield(state, metatable, "__newindex"); + for (const char* name : + {"__getters", "__setters", "__nativeProperties", "__nativeBases"}) { + lua_pushstring(state, name); + lua_rawget(state, 2); + if (!lua_istable(state, -1)) { + lua_pop(state, 1); + lua_newtable(state); + lua_pushstring(state, name); + lua_pushvalue(state, -2); + lua_rawset(state, 2); + } + lua_pop(state, 1); + } + lua_pushvalue(state, metatable); + lua_setmetatable(state, 2); + return 0; +} + +int createNative(lua_State* state) { + auto* context = static_cast(lua_touserdata(state, 1)); + lua_getfield(state, LUA_REGISTRYINDEX, + context->type->metatableName.c_str()); + const int metatable = lua_absindex(state, -1); + void* memory = + lua_newuserdatauv(state, sizeof(lua_glue::detail::NativeStorage), 1); + lua_pushvalue(state, metatable); + lua_setmetatable(state, -2); + auto* storage = std::construct_at( + static_cast(memory)); + storage->type = context->type; + storage->pointer = context->pointer; + storage->isConst = context->isConst; + storage->owner = *context->owner; + return 1; +} + +void operation(lua_State* state, lua_CFunction function, void* context, + int results) { + lua_pushcfunction(state, function); + lua_pushlightuserdata(state, context); + if (lua_pcall(state, 1, results, 0) != LUA_OK) { + const char* message = lua_tostring(state, -1); + std::string error = + message ? message : "LuaGlue native operation failed"; + lua_pop(state, 1); + throw std::runtime_error(error); + } +} + +lua_glue::detail::ExternalResolvers* resolvers(lua_State* state) { + lua_rawgetp(state, LUA_REGISTRYINDEX, &externalResolversKey); + auto* result = static_cast( + lua_touserdata(state, -1)); + lua_pop(state, 1); + return result; +} + +struct FindTypeContext { + std::string_view key; + lua_glue::detail::TypeRecord* record = nullptr; +}; +int findType(lua_State* state) { + auto* context = static_cast(lua_touserdata(state, 1)); + lua_getfield(state, LUA_REGISTRYINDEX, typesKey); + if (lua_istable(state, -1)) { + lua_pushlstring(state, context->key.data(), context->key.size()); + lua_rawget(state, -2); + auto* record = static_cast( + luaL_testudata(state, -1, recordMetatable)); + if (record && record->ready) { + context->record = record; + } + } + return 0; +} + +int createResolver(lua_State* state) { + auto* context = static_cast( + lua_touserdata(state, 1)); + auto* memory = + static_cast(lua_newuserdatauv( + state, sizeof(lua_glue::detail::ExternalResolvers), 0)); + std::construct_at(memory, *context); + lua_rawsetp(state, LUA_REGISTRYINDEX, &externalResolversKey); + return 0; +} + +int retainDependency(lua_State* state) { + if (lua_getiuservalue(state, 1, 1) != LUA_TTABLE) { + lua_pop(state, 1); + lua_newtable(state); + lua_pushvalue(state, -1); + lua_setiuservalue(state, 1, 1); + } + lua_getfield(state, -1, "__LuaGlueDependencies"); + if (!lua_istable(state, -1)) { + lua_pop(state, 1); + lua_newtable(state); + lua_pushvalue(state, -1); + lua_setfield(state, -3, "__LuaGlueDependencies"); + } + lua_pushvalue(state, 2); + lua_rawseti(state, -2, static_cast(lua_rawlen(state, -2)) + 1); + return 0; +} + +} // namespace + +namespace lua_glue::detail { + +TypeRecord* PreparePropertyScope(const Table& table) { + lua_State* state = table.lua_state(); + StackGuard stack(state); + table.push(state); + if (auto* existing = GetTypeRecord(state, -1)) { + return existing; + } + auto record = std::make_unique(); + record->luaName = "module"; + RegisterContext context{record.get()}; + if (!lua_checkstack(state, 3)) { + throw std::runtime_error( + "not enough stack to register module properties"); + } + lua_pushcfunction(state, createPropertyScope); + lua_pushlightuserdata(state, &context); + table.push(state); + if (lua_pcall(state, 2, 0, 0) != LUA_OK) { + const char* text = lua_tostring(state, -1); + throw std::runtime_error(text ? text + : "module property registration failed"); + } + return context.record; +} + +TypeRecord* FindType(lua_State* state, std::string_view key) { + FindTypeContext context{key}; + operation(state, findType, &context, 0); + return context.record; +} + +TypeRecord* GetTypeRecord(lua_State* state, int index) { + if (!lua_checkstack(state, 2)) { + throw std::runtime_error("not enough stack for native type lookup"); + } + const int top = lua_gettop(state); + TypeRecord* record = nullptr; + if (lua_getmetatable(state, index)) { + lua_rawgetp(state, -1, &nativeTypeKey); + record = static_cast(lua_touserdata(state, -1)); + } + lua_settop(state, top); + return record; +} + +NativeStorage* GetNativeStorage(lua_State* state, int index) { + if (lua_type(state, index) != LUA_TUSERDATA || + lua_rawlen(state, index) != sizeof(NativeStorage)) { + return nullptr; + } + auto* record = GetTypeRecord(state, index); + if (!record) { + return nullptr; + } + auto* storage = static_cast(lua_touserdata(state, index)); + if (storage->magic != NativeStorage::expectedMagic || + storage->type != record) { + return nullptr; + } + return storage; +} + +void* CastNative(TypeRecord* type, void* pointer, std::string_view key) { + if (!pointer) { + return nullptr; + } + if (key.empty() || type->cppName == key) { + return pointer; + } + for (auto& base : type->bases) { + void* basePointer = base.cast(pointer); + if (key == base.cppName) { + return basePointer; + } + if (void* result = + base.type ? CastNative(base.type, basePointer, key) : nullptr) { + return result; + } + } + return nullptr; +} + +Table RegisterClass(const Table& module, std::string_view name, + std::string_view cppName, bool valueType) { + lua_State* state = module.lua_state(); + if (FindType(state, cppName)) { + Table result = NativeTypeTable(state, cppName); + module.raw_set(name, result); + return result; + } + auto record = std::make_unique(); + record->cppName = cppName; + record->luaName = name; + record->metatableName = "LuaGlue.Native." + std::string(cppName); + record->classRegistryName = "LuaGlue.Class." + std::string(cppName); + record->valueType = valueType; + RegisterContext context{record.get()}; + operation(state, createType, &context, 1); + Table result(state, -1); + lua_pop(state, 1); + module.raw_set(name, result); + return result; +} + +void RegisterBase(const Table& table, std::string_view derivedName, + std::string_view baseName, NativeCast cast) { + auto* derived = FindType(table.lua_state(), derivedName); + auto* base = FindType(table.lua_state(), baseName); + if (!derived || !base) { + throw std::runtime_error( + "LuaGlue: base type must be registered first: " + + std::string(baseName)); + } + for (auto& entry : derived->bases) { + if (entry.cppName == baseName) { + if (entry.visible) { + return; + } + entry.type = base; + entry.visible = true; + table.raw_get
("__nativeBases") + .add(NativeTypeTable(table.lua_state(), baseName)); + return; + } + } + derived->bases.push_back({base, cast, std::string(baseName), true}); + table.raw_get
("__nativeBases") + .add(NativeTypeTable(table.lua_state(), baseName)); +} + +void RegisterCast(const Table& table, std::string_view derivedName, + std::string_view baseName, NativeCast cast) { + auto* derived = FindType(table.lua_state(), derivedName); + if (!derived) { + throw std::runtime_error( + "LuaGlue: derived type must be registered first: " + + std::string(derivedName)); + } + for (const auto& entry : derived->bases) { + if (entry.cppName == baseName) { + return; + } + } + derived->bases.push_back({FindType(table.lua_state(), baseName), cast, + std::string(baseName), false}); +} + +int PushNative(lua_State* state, std::string_view name, void* pointer, + std::shared_ptr owner, bool isConst) { + if (!pointer) { + lua_pushnil(state); + return 1; + } + auto* type = FindType(state, name); + if (!type) { + throw std::runtime_error("LuaGlue: unregistered native type " + + std::string(name)); + } + PushContext context{type, pointer, &owner, isConst}; + operation(state, createNative, &context, 1); + return 1; +} + +} // namespace lua_glue::detail + +namespace lua_glue { + +void* NativePointer(lua_State* state, int index, std::string_view name) { + if (auto* storage = detail::GetNativeStorage(state, index)) { + return detail::CastNative(storage->type, storage->pointer, name); + } + if (auto* external = resolvers(state); external && external->pointer) { + return external->pointer(state, index, name); + } + return nullptr; +} + +std::shared_ptr NativeSharedOwner(lua_State* state, int index, + std::string_view name) { + if (auto* storage = detail::GetNativeStorage(state, index)) { + void* pointer = + detail::CastNative(storage->type, storage->pointer, name); + if (pointer && storage->owner.use_count()) { + return {storage->owner, pointer}; + } + return {}; + } + if (auto* external = resolvers(state); external && external->owner) { + return external->owner(state, index, name); + } + return {}; +} + +bool NativeIsConst(lua_State* state, int index) { + auto* storage = detail::GetNativeStorage(state, index); + return storage && storage->isConst; +} + +Table NativeTypeTable(lua_State* state, std::string_view key) { + auto* record = detail::FindType(state, key); + if (!record) { + return {}; + } + lua_getfield(state, LUA_REGISTRYINDEX, record->classRegistryName.c_str()); + Table result(state, -1); + lua_pop(state, 1); + return result; +} + +Table NativeTypeTable(lua_State* state, int index) { + auto* storage = detail::GetNativeStorage(state, index); + return storage ? NativeTypeTable(state, storage->type->cppName) : Table{}; +} + +void RegisterExternalResolver(lua_State* state, NativeResolver pointer, + SharedOwnerResolver owner) { + if (auto* external = resolvers(state)) { + *external = {pointer, owner}; + return; + } + detail::ExternalResolvers context{pointer, owner}; + operation(state, createResolver, &context, 0); +} + +void RetainDependency(lua_State* state, int dependent, int owner) { + dependent = lua_absindex(state, dependent); + owner = lua_absindex(state, owner); + if (lua_type(state, dependent) != LUA_TUSERDATA || + lua_isnil(state, owner)) { + return; + } + auto* child = detail::GetNativeStorage(state, dependent); + auto* parent = detail::GetNativeStorage(state, owner); + if (child && parent && !child->owner.use_count() && + parent->owner.use_count()) { + child->owner = {parent->owner, child->pointer}; + } + if (!lua_checkstack(state, 3)) { + throw std::runtime_error("not enough Lua stack for object dependency"); + } + lua_pushcfunction(state, retainDependency); + lua_pushvalue(state, dependent); + lua_pushvalue(state, owner); + if (lua_pcall(state, 2, 0, 0) != LUA_OK) { + const char* message = lua_tostring(state, -1); + std::string error = + message ? message : "failed to retain object dependency"; + lua_pop(state, 1); + throw std::runtime_error(error); + } +} + +void AttachSharedOwner(lua_State* state, int index, + const std::shared_ptr& owner) { + auto* storage = detail::GetNativeStorage(state, index); + if (storage && owner.use_count()) { + storage->owner = {owner, storage->pointer}; + } +} + +} // namespace lua_glue diff --git a/LuaGlue/src/NativeImpl.hpp b/LuaGlue/src/NativeImpl.hpp new file mode 100644 index 0000000..f0a9426 --- /dev/null +++ b/LuaGlue/src/NativeImpl.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include +#include +#include + +namespace lua_glue::detail { + +struct FunctionRecord; + +struct PropertyRecord { + std::shared_ptr getter; + std::shared_ptr setter; +}; + +struct TypeRecord { + struct Base { + TypeRecord* type; + NativeCast cast; + std::string cppName; + bool visible; + }; + std::string cppName; + std::string luaName; + std::string metatableName; + std::string classRegistryName; + bool valueType = false; + bool ready = false; + std::vector bases; + std::unordered_map properties; + std::unordered_map staticProperties; +}; + +struct NativeStorage { + static constexpr std::uint64_t expectedMagic = 0x4c5541474c554531ULL; + std::uint64_t magic = expectedMagic; + TypeRecord* type = nullptr; + void* pointer = nullptr; + std::shared_ptr owner; + bool isConst = false; +}; + +struct ExternalResolvers { + NativeResolver pointer = nullptr; + SharedOwnerResolver owner = nullptr; +}; + +TypeRecord* FindType(lua_State*, std::string_view); +TypeRecord* PreparePropertyScope(const Table&); +NativeStorage* GetNativeStorage(lua_State*, int); +void* CastNative(TypeRecord*, void*, std::string_view); +int InvokeFunction(lua_State*, const std::shared_ptr&); +void PushFunction(lua_State*, const std::shared_ptr&); + +} // namespace lua_glue::detail diff --git a/LuaGlue/src/Value.cpp b/LuaGlue/src/Value.cpp new file mode 100644 index 0000000..7dc82cc --- /dev/null +++ b/LuaGlue/src/Value.cpp @@ -0,0 +1,546 @@ +#include + +#include +#include + +namespace lua_glue { +namespace { + +const char errorHandlerKey{}; + +int Traceback(lua_State* state) { + const char* message = lua_tostring(state, 1); + if (message == nullptr) { + message = "Lua error (non-string error object)"; + } + luaL_traceback(state, state, message, 1); + return 1; +} + +int IndexedGet(lua_State* state) { + lua_settop(state, 2); + lua_gettable(state, 1); + return 1; +} + +int IndexedSet(lua_State* state) { + lua_settop(state, 3); + lua_settable(state, 1); + return 0; +} + +int NextEntry(lua_State* state) { + lua_settop(state, 2); + return lua_next(state, 1) ? 2 : 0; +} + +int AssignMetatable(lua_State* state) { + lua_settop(state, 2); + lua_setmetatable(state, 1); + return 0; +} + +int RawSet(lua_State* state) { + lua_settop(state, 3); + lua_rawset(state, 1); + return 0; +} + +int CreateTable(lua_State* state) { + lua_createtable(state, static_cast(lua_tointeger(state, 1)), + static_cast(lua_tointeger(state, 2))); + return 1; +} + +std::string ErrorText(lua_State* state) { + std::size_t length = 0; + const char* text = lua_tolstring(state, -1, &length); + return text ? std::string(text, length) + : "Lua error (non-string error object)"; +} + +void RequireState(lua_State* state, const detail::AccessScope& scope) { + if (state == nullptr || !scope.active()) { + throw std::runtime_error("Lua state is unavailable or stopping"); + } +} + +void CheckObjectType(const Object& object, Type expected) { + const Type actual = object.get_type(); + if (actual != expected && actual != Type::Nil && actual != Type::None) { + throw std::invalid_argument(std::string("Expected Lua ") + + TypeName(expected) + ", received " + + TypeName(actual)); + } +} + +} // namespace + +const char* TypeName(Type type) noexcept { + switch (type) { + case Type::None: + return "no value"; + case Type::Nil: + return "nil"; + case Type::Boolean: + return "boolean"; + case Type::Number: + return "number"; + case Type::String: + return "string"; + case Type::Table: + return "table"; + case Type::Function: + return "function"; + case Type::Userdata: + return "userdata"; + case Type::LightUserdata: + return "light userdata"; + case Type::Thread: + return "thread"; + } + return "unknown"; +} + +StackGuard::StackGuard(lua_State* state) noexcept + : state_(state), top_(state ? lua_gettop(state) : 0) {} +StackGuard::~StackGuard() { + if (state_) { + lua_settop(state_, top_); + } +} + +Object::Object(lua_State* state, int index) { + if (!state) { + throw std::invalid_argument("Lua value requires a state"); + } + if (InitializeState(state) != 0) { + throw std::runtime_error("Lua state is stopping"); + } + reference_ = RegistryReference(state, index); + type_ = static_cast(lua_type(state, index)); + if (type_ == Type::None) { + type_ = Type::Nil; + } +} + +Object::Object(const FieldRef& field) : Object(field.object()) {} + +bool Object::valid() const noexcept { + Type type = get_type(); + return type != Type::None && type != Type::Nil; +} + +Type Object::get_type() const noexcept { + return lua_state() ? type_ : Type::None; +} + +int Object::push(lua_State* target) const { + if (!target) { + target = lua_state(); + } + if (!target) { + return 0; + } + if (!reference_) { + lua_pushnil(target); + return 1; + } + if (!reference_.push(target)) { + throw std::runtime_error( + "Cannot push a Lua value after its state stopped"); + } + return 1; +} + +bool operator==(const Object& left, const Object& right) { + if (left == nil && right == nil) { + return true; + } + return left.reference_.equals(right.reference_); +} + +Table Object::metatable() const { + return GetMetatable(*this); +} +void Object::set_metatable(const Table& table) const { + SetMetatable(*this, table); +} + +Table::Table(lua_State* state, int index) : Object(state, index) { + CheckObjectType(*this, Type::Table); +} +Table::Table(const Object& value) : Object(value) { + CheckObjectType(*this, Type::Table); +} +Table::Table(Object&& value) : Object(std::move(value)) { + CheckObjectType(*this, Type::Table); +} +Table::Table(const FieldRef& field) : Table(field.object()) {} + +std::size_t Table::size() const { + lua_State* state = lua_state(); + detail::AccessScope execution(state); + RequireState(state, execution); + StackGuard stack(state); + push(state); + return lua_rawlen(state, -1); +} + +bool Table::empty() const { + return begin() == end(); +} +Table::Iterator Table::begin() const { + return Iterator(*this); +} +Table::Iterator::Iterator(const Table& table) : table_(table), done_(false) { + ++*this; +} +Table::Iterator& Table::Iterator::operator++() { + if (done_) { + return *this; + } + lua_State* state = table_.lua_state(); + detail::AccessScope execution(state); + RequireState(state, execution); + StackGuard stack(state); + lua_pushcfunction(state, NextEntry); + table_.push(state); + value_.first.push(state); + if (lua_pcall(state, 2, LUA_MULTRET, 0) != LUA_OK) { + throw std::runtime_error(ErrorText(state)); + } + if (lua_gettop(state) - stack.top() == 2) { + value_ = {Object(state, -2), Object(state, -1)}; + } else { + value_ = {}; + done_ = true; + } + return *this; +} + +FieldRef& FieldRef::operator=(const FieldRef& value) { + SetIndex(parent_, key_, value.object()); + return *this; +} +FieldRef& FieldRef::operator=(FieldRef&& value) { + return *this = static_cast(value); +} +Object FieldRef::object() const { + return GetIndex(parent_, key_); +} + +int StackValue::push(lua_State* target) const { + if (!state_) { + throw std::invalid_argument( + "Cannot push an empty borrowed stack value"); + } + if (!target) { + target = state_; + } + if (target == state_) { + lua_pushvalue(state_, index_); + } else { + Object(state_, index_).push(target); + } + return 1; +} + +Arguments::Arguments(lua_State* state, int first, int count) noexcept + : state_(state), + first_(state ? lua_absindex(state, first) : 1), + count_(state ? std::max( + 0, count < 0 ? lua_gettop(state) - first_ + 1 : count) + : 0) {} +StackValue Arguments::operator[](std::size_t index) const { + if (index >= size()) { + throw std::out_of_range("Lua argument index out of range"); + } + return StackValue(state_, first_ + static_cast(index)); +} +Arguments Arguments::slice(std::size_t offset) const { + if (offset > size()) { + throw std::out_of_range("Lua argument slice out of range"); + } + return Arguments(state_, first_ + static_cast(offset), + count_ - static_cast(offset)); +} +int Arguments::push(lua_State* target) const { + if (!target) { + target = state_; + } + if (target == nullptr || !lua_checkstack(target, count_)) { + throw std::runtime_error("Lua argument stack cannot grow"); + } + for (int index = 0; index < count_; ++index) { + StackValue(state_, first_ + index).push(target); + } + return count_; +} +int MultipleResults::push(lua_State* target) const { + if (size() > static_cast(std::numeric_limits::max()) || + !lua_checkstack(target, static_cast(size()))) { + throw std::runtime_error("Lua result stack cannot grow"); + } + for (const auto& value : *this) { + value.push(target); + } + return static_cast(size()); +} + +void SetErrorHandler(lua_State* state, lua_CFunction handler) { + StateView initialized(state); + detail::AccessScope execution(state); + RequireState(state, execution); + detail::ProtectedCallOperation( + state, + [](lua_State* inner) -> int { + auto* handler = + static_cast(lua_touserdata(inner, 1)); + if (*handler) { + lua_pushcfunction(inner, *handler); + } else { + lua_pushnil(inner); + } + lua_rawsetp(inner, LUA_REGISTRYINDEX, &errorHandlerKey); + return 0; + }, + &handler); +} + +int ProtectedStackCall(lua_State* state, int argumentCount, int resultCount) { + if (!state || argumentCount < 0 || lua_gettop(state) < argumentCount + 1) { + throw std::invalid_argument("Invalid Lua protected-call stack"); + } + if (!lua_checkstack(state, 1)) { + throw std::runtime_error("Lua protected-call stack cannot grow"); + } + const int functionIndex = lua_gettop(state) - argumentCount; + lua_rawgetp(state, LUA_REGISTRYINDEX, &errorHandlerKey); + if (!lua_isfunction(state, -1)) { + lua_pop(state, 1); + lua_pushcfunction(state, Traceback); + } + lua_insert(state, functionIndex); + const int status = + lua_pcall(state, argumentCount, resultCount, functionIndex); + if (status != LUA_OK) { + std::string error = ErrorText(state); + lua_settop(state, functionIndex - 1); + throw std::runtime_error(error); + } + const int count = lua_gettop(state) - functionIndex; + lua_remove(state, functionIndex); + return count; +} + +CallResult ProtectedCall(lua_State* state, int argumentCount, int resultCount) { + if (!state || argumentCount < 0 || lua_gettop(state) < argumentCount + 1) { + return CallResult("Invalid Lua protected-call stack"); + } + const int originalTop = lua_gettop(state) - argumentCount - 1; + try { + const int count = ProtectedStackCall(state, argumentCount, resultCount); + std::vector results; + results.reserve(count); + for (int index = 1; index <= count; ++index) { + results.emplace_back(state, originalTop + index); + } + lua_settop(state, originalTop); + return CallResult(std::move(results)); + } catch (const std::exception& error) { + lua_settop(state, originalTop); + return CallResult(error.what()); + } +} + +namespace detail { +void ProtectedCallOperation(lua_State* state, lua_CFunction operation, + void* context, int resultCount) { + if (!state || !operation || !lua_checkstack(state, 3)) { + throw std::runtime_error("Lua operation stack cannot grow"); + } + const int originalTop = lua_gettop(state); + lua_pushcfunction(state, operation); + lua_pushlightuserdata(state, context); + if (lua_pcall(state, 1, resultCount, 0) != LUA_OK) { + std::string error = ErrorText(state); + lua_settop(state, originalTop); + throw std::runtime_error(error); + } +} +} // namespace detail + +Function::Function(lua_State* state, int index) : Object(state, index) { + CheckObjectType(*this, Type::Function); +} +Function::Function(const Object& object) : Object(object) { + CheckObjectType(*this, Type::Function); +} +Function::Function(Object&& object) : Object(std::move(object)) { + CheckObjectType(*this, Type::Function); +} +Function::Function(const FieldRef& field) : Function(field.object()) {} + +StateView::StateView(lua_State* state) : state_(state) { + if (InitializeState(state_) != 0) { + throw std::runtime_error("Lua state is unavailable or stopping"); + } +} +Table StateView::globals() const { + detail::AccessScope execution(state_); + RequireState(state_, execution); + StackGuard stack(state_); + lua_pushglobaltable(state_); + return Table(state_, -1); +} +Table StateView::registry() const { + detail::AccessScope execution(state_); + RequireState(state_, execution); + return Table(state_, LUA_REGISTRYINDEX); +} +Table StateView::create_table(int arraySize, int mapSize) const { + if (arraySize < 0 || mapSize < 0) { + throw std::invalid_argument("Lua table capacity must not be negative"); + } + detail::AccessScope execution(state_); + RequireState(state_, execution); + StackGuard stack(state_); + lua_pushcfunction(state_, CreateTable); + lua_pushinteger(state_, arraySize); + lua_pushinteger(state_, mapSize); + if (lua_pcall(state_, 2, 1, 0) != LUA_OK) { + throw std::runtime_error(ErrorText(state_)); + } + return Table(state_, -1); +} +CallResult StateView::load(std::string_view source, + std::string_view label) const { + detail::AccessScope execution(state_); + if (!execution.active()) { + return CallResult("Lua state is stopping"); + } + StackGuard stack(state_); + std::string name(label); + if (luaL_loadbuffer(state_, source.data(), source.size(), name.c_str()) != + LUA_OK) { + return CallResult(ErrorText(state_)); + } + return CallResult(std::vector{Object(state_, -1)}); +} +CallResult StateView::script(std::string_view source, + std::string_view label) const { + CallResult loaded = load(source, label); + if (!loaded.valid()) { + return loaded; + } + return Function(loaded.values().front()).call(); +} + +Table GetMetatable(const Object& value) { + lua_State* state = value.lua_state(); + detail::AccessScope execution(state); + RequireState(state, execution); + StackGuard stack(state); + value.push(state); + if (!lua_getmetatable(state, -1)) { + lua_pushnil(state); + } + return Table(state, -1); +} +void SetMetatable(const Object& value, const Table& metatable) { + lua_State* state = value.lua_state(); + detail::AccessScope execution(state); + RequireState(state, execution); + StackGuard stack(state); + lua_pushcfunction(state, AssignMetatable); + value.push(state); + metatable.push(state); + if (lua_pcall(state, 2, 0, 0) != LUA_OK) { + throw std::runtime_error(ErrorText(state)); + } +} +Object GetIndex(const Object& value, const Object& key, bool raw) { + lua_State* state = value.lua_state(); + detail::AccessScope execution(state); + RequireState(state, execution); + StackGuard stack(state); + key.push(state); + detail::PushIndex(value, -1, raw); + return Object(state, -1); +} +void SetIndex(const Object& value, const Object& key, const Object& entry, + bool raw) { + lua_State* state = value.lua_state(); + detail::AccessScope execution(state); + RequireState(state, execution); + StackGuard stack(state); + key.push(state); + entry.push(state); + detail::AssignIndex(value, -2, -1, raw); +} + +namespace detail { +void PushIndex(const Object& value, int keyIndex, bool raw) { + lua_State* state = value.lua_state(); + if (!state || !lua_checkstack(state, 3)) { + throw std::runtime_error("Lua index stack cannot grow"); + } + keyIndex = lua_absindex(state, keyIndex); + if (raw) { + value.push(state); + if (!lua_istable(state, -1)) { + throw std::invalid_argument("Raw Lua indexing requires a table"); + } + lua_pushvalue(state, keyIndex); + lua_rawget(state, -2); + lua_remove(state, -2); + } else { + lua_pushcfunction(state, IndexedGet); + value.push(state); + lua_pushvalue(state, keyIndex); + if (lua_pcall(state, 2, 1, 0) != LUA_OK) { + throw std::runtime_error(ErrorText(state)); + } + } +} +void AssignIndex(const Object& value, int keyIndex, int entryIndex, bool raw) { + lua_State* state = value.lua_state(); + if (!state || !lua_checkstack(state, 4)) { + throw std::runtime_error("Lua index stack cannot grow"); + } + keyIndex = lua_absindex(state, keyIndex); + entryIndex = lua_absindex(state, entryIndex); + lua_pushcfunction(state, raw ? RawSet : IndexedSet); + value.push(state); + if (raw && !lua_istable(state, -1)) { + throw std::invalid_argument("Raw Lua assignment requires a table"); + } + lua_pushvalue(state, keyIndex); + lua_pushvalue(state, entryIndex); + if (lua_pcall(state, 3, 0, 0) != LUA_OK) { + throw std::runtime_error(ErrorText(state)); + } +} +} // namespace detail + +PushGuard::PushGuard(const Object& value) : state_(value.lua_state()) { + if (!state_) { + throw std::runtime_error( + "Cannot borrow a value from a closed Lua state"); + } + index_ = lua_gettop(state_) + 1; + value.push(state_); +} +PushGuard::~PushGuard() { + if (state_) { + lua_settop(state_, index_ - 1); + } +} +PopGuard::~PopGuard() { + if (state_ && count_ > 0) { + lua_pop(state_, count_); + } +} + +} // namespace lua_glue diff --git a/LuaSF.cpp.in b/LuaSF.cpp.in index cb91fba..478ab2f 100644 --- a/LuaSF.cpp.in +++ b/LuaSF.cpp.in @@ -2,7 +2,9 @@ #include "LuaSF.hpp" -#include +#include +#include +#include #include "lua_stub.hpp" @@ -25,13 +27,26 @@ extern "C" LUASF_API int {module_name}_register(lua_State* state) return 1; }} - sol::state_view lua(state); - sol::table sf = lua["sf"].get_or_create(); - (void)sf; + try + {{ + lua_glue::StateView lua(state); + lua_glue::Table sf = lua["sf"].get_or_create(); + (void)sf; {bind_calls} - return 0; + return 0; + }} + catch (const std::exception& error) + {{ + std::fprintf(stderr, "{module_name} registration failed: %s\n", error.what()); + return 1; + }} + catch (...) + {{ + std::fprintf(stderr, "{module_name} registration failed: unknown C++ exception\n"); + return 1; + }} }} extern "C" LUASF_API int {module_name}_write_stub(lua_State* state, diff --git a/LuaSF.hpp.in b/LuaSF.hpp.in index 87b1e2e..a4dcfe4 100644 --- a/LuaSF.hpp.in +++ b/LuaSF.hpp.in @@ -2,6 +2,8 @@ #include +#include "LuaSFValueTraits.hpp" + #if defined(_WIN32) #if defined(LUASF_BUILD_DLL) #define LUASF_API __declspec(dllexport) diff --git a/README.md b/README.md index 46b3bec..beb4e20 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ English | [简体中文](README_zh_CN.md) -LuaSF AutoGenerator generates a CMake-based Lua module that exposes SFML to Lua through sol2. The generated module can be consumed either from source or from a collected binary package. +LuaSF AutoGenerator generates a CMake-based Lua module that exposes SFML to Lua through LuaGlue. The generated module can be consumed either from source or from a collected binary package. The generated Lua module is named `LuaSF`. In CMake, consumers link against `LuaSF::LuaSF`. -LuaSF is a binder only. It does not bundle Lua, sol2 or SFML, and it does not create or close the `lua_State`: the host owns the VM and hands it to LuaSF through `LuaSF_register`. +LuaSF is a binder only. It ships the independent LuaGlue runtime. Lua and SFML are supplied by the host, which also owns the `lua_State`: the host hands the VM to LuaSF through `LuaSF_register`. ## Requirements @@ -50,7 +50,7 @@ build.bat Release ME sh build.sh Release ME ``` -This creates the generated source project under `output/` and builds the embedded LuaSF dynamic library, the host `luac`, and the Lua language-server stub. +This creates the generated source project under `output/LuaSF/`, alongside `output/LuaGlue/`, and builds the embedded LuaSF dynamic library, the host `luac`, and the Lua language-server stub. Switching variants replaces `third_party/SFML`; `init` and `build` both verify that the checked-out SFML matches the requested variant before doing any work. @@ -78,6 +78,8 @@ pack_result.bat sh pack_result.sh ``` +To package generated sources without collecting binaries, run `sh pack_result.sh --source-only` or `pack_result.bat --source-only`. This preserves other archives in `output/packages/`; for an `ME-OH` workspace it creates `LuaSF-source-ME`, since both variants share the binding API. The source archive contains only the `LuaSF/` and `LuaGlue/` projects. + Archives are written to `output/packages/`: | Variant | Source archive | Embedded archive | @@ -88,16 +90,18 @@ Archives are written to `output/packages/`: `pack_result.sh` creates `.tar.gz` archives. `pack_result.bat` creates `.zip` archives. -ME and ME-OH share the same binding interfaces, so ME-OH builds only produce an embedded archive. The ME source archive supports both variants; source archives do not include SFML. +ME and ME-OH share the same binding interfaces, so default ME-OH packaging produces only an embedded archive. The ME source archive supports both variants; source archives do not include SFML. + +To build without pushing a tag, open **Actions → Build packages → Run workflow** on GitHub and select the branch to build. This runs the Windows VS2022 x64 and macOS arm64 matrices for the default, ME, and ME-OH variants, checks the packages, and uploads downloadable Actions artifacts. It does not create a Release. GitHub requires the manually runnable workflow to be present on the default branch before this entry is available. -CI runs only when a `v*` tag is pushed. After all builds and package checks pass, it creates a draft Release with ten assets: four source archives and six embedded archives. Ordinary branch pushes do not run CI, and the workflow does not publish the draft or overwrite an existing Release. +Pushing a `v*` tag runs the release workflow. After all builds and package checks pass, it creates a draft Release with ten assets: four source archives and six embedded archives. Ordinary branch pushes do not run CI, and the workflow does not publish the draft or overwrite an existing Release. ## Use From A CMake Project There are two supported integration styles: - **Packaged integration**: copy `output/result/embedded/` into your project, for example as `LuaSF/`. -- **Source integration**: copy or vendor the generated `output/` source project into your project, for example as `LuaSF/`. +- **Source integration**: copy or vendor the generated `output/LuaSF/` and `output/LuaGlue/` source projects into your project as sibling directories. The CMake integration target for embedded use is `LuaSF::LuaSF`. @@ -129,12 +133,11 @@ luasf_copy_runtime_dlls(SFLua) ### Source Integration -Use this when you have copied or vendored the generated `output/` source project into your project. +Use this when you have copied or vendored the generated `output/LuaSF/` and `output/LuaGlue/` source projects as siblings into your project. -The source project has no vendored dependencies. Point it at your own Lua, sol2 and SFML checkouts through the `LUASF_LUA_ROOT`, `LUASF_SOL2_ROOT` and `LUASF_SFML_ROOT` cache variables; all three are required: +The source archive extracts directly to `LuaSF/` and `LuaGlue/`. LuaSF reuses an existing `LuaGlue::LuaGlue` target or adds the sibling project; `LUASF_GLUE_ROOT` can override that path. Set the required `LUASF_LUA_ROOT` and `LUASF_SFML_ROOT` cache variables to your Lua and SFML checkouts: - `LUASF_LUA_ROOT` — Lua source directory containing `src/lua.h`. -- `LUASF_SOL2_ROOT` — sol2 source directory containing `include/sol2/sol.hpp`. LuaSF needs the `sol2` `optional::emplace` fix from `cmake/sol/pr1606.patch`, which the LuaSF repository applies to its own checkout; apply it to yours as well, or your build will not compile. - `LUASF_SFML_ROOT` — SFML source project. When your project already provides the `sfml-*` targets, LuaSF reuses them instead of adding the directory again. ```cmake @@ -144,7 +147,6 @@ project(SFLua LANGUAGES C CXX) set(LUASF_LUA_STUB_OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/Scripts/stub/LuaSF.d.lua") set(LUASF_LUA_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/Lua") -set(LUASF_SOL2_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/sol2") set(LUASF_SFML_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/SFML") add_subdirectory(LuaSF) @@ -326,11 +328,15 @@ LuaSF uses the `LUA_RIDX_MAINTHREAD` value as the identity for every registered The host must serialize every Lua entry point—including `lua_pcall`, `luaL_dofile`, and equivalent APIs—with native callbacks. Wrap each entry with a successful `LuaSF_enter_state` and a matching `LuaSF_leave_state`, or install execution hooks and use that same re-entrant/recursive VM lock consistently for host execution. The effect path intentionally uses only the non-blocking try-enter hook. An effect callback must never invoke a producer-waiting lifecycle operation on its own source, because that operation would wait for the current callback itself. +A C++ host may opt in to cheaper nested value access with `lua_glue::SetStateOwnsExecutionHook` after installing its execution hooks. The ownership probe must report whether the current thread actually holds the VM lock; it must not block, allocate, call Lua, re-enter LuaGlue, or change state. Internal value operations reuse an enclosing scope only while this probe confirms ownership and the hook generation is unchanged. Public `ExecutionScope`, `TryExecutionScope`, and enter/leave APIs retain their own entries. Replacing execution hooks clears the ownership probe; reinstall it when appropriate. + Before closing a state, stop callback producers and detach or destroy their processors/factories while the state is still valid, then always perform `LuaSF_quiesce_state` → `LuaSF_shutdown_state` → `lua_close`. Quiesce/shutdown is not a substitute for stopping producer threads, and no callback may access the state after shutdown. ## Runtime Notes -LuaSF is built as a dynamic library. At runtime, the executable must be able to load LuaSF itself, the Lua runtime, and the SFML runtime libraries. Use `luasf_copy_runtime_dlls(target)` or `luasf_copy_runtime_files(target)` to copy the required runtime libraries next to your executable. +LuaSF is built as a dynamic library. At runtime, the executable must be able to load LuaSF itself, the LuaGlue and Lua runtimes, and the SFML runtime libraries. Use `luasf_copy_runtime_dlls(target)` or `luasf_copy_runtime_files(target)` to copy the required runtime libraries next to your executable. + +LuaGlue is a C++20 CMake project that depends only on Lua 5.5 and supports disabled RTTI. It exports `LuaGlue::LuaGlue`; supply `LUAGLUE_LUA_TARGET` to reuse the host Lua target, or let standalone configuration find Lua. Desktop builds share one runtime; mobile builds link it statically. Bindings use `.new(...)`, live instance and static properties, explicit callable signatures, trailing defaults evaluated per call, and per-source `constexpr` docstring arrays. Independently owned value types additionally expose `copy()` and `deepcopy()`; resource types do not gain automatic deep copying. ## License @@ -346,6 +352,5 @@ LuaSF does not ship these dependencies; the version table records which variants | [SFML-ME](https://github.com/JasonLeon01/SFML-ME) | `310ME-iOSJoystick` tag | [zlib/libpng](https://opensource.org/licenses/Zlib) — see `third_party/SFML/license.md` | | [SFML-ME-OH](https://github.com/JasonLeon01/SFML-ME) | `310-ME-OH-GLESVER` tag | [zlib/libpng](https://opensource.org/licenses/Zlib) — see `third_party/SFML/license.md` | | [Lua](https://www.lua.org/) | 5.5.0 | [MIT](https://www.lua.org/license.html) | -| [sol2](https://github.com/ThePhD/sol2) | 3.3.0 | [MIT](https://github.com/ThePhD/sol2/blob/develop/LICENSE.txt) | SFML may also redistribute external libraries under their own licenses; see the SFML documentation and `third_party/SFML/license.md` for details. diff --git a/README_zh_CN.md b/README_zh_CN.md index 7ff0cfe..cebb860 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -2,11 +2,11 @@ [English](README.md) | 简体中文 -LuaSF AutoGenerator 会生成一个基于 CMake 的 Lua 模块,通过 sol2 将 SFML 暴露给 Lua。生成的模块既可以以源码形式接入,也可以以收集后的二进制包形式接入。 +LuaSF AutoGenerator 会生成一个基于 CMake 的 Lua 模块,通过 LuaGlue 将 SFML 暴露给 Lua。生成的模块既可以以源码形式接入,也可以以收集后的二进制包形式接入。 生成的 Lua 模块名为 `LuaSF`。在 CMake 中,嵌入式使用方链接 `LuaSF::LuaSF`。 -LuaSF 只是一个绑定器。它不自带 Lua、sol2 和 SFML,也不创建或关闭 `lua_State`:VM 由宿主持有,并通过 `LuaSF_register` 交给 LuaSF。 +LuaSF 只是一个绑定器。它随源码分发独立的 LuaGlue 运行时,不自带 Lua 和 SFML,也不创建或关闭 `lua_State`:VM 由宿主持有,并通过 `LuaSF_register` 交给 LuaSF。 ## 环境要求 @@ -50,7 +50,7 @@ build.bat Release ME sh build.sh Release ME ``` -这会在 `output/` 下创建生成后的源码工程,并构建嵌入式 LuaSF 动态库、宿主 `luac` 以及 Lua language-server stub。 +这会在 `output/LuaSF/` 下创建生成后的源码工程,在 `output/LuaGlue/` 下复制胶水运行时,并构建嵌入式 LuaSF 动态库、宿主 `luac` 以及 Lua language-server stub。 切换变体会替换 `third_party/SFML`;`init` 和 `build` 都会在开始工作前校验当前检出的 SFML 是否与请求的变体一致。 @@ -78,6 +78,8 @@ pack_result.bat sh pack_result.sh ``` +无需收集二进制、仅打包生成源码时,运行 `sh pack_result.sh --source-only` 或 `pack_result.bat --source-only`。此模式保留 `output/packages/` 中的其他压缩包;在 `ME-OH` 工作区生成 `LuaSF-source-ME`,因为两者共用绑定接口。源码包仅包含 `LuaSF/` 与 `LuaGlue/` 两个工程。 + 压缩包会写入 `output/packages/`: | 变体 | 源码包 | 嵌入式包 | @@ -88,16 +90,18 @@ sh pack_result.sh `pack_result.sh` 生成 `.tar.gz` 压缩包,`pack_result.bat` 生成 `.zip` 压缩包。 -ME 与 ME-OH 的绑定接口相同,因此 ME-OH 构建只生成嵌入式包。ME 源码包同时支持这两个变体;源码包不包含 SFML。 +ME 与 ME-OH 的绑定接口相同,因此默认 ME-OH 打包只生成嵌入式包。ME 源码包同时支持这两个变体;源码包不包含 SFML。 + +无需推送 tag 时,可在 GitHub 打开 **Actions → Build packages → Run workflow**,选择要构建的分支。这会运行 Windows VS2022 x64 与 macOS arm64 的 default、ME、ME-OH 构建矩阵,检查包并上传可下载的 Actions artifacts,不创建 Release。GitHub 要求默认分支中已存在支持手动触发的 workflow,才会提供此入口。 -CI 仅在推送 `v*` tag 时运行。所有构建及包检查通过后,创建包含十个附件的草稿 Release:四个源码包、六个嵌入式包。普通分支推送不运行 CI,工作流不会发布草稿或覆盖已有 Release。 +推送 `v*` tag 会运行发布工作流。所有构建及包检查通过后,创建包含十个附件的草稿 Release:四个源码包、六个嵌入式包。普通分支推送不运行 CI,工作流不会发布草稿或覆盖已有 Release。 ## 从 CMake 工程使用 支持两种接入方式: - **包集成**:将 `output/result/embedded/` 复制到你的工程中,例如复制为 `LuaSF/`。 -- **源码集成**:将生成的 `output/` 源码工程复制或 vendor 到你的工程中,例如复制为 `LuaSF/`。 +- **源码集成**:将生成的 `output/LuaSF/` 与 `output/LuaGlue/` 两个兄弟目录复制或 vendor 到你的工程中。 嵌入式使用的 CMake 集成目标是 `LuaSF::LuaSF`。 @@ -129,12 +133,11 @@ luasf_copy_runtime_dlls(SFLua) ### 源码集成 -当你已经复制或 vendor 了生成的 `output/` 源码工程时,使用这种方式。 +当你已经复制或 vendor 了生成的 `output/LuaSF/` 与 `output/LuaGlue/` 两个兄弟目录时,使用这种方式。 -源码工程不自带依赖。通过 `LUASF_LUA_ROOT`、`LUASF_SOL2_ROOT` 和 `LUASF_SFML_ROOT` 三个 cache 变量指向你自己的 Lua、sol2 和 SFML 检出目录,三者都是必需的: +源码包解压后直接得到 `LuaSF/` 与 `LuaGlue/`。LuaSF 优先复用现有的 `LuaGlue::LuaGlue` target,否则添加兄弟目录工程;可通过 `LUASF_GLUE_ROOT` 覆盖路径。必需的 `LUASF_LUA_ROOT` 与 `LUASF_SFML_ROOT` cache 变量分别指向 Lua 与 SFML 检出: - `LUASF_LUA_ROOT` — Lua 源码目录,包含 `src/lua.h`。 -- `LUASF_SOL2_ROOT` — sol2 源码目录,包含 `include/sol2/sol.hpp`。LuaSF 需要 `cmake/sol/pr1606.patch` 中的 sol2 `optional::emplace` 修复;LuaSF 仓库会对自己那份检出打上补丁,你的检出也需要打,否则编译不过。 - `LUASF_SFML_ROOT` — SFML 源码工程。如果你的工程已经提供 `sfml-*` target,LuaSF 会复用它们,而不会重复 add_subdirectory。 ```cmake @@ -144,7 +147,6 @@ project(SFLua LANGUAGES C CXX) set(LUASF_LUA_STUB_OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/Scripts/stub/LuaSF.d.lua") set(LUASF_LUA_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/Lua") -set(LUASF_SOL2_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/sol2") set(LUASF_SFML_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/SFML") add_subdirectory(LuaSF) @@ -326,12 +328,16 @@ LuaSF 以 `LUA_RIDX_MAINTHREAD` 作为每个已登记 `lua_State*`(包括 coro 宿主必须让所有 Lua 入口(包括 `lua_pcall`、`luaL_dofile` 及同类 API)与 native callback 串行化。每个入口都应在成功的 `LuaSF_enter_state` 后执行并匹配 `LuaSF_leave_state`,或者安装 execution hooks,并让宿主执行始终使用同一把可重入/递归 VM lock。effect 路径有意只使用非阻塞 try-enter hook。effect callback 绝不得对自己所属的 source 调用需等待 producer 的生命周期操作,否则该操作会等待当前 callback 自身。 +C++ 宿主可在安装执行 hooks 后,通过 `lua_glue::SetStateOwnsExecutionHook` 启用嵌套值访问优化。所有权探针必须报告当前线程是否实际持有 VM 锁,不得阻塞、分配内存、调用 Lua、重入 LuaGlue 或修改状态。内部值操作仅在探针确认仍持锁且 hooks 版本未变化时复用外层执行范围。公开的 `ExecutionScope`、`TryExecutionScope` 与 enter/leave API 仍保留各自的进入记录。更换执行 hooks 会清除所有权探针,需要时应重新安装。 + 关闭 state 前必须先停止 callback producer,并在 state 仍有效时解除或销毁它们的 processor/factory,然后严格执行 `LuaSF_quiesce_state` → `LuaSF_shutdown_state` → `lua_close`。quiesce/shutdown 不能替代停止 producer thread;shutdown 之后 callback 不得再访问该 state。 ## 运行时说明 LuaSF 会被构建为动态库。运行时,可执行程序必须能够加载 LuaSF 本身、Lua 运行时和 SFML 运行时库。可以使用 `luasf_copy_runtime_dlls(target)` 或 `luasf_copy_runtime_files(target)` 将所需运行时库复制到可执行文件旁边。 +LuaGlue 是只依赖 Lua 5.5 的 C++20 CMake 工程,支持关闭 RTTI,并导出 `LuaGlue::LuaGlue`。设置 `LUAGLUE_LUA_TARGET` 可复用宿主 Lua target,独立配置时也可查找 Lua。桌面共享同一个运行时,移动平台静态链接。绑定使用 `.new(...)`、实际实例和静态属性、确定的 callable 签名、按调用求值的尾部默认参数与各源文件中的 `constexpr` 文档数组。拥有独立值语义的类型额外提供 `copy()`、`deepcopy()`,资源类型不自动获得深复制。 + ## 许可证 本项目采用 [MIT License](LICENSE)。 @@ -346,6 +352,5 @@ LuaSF 不附带这些依赖;版本表记录构建脚本会下载哪些变体 | [SFML-ME](https://github.com/JasonLeon01/SFML-ME) | `310ME-iOSJoystick` 标签 | [zlib/libpng](https://opensource.org/licenses/Zlib) — 详见 `third_party/SFML/license.md` | | [SFML-ME-OH](https://github.com/JasonLeon01/SFML-ME) | `310-ME-OH-GLESVER` 标签 | [zlib/libpng](https://opensource.org/licenses/Zlib) — 详见 `third_party/SFML/license.md` | | [Lua](https://www.lua.org/) | 5.5.0 | [MIT](https://www.lua.org/license.html) | -| [sol2](https://github.com/ThePhD/sol2) | 3.3.0 | [MIT](https://github.com/ThePhD/sol2/blob/develop/LICENSE.txt) | SFML 还可能附带其他外部库,这些库遵循各自的许可证;详见 SFML 文档以及 `third_party/SFML/license.md`。 diff --git a/analyze_types.py b/analyze_types.py index dccfe3a..8b1dd3a 100644 --- a/analyze_types.py +++ b/analyze_types.py @@ -1,6 +1,6 @@ import json -with open("output/sfml_api.json", "r", encoding="utf-8") as f: +with open("output/LuaSF/sfml_api.json", "r", encoding="utf-8") as f: data = json.load(f) results = [] diff --git a/build.bat b/build.bat index dff8fac..441735d 100644 --- a/build.bat +++ b/build.bat @@ -59,24 +59,6 @@ if not exist "third_party\Lua\src\lua.h" ( exit /b 1 ) -if not exist "third_party\sol2\include\sol2\sol.hpp" ( - echo Missing third_party\sol2. Run init.bat %SFML_VARIANT% first. - exit /b 1 -) - -echo Applying sol2 PR #1606 patch if needed... -rem The published sol2 headers use CRLF line endings, so the patch has to -rem ignore whitespace to match. -git apply --ignore-whitespace --reverse --check --directory=third_party/sol2 -p1 cmake/sol/pr1606.patch >nul 2>nul -if not errorlevel 1 ( - echo PR #1606 patch already applied to sol2. -) else ( - git apply --ignore-whitespace --check --directory=third_party/sol2 -p1 cmake/sol/pr1606.patch - if errorlevel 1 exit /b 1 - git apply --ignore-whitespace --directory=third_party/sol2 -p1 cmake/sol/pr1606.patch - if errorlevel 1 exit /b 1 -) - set "VARIANT_FILE=%~dp0.luasf-sfml-variant" set "PREVIOUS_VARIANT=" if exist "%VARIANT_FILE%" set /p PREVIOUS_VARIANT=<"%VARIANT_FILE%" @@ -89,8 +71,8 @@ echo Extracting SFML public API... "%PYTHON_EXE%" tools\extract_sfml_api.py --include-dir third_party/SFML/include if errorlevel 1 exit /b 1 -echo Generating sol2 bindings... -"%PYTHON_EXE%" tools\generate_sol2_bindings.py +echo Generating LuaGlue bindings... +"%PYTHON_EXE%" tools\generate_glue_bindings.py if errorlevel 1 exit /b 1 echo Generating standalone output CMake project... @@ -105,13 +87,13 @@ if "%SFML_VARIANT%"=="" ( >"%VARIANT_FILE%" echo %SFML_VARIANT% ) -set "CONFIGURE_ARGS=-DLUASF_SFML_ROOT=%~dp0third_party\SFML -DLUASF_LUA_ROOT=%~dp0third_party\Lua -DLUASF_SOL2_ROOT=%~dp0third_party\sol2" +set "CONFIGURE_ARGS=-DLUASF_SFML_ROOT=%~dp0third_party\SFML -DLUASF_LUA_ROOT=%~dp0third_party\Lua" if not "%CONFIG_OVERRIDE%"=="" ( set "CONFIGURE_ARGS=!CONFIGURE_ARGS! -DLUASF_DEFAULT_CONFIG=%CONFIG_OVERRIDE% -DCMAKE_BUILD_TYPE=%CONFIG_OVERRIDE%" ) echo Configuring output CMake project... -cmake -S output -B output\build %CONFIGURE_ARGS% +cmake -S output\LuaSF -B output\build %CONFIGURE_ARGS% if errorlevel 1 exit /b 1 set "BUILD_CONFIG=" diff --git a/build.sh b/build.sh index f181608..527f9f4 100644 --- a/build.sh +++ b/build.sh @@ -66,28 +66,13 @@ if [ -z "$SFML_TAG" ] || [ -z "$SFML_ME_TAG" ] || [ -z "$SFML_ME_OH_TAG" ]; then exit 1 fi -for dependency in "third_party/Lua/src/lua.h" "third_party/sol2/include/sol2/sol.hpp"; do +for dependency in "third_party/Lua/src/lua.h"; do if [ ! -f "$dependency" ]; then echo "Missing $dependency. Run sh init.sh ${SFML_VARIANT:-} first." >&2 exit 1 fi done -apply_sol2_pr1606_patch() { - echo "Applying sol2 PR #1606 patch if needed..." - patch_file="$SCRIPT_DIR/cmake/sol/pr1606.patch" - # The published sol2 headers use CRLF line endings, so the patch has to - # ignore whitespace to match. - if git apply --ignore-whitespace --reverse --check --directory=third_party/sol2 -p1 "$patch_file" >/dev/null 2>&1; then - echo "PR #1606 patch already applied to sol2." - return - fi - git apply --ignore-whitespace --check --directory=third_party/sol2 -p1 "$patch_file" - git apply --ignore-whitespace --directory=third_party/sol2 -p1 "$patch_file" -} - -apply_sol2_pr1606_patch - VARIANT_FILE="$SCRIPT_DIR/.luasf-sfml-variant" PREVIOUS_VARIANT=$(cat "$VARIANT_FILE" 2>/dev/null || true) if [ "$PREVIOUS_VARIANT" != "$SFML_VARIANT" ]; then @@ -98,8 +83,8 @@ fi echo "Extracting SFML public API..." "$PYTHON_EXE" tools/extract_sfml_api.py --include-dir third_party/SFML/include -echo "Generating sol2 bindings..." -"$PYTHON_EXE" tools/generate_sol2_bindings.py +echo "Generating LuaGlue bindings..." +"$PYTHON_EXE" tools/generate_glue_bindings.py echo "Generating standalone output CMake project..." "$PYTHON_EXE" tools/generate_build_files.py --force-sort @@ -108,19 +93,17 @@ printf '%s' "$SFML_VARIANT" > "$VARIANT_FILE" SFML_ROOT="$SCRIPT_DIR/third_party/SFML" LUA_ROOT="$SCRIPT_DIR/third_party/Lua" -SOL2_ROOT="$SCRIPT_DIR/third_party/sol2" echo "Configuring output CMake project..." set -- \ -DLUASF_SFML_ROOT="$SFML_ROOT" \ - -DLUASF_LUA_ROOT="$LUA_ROOT" \ - -DLUASF_SOL2_ROOT="$SOL2_ROOT" + -DLUASF_LUA_ROOT="$LUA_ROOT" if [ -n "$CONFIG_OVERRIDE" ]; then set -- "$@" \ -DLUASF_DEFAULT_CONFIG="$CONFIG_OVERRIDE" \ -DCMAKE_BUILD_TYPE="$CONFIG_OVERRIDE" fi -cmake -S output -B output/build "$@" +cmake -S output/LuaSF -B output/build "$@" BUILD_CONFIG=$(cmake -N -LA output/build 2>/dev/null | sed -n 's/^LUASF_DEFAULT_CONFIG:[^=]*=//p' | head -n 1) if [ -z "$BUILD_CONFIG" ]; then @@ -135,7 +118,7 @@ EMBEDDED_MODULE_FILE=$(find output/build/bin -path "*/embedded/*" \( -type f -o echo echo "Done." -echo "Project: $SCRIPT_DIR/output" +echo "Project: $SCRIPT_DIR/output/LuaSF" echo "SFML variant: ${SFML_VARIANT:-default} ($SFML_VARIANT_TAG)" if [ -n "$EMBEDDED_MODULE_FILE" ]; then echo "Embedded module: $SCRIPT_DIR/$EMBEDDED_MODULE_FILE" diff --git a/cmake/LuaSFTargets.cmake b/cmake/LuaSFTargets.cmake index fe98856..a98200a 100644 --- a/cmake/LuaSFTargets.cmake +++ b/cmake/LuaSFTargets.cmake @@ -17,14 +17,18 @@ endif() if(WIN32) set(LUASF_MODULE_FILE "${LUASF_RUNTIME_DIR}/LuaSF.dll") set(LUASF_LUA_FILE "${LUASF_RUNTIME_DIR}/lua.dll") + set(LUASF_GLUE_FILE "${LUASF_RUNTIME_DIR}/LuaGlue.dll") + set(LUASF_GLUE_IMPLIB "${LUASF_LIBRARY_DIR}/LuaGlue.lib") set(LUASF_MODULE_IMPLIB "${LUASF_LIBRARY_DIR}/LuaSF.lib") set(LUASF_LUA_IMPLIB "${LUASF_LIBRARY_DIR}/lua.lib") elseif(APPLE) set(LUASF_MODULE_FILE "${LUASF_RUNTIME_DIR}/LuaSF.dylib") set(LUASF_LUA_FILE "${LUASF_RUNTIME_DIR}/liblua.dylib") + set(LUASF_GLUE_FILE "${LUASF_RUNTIME_DIR}/libLuaGlue.dylib") else() set(LUASF_MODULE_FILE "${LUASF_RUNTIME_DIR}/LuaSF.so") set(LUASF_LUA_FILE "${LUASF_RUNTIME_DIR}/liblua.so") + set(LUASF_GLUE_FILE "${LUASF_RUNTIME_DIR}/libLuaGlue.so") endif() if(NOT EXISTS "${LUASF_MODULE_FILE}") @@ -56,11 +60,26 @@ if(WIN32) ) endif() +if(NOT EXISTS "${LUASF_GLUE_FILE}") + message(FATAL_ERROR "LuaGlue runtime library was not found: ${LUASF_GLUE_FILE}") +endif() +if(NOT TARGET LuaGlue::LuaGlue) + add_library(LuaGlue::LuaGlue SHARED IMPORTED GLOBAL) + set_target_properties(LuaGlue::LuaGlue PROPERTIES + IMPORTED_LOCATION "${LUASF_GLUE_FILE}" + INTERFACE_INCLUDE_DIRECTORIES "${LUASF_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES LuaSF::Lua + INTERFACE_COMPILE_FEATURES cxx_std_20) + if(WIN32) + set_target_properties(LuaGlue::LuaGlue PROPERTIES IMPORTED_IMPLIB "${LUASF_GLUE_IMPLIB}") + endif() +endif() + add_library(LuaSF::LuaSF SHARED IMPORTED GLOBAL) set_target_properties(LuaSF::LuaSF PROPERTIES IMPORTED_LOCATION "${LUASF_MODULE_FILE}" INTERFACE_INCLUDE_DIRECTORIES "${LUASF_INCLUDE_DIR}" - INTERFACE_LINK_LIBRARIES LuaSF::Lua + INTERFACE_LINK_LIBRARIES "LuaSF::Lua;LuaGlue::LuaGlue" INTERFACE_COMPILE_FEATURES cxx_std_20 ) if(WIN32) diff --git a/cmake/sol/pr1606.patch b/cmake/sol/pr1606.patch deleted file mode 100644 index 3204f6d..0000000 --- a/cmake/sol/pr1606.patch +++ /dev/null @@ -1,37 +0,0 @@ ---- a/include/sol2/sol.hpp -+++ b/include/sol2/sol.hpp -@@ -5843,18 +5843,18 @@ - /// \group emplace - template - T& emplace(Args&&... args) { - static_assert(std::is_constructible::value, "T must be constructible with Args"); - - *this = nullopt; -- this->construct(std::forward(args)...); -- return value(); -+ new (static_cast(this)) optional(std::in_place, std::forward(args)...); -+ return **this; - } - - /// \group emplace - /// \synopsis template \n T& emplace(std::initializer_list il, Args &&... args); - template - detail::enable_if_t&, Args&&...>::value, T&> emplace(std::initializer_list il, Args&&... args) { - *this = nullopt; -- this->construct(il, std::forward(args)...); -- return value(); -+ new (static_cast(this)) optional(std::in_place, il, std::forward(args)...); -+ return **this; - } -@@ -6749,8 +6749,5 @@ - /// \group emplace -- template -- T& emplace(Args&&... args) noexcept { -- static_assert(std::is_constructible::value, "T must be constructible with Args"); -- -- *this = nullopt; -- this->construct(std::forward(args)...); -+ T& emplace(T& arg) noexcept { -+ m_value = std::addressof(arg); -+ return **this; - } diff --git a/collect_result.bat b/collect_result.bat index aaac5da..4cafbf7 100644 --- a/collect_result.bat +++ b/collect_result.bat @@ -41,8 +41,8 @@ set "LUAC_FILE=%BUILD_DIR%\tools\%CONFIG%\luac.exe" if not exist "%LUAC_FILE%" set "LUAC_FILE=%BUILD_DIR%\tools\luac.exe" set "STUB_FILE=%BUILD_DIR%\LuaSF.d.lua" -set "CALLBACK_CODECS_FILE=%OUTPUT_DIR%\callback_codecs.json" -set "SFML_API_FILE=%OUTPUT_DIR%\sfml_api.json" +set "CALLBACK_CODECS_FILE=%OUTPUT_DIR%\LuaSF\callback_codecs.json" +set "SFML_API_FILE=%OUTPUT_DIR%\LuaSF\sfml_api.json" if not exist "%EMBEDDED_BIN_DIR%\LuaSF.dll" ( echo Missing embedded LuaSF.dll under "%EMBEDDED_BIN_DIR%". @@ -117,34 +117,26 @@ if errorlevel 1 ( exit /b 1 ) -if exist "%OUTPUT_DIR%\include" ( - robocopy "%OUTPUT_DIR%\include" "%EMBEDDED_RESULT_DIR%\include" /E /NFL /NDL /NJH /NJS /NP >nul +if exist "%OUTPUT_DIR%\LuaSF\include" ( + robocopy "%OUTPUT_DIR%\LuaSF\include" "%EMBEDDED_RESULT_DIR%\include" /E /NFL /NDL /NJH /NJS /NP >nul if errorlevel 8 exit /b 1 ) -if exist "%BUILD_DIR%\generated_include\sol" ( - robocopy "%BUILD_DIR%\generated_include\sol" "%EMBEDDED_RESULT_DIR%\include\sol" /E /NFL /NDL /NJH /NJS /NP >nul - if errorlevel 8 exit /b 1 -) + +robocopy "%OUTPUT_DIR%\LuaGlue\include" "%EMBEDDED_RESULT_DIR%\include" /E /NFL /NDL /NJH /NJS /NP >nul +if errorlevel 8 exit /b 1 if exist "%DEPENDENCY_DIR%\SFML\include" ( robocopy "%DEPENDENCY_DIR%\SFML\include" "%EMBEDDED_RESULT_DIR%\include" /E /NFL /NDL /NJH /NJS /NP >nul if errorlevel 8 exit /b 1 ) -if exist "%DEPENDENCY_DIR%\sol2\include" ( - robocopy "%DEPENDENCY_DIR%\sol2\include" "%EMBEDDED_RESULT_DIR%\include" /E /NFL /NDL /NJH /NJS /NP >nul - if errorlevel 8 exit /b 1 -) if exist "%DEPENDENCY_DIR%\Lua\src" ( - mkdir "%EMBEDDED_RESULT_DIR%\include\lua" >nul 2>nul - copy /y "%DEPENDENCY_DIR%\Lua\src\*.h" "%EMBEDDED_RESULT_DIR%\include\lua\" >nul + mkdir "%EMBEDDED_RESULT_DIR%\include" >nul 2>nul + copy /y "%DEPENDENCY_DIR%\Lua\src\*.h" "%EMBEDDED_RESULT_DIR%\include\" >nul if errorlevel 1 exit /b 1 - copy /y "%DEPENDENCY_DIR%\Lua\src\*.hpp" "%EMBEDDED_RESULT_DIR%\include\lua\" >nul 2>nul - call :write_lua_compat_header lua.h - call :write_lua_compat_header lauxlib.h - call :write_lua_compat_header lualib.h + copy /y "%DEPENDENCY_DIR%\Lua\src\*.hpp" "%EMBEDDED_RESULT_DIR%\include\" >nul 2>nul ) if exist "%EMBEDDED_LIB_DIR%" ( @@ -170,6 +162,13 @@ if exist "%SFML_LIB_DIR%" ( copy /y "%SFML_LIB_DIR%\*.a" "%EMBEDDED_RESULT_DIR%\lib\" >nul 2>nul ) +for %%D in ("%BUILD_DIR%\LuaGlue" "%BUILD_DIR%\LuaGlue\%CONFIG%") do ( + if exist "%%~D" ( + copy /y "%%~D\*.lib" "%EMBEDDED_RESULT_DIR%\lib\" >nul 2>nul + copy /y "%%~D\*.a" "%EMBEDDED_RESULT_DIR%\lib\" >nul 2>nul + ) +) + mkdir "%EMBEDDED_RESULT_DIR%\cmake" >nul 2>nul copy /y "cmake\result_CMakeLists.txt" "%EMBEDDED_RESULT_DIR%\CMakeLists.txt" >nul if errorlevel 1 ( @@ -207,12 +206,10 @@ if errorlevel 1 ( dir /b "%EMBEDDED_RESULT_DIR%\tools" echo. echo include: - echo - LuaSF generated headers from output\include + echo - LuaSF generated headers from output\LuaSF\include echo - SFML public headers - echo - sol2 public headers - echo - Lua headers under include\lua - echo - CMake generated sol compatibility headers under include\sol - echo - Lua compatibility wrappers at include\lua.h, include\lauxlib.h, include\lualib.h + echo - LuaGlue public headers + echo - Native Lua 5.5 headers under include echo - Windows MSVC redistributable DLLs from requirements are bundled in bin if exist "%EMBEDDED_RESULT_DIR%\lib" ( echo. @@ -249,11 +246,3 @@ echo Host luac: %EMBEDDED_RESULT_DIR%\tools\luac.exe endlocal exit /b 0 - -:write_lua_compat_header -> "%EMBEDDED_RESULT_DIR%\include\%~1" echo #include "lua/%~1" ->> "%EMBEDDED_RESULT_DIR%\include\%~1" echo #undef LUA_VERSION_NUM ->> "%EMBEDDED_RESULT_DIR%\include\%~1" echo #define LUA_VERSION_NUM 504 ->> "%EMBEDDED_RESULT_DIR%\include\%~1" echo #undef lua_newstate ->> "%EMBEDDED_RESULT_DIR%\include\%~1" echo #define lua_newstate(f, ud) lua_newstate((f), (ud), 0u) -exit /b 0 diff --git a/collect_result.sh b/collect_result.sh index dd62f6a..182b98c 100644 --- a/collect_result.sh +++ b/collect_result.sh @@ -52,8 +52,8 @@ if [ ! -d "$EMBEDDED_LIB_DIR" ]; then fi STUB_FILE="$BUILD_DIR/LuaSF.d.lua" -CALLBACK_CODECS_FILE="$OUTPUT_DIR/callback_codecs.json" -SFML_API_FILE="$OUTPUT_DIR/sfml_api.json" +CALLBACK_CODECS_FILE="$OUTPUT_DIR/LuaSF/callback_codecs.json" +SFML_API_FILE="$OUTPUT_DIR/LuaSF/sfml_api.json" LUAC_FILE=$(find "$BUILD_DIR/tools" -maxdepth 2 \( -type f -o -type l \) \( -name 'luac' -o -name 'luac.exe' \) 2>/dev/null | head -n 1 || true) EMBEDDED_MODULE_FILE=$( @@ -112,17 +112,6 @@ copy_runtime_files() { -exec cp -L {} "$dst"/ \; } -write_lua_compat_header() { - header=$1 - cat > "$EMBEDDED_RESULT_DIR/include/$header" << EOF -#include "lua/$header" -#undef LUA_VERSION_NUM -#define LUA_VERSION_NUM 504 -#undef lua_newstate -#define lua_newstate(f, ud) lua_newstate((f), (ud), 0u) -EOF -} - echo "Collecting LuaSF build result..." echo "Config: $CONFIG" echo "Source: $BUILD_DIR" @@ -157,26 +146,22 @@ cp "$CALLBACK_CODECS_FILE" "$EMBEDDED_RESULT_DIR/callback_codecs.json" cp "$SFML_API_FILE" "$EMBEDDED_RESULT_DIR/sfml_api.json" cp "$LUAC_FILE" "$EMBEDDED_RESULT_DIR/tools/" -copy_tree_contents "$OUTPUT_DIR/include" "$EMBEDDED_RESULT_DIR/include" -copy_tree_contents "$BUILD_DIR/generated_include/sol" "$EMBEDDED_RESULT_DIR/include/sol" +copy_tree_contents "$OUTPUT_DIR/LuaSF/include" "$EMBEDDED_RESULT_DIR/include" +copy_tree_contents "$OUTPUT_DIR/LuaGlue/include" "$EMBEDDED_RESULT_DIR/include" copy_tree_contents "$DEPENDENCY_DIR/SFML/include" "$EMBEDDED_RESULT_DIR/include" -copy_tree_contents "$DEPENDENCY_DIR/sol2/include" "$EMBEDDED_RESULT_DIR/include" if [ -d "$DEPENDENCY_DIR/Lua/src" ]; then - mkdir -p "$EMBEDDED_RESULT_DIR/include/lua" - cp "$DEPENDENCY_DIR"/Lua/src/*.h "$EMBEDDED_RESULT_DIR/include/lua/" + mkdir -p "$EMBEDDED_RESULT_DIR/include" + cp "$DEPENDENCY_DIR"/Lua/src/*.h "$EMBEDDED_RESULT_DIR/include/" for header in "$DEPENDENCY_DIR"/Lua/src/*.hpp; do [ -e "$header" ] || continue - cp "$header" "$EMBEDDED_RESULT_DIR/include/lua/" + cp "$header" "$EMBEDDED_RESULT_DIR/include/" done - write_lua_compat_header lua.h - write_lua_compat_header lauxlib.h - write_lua_compat_header lualib.h fi if [ -d "$EMBEDDED_LIB_DIR" ] || [ -d "$LIB_DIR" ] || [ -d "$SFML_LIB_DIR" ]; then mkdir -p "$EMBEDDED_RESULT_DIR/lib" - for source_dir in "$EMBEDDED_LIB_DIR" "$LIB_DIR" "$SFML_LIB_DIR"; do + for source_dir in "$EMBEDDED_LIB_DIR" "$LIB_DIR" "$SFML_LIB_DIR" "$BUILD_DIR/LuaGlue" "$BUILD_DIR/LuaGlue/$CONFIG"; do [ -d "$source_dir" ] || continue find "$source_dir" -maxdepth 1 \( -type f -o -type l \) \ \( -name '*.lib' -o -name '*.exp' -o -name '*.a' \) \ @@ -210,12 +195,10 @@ cp "cmake/result_README.md" "$EMBEDDED_RESULT_DIR/README.md" ls -1 "$EMBEDDED_RESULT_DIR/tools" echo echo "include:" - echo "- LuaSF generated headers from output/include" + echo "- LuaSF generated headers from output/LuaSF/include" echo "- SFML public headers" - echo "- sol2 public headers" - echo "- Lua headers under include/lua" - echo "- CMake generated sol compatibility headers under include/sol" - echo "- Lua compatibility wrappers at include/lua.h, include/lauxlib.h, include/lualib.h" + echo "- LuaGlue public headers" + echo "- Native Lua 5.5 headers under include" if [ -d "$EMBEDDED_RESULT_DIR/lib" ]; then echo echo "lib:" diff --git a/include/LuaCallbackCodec.hpp b/include/LuaCallbackCodec.hpp index 5275cc3..635ba14 100644 --- a/include/LuaCallbackCodec.hpp +++ b/include/LuaCallbackCodec.hpp @@ -1,7 +1,7 @@ #pragma once #include "LuaStateLifecycle.hpp" -#include "luasf_sol.hpp" +#include "luasf_glue.hpp" #include #include @@ -19,11 +19,12 @@ namespace lua_sf::callback { template -std::function native_thread_from_object(const sol::object &object); +std::function native_thread_from_object( + const lua_glue::Object& object); struct CallbackOptions { - std::string label; - bool allowNil{}; + std::string label; + bool allowNil{}; }; struct InterleavedFloatTransformCodec; @@ -37,135 +38,146 @@ namespace detail { class CallbackContext; -LUASF_API std::shared_ptr -makeCallbackContext(const sol::object &object, std::string label); +LUASF_API std::shared_ptr makeCallbackContext( + const lua_glue::Object& object, std::string label); LUASF_API void invokeInterleavedFloatTransform( - const std::shared_ptr &context, const float *inputFrames, - unsigned int &inputFrameCount, float *outputFrames, - unsigned int &outputFrameCount, unsigned int frameChannelCount) noexcept; + const std::shared_ptr& context, const float* inputFrames, + unsigned int& inputFrameCount, float* outputFrames, + unsigned int& outputFrameCount, unsigned int frameChannelCount) noexcept; -LUASF_API void -invokeGlyphPreProcessor(const std::shared_ptr &context, - const sf::Text::ShapedGlyph &shapedGlyph, - std::uint32_t &style, sf::Color &fillColor, - sf::Color &outlineColor, float &outlineThickness); +LUASF_API void invokeGlyphPreProcessor( + const std::shared_ptr& context, + const sf::Text::ShapedGlyph& shapedGlyph, std::uint32_t& style, + sf::Color& fillColor, sf::Color& outlineColor, float& outlineThickness); -LUASF_API bool -invokeSftpDownload(const std::shared_ptr &context, - const void *data, std::size_t size); +LUASF_API bool invokeSftpDownload( + const std::shared_ptr& context, const void* data, + std::size_t size); -LUASF_API bool invokeSftpUpload(const std::shared_ptr &context, - void *data, std::size_t &size); +LUASF_API bool invokeSftpUpload(const std::shared_ptr& context, + void* data, std::size_t& size); -template struct FromObject; +template +struct FromObject; template <> struct FromObject { - static sf::SoundSource::EffectProcessor read(const sol::object &object, - CallbackOptions options) { - const auto context = makeCallbackContext(object, std::move(options.label)); - return [context](const float *inputFrames, unsigned int &inputFrameCount, - float *outputFrames, unsigned int &outputFrameCount, - unsigned int frameChannelCount) noexcept { - invokeInterleavedFloatTransform(context, inputFrames, inputFrameCount, - outputFrames, outputFrameCount, - frameChannelCount); - }; - } + static sf::SoundSource::EffectProcessor read(const lua_glue::Object& object, + CallbackOptions options) { + const auto context = + makeCallbackContext(object, std::move(options.label)); + return + [context](const float* inputFrames, unsigned int& inputFrameCount, + float* outputFrames, unsigned int& outputFrameCount, + unsigned int frameChannelCount) noexcept { + invokeInterleavedFloatTransform( + context, inputFrames, inputFrameCount, outputFrames, + outputFrameCount, frameChannelCount); + }; + } }; template <> struct FromObject { - static sf::Text::GlyphPreProcessor read(const sol::object &object, - CallbackOptions options) { - const auto context = makeCallbackContext(object, std::move(options.label)); - return [context](const sf::Text::ShapedGlyph &glyph, std::uint32_t &style, - sf::Color &fill, sf::Color &outline, float &thickness) { - invokeGlyphPreProcessor(context, glyph, style, fill, outline, thickness); - }; - } + static sf::Text::GlyphPreProcessor read(const lua_glue::Object& object, + CallbackOptions options) { + const auto context = + makeCallbackContext(object, std::move(options.label)); + return + [context](const sf::Text::ShapedGlyph& glyph, std::uint32_t& style, + sf::Color& fill, sf::Color& outline, float& thickness) { + invokeGlyphPreProcessor(context, glyph, style, fill, outline, + thickness); + }; + } }; template <> -struct FromObject, +struct FromObject, SftpDownloadBufferCodec> { - static std::function - read(const sol::object &object, CallbackOptions options) { - const auto context = makeCallbackContext(object, std::move(options.label)); - return [context](const void *data, std::size_t size) { - return invokeSftpDownload(context, data, size); - }; - } + static std::function read( + const lua_glue::Object& object, CallbackOptions options) { + const auto context = + makeCallbackContext(object, std::move(options.label)); + return [context](const void* data, std::size_t size) { + return invokeSftpDownload(context, data, size); + }; + } }; template <> -struct FromObject, +struct FromObject, SftpUploadBufferCodec> { - static std::function - read(const sol::object &object, CallbackOptions options) { - const auto context = makeCallbackContext(object, std::move(options.label)); - return [context](void *data, std::size_t &size) { - return invokeSftpUpload(context, data, size); - }; - } + static std::function read( + const lua_glue::Object& object, CallbackOptions options) { + const auto context = + makeCallbackContext(object, std::move(options.label)); + return [context](void* data, std::size_t& size) { + return invokeSftpUpload(context, data, size); + }; + } }; template struct FromObject, NativeThreadBoundaryCodec> { - static std::function read(const sol::object &object, - CallbackOptions) { - return native_thread_from_object(object); - } + static std::function read( + const lua_glue::Object& object, CallbackOptions) { + return native_thread_from_object(object); + } }; -} // namespace detail +} // namespace detail template -NativeCallable from_object(const sol::object &object, +NativeCallable from_object(const lua_glue::Object& object, CallbackOptions options = {}) { - const bool isNil = !object.valid() || object == lua_sf::LUASF_SOL_NIL; - if (isNil) { - if (options.allowNil) - return NativeCallable{}; - throw std::invalid_argument(options.label.empty() - ? "Lua callback does not allow nil" - : options.label + " does not allow nil"); - } - if (!object.is()) - throw std::invalid_argument(options.label.empty() - ? "expected a Lua callback function" - : "expected " + options.label); - return detail::FromObject::read(object, - std::move(options)); + const bool isNil = !object.valid() || object == lua_glue::nil; + if (isNil) { + if (options.allowNil) { + return NativeCallable{}; + } + throw std::invalid_argument( + options.label.empty() ? "Lua callback does not allow nil" + : options.label + " does not allow nil"); + } + if (!object.is()) { + throw std::invalid_argument(options.label.empty() + ? "expected a Lua callback function" + : "expected " + options.label); + } + return detail::FromObject::read(object, + std::move(options)); } template -NativeCallable from_object(const sol::object &object, std::string_view label) { - return from_object( - object, CallbackOptions{std::string(label), false}); +NativeCallable from_object(const lua_glue::Object& object, + std::string_view label) { + return from_object( + object, CallbackOptions{std::string(label), false}); } template -sol::object to_object(sol::state_view, const NativeCallable &, - CallbackOptions = {}) { - static_assert(!std::is_same_v, - "callback codec does not support conversion to Lua"); +lua_glue::Object to_object(lua_glue::StateView, const NativeCallable&, + CallbackOptions = {}) { + static_assert(!std::is_same_v, + "callback codec does not support conversion to Lua"); } template -sol::object to_object(sol::state_view lua, const NativeCallable &callable, - std::string_view label) { - return to_object(lua, callable, - CallbackOptions{std::string(label)}); +lua_glue::Object to_object(lua_glue::StateView lua, + const NativeCallable& callable, + std::string_view label) { + return to_object( + lua, callable, CallbackOptions{std::string(label)}); } template <> -LUASF_API sol::object +LUASF_API lua_glue::Object to_object( - sol::state_view lua, const sf::SoundSource::EffectProcessor &callable, + lua_glue::StateView lua, const sf::SoundSource::EffectProcessor& callable, CallbackOptions options); -} // namespace lua_sf::callback +} // namespace lua_sf::callback diff --git a/include/LuaStateLifecycle.hpp b/include/LuaStateLifecycle.hpp index dc5262c..40b2e21 100644 --- a/include/LuaStateLifecycle.hpp +++ b/include/LuaStateLifecycle.hpp @@ -1,77 +1,21 @@ #pragma once #include "LuaSF.hpp" - -#include -#include - -struct lua_State; +#include namespace lua_sf { -struct LuaRegistryReferenceState; -using LuaStateQuiesceCallback = void (*)() noexcept; - -class LUASF_API LuaStateExecutionScope { -public: - explicit LuaStateExecutionScope(lua_State *state) noexcept; - ~LuaStateExecutionScope(); - - LuaStateExecutionScope(const LuaStateExecutionScope &) = delete; - LuaStateExecutionScope &operator=(const LuaStateExecutionScope &) = delete; - - [[nodiscard]] bool active() const noexcept; - -private: - lua_State *state_{}; - bool active_{}; -}; - -class LUASF_API LuaStateTryExecutionScope { -public: - explicit LuaStateTryExecutionScope(lua_State *state) noexcept; - ~LuaStateTryExecutionScope(); - - LuaStateTryExecutionScope(const LuaStateTryExecutionScope &) = delete; - LuaStateTryExecutionScope & - operator=(const LuaStateTryExecutionScope &) = delete; - - [[nodiscard]] bool active() const noexcept; - -private: - lua_State *state_{}; - bool active_{}; -}; - -class LUASF_API LuaRegistryReference { -public: - LuaRegistryReference() noexcept = default; - LuaRegistryReference(lua_State *state, int stackIndex); - - [[nodiscard]] lua_State *state() const noexcept; - [[nodiscard]] bool push() const; - [[nodiscard]] bool pushUnderExecutionScope() const noexcept; - void deferCallbackError(std::string_view label, - std::string_view message) const noexcept; - [[nodiscard]] bool equals(const LuaRegistryReference &other) const; - explicit operator bool() const noexcept; - -private: - std::shared_ptr reference_; -}; +using LuaStateExecutionScope = lua_glue::ExecutionScope; +using LuaStateTryExecutionScope = lua_glue::TryExecutionScope; +using LuaRegistryReference = lua_glue::RegistryReference; +using LuaStateQuiesceCallback = lua_glue::StateQuiesceCallback; namespace detail { - -LUASF_API void registerLuaThreadForRegistryReference(lua_State *state); -LUASF_API void -retainLuaRegistryReference(const void *owner, - const LuaRegistryReference &reference); -LUASF_API void releaseLuaRegistryReference(const void *owner); -LUASF_API void registerStateQuiesceCallback(lua_State *state, const void *owner, - LuaStateQuiesceCallback callback); -LUASF_API void unregisterStateQuiesceCallback(lua_State *state, - const void *owner) noexcept; - -} // namespace detail - -} // namespace lua_sf +using lua_glue::detail::registerLuaThreadForRegistryReference; +using lua_glue::detail::registerStateQuiesceCallback; +using lua_glue::detail::releaseLuaRegistryReference; +using lua_glue::detail::retainLuaRegistryReference; +using lua_glue::detail::unregisterStateQuiesceCallback; +} // namespace detail + +} // namespace lua_sf diff --git a/include/bind_ClassSupport.hpp b/include/bind_ClassSupport.hpp index 9d840bb..c338174 100644 --- a/include/bind_ClassSupport.hpp +++ b/include/bind_ClassSupport.hpp @@ -9,14 +9,14 @@ namespace lua_sf { class ClassSpriteTexture { protected: - sf::Texture texture; + sf::Texture texture; }; class ClassSprite final : private ClassSpriteTexture, public sf::Sprite { public: - ClassSprite(); + ClassSprite(); }; -} // namespace lua_sf +} // namespace lua_sf -void bind_ClassSupport(sol::state_view lua); +void bind_ClassSupport(lua_glue::StateView lua); diff --git a/include/bind_Drawable.hpp b/include/bind_Drawable.hpp index b4c258b..3b3e125 100644 --- a/include/bind_Drawable.hpp +++ b/include/bind_Drawable.hpp @@ -12,17 +12,17 @@ namespace lua_sf { class LuaDrawable : public sf::Drawable { public: - using DrawCallback = - std::function; + using DrawCallback = + std::function; - explicit LuaDrawable(DrawCallback drawCallback); + explicit LuaDrawable(DrawCallback drawCallback); private: - void draw(sf::RenderTarget &target, sf::RenderStates states) const override; + void draw(sf::RenderTarget& target, sf::RenderStates states) const override; - DrawCallback m_drawCallback; + DrawCallback m_drawCallback; }; -} // namespace lua_sf +} // namespace lua_sf -void bind_Drawable(sol::state_view lua); +void bind_Drawable(lua_glue::StateView lua); diff --git a/include/bind_Event.hpp b/include/bind_Event.hpp index b026ebb..ba6ec28 100644 --- a/include/bind_Event.hpp +++ b/include/bind_Event.hpp @@ -4,4 +4,4 @@ #include -void bind_Event(sol::state_view lua); +void bind_Event(lua_glue::StateView lua); diff --git a/include/bind_Handle.hpp b/include/bind_Handle.hpp index d90e578..994fee7 100644 --- a/include/bind_Handle.hpp +++ b/include/bind_Handle.hpp @@ -4,4 +4,4 @@ #include -void bind_Handle(sol::state_view lua); +void bind_Handle(lua_glue::StateView lua); diff --git a/include/lua_stub.hpp b/include/lua_stub.hpp index 090dbe0..6c0a841 100644 --- a/include/lua_stub.hpp +++ b/include/lua_stub.hpp @@ -10,468 +10,509 @@ namespace lua_sf::stub { -inline std::ofstream &output() { - static std::ofstream stream; - return stream; +inline std::ofstream& output() { + static std::ofstream stream; + return stream; } -inline std::string &pending_class() { - static std::string name; - return name; +inline std::string& pending_class() { + static std::string name; + return name; } -inline std::string &pending_doc() { - static std::string text; - return text; +inline std::string& pending_doc() { + static std::string text; + return text; } -inline std::set &declared_tables() { - static std::set tables; - return tables; +inline std::set& declared_tables() { + static std::set tables; + return tables; } struct PendingFunction { - std::string owner; - std::string name; - std::string type; - std::string doc; - std::vector overloads; + std::string owner; + std::string name; + std::string type; + std::string doc; + std::vector overloads; }; struct FunctionParameter { - std::string name; - std::string type; + std::string name; + std::string type; + bool optional{}; }; struct FunctionType { - std::vector parameters; - std::vector returns; - bool valid = false; + std::vector parameters; + std::vector returns; + bool valid = false; }; -inline PendingFunction &pending_function() { - static PendingFunction pf; - return pf; +inline PendingFunction& pending_function() { + static PendingFunction pf; + return pf; } -inline bool enabled() { return output().is_open(); } +inline bool enabled() { + return output().is_open(); +} -inline void declare_table(const std::string &name) { - if (!enabled() || declared_tables().contains(name)) - return; +inline void declare_table(const std::string& name) { + if (!enabled() || declared_tables().contains(name)) { + return; + } - const auto dot = name.rfind('.'); - if (dot != std::string::npos) - declare_table(name.substr(0, dot)); + const auto dot = name.rfind('.'); + if (dot != std::string::npos) { + declare_table(name.substr(0, dot)); + } - output() << name << " = " << name << " or {}\n"; - declared_tables().insert(name); + output() << name << " = " << name << " or {}\n"; + declared_tables().insert(name); } inline void close_pending_class(); inline bool is_doc_command_char(char value) { - return (value >= 'A' && value <= 'Z') || - (value >= 'a' && value <= 'z'); + return (value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'); } inline bool is_doc_path_prefix(char value) { - return is_doc_command_char(value) || - (value >= '0' && value <= '9') || value == '_' || value == ':' || - value == '/' || value == '\\'; + return is_doc_command_char(value) || (value >= '0' && value <= '9') || + value == '_' || value == ':' || value == '/' || value == '\\'; } inline bool is_doxygen_command(std::string_view value) { - static constexpr std::array commands = { - "a", "b", "brief", "c", "code", - "deprecated", "e", "em", "endcode", "ingroup", - "li", "note", "overload", "p", "param", - "relates", "return", "see", "throws", "warning"}; - for (const std::string_view command : commands) { - if (value == command) - return true; - } - return false; + static constexpr std::array commands = { + "a", "b", "brief", "c", "code", "deprecated", "e", + "em", "endcode", "ingroup", "li", "note", "overload", "p", + "param", "relates", "return", "see", "throws", "warning"}; + for (const std::string_view command : commands) { + if (value == command) { + return true; + } + } + return false; } -inline std::string normalize_doc_line(const std::string &line) { - std::string result; - result.reserve(line.size()); - for (std::size_t index = 0; index < line.size();) { - if (line[index] != '\\' || - (index > 0 && is_doc_path_prefix(line[index - 1])) || - index + 1 >= line.size() || - !is_doc_command_char(line[index + 1])) { - result.push_back(line[index]); - ++index; - continue; - } - std::size_t end = index + 1; - while (end < line.size() && is_doc_command_char(line[end])) - ++end; - const std::string_view command(line.data() + index + 1, end - index - 1); - result.push_back(is_doxygen_command(command) ? '@' : '\\'); - result.append(command); - index = end; - } - return result; +inline std::string normalize_doc_line(const std::string& line) { + std::string result; + result.reserve(line.size()); + for (std::size_t index = 0; index < line.size();) { + if (line[index] != '\\' || + (index > 0 && is_doc_path_prefix(line[index - 1])) || + index + 1 >= line.size() || !is_doc_command_char(line[index + 1])) { + result.push_back(line[index]); + ++index; + continue; + } + std::size_t end = index + 1; + while (end < line.size() && is_doc_command_char(line[end])) { + ++end; + } + const std::string_view command(line.data() + index + 1, + end - index - 1); + result.push_back(is_doxygen_command(command) ? '@' : '\\'); + result.append(command); + index = end; + } + return result; } -inline void write_doc(const std::string &text) { - std::istringstream stream(text); - std::string line; - while (std::getline(stream, line)) { - if (!line.empty() && line.back() == '\r') - line.pop_back(); - const std::string normalized = normalize_doc_line(line); - output() << "---"; - if (!normalized.empty()) - output() << " " << normalized; - output() << "\n"; - } +inline void write_doc(const std::string& text) { + std::istringstream stream(text); + std::string line; + while (std::getline(stream, line)) { + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + const std::string normalized = normalize_doc_line(line); + output() << "---"; + if (!normalized.empty()) { + output() << " " << normalized; + } + output() << "\n"; + } } inline void write_pending_doc() { - auto &text = pending_doc(); - if (text.empty()) - return; + auto& text = pending_doc(); + if (text.empty()) { + return; + } - const std::string current = std::move(text); - text.clear(); - write_doc(current); + const std::string current = std::move(text); + text.clear(); + write_doc(current); } -inline std::string trim(const std::string &value) { - const auto first = value.find_first_not_of(" \t\r\n"); - if (first == std::string::npos) - return {}; - const auto last = value.find_last_not_of(" \t\r\n"); - return value.substr(first, last - first + 1); +inline std::string trim(const std::string& value) { + const auto first = value.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) { + return {}; + } + const auto last = value.find_last_not_of(" \t\r\n"); + return value.substr(first, last - first + 1); } -inline std::vector split_top_level(const std::string &value) { - std::vector result; - std::size_t start = 0; - int parentheses = 0; - int brackets = 0; - int braces = 0; - int angles = 0; - char quote = '\0'; - bool escaped = false; - for (std::size_t index = 0; index < value.size(); ++index) { - const char current = value[index]; - if (quote != '\0') { - if (escaped) { - escaped = false; - } else if (current == '\\') { - escaped = true; - } else if (current == quote) { - quote = '\0'; - } - continue; - } - if (current == '\'' || current == '"') { - quote = current; - } else if (current == '(') { - ++parentheses; - } else if (current == ')') { - --parentheses; - } else if (current == '[') { - ++brackets; - } else if (current == ']') { - --brackets; - } else if (current == '{') { - ++braces; - } else if (current == '}') { - --braces; - } else if (current == '<') { - ++angles; - } else if (current == '>') { - --angles; - } else if (current == ',' && parentheses == 0 && brackets == 0 && - braces == 0 && angles == 0) { - result.push_back(trim(value.substr(start, index - start))); - start = index + 1; - } - } - const std::string tail = trim(value.substr(start)); - if (!tail.empty()) - result.push_back(tail); - return result; +inline std::vector split_top_level(const std::string& value) { + std::vector result; + std::size_t start = 0; + int parentheses = 0; + int brackets = 0; + int braces = 0; + int angles = 0; + char quote = '\0'; + bool escaped = false; + for (std::size_t index = 0; index < value.size(); ++index) { + const char current = value[index]; + if (quote != '\0') { + if (escaped) { + escaped = false; + } else if (current == '\\') { + escaped = true; + } else if (current == quote) { + quote = '\0'; + } + continue; + } + if (current == '\'' || current == '"') { + quote = current; + } else if (current == '(') { + ++parentheses; + } else if (current == ')') { + --parentheses; + } else if (current == '[') { + ++brackets; + } else if (current == ']') { + --brackets; + } else if (current == '{') { + ++braces; + } else if (current == '}') { + --braces; + } else if (current == '<') { + ++angles; + } else if (current == '>') { + --angles; + } else if (current == ',' && parentheses == 0 && brackets == 0 && + braces == 0 && angles == 0) { + result.push_back(trim(value.substr(start, index - start))); + start = index + 1; + } + } + const std::string tail = trim(value.substr(start)); + if (!tail.empty()) { + result.push_back(tail); + } + return result; } -inline std::size_t find_top_level_colon(const std::string &value) { - int parentheses = 0; - int brackets = 0; - int braces = 0; - int angles = 0; - for (std::size_t index = 0; index < value.size(); ++index) { - const char current = value[index]; - if (current == '(') - ++parentheses; - else if (current == ')') - --parentheses; - else if (current == '[') - ++brackets; - else if (current == ']') - --brackets; - else if (current == '{') - ++braces; - else if (current == '}') - --braces; - else if (current == '<') - ++angles; - else if (current == '>') - --angles; - else if (current == ':' && parentheses == 0 && brackets == 0 && - braces == 0 && angles == 0) - return index; - } - return std::string::npos; +inline std::size_t find_top_level_colon(const std::string& value) { + int parentheses = 0; + int brackets = 0; + int braces = 0; + int angles = 0; + for (std::size_t index = 0; index < value.size(); ++index) { + const char current = value[index]; + if (current == '(') { + ++parentheses; + } else if (current == ')') { + --parentheses; + } else if (current == '[') { + ++brackets; + } else if (current == ']') { + --brackets; + } else if (current == '{') { + ++braces; + } else if (current == '}') { + --braces; + } else if (current == '<') { + ++angles; + } else if (current == '>') { + --angles; + } else if (current == ':' && parentheses == 0 && brackets == 0 && + braces == 0 && angles == 0) { + return index; + } + } + return std::string::npos; } -inline FunctionType parse_function_type(const std::string &value) { - FunctionType result; - const std::string type = trim(value); - if (!type.starts_with("fun(")) - return result; +inline FunctionType parse_function_type(const std::string& value) { + FunctionType result; + const std::string type = trim(value); + if (!type.starts_with("fun(")) { + return result; + } - int depth = 0; - std::size_t close = std::string::npos; - for (std::size_t index = 3; index < type.size(); ++index) { - if (type[index] == '(') { - ++depth; - } else if (type[index] == ')') { - --depth; - if (depth == 0) { - close = index; - break; - } - } - } - if (close == std::string::npos) - return result; + int depth = 0; + std::size_t close = std::string::npos; + for (std::size_t index = 3; index < type.size(); ++index) { + if (type[index] == '(') { + ++depth; + } else if (type[index] == ')') { + --depth; + if (depth == 0) { + close = index; + break; + } + } + } + if (close == std::string::npos) { + return result; + } - for (const std::string ¶meter : - split_top_level(type.substr(4, close - 4))) { - const auto colon = find_top_level_colon(parameter); - if (colon == std::string::npos) - return result; - std::string name = trim(parameter.substr(0, colon)); - if (name.ends_with('?')) - name.pop_back(); - const std::string parameter_type = trim(parameter.substr(colon + 1)); - if (name.empty() || parameter_type.empty()) - return result; - result.parameters.push_back({std::move(name), parameter_type}); - } - - std::string return_text = trim(type.substr(close + 1)); - if (!return_text.empty()) { - if (!return_text.starts_with(':')) - return result; - result.returns = split_top_level(return_text.substr(1)); - } - result.valid = true; - return result; + for (const std::string& parameter : + split_top_level(type.substr(4, close - 4))) { + const auto colon = find_top_level_colon(parameter); + if (colon == std::string::npos) { + return result; + } + std::string name = trim(parameter.substr(0, colon)); + const bool optional = name.ends_with('?'); + if (optional) { + name.pop_back(); + } + const std::string parameter_type = trim(parameter.substr(colon + 1)); + if (name.empty() || parameter_type.empty()) { + return result; + } + result.parameters.push_back( + {std::move(name), parameter_type, optional}); + } + + std::string return_text = trim(type.substr(close + 1)); + if (!return_text.empty()) { + if (!return_text.starts_with(':')) { + return result; + } + result.returns = split_top_level(return_text.substr(1)); + } + result.valid = true; + return result; } inline void flush_pending_function() { - auto &pf = pending_function(); - if (!enabled() || pf.name.empty()) - return; - - // Capture and clear before any call that may re-enter flush_pending_function. - const PendingFunction current = std::move(pf); - pf = PendingFunction{}; - - close_pending_class(); - declare_table(current.owner); - write_doc(current.doc); - if (!current.overloads.empty()) { - const FunctionType function_type = parse_function_type(current.type); - if (function_type.valid) { - for (const auto &overload : current.overloads) - output() << "---@overload " << overload << "\n"; - for (const auto ¶meter : function_type.parameters) { - if (parameter.name == "...") - output() << "---@vararg " << parameter.type << "\n"; - else - output() << "---@param " << parameter.name << " " << parameter.type - << "\n"; - } - for (const auto &return_type : function_type.returns) - output() << "---@return " << return_type << "\n"; - output() << "function " << current.owner << "." << current.name << "("; - for (std::size_t index = 0; index < function_type.parameters.size(); - ++index) { - if (index != 0) - output() << ", "; - output() << function_type.parameters[index].name; - } - output() << ") end\n"; - return; - } - } - output() << "---@type " << current.type << "\n"; - for (const auto &ol : current.overloads) - output() << "---@overload " << ol << "\n"; - output() << current.owner << "." << current.name << " = function() end\n"; + auto& pf = pending_function(); + if (!enabled() || pf.name.empty()) { + return; + } + + // Capture and clear before any call that may re-enter + // flush_pending_function. + const PendingFunction current = std::move(pf); + pf = PendingFunction{}; + + close_pending_class(); + declare_table(current.owner); + write_doc(current.doc); + if (!current.overloads.empty()) { + const FunctionType function_type = parse_function_type(current.type); + if (function_type.valid) { + for (const auto& overload : current.overloads) { + output() << "---@overload " << overload << "\n"; + } + for (const auto& parameter : function_type.parameters) { + if (parameter.name == "...") { + output() << "---@vararg " << parameter.type << "\n"; + } else { + output() << "---@param " << parameter.name + << (parameter.optional ? "? " : " ") + << parameter.type << "\n"; + } + } + for (const auto& return_type : function_type.returns) { + output() << "---@return " << return_type << "\n"; + } + output() << "function " << current.owner << "." << current.name + << "("; + for (std::size_t index = 0; index < function_type.parameters.size(); + ++index) { + if (index != 0) { + output() << ", "; + } + output() << function_type.parameters[index].name; + } + output() << ") end\n"; + return; + } + } + output() << "---@type " << current.type << "\n"; + for (const auto& ol : current.overloads) { + output() << "---@overload " << ol << "\n"; + } + output() << current.owner << "." << current.name << " = function() end\n"; } inline void close_pending_class() { - flush_pending_function(); - if (!enabled() || pending_class().empty()) - return; + flush_pending_function(); + if (!enabled() || pending_class().empty()) { + return; + } - declare_table(pending_class()); - pending_class().clear(); + declare_table(pending_class()); + pending_class().clear(); } -inline bool begin(const char *path) { - output().open(path, std::ios::binary); - if (!enabled()) - return false; +inline bool begin(const char* path) { + output().open(path, std::ios::binary); + if (!enabled()) { + return false; + } - declared_tables().clear(); - pending_doc().clear(); - output() << "---@meta\n\n"; - output() << "sf = sf or {}\n"; - declared_tables().insert("sf"); - return true; + declared_tables().clear(); + pending_doc().clear(); + output() << "---@meta\n\n"; + output() << "sf = sf or {}\n"; + declared_tables().insert("sf"); + return true; } inline void end() { - flush_pending_function(); - close_pending_class(); - declared_tables().clear(); - pending_doc().clear(); - if (enabled()) - output().close(); + flush_pending_function(); + close_pending_class(); + declared_tables().clear(); + pending_doc().clear(); + if (enabled()) { + output().close(); + } } -inline void class_(const char *name, const char *bases = "") { - if (!enabled()) - return; - - flush_pending_function(); - close_pending_class(); - const bool documented = !pending_doc().empty(); - write_pending_doc(); - pending_class() = name; - output() << (documented ? "---@class " : "\n---@class ") << name; - if (bases != nullptr && bases[0] != '\0') - output() << " : " << bases; - output() << "\n"; +inline void class_(const char* name, const char* bases = "") { + if (!enabled()) { + return; + } + + flush_pending_function(); + close_pending_class(); + const bool documented = !pending_doc().empty(); + write_pending_doc(); + pending_class() = name; + output() << (documented ? "---@class " : "\n---@class ") << name; + if (bases != nullptr && bases[0] != '\0') { + output() << " : " << bases; + } + output() << "\n"; } -inline void alias_(const char *name, const char *target) { - if (!enabled()) - return; +inline void alias_(const char* name, const char* target) { + if (!enabled()) { + return; + } - flush_pending_function(); - close_pending_class(); - write_pending_doc(); - output() << "---@alias " << name << " " << target << "\n"; + flush_pending_function(); + close_pending_class(); + write_pending_doc(); + output() << "---@alias " << name << " " << target << "\n"; } -inline void doc(const char *text) { - if (!enabled() || text == nullptr || text[0] == '\0') - return; +inline void doc(std::string_view text) { + if (!enabled() || text.empty()) { + return; + } - // A docstring belongs to the declaration emitted by the following stub macro. - // Flush a preceding function first, but retain a pending class so this can - // also annotate its fields. - flush_pending_function(); - pending_doc() = text; + // A docstring belongs to the declaration emitted by the following stub + // macro. Flush a preceding function first, but retain a pending class so + // this can also annotate its fields. + flush_pending_function(); + pending_doc() = text; } -inline void field(const char *name, const char *type) { - if (!enabled()) - return; +inline void field(const char* name, const char* type) { + if (!enabled()) { + return; + } - if (!pending_class().empty()) { + if (!pending_class().empty()) { + write_pending_doc(); + output() << "---@field " << name << " " << type << "\n"; + return; + } + + flush_pending_function(); write_pending_doc(); - output() << "---@field " << name << " " << type << "\n"; - return; - } - - flush_pending_function(); - write_pending_doc(); - output() << "---@type " << type << "\n"; - output() << name << " = nil\n"; + output() << "---@type " << type << "\n"; + output() << name << " = nil\n"; } -inline void value(const char *owner, const char *name, const char *type) { - if (!enabled()) - return; +inline void value(const char* owner, const char* name, const char* type) { + if (!enabled()) { + return; + } - flush_pending_function(); - close_pending_class(); - declare_table(owner); - write_pending_doc(); - output() << "---@type " << type << "\n"; - output() << owner << "." << name << " = nil\n"; + flush_pending_function(); + close_pending_class(); + declare_table(owner); + write_pending_doc(); + output() << "---@type " << type << "\n"; + output() << owner << "." << name << " = nil\n"; } -inline void function(const char *owner, const char *name, const char *type) { - if (!enabled()) - return; +inline void function(const char* owner, const char* name, const char* type) { + if (!enabled()) { + return; + } - flush_pending_function(); - pending_function() = {owner, name, type, std::move(pending_doc()), {}}; - pending_doc().clear(); + flush_pending_function(); + pending_function() = {owner, name, type, std::move(pending_doc()), {}}; + pending_doc().clear(); } -inline void overload(const char *owner, const char *name, const char *type) { - if (!enabled()) - return; +inline void overload(const char* owner, const char* name, const char* type) { + if (!enabled()) { + return; + } - auto &pf = pending_function(); - if (pf.owner == owner && pf.name == name) - pf.overloads.push_back(type); + auto& pf = pending_function(); + if (pf.owner == owner && pf.name == name) { + pf.overloads.push_back(type); + } } -inline void operator_(const char *owner, const char *annotation) { - if (!enabled()) - return; - - flush_pending_function(); - close_pending_class(); - const bool documented = !pending_doc().empty(); - write_pending_doc(); - output() << (documented ? "---@class " : "\n---@class ") << owner << "\n"; - output() << "---@operator " << annotation << "\n"; - declare_table(owner); +inline void operator_(const char* owner, const char* annotation) { + if (!enabled()) { + return; + } + + flush_pending_function(); + close_pending_class(); + const bool documented = !pending_doc().empty(); + write_pending_doc(); + output() << (documented ? "---@class " : "\n---@class ") << owner << "\n"; + output() << "---@operator " << annotation << "\n"; + declare_table(owner); } -inline void indexed_field(const char *owner, const char *key_type, - const char *value_type) { - if (!enabled()) - return; - - flush_pending_function(); - close_pending_class(); - const bool documented = !pending_doc().empty(); - write_pending_doc(); - output() << (documented ? "---@class " : "\n---@class ") << owner << "\n"; - output() << "---@field [" << key_type << "] " << value_type << "\n"; - declare_table(owner); +inline void indexed_field(const char* owner, const char* key_type, + const char* value_type) { + if (!enabled()) { + return; + } + + flush_pending_function(); + close_pending_class(); + const bool documented = !pending_doc().empty(); + write_pending_doc(); + output() << (documented ? "---@class " : "\n---@class ") << owner << "\n"; + output() << "---@field [" << key_type << "] " << value_type << "\n"; + declare_table(owner); } -} // namespace lua_sf::stub +} // namespace lua_sf::stub #define LUASF_STUB_CLASS(...) ::lua_sf::stub::class_(__VA_ARGS__) #define LUASF_STUB_ALIAS(name, target) ::lua_sf::stub::alias_(name, target) #define LUASF_STUB_DOC(text) ::lua_sf::stub::doc(text) #define LUASF_STUB_FIELD(name, type_str) ::lua_sf::stub::field(name, type_str) -#define LUASF_STUB_VALUE(owner, name, type_str) \ - ::lua_sf::stub::value(owner, name, type_str) -#define LUASF_STUB_FUNCTION(owner, name, type_str) \ - ::lua_sf::stub::function(owner, name, type_str) -#define LUASF_STUB_OVERLOAD(owner, name, type_str) \ - ::lua_sf::stub::overload(owner, name, type_str) -#define LUASF_STUB_OPERATOR(owner, annotation) \ - ::lua_sf::stub::operator_(owner, annotation) -#define LUASF_STUB_INDEX_FIELD(owner, key_type, value_type) \ - ::lua_sf::stub::indexed_field(owner, key_type, value_type) +#define LUASF_STUB_VALUE(owner, name, type_str) \ + ::lua_sf::stub::value(owner, name, type_str) +#define LUASF_STUB_FUNCTION(owner, name, type_str) \ + ::lua_sf::stub::function(owner, name, type_str) +#define LUASF_STUB_OVERLOAD(owner, name, type_str) \ + ::lua_sf::stub::overload(owner, name, type_str) +#define LUASF_STUB_OPERATOR(owner, annotation) \ + ::lua_sf::stub::operator_(owner, annotation) +#define LUASF_STUB_INDEX_FIELD(owner, key_type, value_type) \ + ::lua_sf::stub::indexed_field(owner, key_type, value_type) diff --git a/include/luasf_glue.hpp b/include/luasf_glue.hpp new file mode 100644 index 0000000..5c6fcee --- /dev/null +++ b/include/luasf_glue.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include + +#include +#include +#include + +namespace lua_sf { + +namespace detail { + +template +void attach_shared_owner(lua_State* state, int index, + const std::shared_ptr& owner) { + lua_glue::AttachSharedOwner(state, index, + std::shared_ptr(owner, nullptr)); +} + +template +T* get_pushed_luasf_native_object(lua_State* state) { + return static_cast( + lua_glue::NativePointer(state, -1, lua_glue::TypeName())); +} + +inline bool copy_pushed_luasf_shared_owner(lua_State* state, + std::shared_ptr& owner) { + owner = lua_glue::NativeSharedOwner(state, -1); + return owner.use_count() != 0; +} + +template +bool get_pushed_luasf_shared_object(lua_State* state, + std::shared_ptr& value) { + auto* pointer = get_pushed_luasf_native_object(state); + auto owner = + lua_glue::NativeSharedOwner(state, -1, lua_glue::TypeName()); + if (pointer == nullptr || owner.use_count() == 0) { + return false; + } + value = std::shared_ptr(std::move(owner), pointer); + return true; +} + +} // namespace detail + +template +struct LuaSharedObject { + std::shared_ptr owner; +}; + +template +LuaSharedObject wrapLuaSharedObject(std::shared_ptr owner) { + return {std::move(owner)}; +} + +template +LuaSharedObject makeLuaSharedObject(Args&&... args) { + return wrapLuaSharedObject( + std::make_shared(std::forward(args)...)); +} + +template +void mark_shared_usertype(lua_glue::StateView lua) { + if (!lua_glue::NativeTypeTable(lua.lua_state(), lua_glue::TypeName()) + .valid()) { + throw std::logic_error( + "Shared LuaSF type must be registered before use"); + } +} + +template +void register_external_usertype(lua_glue::StateView lua) { + auto type = lua_glue::Class( + lua_glue::NativeTypeTable(lua.lua_state(), lua_glue::TypeName())); + (lua_glue::BindBase(type), ...); +} + +} // namespace lua_sf + +namespace lua_glue { + +template +struct Codec> { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + return Codec>::Check(state, index); + } + static lua_sf::LuaSharedObject Read(lua_State* state, int index) { + return {Codec>::Read(state, index)}; + } + static int Push(lua_State* state, const lua_sf::LuaSharedObject& value) { + return Codec>::Push(state, value.owner); + } +}; + +} // namespace lua_glue diff --git a/include/luasf_sol.hpp b/include/luasf_sol.hpp deleted file mode 100644 index e05a35c..0000000 --- a/include/luasf_sol.hpp +++ /dev/null @@ -1,387 +0,0 @@ -#pragma once - -// LuaSF entry point for sol2. Binding code must include this instead of -// . - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace lua_sf { - -// Use on all platforms so generated binding code stays identical (sol::nil is -// disabled on macOS). -inline constexpr decltype(sol::lua_nil) LUASF_SOL_NIL = sol::lua_nil; - -namespace detail { - -enum class LuaSFNativeLookup { - notInterop, - missing, - found, - external, -}; - -inline constexpr const char *LUASF_EXTERNAL_CAST_KEY = "__LuaSFExternalCast"; -inline constexpr const char *LUASF_SHARED_OWNER_KEY = "__LuaSFSharedOwner"; -inline constexpr const char *LUASF_SHARED_OWNER_METATABLE = - "LuaSF.SharedOwner"; - -struct LuaSFSharedOwner { - bool constructed = false; - alignas(std::shared_ptr) - std::byte ownerStorage[sizeof(std::shared_ptr)]; - - std::shared_ptr *owner() noexcept { - return std::launder( - reinterpret_cast *>(ownerStorage)); - } -}; - -inline int destroy_shared_owner(lua_State *state) noexcept { - LuaSFSharedOwner *token = static_cast( - luaL_testudata(state, 1, LUASF_SHARED_OWNER_METATABLE)); - if (token != nullptr && token->constructed) { - token->constructed = false; - std::destroy_at(token->owner()); - } - return 0; -} - -inline void push_shared_owner(lua_State *state, - const std::shared_ptr &owner) { - void *memory = lua_newuserdatauv(state, sizeof(LuaSFSharedOwner), 0); - LuaSFSharedOwner *token = - std::construct_at(static_cast(memory)); - if (luaL_newmetatable(state, LUASF_SHARED_OWNER_METATABLE) != 0) { - lua_pushcfunction(state, &destroy_shared_owner); - lua_setfield(state, -2, "__gc"); - } - lua_setmetatable(state, -2); - std::construct_at( - reinterpret_cast *>(token->ownerStorage), owner); - token->constructed = true; -} - -template -void attach_shared_owner(lua_State *state, int index, - const std::shared_ptr &owner) { - if (owner.use_count() == 0 || lua_type(state, index) != LUA_TUSERDATA) - return; - luaL_checkstack(state, 5, "not enough stack space for shared owner"); - index = lua_absindex(state, index); - if (lua_getiuservalue(state, index, 1) != LUA_TTABLE) { - lua_pop(state, 1); - lua_newtable(state); - } - const std::shared_ptr ownerControlBlock(owner, nullptr); - push_shared_owner(state, ownerControlBlock); - lua_setfield(state, -2, LUASF_SHARED_OWNER_KEY); - lua_setiuservalue(state, index, 1); -} - -inline void register_external_metatable(lua_State *state, - const std::string &metatableName) { - lua_getfield(state, LUA_REGISTRYINDEX, metatableName.c_str()); - if (!lua_istable(state, -1)) { - lua_pop(state, 1); - return; - } - lua_getfield(state, -1, &sol::detail::base_class_cast_key()[0]); - const int castType = lua_type(state, -1); - if (castType == LUA_TUSERDATA || castType == LUA_TLIGHTUSERDATA) - lua_setfield(state, -2, LUASF_EXTERNAL_CAST_KEY); - else - lua_pop(state, 1); - lua_pop(state, 1); -} - -template -T *get_pushed_luasf_native_object(lua_State *state); - -template -LuaSFNativeLookup push_luasf_native_object(lua_State *state, int index) { - index = lua_absindex(state, index); - const int originalTop = lua_gettop(state); - if (lua_checkstack(state, 6) == 0) - return LuaSFNativeLookup::missing; - if (lua_type(state, index) != LUA_TUSERDATA || - lua_getmetatable(state, index) == 0) - return LuaSFNativeLookup::notInterop; - lua_getfield(state, -1, "__LuaSFNativeComposite"); - const bool isComposite = lua_toboolean(state, -1) != 0; - lua_pop(state, 1); - if (!isComposite) { - lua_getfield(state, -1, LUASF_EXTERNAL_CAST_KEY); - const int castType = lua_type(state, -1); - const bool isExternalUsertype = castType == LUA_TUSERDATA || - castType == LUA_TLIGHTUSERDATA; - lua_settop(state, originalTop); - if (!isExternalUsertype) - return LuaSFNativeLookup::notInterop; - lua_pushvalue(state, index); - return LuaSFNativeLookup::external; - } - lua_settop(state, originalTop); - if (lua_getiuservalue(state, index, 1) != LUA_TTABLE) { - lua_settop(state, originalTop); - return LuaSFNativeLookup::missing; - } - lua_getfield(state, -1, "__nativeObjects"); - if (!lua_istable(state, -1)) { - lua_settop(state, originalTop); - return LuaSFNativeLookup::missing; - } - const int nativeObjectsIndex = lua_absindex(state, -1); - const std::string &typeName = sol::usertype_traits::qualified_name(); - lua_getfield(state, -1, typeName.c_str()); - if (lua_type(state, -1) == LUA_TUSERDATA && - get_pushed_luasf_native_object(state) != nullptr) { - lua_pushvalue(state, -1); - lua_replace(state, originalTop + 1); - lua_settop(state, originalTop + 1); - return LuaSFNativeLookup::found; - } - lua_pop(state, 1); - lua_pushnil(state); - while (lua_next(state, nativeObjectsIndex) != 0) { - if (lua_type(state, -1) == LUA_TUSERDATA && - get_pushed_luasf_native_object(state) != nullptr) { - lua_pushvalue(state, -1); - lua_replace(state, originalTop + 1); - lua_settop(state, originalTop + 1); - return LuaSFNativeLookup::external; - } - lua_pop(state, 1); - } - lua_settop(state, originalTop); - return LuaSFNativeLookup::missing; -} - -template -T *get_pushed_luasf_native_object(lua_State *state) { - void *memory = lua_touserdata(state, -1); - if (memory == nullptr) - return nullptr; - void *rawData = sol::detail::align_usertype_pointer(memory); - void *nativeObject = *static_cast(rawData); - if (nativeObject == nullptr || lua_getmetatable(state, -1) == 0) - return nullptr; - lua_getfield(state, -1, LUASF_EXTERNAL_CAST_KEY); - if (lua_type(state, -1) == LUA_TNIL) { - lua_pop(state, 1); - lua_getfield(state, -1, &sol::detail::base_class_cast_key()[0]); - } - if (lua_type(state, -1) != LUA_TNIL) { - void *castData = lua_touserdata(state, -1); - if (castData == nullptr) { - lua_pop(state, 2); - return nullptr; - } - sol::detail::inheritance_cast_function cast = - reinterpret_cast(castData); - nativeObject = cast(nativeObject, sol::usertype_traits::qualified_name()); - } - lua_pop(state, 2); - return static_cast(nativeObject); -} - -inline bool copy_pushed_shared_owner(lua_State *state, - std::shared_ptr &result, - bool &hasOwnerToken) { - const int originalTop = lua_gettop(state); - hasOwnerToken = false; - if (lua_getiuservalue(state, -1, 1) != LUA_TTABLE) { - lua_settop(state, originalTop); - return false; - } - lua_getfield(state, -1, LUASF_SHARED_OWNER_KEY); - hasOwnerToken = lua_type(state, -1) != LUA_TNIL; - LuaSFSharedOwner *token = static_cast( - luaL_testudata(state, -1, LUASF_SHARED_OWNER_METATABLE)); - const bool success = token != nullptr && token->constructed && - token->owner()->use_count() != 0; - if (success) - result = *token->owner(); - lua_settop(state, originalTop); - return success; -} - -inline bool push_single_shared_owner_dependency(lua_State *state) { - const int originalTop = lua_gettop(state); - if (lua_getiuservalue(state, -1, 1) != LUA_TTABLE) { - lua_settop(state, originalTop); - return false; - } - const int dependenciesIndex = lua_absindex(state, -1); - bool valid = true; - bool foundFreeList = false; - bool foundDependency = false; - lua_pushnil(state); - while (lua_next(state, dependenciesIndex) != 0) { - const bool integerKey = lua_isinteger(state, -2) != 0; - const lua_Integer key = integerKey ? lua_tointeger(state, -2) : 0; - const bool freeListEntry = - key == 1 && !foundFreeList && lua_isinteger(state, -1) != 0 && - lua_tointeger(state, -1) == 0; - const bool dependencyEntry = key == 2 && !foundDependency && - lua_type(state, -1) == LUA_TUSERDATA; - if (!integerKey || (!freeListEntry && !dependencyEntry)) - valid = false; - else if (freeListEntry) - foundFreeList = true; - else if (dependencyEntry) - foundDependency = true; - lua_pop(state, 1); - } - if (!valid || !foundFreeList || !foundDependency) { - lua_settop(state, originalTop); - return false; - } - lua_rawgeti(state, dependenciesIndex, 2); - if (lua_type(state, -1) != LUA_TUSERDATA) { - lua_settop(state, originalTop); - return false; - } - lua_remove(state, dependenciesIndex); - return true; -} - -inline bool copy_pushed_luasf_shared_owner(lua_State *state, - std::shared_ptr &result) { - if (lua_type(state, -1) != LUA_TUSERDATA || - lua_checkstack(state, 4) == 0) - return false; - const int originalTop = lua_gettop(state); - lua_pushvalue(state, -1); - std::unordered_set visited; - constexpr std::size_t maximumOwnerChainLength = 64; - bool success = false; - for (std::size_t count = 0; count < maximumOwnerChainLength; ++count) { - const void *identity = lua_topointer(state, -1); - if (identity == nullptr || !visited.insert(identity).second) - break; - bool hasOwnerToken = false; - if (copy_pushed_shared_owner(state, result, hasOwnerToken)) { - success = true; - break; - } - if (hasOwnerToken || !push_single_shared_owner_dependency(state)) - break; - lua_replace(state, -2); - } - lua_settop(state, originalTop); - return success; -} - -template -bool get_pushed_luasf_shared_object(lua_State *state, - std::shared_ptr &result) { - T *nativeObject = get_pushed_luasf_native_object(state); - if (nativeObject == nullptr) - return false; - std::shared_ptr owner; - if (!copy_pushed_luasf_shared_owner(state, owner)) - return false; - result = std::shared_ptr(std::move(owner), nativeObject); - return true; -} - -} // namespace detail - -template struct LuaSharedObject { - std::shared_ptr owner; -}; - -template -LuaSharedObject wrapLuaSharedObject(std::shared_ptr owner) { - return {std::move(owner)}; -} - -template -LuaSharedObject makeLuaSharedObject(Args &&...args) { - return wrapLuaSharedObject( - std::make_shared(std::forward(args)...)); -} - -template -int sol_lua_push(lua_State *state, const LuaSharedObject &value) { - const int pushed = sol::stack::push(state, value.owner); - if (pushed == 1) - detail::attach_shared_owner(state, -1, value.owner); - return pushed; -} - -template void mark_shared_usertype(sol::state_view lua) { - lua_State *state = lua.lua_state(); - detail::register_external_metatable( - state, sol::usertype_traits::metatable()); - detail::register_external_metatable( - state, sol::usertype_traits::metatable()); - detail::register_external_metatable( - state, sol::usertype_traits::metatable()); - detail::register_external_metatable( - state, sol::usertype_traits::metatable()); - detail::register_external_metatable( - state, sol::usertype_traits>::metatable()); -} - -template -void register_external_usertype(sol::state_view lua) { - (void)sizeof...(Bases); - mark_shared_usertype(lua); -} - -} // namespace lua_sf - -namespace sol { - -template -bool sol_lua_interop_check(types, lua_State *state, int index, type, - Handler &&, stack::record &tracking) { - const lua_sf::detail::LuaSFNativeLookup lookup = - lua_sf::detail::push_luasf_native_object(state, index); - if (lookup != lua_sf::detail::LuaSFNativeLookup::found && - lookup != lua_sf::detail::LuaSFNativeLookup::external) - return false; - T *value = lua_sf::detail::get_pushed_luasf_native_object(state); - lua_pop(state, 1); - if (value == nullptr) - return false; - tracking.use(1); - return true; -} - -template -std::pair sol_lua_interop_get(types, lua_State *state, int index, - void *, stack::record &tracking) { - const lua_sf::detail::LuaSFNativeLookup lookup = - lua_sf::detail::push_luasf_native_object(state, index); - if (lookup == lua_sf::detail::LuaSFNativeLookup::notInterop) - return {false, nullptr}; - if (lookup == lua_sf::detail::LuaSFNativeLookup::missing) { - luaL_typeerror(state, index, - sol::usertype_traits::qualified_name().c_str()); - return {false, nullptr}; - } - T *value = lua_sf::detail::get_pushed_luasf_native_object(state); - lua_pop(state, 1); - if (value == nullptr) { - if (lookup == lua_sf::detail::LuaSFNativeLookup::external) - return {false, nullptr}; - luaL_typeerror(state, index, - sol::usertype_traits::qualified_name().c_str()); - return {false, nullptr}; - } - tracking.use(1); - return {true, value}; -} - -} // namespace sol diff --git a/include/utils.hpp b/include/utils.hpp index 12979b1..2a66ded 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -1,7 +1,7 @@ #pragma once #include "LuaStateLifecycle.hpp" -#include "luasf_sol.hpp" +#include "luasf_glue.hpp" #include #include #include @@ -10,8 +10,8 @@ #include #include -#include "lua_stub.hpp" #include "LuaCallbackCodec.hpp" +#include "lua_stub.hpp" #include #include @@ -37,71 +37,75 @@ namespace lua_sf { inline sf::String to_sf_string(std::string_view value) { - return sf::String::fromUtf8(value.begin(), value.end()); + return sf::String::fromUtf8(value.begin(), value.end()); } -inline std::string to_utf8_string(const sf::String &value) { - const auto utf8 = value.toUtf8(); - return std::string(reinterpret_cast(utf8.data()), utf8.size()); +inline std::string to_utf8_string(const sf::String& value) { + const auto utf8 = value.toUtf8(); + return std::string(reinterpret_cast(utf8.data()), utf8.size()); } -inline sol::table sf_table(sol::state_view lua) { - return lua["sf"].get_or_create(); +inline lua_glue::Table sf_table(lua_glue::StateView lua) { + return lua["sf"].get_or_create(); } class WindowHandle { public: - WindowHandle() = default; + WindowHandle() = default; - explicit WindowHandle(std::uintptr_t value) - : handle_(nativeFromInteger(value)) {} + explicit WindowHandle(std::uintptr_t value) + : handle_(nativeFromInteger(value)) {} - [[nodiscard]] static WindowHandle fromNative(sf::WindowHandle handle) { - WindowHandle result; - result.handle_ = handle; - return result; - } + [[nodiscard]] static WindowHandle fromNative(sf::WindowHandle handle) { + WindowHandle result; + result.handle_ = handle; + return result; + } - [[nodiscard]] sf::WindowHandle getHandle() const { return handle_; } + [[nodiscard]] sf::WindowHandle getHandle() const { + return handle_; + } - [[nodiscard]] sf::WindowHandle native() const { return getHandle(); } + [[nodiscard]] sf::WindowHandle native() const { + return getHandle(); + } - [[nodiscard]] std::uintptr_t toInteger() const { - return integerFromNative(handle_); - } + [[nodiscard]] std::uintptr_t toInteger() const { + return integerFromNative(handle_); + } private: - [[nodiscard]] static sf::WindowHandle - nativeFromInteger(std::uintptr_t value) { -#if defined(SFML_SYSTEM_WINDOWS) || defined(SFML_SYSTEM_MACOS) || \ - defined(SFML_SYSTEM_IOS) || defined(SFML_SYSTEM_ANDROID) || \ + [[nodiscard]] static sf::WindowHandle nativeFromInteger( + std::uintptr_t value) { +#if defined(SFML_SYSTEM_WINDOWS) || defined(SFML_SYSTEM_MACOS) || \ + defined(SFML_SYSTEM_IOS) || defined(SFML_SYSTEM_ANDROID) || \ defined(SFML_SYSTEM_HARMONY) - return reinterpret_cast(value); + return reinterpret_cast(value); #else - return static_cast(value); + return static_cast(value); #endif - } + } - [[nodiscard]] static std::uintptr_t - integerFromNative(sf::WindowHandle handle) { -#if defined(SFML_SYSTEM_WINDOWS) || defined(SFML_SYSTEM_MACOS) || \ - defined(SFML_SYSTEM_IOS) || defined(SFML_SYSTEM_ANDROID) || \ + [[nodiscard]] static std::uintptr_t integerFromNative( + sf::WindowHandle handle) { +#if defined(SFML_SYSTEM_WINDOWS) || defined(SFML_SYSTEM_MACOS) || \ + defined(SFML_SYSTEM_IOS) || defined(SFML_SYSTEM_ANDROID) || \ defined(SFML_SYSTEM_HARMONY) - return reinterpret_cast(handle); + return reinterpret_cast(handle); #else - return static_cast(handle); + return static_cast(handle); #endif - } + } - sf::WindowHandle handle_{}; + sf::WindowHandle handle_{}; }; inline sf::WindowHandle window_handle_from_integer(std::uintptr_t value) { - return WindowHandle(value).getHandle(); + return WindowHandle(value).getHandle(); } inline std::uintptr_t window_handle_to_integer(sf::WindowHandle handle) { - return WindowHandle::fromNative(handle).toInteger(); + return WindowHandle::fromNative(handle).toInteger(); } template @@ -115,127 +119,150 @@ inline constexpr bool is_lua_integral_v = std::is_integral_v>> && !std::is_same_v>, bool>; -template class LuaIntegral { +template +class LuaIntegral { public: - using value_type = T; + using value_type = T; - LuaIntegral() = default; - explicit LuaIntegral(T value) : value_(value) {} + LuaIntegral() = default; + explicit LuaIntegral(T value) : value_(value) {} - [[nodiscard]] T value() const noexcept { return value_; } - explicit operator T() const noexcept { return value_; } + [[nodiscard]] T value() const noexcept { + return value_; + } + explicit operator T() const noexcept { + return value_; + } private: - T value_{}; + T value_{}; }; template -using LuaNumeric = - std::conditional_t, LuaIntegral, T>; +using LuaNumeric = std::conditional_t, LuaIntegral, T>; template -T unwrapLuaNumeric(const LuaNumeric &value); +T unwrapLuaNumeric(const LuaNumeric& value); template -bool tryReadLuaIntegral(lua_State *state, int index, T &value); - -template -bool sol_lua_check(sol::types>, lua_State *state, int index, - Handler &&handler, sol::stack::record &tracking); +bool tryReadLuaIntegral(lua_State* state, int index, T& value); template -LuaIntegral sol_lua_get(sol::types>, lua_State *state, - int index, sol::stack::record &tracking); - -template T object_as(const sol::object &object); +T object_as(const lua_glue::Object& object); template -std::vector array_from_object(const sol::object &object); +std::vector array_from_object(const lua_glue::Object& object); template -std::optional optional_from_object(const sol::object &object); +std::optional optional_from_object(const lua_glue::Object& object); template -sol::object as_lua_object(sol::state_view lua, const T &value); +lua_glue::Object as_lua_object(lua_glue::StateView lua, const T& value); template -sol::object vector_to_object(sol::state_view lua, - const std::vector &values); +lua_glue::Object vector_to_object(lua_glue::StateView lua, + const std::vector& values); template -sol::object optional_to_object(sol::state_view lua, - const std::optional &value); +lua_glue::Object optional_to_object(lua_glue::StateView lua, + const std::optional& value); template -sol::object optional_to_object(sol::state_view lua, std::optional &&value); +lua_glue::Object optional_to_object(lua_glue::StateView lua, + std::optional&& value); namespace detail { -template struct ShaderUniformArrayVariant { - using element_type = Element; +template +struct ShaderUniformArrayVariant { + using element_type = Element; - std::string methodName; - std::string luaArrayType; + std::string methodName; + std::string luaArrayType; }; template -ShaderUniformArrayVariant -shaderUniformArrayVariant(std::string methodName, std::string luaArrayType); +ShaderUniformArrayVariant shaderUniformArrayVariant( + std::string methodName, std::string luaArrayType); template -void bindShaderUniformArrays( - Usertype &usertype, std::string_view luaOwner, - std::string_view inferredMethod, - ShaderUniformArrayVariant... variants); +void bindShaderUniformArrays(Usertype& usertype, std::string_view luaOwner, + std::string_view inferredMethod, + ShaderUniformArrayVariant... variants); -} // namespace detail +} // namespace detail template -std::function function_from_object(const sol::object &object); +std::function function_from_object(const lua_glue::Object& object); template -std::function -function_from_object_at_native_thread_boundary(const sol::object &object); +std::function function_from_object_at_native_thread_boundary( + const lua_glue::Object& object); namespace callback { template -std::function -native_thread_from_object(const sol::object &object) { - return function_from_object_at_native_thread_boundary(object); +std::function native_thread_from_object( + const lua_glue::Object& object) { + return function_from_object_at_native_thread_boundary(object); } -} // namespace callback +} // namespace callback using LongLivedMemoryBuffer = std::shared_ptr>; -LongLivedMemoryBuffer makeLongLivedMemoryBuffer(const sol::object &object); +LongLivedMemoryBuffer makeLongLivedMemoryBuffer(const lua_glue::Object& object); -void rememberLongLivedMemory(const void *owner, LongLivedMemoryBuffer buffer); +void rememberLongLivedMemory(const void* owner, LongLivedMemoryBuffer buffer); -void releaseLongLivedMemory(const void *owner); +void releaseLongLivedMemory(const void* owner); -void rememberLongLivedStream(const void *owner, const sol::object &stream); +void rememberLongLivedStream(const void* owner, const lua_glue::Object& stream); -void releaseLongLivedStream(const void *owner); +void releaseLongLivedStream(const void* owner); -void releaseLongLivedResources(const void *owner); +void releaseLongLivedResources(const void* owner); template -void rememberLongLivedMemory(const T &owner, LongLivedMemoryBuffer buffer); +void rememberLongLivedMemory(const T& owner, LongLivedMemoryBuffer buffer); -template void releaseLongLivedMemory(const T &owner); +template +void releaseLongLivedMemory(const T& owner); template -void rememberLongLivedStream(const T &owner, const sol::object &stream); +void rememberLongLivedStream(const T& owner, const lua_glue::Object& stream); -template void releaseLongLivedStream(const T &owner); +template +void releaseLongLivedStream(const T& owner); -template void releaseLongLivedResources(const T &owner); +template +void releaseLongLivedResources(const T& owner); template -std::shared_ptr makeLongLivedMemoryObject(Args &&...args); +std::shared_ptr makeLongLivedMemoryObject(Args&&... args); -} // namespace lua_sf +} // namespace lua_sf + +namespace lua_glue { +template +struct Codec> { + static constexpr bool native = false; + static bool Check(lua_State* state, int index) { + T value{}; + return lua_sf::tryReadLuaIntegral(state, index, value); + } + static lua_sf::LuaIntegral Read(lua_State* state, int index) { + T value{}; + if (!lua_sf::tryReadLuaIntegral(state, index, value)) { + throw std::runtime_error( + "expected a finite, in-range integer value"); + } + return lua_sf::LuaIntegral(value); + } + static int Push(lua_State* state, lua_sf::LuaIntegral value) { + return lua_glue::Push(state, value.value()); + } +}; +} // namespace lua_glue #include "utils.inl" diff --git a/include/utils.inl b/include/utils.inl index dd6ed78..e72170b 100644 --- a/include/utils.inl +++ b/include/utils.inl @@ -2,7 +2,8 @@ namespace lua_sf { -template struct is_std_vector : std::false_type {}; +template +struct is_std_vector : std::false_type {}; template struct is_std_vector> : std::true_type {}; @@ -11,7 +12,8 @@ template inline constexpr bool is_std_vector_v = is_std_vector>>::value; -template struct is_std_optional : std::false_type {}; +template +struct is_std_optional : std::false_type {}; template struct is_std_optional> : std::true_type {}; @@ -20,378 +22,389 @@ template inline constexpr bool is_std_optional_v = is_std_optional>>::value; -template T unwrapLuaNumeric(const LuaNumeric &value) { - if constexpr (is_lua_integral_v) - return value.value(); - else - return value; +template +T unwrapLuaNumeric(const LuaNumeric& value) { + if constexpr (is_lua_integral_v) { + return value.value(); + } else { + return value; + } } -template bool luaIntegerFits(lua_Integer value) { - static_assert(is_lua_integral_v); - if constexpr (std::is_signed_v) { - if constexpr (std::numeric_limits::digits >= - std::numeric_limits::digits) { - return true; - } else { - return value >= static_cast(std::numeric_limits::min()) && - value <= static_cast(std::numeric_limits::max()); - } - } else { - if (value < 0) - return false; - using LuaUnsigned = std::make_unsigned_t; - if constexpr (std::numeric_limits::digits >= - std::numeric_limits::digits) { - return true; +template +bool luaIntegerFits(lua_Integer value) { + static_assert(is_lua_integral_v); + if constexpr (std::is_signed_v) { + if constexpr (std::numeric_limits::digits >= + std::numeric_limits::digits) { + return true; + } else { + return value >= static_cast( + std::numeric_limits::min()) && + value <= + static_cast(std::numeric_limits::max()); + } } else { - return static_cast(value) <= - static_cast(std::numeric_limits::max()); + if (value < 0) { + return false; + } + using LuaUnsigned = std::make_unsigned_t; + if constexpr (std::numeric_limits::digits >= + std::numeric_limits::digits) { + return true; + } else { + return static_cast(value) <= + static_cast(std::numeric_limits::max()); + } } - } } template -bool tryReadLuaIntegral(lua_State *state, int index, T &value) { - static_assert(is_lua_integral_v); - if (lua_type(state, index) != LUA_TNUMBER) - return false; +bool tryReadLuaIntegral(lua_State* state, int index, T& value) { + static_assert(is_lua_integral_v); + if (lua_type(state, index) != LUA_TNUMBER) { + return false; + } - if (lua_isinteger(state, index)) { - const lua_Integer integer = lua_tointeger(state, index); - if (!luaIntegerFits(integer)) - return false; + if (lua_isinteger(state, index)) { + const lua_Integer integer = lua_tointeger(state, index); + if (!luaIntegerFits(integer)) { + return false; + } - value = static_cast(integer); - return true; - } - - const lua_Number number = lua_tonumber(state, index); - if (!std::isfinite(number) || std::trunc(number) != number) - return false; - - constexpr int valueBits = std::numeric_limits::digits; - const lua_Number upperBound = std::ldexp(lua_Number{1}, valueBits); - if constexpr (std::is_signed_v) { - if (number < -upperBound || number >= upperBound) - return false; - } else if (number < lua_Number{0} || number >= upperBound) { - return false; - } - - value = static_cast(number); - return true; -} - -template -bool sol_lua_check(sol::types>, lua_State *state, int index, - Handler &&handler, sol::stack::record &tracking) { - tracking.use(1); - T value{}; - const bool success = tryReadLuaIntegral(state, index, value); - if (!success) { - handler(state, index, sol::type::number, sol::type_of(state, index), - "expected a finite, in-range integer value"); - } - return success; -} + value = static_cast(integer); + return true; + } -template -LuaIntegral sol_lua_get(sol::types>, lua_State *state, - int index, sol::stack::record &tracking) { - tracking.use(1); - T value{}; - if (!tryReadLuaIntegral(state, index, value)) - luaL_error(state, "expected a finite, in-range integer value"); - return LuaIntegral(value); + const lua_Number number = lua_tonumber(state, index); + if (!std::isfinite(number) || std::trunc(number) != number) { + return false; + } + + constexpr int valueBits = std::numeric_limits::digits; + const lua_Number upperBound = std::ldexp(lua_Number{1}, valueBits); + if constexpr (std::is_signed_v) { + if (number < -upperBound || number >= upperBound) { + return false; + } + } else if (number < lua_Number{0} || number >= upperBound) { + return false; + } + + value = static_cast(number); + return true; } -inline bool is_nil_object(const sol::object &object) { - return !object.valid() || object == LUASF_SOL_NIL; +inline bool is_nil_object(const lua_glue::Object& object) { + return !object.valid() || object == lua_glue::nil; } -inline void throw_on_lua_error(const sol::protected_function_result &result) { - if (result.valid()) - return; +inline void throw_on_lua_error(const lua_glue::CallResult& result) { + if (result.valid()) { + return; + } - const sol::error error = result; - throw std::runtime_error(error.what()); + throw std::runtime_error(result.error()); } -inline LuaRegistryReference -makeLuaRegistryReference(const sol::object &object) { - lua_State *state = object.lua_state(); - detail::registerLuaThreadForRegistryReference(state); - LuaStateExecutionScope execution(state); - if (!execution.active()) - throw std::runtime_error("Lua state is stopping"); - auto pushed = sol::stack::push_pop(object); - return LuaRegistryReference(state, pushed.index_of(object)); +inline LuaRegistryReference makeLuaRegistryReference( + const lua_glue::Object& object) { + lua_State* state = object.lua_state(); + detail::registerLuaThreadForRegistryReference(state); + LuaStateExecutionScope execution(state); + if (!execution.active()) { + throw std::runtime_error("Lua state is stopping"); + } + auto pushed = lua_glue::PushGuard(object); + return LuaRegistryReference(state, pushed.index()); } template -decltype(auto) withLuaRegistryCallback(const LuaRegistryReference &reference, - Callback &&callback) { - lua_State *state = reference.state(); - LuaStateExecutionScope execution(state); - if (!execution.active()) - throw std::runtime_error("Lua state is stopping"); - if (!reference.pushUnderExecutionScope()) - throw std::runtime_error("Lua callback is no longer available"); - auto popper = sol::stack::pop_n(state, 1); - sol::protected_function function = - sol::stack::get(state, -1); - return std::forward(callback)(function, sol::state_view(state)); -} - -inline std::unordered_map & +decltype(auto) withLuaRegistryCallback(const LuaRegistryReference& reference, + Callback&& callback) { + lua_State* state = reference.state(); + LuaStateExecutionScope execution(state); + if (!execution.active()) { + throw std::runtime_error("Lua state is stopping"); + } + if (!reference.pushUnderExecutionScope()) { + throw std::runtime_error("Lua callback is no longer available"); + } + auto popper = lua_glue::PopGuard(state, 1); + lua_glue::Function function = lua_glue::Read(state, -1); + return std::forward(callback)(function, + lua_glue::StateView(state)); +} + +inline std::unordered_map& longLivedMemoryStore() { - static std::unordered_map store; - return store; + static std::unordered_map store; + return store; } -inline std::mutex &longLivedMemoryStoreMutex() { - static std::mutex mutex; - return mutex; +inline std::mutex& longLivedMemoryStoreMutex() { + static std::mutex mutex; + return mutex; } -inline LongLivedMemoryBuffer -makeLongLivedMemoryBuffer(const sol::object &object) { - return std::make_shared>( - array_from_object(object)); +inline LongLivedMemoryBuffer makeLongLivedMemoryBuffer( + const lua_glue::Object& object) { + return std::make_shared>( + array_from_object(object)); } -inline void rememberLongLivedMemory(const void *owner, +inline void rememberLongLivedMemory(const void* owner, LongLivedMemoryBuffer buffer) { - if (!owner) - return; + if (!owner) { + return; + } - std::lock_guard lock(longLivedMemoryStoreMutex()); - if (buffer) - longLivedMemoryStore()[owner] = std::move(buffer); - else - longLivedMemoryStore().erase(owner); + std::lock_guard lock(longLivedMemoryStoreMutex()); + if (buffer) { + longLivedMemoryStore()[owner] = std::move(buffer); + } else { + longLivedMemoryStore().erase(owner); + } } -inline void releaseLongLivedMemory(const void *owner) { - if (!owner) - return; +inline void releaseLongLivedMemory(const void* owner) { + if (!owner) { + return; + } - std::lock_guard lock(longLivedMemoryStoreMutex()); - longLivedMemoryStore().erase(owner); + std::lock_guard lock(longLivedMemoryStoreMutex()); + longLivedMemoryStore().erase(owner); } -inline void rememberLongLivedStream(const void *owner, - const sol::object &stream) { - if (!owner) - return; - if (!stream.valid()) { - detail::releaseLuaRegistryReference(owner); - return; - } - detail::retainLuaRegistryReference(owner, makeLuaRegistryReference(stream)); +inline void rememberLongLivedStream(const void* owner, + const lua_glue::Object& stream) { + if (!owner) { + return; + } + if (!stream.valid()) { + detail::releaseLuaRegistryReference(owner); + return; + } + detail::retainLuaRegistryReference(owner, makeLuaRegistryReference(stream)); } -inline void releaseLongLivedStream(const void *owner) { - if (!owner) - return; +inline void releaseLongLivedStream(const void* owner) { + if (!owner) { + return; + } - detail::releaseLuaRegistryReference(owner); + detail::releaseLuaRegistryReference(owner); } -inline void releaseLongLivedResources(const void *owner) { - releaseLongLivedMemory(owner); - releaseLongLivedStream(owner); +inline void releaseLongLivedResources(const void* owner) { + releaseLongLivedMemory(owner); + releaseLongLivedStream(owner); } template -void rememberLongLivedMemory(const T &owner, LongLivedMemoryBuffer buffer) { - rememberLongLivedMemory(static_cast(&owner), std::move(buffer)); +void rememberLongLivedMemory(const T& owner, LongLivedMemoryBuffer buffer) { + rememberLongLivedMemory(static_cast(&owner), + std::move(buffer)); } -template void releaseLongLivedMemory(const T &owner) { - releaseLongLivedMemory(static_cast(&owner)); +template +void releaseLongLivedMemory(const T& owner) { + releaseLongLivedMemory(static_cast(&owner)); } template -void rememberLongLivedStream(const T &owner, const sol::object &stream) { - rememberLongLivedStream(static_cast(&owner), stream); +void rememberLongLivedStream(const T& owner, const lua_glue::Object& stream) { + rememberLongLivedStream(static_cast(&owner), stream); } -template void releaseLongLivedStream(const T &owner) { - releaseLongLivedStream(static_cast(&owner)); +template +void releaseLongLivedStream(const T& owner) { + releaseLongLivedStream(static_cast(&owner)); } -template void releaseLongLivedResources(const T &owner) { - releaseLongLivedResources(static_cast(&owner)); +template +void releaseLongLivedResources(const T& owner) { + releaseLongLivedResources(static_cast(&owner)); } template -std::shared_ptr makeLongLivedMemoryObject(Args &&...args) { - return std::shared_ptr(new T(std::forward(args)...), [](T *object) { - releaseLongLivedResources(static_cast(object)); - delete object; - }); -} - -template T object_as(const sol::object &object) { - using U = std::remove_cv_t>; - if constexpr (std::is_same_v) { - return to_sf_string(object.as()); - } else if constexpr (std::is_same_v) { - return to_sf_string(object.as()).toWideString(); - } else if constexpr (std::is_same_v) { - return std::filesystem::path(object.as()); -#if defined(LUASF_IOS_COMPAT) - } else if constexpr (std::is_same_v) { - using Duration = typename U::duration; - using Rep = typename U::duration::rep; - return U(Duration(static_cast(object.as()))); -#endif - } else if constexpr (is_lua_integral_v) { - return object.as>().value(); - } else if constexpr (std::is_same_v) { - return static_cast( - object.as>().value()); - } else if constexpr (is_std_vector_v) { - return array_from_object(object); - } else if constexpr (is_std_optional_v) { - return optional_from_object(object); - } else { - return object.as(); - } +std::shared_ptr makeLongLivedMemoryObject(Args&&... args) { + return std::shared_ptr( + new T(std::forward(args)...), [](T* object) { + releaseLongLivedResources(static_cast(object)); + delete object; + }); } template -std::vector array_from_object(const sol::object &object) { - if (object.get_type() == sol::type::table) { - const sol::table table = object.as(); - std::vector values; - values.reserve(table.size()); - - if constexpr (is_byte_like_v) { - for (const auto &entry : table) - values.push_back(static_cast( - object_as(entry.second.as()))); - return values; +T object_as(const lua_glue::Object& object) { + using U = std::remove_cv_t>; + if constexpr (std::is_same_v) { + return to_sf_string(object.as()); + } else if constexpr (std::is_same_v) { + return to_sf_string(object.as()).toWideString(); + } else if constexpr (std::is_same_v) { + return std::filesystem::path(object.as()); +#if defined(LUASF_IOS_COMPAT) + } else if constexpr (std::is_same_v) { + using Duration = typename U::duration; + using Rep = typename U::duration::rep; + return U(Duration(static_cast(object.as()))); +#endif + } else if constexpr (is_lua_integral_v) { + return object.as>().value(); + } else if constexpr (std::is_same_v) { + return static_cast( + object.as>().value()); + } else if constexpr (is_std_vector_v) { + return array_from_object(object); + } else if constexpr (is_std_optional_v) { + return optional_from_object(object); + } else { + return object.as(); } +} - for (const auto &entry : table) { - const sol::object value = entry.second; - values.push_back(object_as(value)); +template +std::vector array_from_object(const lua_glue::Object& object) { + if (object.get_type() == lua_glue::Type::Table) { + const lua_glue::Table table = object.as(); + std::vector values; + values.reserve(table.size()); + + if constexpr (is_byte_like_v) { + for (const auto& entry : table) { + values.push_back(static_cast(object_as( + entry.second.as()))); + } + return values; + } + + for (const auto& entry : table) { + const lua_glue::Object value = entry.second; + values.push_back(object_as(value)); + } + return values; } - return values; - } - if constexpr (is_byte_like_v) { - if (object.get_type() == sol::type::string) { - const auto data = object.as(); - std::vector values; - values.reserve(data.size()); - for (unsigned char byte : data) - values.push_back(static_cast(byte)); - return values; + if constexpr (is_byte_like_v) { + if (object.get_type() == lua_glue::Type::String) { + const auto data = object.as(); + std::vector values; + values.reserve(data.size()); + for (unsigned char byte : data) { + values.push_back(static_cast(byte)); + } + return values; + } } - } - throw std::runtime_error( - "expected Lua table or byte string for array conversion"); + throw std::runtime_error( + "expected Lua table or byte string for array conversion"); } template -std::optional optional_from_object(const sol::object &object) { - if (!object.valid() || object == LUASF_SOL_NIL) - return std::nullopt; - return object_as(object); +std::optional optional_from_object(const lua_glue::Object& object) { + if (!object.valid() || object == lua_glue::nil) { + return std::nullopt; + } + return object_as(object); } template -sol::object as_lua_object(sol::state_view lua, const T &value) { - using U = std::remove_cv_t>; - if constexpr (std::is_same_v) { - return sol::make_object(lua, to_utf8_string(value)); - } else if constexpr (std::is_same_v) { - return sol::make_object(lua, to_utf8_string(sf::String(value))); - } else if constexpr (std::is_same_v) { - return sol::make_object(lua, value.string()); +lua_glue::Object as_lua_object(lua_glue::StateView lua, const T& value) { + using U = std::remove_cv_t>; + if constexpr (std::is_same_v) { + return lua_glue::MakeObject(lua, to_utf8_string(value)); + } else if constexpr (std::is_same_v) { + return lua_glue::MakeObject(lua, to_utf8_string(sf::String(value))); + } else if constexpr (std::is_same_v) { + return lua_glue::MakeObject(lua, value.string()); #if defined(LUASF_IOS_COMPAT) - } else if constexpr (std::is_same_v) { - return sol::make_object( - lua, static_cast(value.time_since_epoch().count())); + } else if constexpr (std::is_same_v) { + return lua_glue::MakeObject( + lua, static_cast(value.time_since_epoch().count())); #endif - } else if constexpr (is_std_vector_v) { - return vector_to_object(lua, value); - } else if constexpr (is_std_optional_v) { - return optional_to_object(lua, value); - } else { - return sol::make_object(lua, value); - } + } else if constexpr (is_std_vector_v) { + return vector_to_object(lua, value); + } else if constexpr (is_std_optional_v) { + return optional_to_object(lua, value); + } else { + return lua_glue::MakeObject(lua, value); + } } template -sol::object vector_to_object(sol::state_view lua, - const std::vector &values) { - sol::table table = lua.create_table(static_cast(values.size()), 0); - int index = 1; - for (const auto &value : values) - table[index++] = as_lua_object(lua, value); - return sol::make_object(lua, table); +lua_glue::Object vector_to_object(lua_glue::StateView lua, + const std::vector& values) { + lua_glue::Table table = + lua.create_table(static_cast(values.size()), 0); + int index = 1; + for (const auto& value : values) { + table[index++] = as_lua_object(lua, value); + } + return lua_glue::MakeObject(lua, table); } template -sol::object optional_to_object(sol::state_view lua, - const std::optional &value) { - if (!value) - return sol::make_object(lua, LUASF_SOL_NIL); +lua_glue::Object optional_to_object(lua_glue::StateView lua, + const std::optional& value) { + if (!value) { + return lua_glue::MakeObject(lua, lua_glue::nil); + } - return as_lua_object(lua, *value); + return as_lua_object(lua, *value); } template -sol::object optional_to_object(sol::state_view lua, std::optional &&value) { - if (!value) - return sol::make_object(lua, LUASF_SOL_NIL); - - using U = std::remove_cv_t; - if constexpr (std::is_same_v) { - return sol::make_object(lua, to_utf8_string(*value)); - } else if constexpr (std::is_same_v) { - return sol::make_object(lua, value->string()); - } else if constexpr (is_std_vector_v) { - return vector_to_object(lua, *value); - } else { - return sol::make_object(lua, std::move(*value)); - } +lua_glue::Object optional_to_object(lua_glue::StateView lua, + std::optional&& value) { + if (!value) { + return lua_glue::MakeObject(lua, lua_glue::nil); + } + + using U = std::remove_cv_t; + if constexpr (std::is_same_v) { + return lua_glue::MakeObject(lua, to_utf8_string(*value)); + } else if constexpr (std::is_same_v) { + return lua_glue::MakeObject(lua, value->string()); + } else if constexpr (is_std_vector_v) { + return vector_to_object(lua, *value); + } else { + return lua_glue::MakeObject(lua, std::move(*value)); + } } template -sol::object -optional_to_object(sol::state_view lua, - const std::optional> &value) { - if (!value) - return sol::make_object(lua, LUASF_SOL_NIL); - return vector_to_object(lua, *value); +lua_glue::Object optional_to_object( + lua_glue::StateView lua, + const std::optional>& value) { + if (!value) { + return lua_glue::MakeObject(lua, lua_glue::nil); + } + return vector_to_object(lua, *value); } template -sol::object -optional_to_object(sol::state_view lua, - std::optional> &&value) { - if (!value) - return sol::make_object(lua, LUASF_SOL_NIL); - return vector_to_object(lua, *value); +lua_glue::Object optional_to_object( + lua_glue::StateView lua, std::optional>&& value) { + if (!value) { + return lua_glue::MakeObject(lua, lua_glue::nil); + } + return vector_to_object(lua, *value); } namespace detail { template inline constexpr bool hasExactShaderUniformArrayOverload = requires { - static_cast( - &sf::Shader::setUniformArray); + static_cast( + &sf::Shader::setUniformArray); }; -template struct AreUniqueTypes : std::true_type {}; +template +struct AreUniqueTypes : std::true_type {}; template struct AreUniqueTypes @@ -402,340 +415,369 @@ template inline constexpr bool areUniqueTypes = AreUniqueTypes::value; template -bool shaderUniformArrayElementMatches(const sol::object &first) { - if constexpr (std::is_floating_point_v) - return first.get_type() == sol::type::number; - else - return first.is(); +bool shaderUniformArrayElementMatches(const lua_glue::Object& first) { + if constexpr (std::is_floating_point_v) { + return first.get_type() == lua_glue::Type::Number; + } else { + return first.is(); + } } template -std::vector -shaderUniformArrayFromObject(const sol::object &values, - const std::string &errorLabel) { - if (values.get_type() != sol::type::table) - throw std::runtime_error(errorLabel + " expects a Lua array"); - - const sol::table table = values.as(); - const std::size_t length = table.size(); - std::size_t entryCount = 0; - for ([[maybe_unused]] const auto &entry : table) - ++entryCount; - if (entryCount != length) - throw std::runtime_error(errorLabel + - " expects a dense 1-based Lua array"); - - std::vector buffer; - buffer.reserve(length); - for (std::size_t index = 1; index <= length; ++index) { - const sol::object value = table[index]; - if (is_nil_object(value)) - throw std::runtime_error(errorLabel + - " expects a dense 1-based Lua array"); - if (!shaderUniformArrayElementMatches(value)) - throw std::runtime_error(errorLabel + " element " + - std::to_string(index) + - " has an incompatible type"); - buffer.push_back(object_as(value)); - } - return buffer; +std::vector shaderUniformArrayFromObject( + const lua_glue::Object& values, const std::string& errorLabel) { + if (values.get_type() != lua_glue::Type::Table) { + throw std::runtime_error(errorLabel + " expects a Lua array"); + } + + const lua_glue::Table table = values.as(); + const std::size_t length = table.size(); + std::size_t entryCount = 0; + for ([[maybe_unused]] const auto& entry : table) { + ++entryCount; + } + if (entryCount != length) { + throw std::runtime_error(errorLabel + + " expects a dense 1-based Lua array"); + } + + std::vector buffer; + buffer.reserve(length); + for (std::size_t index = 1; index <= length; ++index) { + const lua_glue::Object value = table[index]; + if (is_nil_object(value)) { + throw std::runtime_error(errorLabel + + " expects a dense 1-based Lua array"); + } + if (!shaderUniformArrayElementMatches(value)) { + throw std::runtime_error(errorLabel + " element " + + std::to_string(index) + + " has an incompatible type"); + } + buffer.push_back(object_as(value)); + } + return buffer; } template auto shaderUniformArraySetter(std::string errorLabel) { - static_assert( - hasExactShaderUniformArrayOverload, - "ShaderUniformArrayVariant element must exactly match an " - "sf::Shader::setUniformArray pointer-element overload"); - - return [errorLabel = std::move(errorLabel)]( - sf::Shader &self, std::string name, sol::object values) { - auto buffer = shaderUniformArrayFromObject(values, errorLabel); - if (buffer.empty()) - return; - - self.setUniformArray(name, buffer.data(), buffer.size()); - }; + static_assert(hasExactShaderUniformArrayOverload, + "ShaderUniformArrayVariant element must exactly match an " + "sf::Shader::setUniformArray pointer-element overload"); + + return [errorLabel = std::move(errorLabel)]( + sf::Shader& self, std::string name, lua_glue::Object values) { + auto buffer = shaderUniformArrayFromObject(values, errorLabel); + if (buffer.empty()) { + return; + } + + self.setUniformArray(name, buffer.data(), buffer.size()); + }; } template -bool trySetInferredShaderUniformArray( - const ShaderUniformArrayVariant &, sf::Shader &self, - const std::string &name, const sol::object &values, - const sol::object &first, const std::string &errorLabel) { - if (!shaderUniformArrayElementMatches(first)) - return false; +bool trySetInferredShaderUniformArray(const ShaderUniformArrayVariant&, + sf::Shader& self, const std::string& name, + const lua_glue::Object& values, + const lua_glue::Object& first, + const std::string& errorLabel) { + if (!shaderUniformArrayElementMatches(first)) { + return false; + } - shaderUniformArraySetter(errorLabel)(self, name, values); - return true; + shaderUniformArraySetter(errorLabel)(self, name, values); + return true; } inline std::string shaderUniformArrayStubSignature(std::string_view luaOwner, std::string_view luaType) { - std::string signature = "fun(self: "; - signature.append(luaOwner); - signature.append(", name: string, values: "); - signature.append(luaType); - signature.push_back(')'); - return signature; + std::string signature = "fun(self: "; + signature.append(luaOwner); + signature.append(", name: string, values: "); + signature.append(luaType); + signature.push_back(')'); + return signature; } template std::string shaderUniformArrayTypedMethodHint( - const ShaderUniformArrayVariant &...variants) { - const std::array methodNames = { - variants.methodName...}; - std::string hint; - for (std::size_t index = 0; index < methodNames.size(); ++index) { - if (index > 0) { - if (index + 1 == methodNames.size()) - hint.append(methodNames.size() == 2 ? " or " : ", or "); - else - hint.append(", "); - } - hint.append(methodNames[index]); - } - return hint; + const ShaderUniformArrayVariant&... variants) { + const std::array methodNames = { + variants.methodName...}; + std::string hint; + for (std::size_t index = 0; index < methodNames.size(); ++index) { + if (index > 0) { + if (index + 1 == methodNames.size()) { + hint.append(methodNames.size() == 2 ? " or " : ", or "); + } else { + hint.append(", "); + } + } + hint.append(methodNames[index]); + } + return hint; } template void validateShaderUniformArrayVariants( std::string_view luaOwner, std::string_view inferredMethod, - const ShaderUniformArrayVariant &...variants) { - static_assert(sizeof...(Elements) > 0, - "bindShaderUniformArrays requires at least one variant"); - static_assert(areUniqueTypes, - "bindShaderUniformArrays requires unique element types"); - static_assert((hasExactShaderUniformArrayOverload && ...), - "ShaderUniformArrayVariant element does not exactly match an " - "sf::Shader::setUniformArray pointer-element overload"); - - if (luaOwner.empty()) - throw std::invalid_argument("Shader uniform-array Lua owner is empty"); - if (inferredMethod.empty()) - throw std::invalid_argument( - "Shader uniform-array inferred method name is empty"); - - const std::array methodNames = { - variants.methodName...}; - const std::array luaArrayTypes = { - variants.luaArrayType...}; - - for (std::size_t index = 0; index < methodNames.size(); ++index) { - if (methodNames[index].empty()) - throw std::invalid_argument( - "Shader uniform-array typed method name is empty"); - if (luaArrayTypes[index].empty()) - throw std::invalid_argument( - "Shader uniform-array Lua array type is empty"); - if (methodNames[index] == inferredMethod) - throw std::invalid_argument( - "Shader uniform-array typed and inferred method names overlap"); - for (std::size_t previous = 0; previous < index; ++previous) { - if (methodNames[index] == methodNames[previous]) + const ShaderUniformArrayVariant&... variants) { + static_assert(sizeof...(Elements) > 0, + "bindShaderUniformArrays requires at least one variant"); + static_assert(areUniqueTypes, + "bindShaderUniformArrays requires unique element types"); + static_assert((hasExactShaderUniformArrayOverload && ...), + "ShaderUniformArrayVariant element does not exactly match an " + "sf::Shader::setUniformArray pointer-element overload"); + + if (luaOwner.empty()) { + throw std::invalid_argument("Shader uniform-array Lua owner is empty"); + } + if (inferredMethod.empty()) { throw std::invalid_argument( - "Shader uniform-array typed method names must be unique"); + "Shader uniform-array inferred method name is empty"); + } + + const std::array methodNames = { + variants.methodName...}; + const std::array luaArrayTypes = { + variants.luaArrayType...}; + + for (std::size_t index = 0; index < methodNames.size(); ++index) { + if (methodNames[index].empty()) { + throw std::invalid_argument( + "Shader uniform-array typed method name is empty"); + } + if (luaArrayTypes[index].empty()) { + throw std::invalid_argument( + "Shader uniform-array Lua array type is empty"); + } + if (methodNames[index] == inferredMethod) { + throw std::invalid_argument( + "Shader uniform-array typed and inferred method names overlap"); + } + for (std::size_t previous = 0; previous < index; ++previous) { + if (methodNames[index] == methodNames[previous]) { + throw std::invalid_argument( + "Shader uniform-array typed method names must be unique"); + } + } } - } } template -ShaderUniformArrayVariant -shaderUniformArrayVariant(std::string methodName, std::string luaArrayType) { - static_assert( - hasExactShaderUniformArrayOverload, - "ShaderUniformArrayVariant element must exactly match an " - "sf::Shader::setUniformArray pointer-element overload"); - static_assert(std::is_same_v>, - "ShaderUniformArrayVariant element must be unqualified"); +ShaderUniformArrayVariant shaderUniformArrayVariant( + std::string methodName, std::string luaArrayType) { + static_assert(hasExactShaderUniformArrayOverload, + "ShaderUniformArrayVariant element must exactly match an " + "sf::Shader::setUniformArray pointer-element overload"); + static_assert(std::is_same_v>, + "ShaderUniformArrayVariant element must be unqualified"); - return {std::move(methodName), std::move(luaArrayType)}; + return {std::move(methodName), std::move(luaArrayType)}; } template -void bindShaderUniformArrays( - Usertype &usertype, std::string_view luaOwner, - std::string_view inferredMethod, - ShaderUniformArrayVariant... variants) { - validateShaderUniformArrayVariants(luaOwner, inferredMethod, variants...); - - const std::string owner(luaOwner); - const std::string inferredName(inferredMethod); - bool firstStub = true; - const auto addInferredStub = [&](const auto &variant) { - const std::string signature = - shaderUniformArrayStubSignature(owner, variant.luaArrayType); - if (firstStub) { - stub::function(owner.c_str(), inferredName.c_str(), signature.c_str()); - firstStub = false; - } else { - stub::overload(owner.c_str(), inferredName.c_str(), signature.c_str()); - } - }; - (addInferredStub(variants), ...); - - const auto addTypedStub = [&](const auto &variant) { - const std::string signature = - shaderUniformArrayStubSignature(owner, variant.luaArrayType); - stub::function(owner.c_str(), variant.methodName.c_str(), - signature.c_str()); - }; - (addTypedStub(variants), ...); - - (usertype.set_function( - variants.methodName, - shaderUniformArraySetter(owner + "." + variants.methodName)), - ...); - - const std::string errorLabel = owner + "." + inferredName; - const std::string typedMethodHint = - shaderUniformArrayTypedMethodHint(variants...); - auto variantTuple = std::make_tuple(std::move(variants)...); - usertype.set_function( - inferredName, - [variants = std::move(variantTuple), errorLabel, - typedMethodHint](sf::Shader &self, std::string name, - sol::object values) { - if (values.get_type() != sol::type::table) - throw std::runtime_error(errorLabel + " expects a Lua array"); - - const sol::table table = values.as(); - const sol::object first = table[1]; - if (is_nil_object(first)) - throw std::runtime_error(errorLabel + - " cannot infer an empty array; use " + - typedMethodHint); +void bindShaderUniformArrays(Usertype& usertype, std::string_view luaOwner, + std::string_view inferredMethod, + ShaderUniformArrayVariant... variants) { + validateShaderUniformArrayVariants(luaOwner, inferredMethod, variants...); + + const std::string owner(luaOwner); + const std::string inferredName(inferredMethod); + bool firstStub = true; + const auto addInferredStub = [&](const auto& variant) { + const std::string signature = + shaderUniformArrayStubSignature(owner, variant.luaArrayType); + if (firstStub) { + stub::function(owner.c_str(), inferredName.c_str(), + signature.c_str()); + firstStub = false; + } else { + stub::overload(owner.c_str(), inferredName.c_str(), + signature.c_str()); + } + }; + (addInferredStub(variants), ...); + + const auto addTypedStub = [&](const auto& variant) { + const std::string signature = + shaderUniformArrayStubSignature(owner, variant.luaArrayType); + stub::function(owner.c_str(), variant.methodName.c_str(), + signature.c_str()); + }; + (addTypedStub(variants), ...); + + (usertype.set_function( + variants.methodName, + shaderUniformArraySetter(owner + "." + variants.methodName)), + ...); + + const std::string errorLabel = owner + "." + inferredName; + const std::string typedMethodHint = + shaderUniformArrayTypedMethodHint(variants...); + auto variantTuple = std::make_tuple(std::move(variants)...); + usertype.set_function(inferredName, [variants = std::move(variantTuple), + errorLabel, typedMethodHint]( + sf::Shader& self, std::string name, + lua_glue::Object values) { + if (values.get_type() != lua_glue::Type::Table) { + throw std::runtime_error(errorLabel + " expects a Lua array"); + } + + const lua_glue::Table table = values.as(); + const lua_glue::Object first = table[1]; + if (is_nil_object(first)) { + throw std::runtime_error(errorLabel + + " cannot infer an empty array; use " + + typedMethodHint); + } const bool handled = std::apply( - [&](const auto &...variant) { - return (trySetInferredShaderUniformArray( - variant, self, name, values, first, errorLabel) || - ...); + [&](const auto&... variant) { + return (trySetInferredShaderUniformArray( + variant, self, name, values, first, errorLabel) || + ...); }, variants); - if (!handled) - throw std::runtime_error(errorLabel + - " received an unsupported array element " - "type"); - }); + if (!handled) { + throw std::runtime_error(errorLabel + + " received an unsupported array element " + "type"); + } + }); } -} // namespace detail +} // namespace detail template -sol::object callback_argument_to_object(sol::state_view lua, T &&value) { - using Argument = T; - using U = std::remove_cv_t>; - static_assert(!std::is_rvalue_reference_v, - "generic Lua callbacks do not support rvalue references"); - static_assert(!std::is_pointer_v, - "generic Lua callbacks require a codec for pointer arguments"); - static_assert(!(std::is_lvalue_reference_v && - !std::is_const_v> && - !std::is_class_v), - "generic Lua callbacks require a codec for mutable scalar " - "references"); - - if constexpr (std::is_lvalue_reference_v && std::is_class_v && - !std::is_copy_constructible_v) { - if constexpr (std::is_const_v>) - return sol::make_object(lua, std::cref(value)); - else - return sol::make_object(lua, std::ref(value)); - } else { - return as_lua_object(lua, value); - } -} - -template struct function_converter; - -template struct function_converter { - static std::function from_object(const sol::object &object, - std::string label) { - static_assert((!std::is_rvalue_reference_v && ...), - "generic Lua callbacks require a codec for rvalue-reference " - "arguments"); +lua_glue::Object callback_argument_to_object(lua_glue::StateView lua, + T&& value) { + using Argument = T; + using U = std::remove_cv_t>; + static_assert(!std::is_rvalue_reference_v, + "generic Lua callbacks do not support rvalue references"); static_assert( - (!std::is_pointer_v> && ...), + !std::is_pointer_v, "generic Lua callbacks require a codec for pointer arguments"); - static_assert( - std::is_void_v || (!std::is_pointer_v> && - !std::is_reference_v), - "generic Lua callbacks require a codec for pointer or reference " - "returns"); - - const LuaRegistryReference callback = makeLuaRegistryReference(object); - return [callback, label = std::move(label)](Args... args) -> R { - try { - return withLuaRegistryCallback( - callback, - [&](sol::protected_function &function, sol::state_view lua) -> R { - sol::protected_function_result result = - function(callback_argument_to_object( - lua, std::forward(args))...); - throw_on_lua_error(result); - if constexpr (!std::is_void_v) { - const sol::object returned = result; - return object_as(returned); - } - }); - } catch (const std::exception &error) { - if (label.empty()) - throw; - throw std::runtime_error(label + ": " + error.what()); - } - }; - } + static_assert(!(std::is_lvalue_reference_v && + !std::is_const_v> && + !std::is_class_v), + "generic Lua callbacks require a codec for mutable scalar " + "references"); + + if constexpr (std::is_lvalue_reference_v && std::is_class_v && + !std::is_copy_constructible_v) { + if constexpr (std::is_const_v>) { + return lua_glue::MakeObject(lua, std::cref(value)); + } else { + return lua_glue::MakeObject(lua, std::ref(value)); + } + } else { + return as_lua_object(lua, value); + } +} + +template +struct function_converter; + +template +struct function_converter { + static std::function from_object(const lua_glue::Object& object, + std::string label) { + static_assert( + (!std::is_rvalue_reference_v && ...), + "generic Lua callbacks require a codec for rvalue-reference " + "arguments"); + static_assert( + (!std::is_pointer_v> && ...), + "generic Lua callbacks require a codec for pointer arguments"); + static_assert( + std::is_void_v || + (!std::is_pointer_v> && + !std::is_reference_v), + "generic Lua callbacks require a codec for pointer or reference " + "returns"); + + const LuaRegistryReference callback = makeLuaRegistryReference(object); + return [callback, label = std::move(label)](Args... args) -> R { + try { + return withLuaRegistryCallback( + callback, + [&](lua_glue::Function& function, + lua_glue::StateView lua) -> R { + lua_glue::CallResult result = + function(callback_argument_to_object( + lua, std::forward(args))...); + throw_on_lua_error(result); + if constexpr (!std::is_void_v) { + const lua_glue::Object returned = result; + return object_as(returned); + } + }); + } catch (const std::exception& error) { + if (label.empty()) { + throw; + } + throw std::runtime_error(label + ": " + error.what()); + } + }; + } }; -template struct native_thread_function_converter; +template +struct native_thread_function_converter; template struct native_thread_function_converter { - static std::function from_object(const sol::object &object) { - if (is_nil_object(object)) - return {}; - - const LuaRegistryReference callback = makeLuaRegistryReference(object); - return [callback](Args... args) noexcept { - try { - withLuaRegistryCallback( - callback, [&](sol::protected_function &function, sol::state_view) { - sol::protected_function_result result = - function(std::forward(args)...); - throw_on_lua_error(result); - }); - } catch (...) { - return; - } - }; - } + static std::function from_object( + const lua_glue::Object& object) { + if (is_nil_object(object)) { + return {}; + } + + const LuaRegistryReference callback = makeLuaRegistryReference(object); + return [callback](Args... args) noexcept { + try { + withLuaRegistryCallback( + callback, + [&](lua_glue::Function& function, lua_glue::StateView) { + lua_glue::CallResult result = + function(std::forward(args)...); + throw_on_lua_error(result); + }); + } catch (...) { + return; + } + }; + } }; template -std::function -function_from_object_at_native_thread_boundary(const sol::object &object) { - return native_thread_function_converter::from_object(object); +std::function function_from_object_at_native_thread_boundary( + const lua_glue::Object& object) { + return native_thread_function_converter::from_object(object); } namespace callback::detail { template struct FromObject, GenericCallbackCodec> { - static std::function read(const sol::object &object, - CallbackOptions options) { - return function_converter::from_object( - object, std::move(options.label)); - } + static std::function read(const lua_glue::Object& object, + CallbackOptions options) { + return function_converter::from_object( + object, std::move(options.label)); + } }; -} // namespace callback::detail +} // namespace callback::detail template -std::function function_from_object(const sol::object &object) { - return callback::from_object, - callback::GenericCallbackCodec>( - object, callback::CallbackOptions{"Lua callback", false}); +std::function function_from_object(const lua_glue::Object& object) { + return callback::from_object, + callback::GenericCallbackCodec>( + object, callback::CallbackOptions{"Lua callback", false}); } -} // namespace lua_sf +} // namespace lua_sf diff --git a/init.bat b/init.bat index 6fdadc8..aca59d2 100644 --- a/init.bat +++ b/init.bat @@ -79,32 +79,6 @@ if not exist "third_party\Lua\src\lua.h" ( echo Using existing third_party\Lua. ) -if not exist "third_party\sol2\include\sol2\sol.hpp" ( - echo Downloading sol2 headers... - mkdir "third_party\sol2\include\sol2" 2>nul - for %%f in (config.hpp forward.hpp sol.hpp) do ( - powershell -Command "Invoke-WebRequest -Uri 'https://github.com/ThePhD/sol2/releases/download/v%SOL2_VERSION%/%%f' -OutFile 'third_party\sol2\include\sol2\%%f'" - if errorlevel 1 ( - echo Failed to download sol2 %%f. - exit /b 1 - ) - ) -) else ( - echo Using existing third_party\sol2. -) - -echo Applying sol2 PR #1606 patch if needed... -rem The published sol2 headers use CRLF line endings, so the patch has to -rem ignore whitespace to match. -git apply --ignore-whitespace --reverse --check --directory=third_party/sol2 -p1 cmake/sol/pr1606.patch >nul 2>nul -if not errorlevel 1 ( - echo PR #1606 patch already applied to sol2. -) else ( - git apply --ignore-whitespace --check --directory=third_party/sol2 -p1 cmake/sol/pr1606.patch - if errorlevel 1 exit /b 1 - git apply --ignore-whitespace --directory=third_party/sol2 -p1 cmake/sol/pr1606.patch - if errorlevel 1 exit /b 1 -) echo. echo SFML variant: %SFML_VARIANT% (%SFML_VARIANT_TAG%) diff --git a/init.sh b/init.sh index 0d8732f..d4d88f7 100644 --- a/init.sh +++ b/init.sh @@ -21,7 +21,6 @@ SFML_ME_OH_REPOSITORY= SFML_ME_OH_TAG= LUA_VERSION= LUA_SHA256= -SOL2_VERSION= while IFS='=' read -r key value; do value=$(printf '%s' "$value" | tr -d '\r') @@ -36,14 +35,13 @@ while IFS='=' read -r key value; do SFML_ME_OH_TAG) SFML_ME_OH_TAG=$value ;; LUA_VERSION) LUA_VERSION=$value ;; LUA_SHA256) LUA_SHA256=$value ;; - SOL2_VERSION) SOL2_VERSION=$value ;; esac done < versions.conf if [ -z "$SFML_REPOSITORY" ] || [ -z "$SFML_TAG" ] || [ -z "$SFML_ME_REPOSITORY" ] || [ -z "$SFML_ME_TAG" ] || [ -z "$SFML_ME_OH_REPOSITORY" ] || [ -z "$SFML_ME_OH_TAG" ] || - [ -z "$LUA_VERSION" ] || [ -z "$LUA_SHA256" ] || [ -z "$SOL2_VERSION" ]; then + [ -z "$LUA_VERSION" ] || [ -z "$LUA_SHA256" ]; then echo "Missing required versions in versions.conf." >&2 exit 1 fi @@ -119,32 +117,6 @@ download_url() { fi } -apply_sol2_pr1606_patch() { - echo "Applying sol2 PR #1606 patch if needed..." - patch_file="$SCRIPT_DIR/cmake/sol/pr1606.patch" - # The published sol2 headers use CRLF line endings, so the patch has to - # ignore whitespace to match. - if git apply --ignore-whitespace --reverse --check --directory=third_party/sol2 -p1 "$patch_file" >/dev/null 2>&1; then - echo "PR #1606 patch already applied to sol2." - return - fi - git apply --ignore-whitespace --check --directory=third_party/sol2 -p1 "$patch_file" - git apply --ignore-whitespace --directory=third_party/sol2 -p1 "$patch_file" -} - -if [ ! -f "third_party/sol2/include/sol2/sol.hpp" ]; then - echo "Downloading sol2 headers..." - mkdir -p "third_party/sol2/include/sol2" - for file in config.hpp forward.hpp sol.hpp; do - download_url \ - "https://github.com/ThePhD/sol2/releases/download/v$SOL2_VERSION/$file" \ - "third_party/sol2/include/sol2/$file" - done -else - echo "Using existing third_party/sol2." -fi - -apply_sol2_pr1606_patch echo echo "Dependencies are ready in $SCRIPT_DIR/third_party." diff --git a/pack_result.bat b/pack_result.bat index 1781df1..4355729 100644 --- a/pack_result.bat +++ b/pack_result.bat @@ -3,32 +3,41 @@ setlocal EnableExtensions EnableDelayedExpansion cd /d "%~dp0" +set "SOURCE_ONLY=0" +if not "%~2"=="" goto :usage +if not "%~1"=="" ( + if /i not "%~1"=="--source-only" goto :usage + set "SOURCE_ONLY=1" +) + set "OUTPUT_DIR=%CD%\output" set "BUILD_DIR=%OUTPUT_DIR%\build" set "RESULT_DIR=%OUTPUT_DIR%\result" set "EMBEDDED_RESULT_DIR=%RESULT_DIR%\embedded" set "PACKAGES_DIR=%OUTPUT_DIR%\packages" set "STAGING_DIR=%PACKAGES_DIR%\.staging" +if "%SOURCE_ONLY%"=="1" set "STAGING_DIR=%PACKAGES_DIR%\.staging-source" -if not exist "%EMBEDDED_RESULT_DIR%" ( - echo Missing output\result\embedded. Run collect_result.bat first. - exit /b 1 +for %%F in (callback_codecs.json sfml_api.json) do ( + if not exist "%OUTPUT_DIR%\LuaSF\%%F" ( + echo Missing output\LuaSF\%%F. Run build.bat first. + exit /b 1 + ) ) - -if not exist "%OUTPUT_DIR%\callback_codecs.json" ( - echo Missing output\callback_codecs.json. Run build.bat first. - exit /b 1 +if "%SOURCE_ONLY%"=="0" ( + if not exist "%EMBEDDED_RESULT_DIR%" ( + echo Missing output\result\embedded. Run collect_result.bat first. + exit /b 1 + ) + for %%F in (callback_codecs.json sfml_api.json) do ( + if not exist "%EMBEDDED_RESULT_DIR%\%%F" ( + echo Missing embedded %%F. Run collect_result.bat first. + exit /b 1 + ) + ) ) -if not exist "%EMBEDDED_RESULT_DIR%\callback_codecs.json" ( - echo Missing embedded callback codec manifest. Run collect_result.bat first. - exit /b 1 -) - -if not exist "%EMBEDDED_RESULT_DIR%\sfml_api.json" ( - echo Missing embedded SFML API snapshot. Run collect_result.bat first. - exit /b 1 -) +if "%SOURCE_ONLY%"=="1" goto :source_names set "PLATFORM_OS=" set "PLATFORM_ARCH=" @@ -58,44 +67,53 @@ call :normalize_compiler "%PLATFORM_COMPILER%" set "PLATFORM_COMPILER=%NORMALIZED%" set "PLATFORM_TAG=%PLATFORM_OS%-%PLATFORM_ARCH%-%PLATFORM_COMPILER%" + +:source_names set "SFML_VARIANT=" if exist "%CD%\.luasf-sfml-variant" set /p SFML_VARIANT=<"%CD%\.luasf-sfml-variant" set "VARIANT_SUFFIX=" if not "%SFML_VARIANT%"=="" set "VARIANT_SUFFIX=-%SFML_VARIANT%" -set "SOURCE_NAME=LuaSF-source%VARIANT_SUFFIX%" +set "SOURCE_VARIANT_SUFFIX=%VARIANT_SUFFIX%" +if "%SFML_VARIANT%"=="ME-OH" set "SOURCE_VARIANT_SUFFIX=-ME" +set "SOURCE_NAME=LuaSF-source%SOURCE_VARIANT_SUFFIX%" set "EMBEDDED_NAME=LuaSF-embedded%VARIANT_SUFFIX%-%PLATFORM_TAG%" set "SOURCE_ZIP=%PACKAGES_DIR%\%SOURCE_NAME%.zip" set "EMBEDDED_ZIP=%PACKAGES_DIR%\%EMBEDDED_NAME%.zip" echo Packing LuaSF redistributable archives... if "%SFML_VARIANT%"=="" (echo SFML variant: default) else (echo SFML variant: %SFML_VARIANT%) -echo Platform: %PLATFORM_TAG% +if "%SOURCE_ONLY%"=="0" echo Platform: %PLATFORM_TAG% echo Packages: %PACKAGES_DIR% -if exist "%PACKAGES_DIR%" rmdir /s /q "%PACKAGES_DIR%" -mkdir "%STAGING_DIR%\%EMBEDDED_NAME%" - -rem Source package: output\ without build, bin, result, packages. -rem ME-OH consumes the ME source package; only its embedded package is distinct. -if "%SFML_VARIANT%"=="ME-OH" goto :pack_embedded -mkdir "%STAGING_DIR%\%SOURCE_NAME%" -for /f "delims=" %%I in ('dir /b /a "%OUTPUT_DIR%"') do ( - if /i not "%%I"=="build" if /i not "%%I"=="bin" if /i not "%%I"=="result" if /i not "%%I"=="packages" ( - if exist "%OUTPUT_DIR%\%%I\*" ( - xcopy /e /i /q /y "%OUTPUT_DIR%\%%I" "%STAGING_DIR%\%SOURCE_NAME%\%%I\" >nul - ) else ( - copy /y "%OUTPUT_DIR%\%%I" "%STAGING_DIR%\%SOURCE_NAME%\" >nul - ) +if "%SOURCE_ONLY%"=="0" ( + if exist "%PACKAGES_DIR%" rmdir /s /q "%PACKAGES_DIR%" +) else ( + if exist "%STAGING_DIR%" rmdir /s /q "%STAGING_DIR%" +) +mkdir "%STAGING_DIR%" + +rem Source archive contains exactly the LuaSF and LuaGlue CMake projects. +if "%SOURCE_ONLY%"=="0" if "%SFML_VARIANT%"=="ME-OH" goto :pack_embedded +for %%P in (LuaSF LuaGlue) do ( + if not exist "%OUTPUT_DIR%\%%P\CMakeLists.txt" ( + echo Missing source project %%P. Run build.bat first. + exit /b 1 ) + robocopy "%OUTPUT_DIR%\%%P" "%STAGING_DIR%\%%P" /E /XD build .git __pycache__ .cache /NFL /NDL /NJH /NJS /NP >nul + if errorlevel 8 exit /b 1 ) - -powershell -NoProfile -Command "Compress-Archive -LiteralPath '%STAGING_DIR%\%SOURCE_NAME%' -DestinationPath '%SOURCE_ZIP%' -CompressionLevel Optimal" +powershell -NoProfile -Command "Compress-Archive -LiteralPath '%STAGING_DIR%\LuaSF','%STAGING_DIR%\LuaGlue' -DestinationPath '%STAGING_DIR%\%SOURCE_NAME%.zip' -CompressionLevel Optimal" if errorlevel 1 ( echo Failed to create "%SOURCE_ZIP%". exit /b 1 ) +move /Y "%STAGING_DIR%\%SOURCE_NAME%.zip" "%SOURCE_ZIP%" >nul +if errorlevel 1 exit /b 1 +if "%SOURCE_ONLY%"=="1" goto :pack_done + :pack_embedded +mkdir "%STAGING_DIR%\%EMBEDDED_NAME%" xcopy /e /i /q /y "%EMBEDDED_RESULT_DIR%\*" "%STAGING_DIR%\%EMBEDDED_NAME%\" >nul powershell -NoProfile -Command "Compress-Archive -LiteralPath '%STAGING_DIR%\%EMBEDDED_NAME%' -DestinationPath '%EMBEDDED_ZIP%' -CompressionLevel Optimal" @@ -104,18 +122,25 @@ if errorlevel 1 ( exit /b 1 ) +:pack_done rmdir /s /q "%STAGING_DIR%" echo. echo Done. -if "%SFML_VARIANT%"=="ME-OH" ( +if "%SOURCE_ONLY%"=="1" ( + echo Source: %SOURCE_ZIP% +) else if "%SFML_VARIANT%"=="ME-OH" ( echo Source: use LuaSF-source-ME ) else ( echo Source: %SOURCE_ZIP% ) -echo Embedded: %EMBEDDED_ZIP% +if "%SOURCE_ONLY%"=="0" echo Embedded: %EMBEDDED_ZIP% exit /b 0 +:usage +echo Usage: pack_result.bat [--source-only] +exit /b 2 + :normalize_os set "NORMALIZED=%~1" if /i "%NORMALIZED%"=="Darwin" set "NORMALIZED=macOS" diff --git a/pack_result.sh b/pack_result.sh index 7419657..b6b8273 100644 --- a/pack_result.sh +++ b/pack_result.sh @@ -1,39 +1,55 @@ #!/usr/bin/env sh set -eu +# Keep macOS AppleDouble metadata out of the portable source archive. +export COPYFILE_DISABLE=1 + SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) cd "$SCRIPT_DIR" +SOURCE_ONLY=0 +case "$#:${1:-}" in + 0:) ;; + 1:--source-only) SOURCE_ONLY=1 ;; + *) + echo "Usage: sh pack_result.sh [--source-only]" >&2 + exit 2 + ;; +esac + OUTPUT_DIR="$SCRIPT_DIR/output" BUILD_DIR="$OUTPUT_DIR/build" RESULT_DIR="$OUTPUT_DIR/result" EMBEDDED_RESULT_DIR="$RESULT_DIR/embedded" PACKAGES_DIR="$OUTPUT_DIR/packages" STAGING_DIR="$PACKAGES_DIR/.staging" +if [ "$SOURCE_ONLY" -eq 1 ]; then + STAGING_DIR="$PACKAGES_DIR/.staging-source" +fi if ! command -v tar >/dev/null 2>&1; then echo "Missing tar. Install tar and retry." >&2 exit 1 fi -if [ ! -d "$EMBEDDED_RESULT_DIR" ]; then - echo "Missing output/result/embedded. Run sh collect_result.sh first." >&2 - exit 1 -fi - -if [ ! -f "$OUTPUT_DIR/callback_codecs.json" ]; then - echo "Missing output/callback_codecs.json. Run sh build.sh first." >&2 - exit 1 -fi - -if [ ! -f "$EMBEDDED_RESULT_DIR/callback_codecs.json" ]; then - echo "Missing embedded callback codec manifest. Run sh collect_result.sh first." >&2 - exit 1 -fi +for source_file in callback_codecs.json sfml_api.json; do + if [ ! -f "$OUTPUT_DIR/LuaSF/$source_file" ]; then + echo "Missing output/LuaSF/$source_file. Run sh build.sh first." >&2 + exit 1 + fi +done -if [ ! -f "$EMBEDDED_RESULT_DIR/sfml_api.json" ]; then - echo "Missing embedded SFML API snapshot. Run sh collect_result.sh first." >&2 - exit 1 +if [ "$SOURCE_ONLY" -eq 0 ]; then + if [ ! -d "$EMBEDDED_RESULT_DIR" ]; then + echo "Missing output/result/embedded. Run sh collect_result.sh first." >&2 + exit 1 + fi + for embedded_file in callback_codecs.json sfml_api.json; do + if [ ! -f "$EMBEDDED_RESULT_DIR/$embedded_file" ]; then + echo "Missing embedded $embedded_file. Run sh collect_result.sh first." >&2 + exit 1 + fi + done fi cmake_cache_value() { @@ -110,10 +126,13 @@ detect_compiler() { normalize_compiler "$value" } -PLATFORM_OS=$(detect_os) -PLATFORM_ARCH=$(detect_arch) -PLATFORM_COMPILER=$(detect_compiler) -PLATFORM_TAG="${PLATFORM_OS}-${PLATFORM_ARCH}-${PLATFORM_COMPILER}" +PLATFORM_TAG= +if [ "$SOURCE_ONLY" -eq 0 ]; then + PLATFORM_OS=$(detect_os) + PLATFORM_ARCH=$(detect_arch) + PLATFORM_COMPILER=$(detect_compiler) + PLATFORM_TAG="${PLATFORM_OS}-${PLATFORM_ARCH}-${PLATFORM_COMPILER}" +fi SFML_VARIANT=$(cat "$SCRIPT_DIR/.luasf-sfml-variant" 2>/dev/null || true) VARIANT_SUFFIX= @@ -121,7 +140,11 @@ if [ -n "$SFML_VARIANT" ]; then VARIANT_SUFFIX="-$SFML_VARIANT" fi -SOURCE_NAME="LuaSF-source${VARIANT_SUFFIX}" +SOURCE_VARIANT_SUFFIX=$VARIANT_SUFFIX +if [ "$SFML_VARIANT" = "ME-OH" ]; then + SOURCE_VARIANT_SUFFIX=-ME +fi +SOURCE_NAME="LuaSF-source${SOURCE_VARIANT_SUFFIX}" EMBEDDED_NAME="LuaSF-embedded${VARIANT_SUFFIX}-${PLATFORM_TAG}" SOURCE_ARCHIVE="$PACKAGES_DIR/${SOURCE_NAME}.tar.gz" @@ -129,40 +152,58 @@ EMBEDDED_ARCHIVE="$PACKAGES_DIR/${EMBEDDED_NAME}.tar.gz" echo "Packing LuaSF redistributable archives..." echo "SFML variant: ${SFML_VARIANT:-default}" -echo "Platform: $PLATFORM_TAG" +if [ "$SOURCE_ONLY" -eq 0 ]; then + echo "Platform: $PLATFORM_TAG" +fi echo "Packages: $PACKAGES_DIR" -rm -rf "$PACKAGES_DIR" +if [ "$SOURCE_ONLY" -eq 0 ]; then + rm -rf "$PACKAGES_DIR" +else + rm -rf "$STAGING_DIR" +fi mkdir -p "$STAGING_DIR" -# Source package: output/ without build, bin, result, packages. +# Source archives contain only the two reusable CMake source projects. # ME-OH consumes the ME source package; only its embedded package is distinct. -if [ "$SFML_VARIANT" != "ME-OH" ]; then - mkdir -p "$STAGING_DIR/$SOURCE_NAME" - find "$OUTPUT_DIR" -mindepth 1 -maxdepth 1 \ - ! -name build ! -name bin ! -name result ! -name packages \ - -exec cp -R {} "$STAGING_DIR/$SOURCE_NAME/" \; +if [ "$SFML_VARIANT" != "ME-OH" ] || [ "$SOURCE_ONLY" -eq 1 ]; then + for source_project in LuaSF LuaGlue; do + if [ ! -f "$OUTPUT_DIR/$source_project/CMakeLists.txt" ]; then + echo "Missing source project $source_project. Run sh build.sh first." >&2 + exit 1 + fi + mkdir -p "$STAGING_DIR/$source_project" + (cd "$OUTPUT_DIR/$source_project" && tar --exclude=build --exclude=.git --exclude=__pycache__ --exclude=.cache -cf - .) | + (cd "$STAGING_DIR/$source_project" && tar -xf -) + done fi # Embedded package with a named top-level folder. -mkdir -p "$STAGING_DIR/$EMBEDDED_NAME" -cp -R "$EMBEDDED_RESULT_DIR"/. "$STAGING_DIR/$EMBEDDED_NAME"/ +if [ "$SOURCE_ONLY" -eq 0 ]; then + mkdir -p "$STAGING_DIR/$EMBEDDED_NAME" + cp -R "$EMBEDDED_RESULT_DIR"/. "$STAGING_DIR/$EMBEDDED_NAME"/ +fi ( cd "$STAGING_DIR" - if [ "$SFML_VARIANT" != "ME-OH" ]; then - tar -czf "$SOURCE_ARCHIVE" "$SOURCE_NAME" + if [ "$SFML_VARIANT" != "ME-OH" ] || [ "$SOURCE_ONLY" -eq 1 ]; then + tar -czf "$STAGING_DIR/${SOURCE_NAME}.tar.gz" LuaSF LuaGlue + mv "$STAGING_DIR/${SOURCE_NAME}.tar.gz" "$SOURCE_ARCHIVE" + fi + if [ "$SOURCE_ONLY" -eq 0 ]; then + tar -czf "$EMBEDDED_ARCHIVE" "$EMBEDDED_NAME" fi - tar -czf "$EMBEDDED_ARCHIVE" "$EMBEDDED_NAME" ) rm -rf "$STAGING_DIR" echo echo "Done." -if [ "$SFML_VARIANT" != "ME-OH" ]; then +if [ "$SFML_VARIANT" != "ME-OH" ] || [ "$SOURCE_ONLY" -eq 1 ]; then echo "Source: $SOURCE_ARCHIVE" else echo "Source: use LuaSF-source-ME" fi -echo "Embedded: $EMBEDDED_ARCHIVE" +if [ "$SOURCE_ONLY" -eq 0 ]; then + echo "Embedded: $EMBEDDED_ARCHIVE" +fi diff --git a/src/LuaCallbackCodec.cpp b/src/LuaCallbackCodec.cpp index bb11b6f..59fcbd2 100644 --- a/src/LuaCallbackCodec.cpp +++ b/src/LuaCallbackCodec.cpp @@ -19,512 +19,553 @@ namespace lua_sf::callback::detail { class CallbackContext final { public: - CallbackContext(const sol::object &object, std::string callbackLabel) - : reference(makeLuaRegistryReference(object)), - label(std::move(callbackLabel)) {} - - LuaRegistryReference reference; - LuaRegistryReference inputFrames; - LuaRegistryReference outputFrames; - std::size_t inputSampleCount{}; - std::size_t outputSampleCount{}; - std::string label; - std::atomic faulted{}; + CallbackContext(const lua_glue::Object& object, std::string callbackLabel) + : reference(makeLuaRegistryReference(object)), + label(std::move(callbackLabel)) {} + + LuaRegistryReference reference; + LuaRegistryReference inputFrames; + LuaRegistryReference outputFrames; + std::size_t inputSampleCount{}; + std::size_t outputSampleCount{}; + std::string label; + std::atomic faulted{}; }; namespace { class AutomaticGcPause final { public: - explicit AutomaticGcPause(lua_State *state) noexcept - : state_(state), wasRunning_(lua_gc(state, LUA_GCISRUNNING) != 0) { - if (wasRunning_) - lua_gc(state_, LUA_GCSTOP); - } - - ~AutomaticGcPause() { - if (wasRunning_) - lua_gc(state_, LUA_GCRESTART); - else - lua_gc(state_, LUA_GCSTOP); - } - - AutomaticGcPause(const AutomaticGcPause &) = delete; - AutomaticGcPause &operator=(const AutomaticGcPause &) = delete; + explicit AutomaticGcPause(lua_State* state) noexcept + : state_(state), wasRunning_(lua_gc(state, LUA_GCISRUNNING) != 0) { + if (wasRunning_) { + lua_gc(state_, LUA_GCSTOP); + } + } + + ~AutomaticGcPause() { + if (wasRunning_) { + lua_gc(state_, LUA_GCRESTART); + } else { + lua_gc(state_, LUA_GCSTOP); + } + } + + AutomaticGcPause(const AutomaticGcPause&) = delete; + AutomaticGcPause& operator=(const AutomaticGcPause&) = delete; private: - lua_State *state_{}; - bool wasRunning_{}; + lua_State* state_{}; + bool wasRunning_{}; }; -void recordFault(const std::shared_ptr &context, +void recordFault(const std::shared_ptr& context, std::string_view message) noexcept { - bool expected = false; - if (!context->faulted.compare_exchange_strong(expected, true, - std::memory_order_acq_rel)) - return; - context->reference.deferCallbackError(context->label, message); + bool expected = false; + if (!context->faulted.compare_exchange_strong(expected, true, + std::memory_order_acq_rel)) { + return; + } + context->reference.deferCallbackError(context->label, message); } -void fallback(const float *input, unsigned int &inputCount, float *output, - unsigned int &outputCount, unsigned int channels) noexcept { - const unsigned int originalInputCount = inputCount; - const unsigned int outputCapacity = outputCount; - if (input == nullptr || output == nullptr || channels == 0) { - inputCount = 0; - outputCount = 0; - return; - } - const unsigned int frameCount = std::min(originalInputCount, outputCapacity); - if (frameCount != 0 && - static_cast(frameCount) > - std::numeric_limits::max() / channels) { - inputCount = 0; - outputCount = 0; - return; - } - const std::size_t samples = static_cast(frameCount) * channels; - if (samples > std::numeric_limits::max() / sizeof(float)) { - inputCount = 0; - outputCount = 0; - return; - } - std::memmove(output, input, samples * sizeof(float)); - inputCount = frameCount; - outputCount = frameCount; +void fallback(const float* input, unsigned int& inputCount, float* output, + unsigned int& outputCount, unsigned int channels) noexcept { + const unsigned int originalInputCount = inputCount; + const unsigned int outputCapacity = outputCount; + if (input == nullptr || output == nullptr || channels == 0) { + inputCount = 0; + outputCount = 0; + return; + } + const unsigned int frameCount = + std::min(originalInputCount, outputCapacity); + if (frameCount != 0 && + static_cast(frameCount) > + std::numeric_limits::max() / channels) { + inputCount = 0; + outputCount = 0; + return; + } + const std::size_t samples = static_cast(frameCount) * channels; + if (samples > std::numeric_limits::max() / sizeof(float)) { + inputCount = 0; + outputCount = 0; + return; + } + std::memmove(output, input, samples * sizeof(float)); + inputCount = frameCount; + outputCount = frameCount; } std::size_t sampleCount(unsigned int frames, unsigned int channels) { - if (channels != 0 && static_cast(frames) > - std::numeric_limits::max() / channels) - throw std::overflow_error("audio callback sample count overflow"); - const std::size_t count = static_cast(frames) * channels; - if (count >= static_cast(std::numeric_limits::max()) || - count > static_cast(std::numeric_limits::max())) - throw std::overflow_error("audio callback exceeds Lua array capacity"); - return count; + if (channels != 0 && + static_cast(frames) > + std::numeric_limits::max() / channels) { + throw std::overflow_error("audio callback sample count overflow"); + } + const std::size_t count = static_cast(frames) * channels; + if (count >= static_cast(std::numeric_limits::max()) || + count > + static_cast(std::numeric_limits::max())) { + throw std::overflow_error("audio callback exceeds Lua array capacity"); + } + return count; } -class LuaStackRestore final { -public: - explicit LuaStackRestore(lua_State *state) noexcept - : state_(state), top_(lua_gettop(state)) {} - - ~LuaStackRestore() { lua_settop(state_, top_); } - - LuaStackRestore(const LuaStackRestore &) = delete; - LuaStackRestore &operator=(const LuaStackRestore &) = delete; +int pushCachedDenseTable(lua_State* state, LuaRegistryReference& reference, + std::size_t sampleCount, + std::size_t& previousSampleCount, + const float* samples) { + if (!lua_checkstack(state, 8)) { + throw std::runtime_error("Lua audio sample stack cannot grow"); + } + if (!reference) { + lua_pushcfunction(state, [](lua_State* inner) -> int { + lua_createtable(inner, static_cast(lua_tointeger(inner, 1)), + 0); + return 1; + }); + lua_pushinteger(state, static_cast(sampleCount)); + lua_glue::ProtectedStackCall(state, 1, 1); + reference = LuaRegistryReference(state, -1); + } else if (!reference.pushUnderExecutionScope()) { + throw std::runtime_error("Lua audio sample table is unavailable"); + } -private: - lua_State *state_{}; - int top_{}; -}; + const int tableIndex = lua_absindex(state, -1); + lua_pushcfunction(state, [](lua_State* inner) -> int { + const auto* samples = + static_cast(lua_touserdata(inner, 2)); + const lua_Integer count = lua_tointeger(inner, 3); + const lua_Integer previous = lua_tointeger(inner, 4); + for (lua_Integer index = 0; index < count; ++index) { + lua_pushnumber(inner, samples == nullptr ? 0.F : samples[index]); + lua_rawseti(inner, 1, index + 1); + } + for (lua_Integer index = count; index < previous; ++index) { + lua_pushnil(inner); + lua_rawseti(inner, 1, index + 1); + } + return 0; + }); + lua_pushvalue(state, tableIndex); + lua_pushlightuserdata(state, const_cast(samples)); + lua_pushinteger(state, static_cast(sampleCount)); + lua_pushinteger(state, static_cast(previousSampleCount)); + lua_glue::ProtectedStackCall(state, 4, 0); + previousSampleCount = sampleCount; + return tableIndex; +} -int pushCachedDenseTable(lua_State *state, LuaRegistryReference &reference, - std::size_t sampleCount, - std::size_t &previousSampleCount, - const float *samples) { - if (!reference) { - lua_createtable(state, static_cast(sampleCount), 0); - reference = LuaRegistryReference(state, -1); - } else if (!reference.pushUnderExecutionScope()) { - throw std::runtime_error("Lua audio sample table is unavailable"); - } - - const int tableIndex = lua_absindex(state, -1); - for (std::size_t index = 0; index < sampleCount; ++index) { - lua_pushnumber(state, samples == nullptr ? 0.F : samples[index]); - lua_rawseti(state, tableIndex, static_cast(index + 1)); - } - for (std::size_t index = sampleCount; index < previousSampleCount; ++index) { +void validateResultFields(lua_State* state, int resultIndex) { + resultIndex = lua_absindex(state, resultIndex); lua_pushnil(state); - lua_rawseti(state, tableIndex, static_cast(index + 1)); - } - previousSampleCount = sampleCount; - return tableIndex; + while (lua_next(state, resultIndex) != 0) { + if (lua_type(state, -2) != LUA_TSTRING) { + throw std::invalid_argument( + "audio callback result must contain only named fields"); + } + std::size_t nameLength = 0; + const char* nameData = lua_tolstring(state, -2, &nameLength); + const std::string_view name(nameData, nameLength); + if (name != "inputFrameCount" && name != "outputFrameCount" && + name != "outputFrames") { + throw std::invalid_argument( + "unknown audio callback result field: " + std::string(name)); + } + lua_pop(state, 1); + } } -void validateResultFields(lua_State *state, int resultIndex) { - resultIndex = lua_absindex(state, resultIndex); - lua_pushnil(state); - while (lua_next(state, resultIndex) != 0) { - if (lua_type(state, -2) != LUA_TSTRING) { - throw std::invalid_argument( - "audio callback result must contain only named fields"); - } - std::size_t nameLength = 0; - const char *nameData = lua_tolstring(state, -2, &nameLength); - const std::string_view name(nameData, nameLength); - if (name != "inputFrameCount" && name != "outputFrameCount" && - name != "outputFrames") { - throw std::invalid_argument("unknown audio callback result field: " + - std::string(name)); +unsigned int readRequiredFrameCount(lua_State* state, int resultIndex, + const char* field, unsigned int capacity) { + lua_glue::Push(state, std::string_view(field)); + lua_rawget(state, resultIndex); + if (lua_isnil(state, -1)) { + throw std::invalid_argument(std::string("audio callback result.") + + field + " is required"); + } + unsigned int count = 0; + if (!tryReadLuaIntegral(state, -1, count)) { + throw std::invalid_argument(std::string("audio callback result.") + + field + + " must be a finite, in-range integer"); } lua_pop(state, 1); - } -} - -unsigned int readRequiredFrameCount(lua_State *state, int resultIndex, - const char *field, unsigned int capacity) { - lua_getfield(state, resultIndex, field); - if (lua_isnil(state, -1)) { - throw std::invalid_argument(std::string("audio callback result.") + field + - " is required"); - } - unsigned int count = 0; - if (!tryReadLuaIntegral(state, -1, count)) { - throw std::invalid_argument(std::string("audio callback result.") + field + - " must be a finite, in-range integer"); - } - lua_pop(state, 1); - if (count > capacity) { - throw std::out_of_range(std::string("audio callback result.") + field + - " exceeds its native capacity"); - } - return count; + if (count > capacity) { + throw std::out_of_range(std::string("audio callback result.") + field + + " exceeds its native capacity"); + } + return count; } -void copyDenseFloatArray(lua_State *state, int tableIndex, +void copyDenseFloatArray(lua_State* state, int tableIndex, std::size_t minimumSize, std::size_t maximumSize, - float *output, std::string_view label) { - if (lua_type(state, tableIndex) != LUA_TTABLE) { - throw std::invalid_argument(std::string(label) + " must be an array"); - } - tableIndex = lua_absindex(state, tableIndex); - std::size_t entryCount = 0; - std::size_t maximumIndex = 0; - lua_pushnil(state); - while (lua_next(state, tableIndex) != 0) { - std::size_t index = 0; - if (!tryReadLuaIntegral(state, -2, index)) { - throw std::invalid_argument(std::string(label) + - " must contain only array indices"); - } - if (index == 0 || index > maximumSize) { - throw std::out_of_range(std::string(label) + - " index exceeds the permitted capacity"); - } - if (lua_type(state, -1) != LUA_TNUMBER) { - throw std::invalid_argument(std::string(label) + - " must contain only numbers"); - } - ++entryCount; - maximumIndex = std::max(maximumIndex, index); - lua_pop(state, 1); - } - if (entryCount != maximumIndex) { - throw std::invalid_argument(std::string(label) + - " must be a dense 1-based array"); - } - if (maximumIndex < minimumSize) { - throw std::out_of_range(std::string(label) + - " does not cover the produced samples"); - } - - for (std::size_t index = 0; index < minimumSize; ++index) { - lua_rawgeti(state, tableIndex, static_cast(index + 1)); - output[index] = static_cast(lua_tonumber(state, -1)); - lua_pop(state, 1); - } + float* output, std::string_view label) { + if (lua_type(state, tableIndex) != LUA_TTABLE) { + throw std::invalid_argument(std::string(label) + " must be an array"); + } + tableIndex = lua_absindex(state, tableIndex); + std::size_t entryCount = 0; + std::size_t maximumIndex = 0; + lua_pushnil(state); + while (lua_next(state, tableIndex) != 0) { + std::size_t index = 0; + if (!tryReadLuaIntegral(state, -2, index)) { + throw std::invalid_argument(std::string(label) + + " must contain only array indices"); + } + if (index == 0 || index > maximumSize) { + throw std::out_of_range(std::string(label) + + " index exceeds the permitted capacity"); + } + if (lua_type(state, -1) != LUA_TNUMBER) { + throw std::invalid_argument(std::string(label) + + " must contain only numbers"); + } + ++entryCount; + maximumIndex = std::max(maximumIndex, index); + lua_pop(state, 1); + } + if (entryCount != maximumIndex) { + throw std::invalid_argument(std::string(label) + + " must be a dense 1-based array"); + } + if (maximumIndex < minimumSize) { + throw std::out_of_range(std::string(label) + + " does not cover the produced samples"); + } + + for (std::size_t index = 0; index < minimumSize; ++index) { + lua_rawgeti(state, tableIndex, static_cast(index + 1)); + output[index] = static_cast(lua_tonumber(state, -1)); + lua_pop(state, 1); + } } -std::vector readDenseFloatArray(const sol::object &object, +std::vector readDenseFloatArray(const lua_glue::Object& object, std::size_t minimumSize, std::size_t maximumSize, std::string_view label) { - if (!object.is()) - throw std::invalid_argument(std::string(label) + " must be an array"); - const sol::table table = object.as(); - std::size_t entryCount = 0; - std::size_t maximumIndex = 0; - for (const auto &entry : table) { - const sol::object key = entry.first; - if (!key.is>()) - throw std::invalid_argument(std::string(label) + - " must contain only array indices"); - const std::size_t index = key.as>().value(); - if (index == 0 || index > maximumSize) - throw std::out_of_range(std::string(label) + - " index exceeds the permitted capacity"); - ++entryCount; - maximumIndex = std::max(maximumIndex, index); - } - if (entryCount != maximumIndex) - throw std::invalid_argument(std::string(label) + - " must be a dense 1-based array"); - if (maximumIndex < minimumSize) - throw std::out_of_range(std::string(label) + - " does not cover the produced samples"); - - std::vector values; - values.reserve(maximumIndex); - for (std::size_t index = 1; index <= maximumIndex; ++index) { - const sol::object value = table.raw_get(index); - if (!value.is()) - throw std::invalid_argument(std::string(label) + - " must contain only numbers"); - values.push_back(value.as()); - } - return values; + if (!object.is()) { + throw std::invalid_argument(std::string(label) + " must be an array"); + } + const lua_glue::Table table = object.as(); + std::size_t entryCount = 0; + std::size_t maximumIndex = 0; + for (const auto& entry : table) { + const lua_glue::Object key = entry.first; + if (!key.is>()) { + throw std::invalid_argument(std::string(label) + + " must contain only array indices"); + } + const std::size_t index = key.as>().value(); + if (index == 0 || index > maximumSize) { + throw std::out_of_range(std::string(label) + + " index exceeds the permitted capacity"); + } + ++entryCount; + maximumIndex = std::max(maximumIndex, index); + } + if (entryCount != maximumIndex) { + throw std::invalid_argument(std::string(label) + + " must be a dense 1-based array"); + } + if (maximumIndex < minimumSize) { + throw std::out_of_range(std::string(label) + + " does not cover the produced samples"); + } + + std::vector values; + values.reserve(maximumIndex); + for (std::size_t index = 1; index <= maximumIndex; ++index) { + const lua_glue::Object value = table.raw_get(index); + if (!value.is()) { + throw std::invalid_argument(std::string(label) + + " must contain only numbers"); + } + values.push_back(value.as()); + } + return values; } template -decltype(auto) withBlockingCallback(const std::shared_ptr &ctx, - Callback &&callback) { - lua_State *state = ctx->reference.state(); - LuaStateExecutionScope execution(state); - if (!execution.active()) - throw std::runtime_error("Lua state is stopping"); - if (!ctx->reference.pushUnderExecutionScope()) - throw std::runtime_error("Lua callback is unavailable"); - auto popper = sol::stack::pop_n(state, 1); - sol::protected_function function = - sol::stack::get(state, -1); - return std::forward(callback)(function, sol::state_view(state)); +decltype(auto) withBlockingCallback(const std::shared_ptr& ctx, + Callback&& callback) { + lua_State* state = ctx->reference.state(); + LuaStateExecutionScope execution(state); + if (!execution.active()) { + throw std::runtime_error("Lua state is stopping"); + } + if (!ctx->reference.pushUnderExecutionScope()) { + throw std::runtime_error("Lua callback is unavailable"); + } + auto popper = lua_glue::PopGuard(state, 1); + lua_glue::Function function = lua_glue::Read(state, -1); + return std::forward(callback)(function, + lua_glue::StateView(state)); } -} // namespace +} // namespace -std::shared_ptr makeCallbackContext(const sol::object &object, - std::string label) { - if (!object.is()) - throw std::invalid_argument("expected a Lua callback function"); - return std::make_shared(object, std::move(label)); +std::shared_ptr makeCallbackContext( + const lua_glue::Object& object, std::string label) { + if (!object.is()) { + throw std::invalid_argument("expected a Lua callback function"); + } + return std::make_shared(object, std::move(label)); } void invokeInterleavedFloatTransform( - const std::shared_ptr &context, const float *inputFrames, - unsigned int &inputFrameCount, float *outputFrames, - unsigned int &outputFrameCount, unsigned int frameChannelCount) noexcept { - const unsigned int originalInput = inputFrameCount; - const unsigned int originalOutput = outputFrameCount; - if (context->faulted.load(std::memory_order_acquire)) { - fallback(inputFrames, inputFrameCount, outputFrames, outputFrameCount, - frameChannelCount); - return; - } - if ((inputFrames == nullptr && originalInput != 0) || - (outputFrames == nullptr && originalOutput != 0) || - frameChannelCount == 0) { - recordFault(context, "invalid native audio callback buffer capacity"); - fallback(inputFrames, inputFrameCount, outputFrames, outputFrameCount, - frameChannelCount); - return; - } - lua_State *state = context->reference.state(); - LuaStateTryExecutionScope execution(state); - if (!execution.active()) { - fallback(inputFrames, inputFrameCount, outputFrames, outputFrameCount, - frameChannelCount); - return; - } - try { - AutomaticGcPause gcPause(state); - LuaStackRestore stackRestore(state); - if (!context->reference.pushUnderExecutionScope()) { - fallback(inputFrames, inputFrameCount, outputFrames, outputFrameCount, - frameChannelCount); - return; - } - const int functionIndex = lua_absindex(state, -1); - const std::size_t inputSamples = sampleCount( - inputFrames == nullptr ? 0 : originalInput, frameChannelCount); - const std::size_t outputSamples = - sampleCount(originalOutput, frameChannelCount); - const int inputTableIndex = - pushCachedDenseTable(state, context->inputFrames, inputSamples, - context->inputSampleCount, inputFrames); - const int outputTableIndex = - pushCachedDenseTable(state, context->outputFrames, outputSamples, - context->outputSampleCount, nullptr); - - const sol::stack_protected_function function(state, functionIndex); - const sol::stack_object outputObject(state, outputTableIndex); - sol::protected_function_result result = - inputFrames == nullptr - ? function(lua_sf::LUASF_SOL_NIL, originalInput, outputObject, - originalOutput, frameChannelCount) - : function(sol::stack_object(state, inputTableIndex), originalInput, - outputObject, originalOutput, frameChannelCount); - throw_on_lua_error(result); - const int resultIndex = result.stack_index(); - if (lua_type(state, resultIndex) != LUA_TTABLE) { - throw std::invalid_argument("audio callback must return a result table"); - } - validateResultFields(state, resultIndex); - const unsigned int consumed = readRequiredFrameCount( - state, resultIndex, "inputFrameCount", originalInput); - const unsigned int produced = readRequiredFrameCount( - state, resultIndex, "outputFrameCount", originalOutput); - const std::size_t producedSamples = - sampleCount(produced, frameChannelCount); - lua_getfield(state, resultIndex, "outputFrames"); - if (lua_isnil(state, -1)) { - lua_pop(state, 1); - lua_pushvalue(state, outputTableIndex); - } - copyDenseFloatArray(state, -1, producedSamples, outputSamples, outputFrames, - "outputFrames"); - inputFrameCount = consumed; - outputFrameCount = produced; - } catch (const std::exception &error) { - recordFault(context, error.what()); - inputFrameCount = originalInput; - outputFrameCount = originalOutput; - fallback(inputFrames, inputFrameCount, outputFrames, outputFrameCount, - frameChannelCount); - } catch (...) { - recordFault(context, "unknown Lua audio callback failure"); - inputFrameCount = originalInput; - outputFrameCount = originalOutput; - fallback(inputFrames, inputFrameCount, outputFrames, outputFrameCount, - frameChannelCount); - } + const std::shared_ptr& context, const float* inputFrames, + unsigned int& inputFrameCount, float* outputFrames, + unsigned int& outputFrameCount, unsigned int frameChannelCount) noexcept { + const unsigned int originalInput = inputFrameCount; + const unsigned int originalOutput = outputFrameCount; + if (context->faulted.load(std::memory_order_acquire)) { + fallback(inputFrames, inputFrameCount, outputFrames, outputFrameCount, + frameChannelCount); + return; + } + if ((inputFrames == nullptr && originalInput != 0) || + (outputFrames == nullptr && originalOutput != 0) || + frameChannelCount == 0) { + recordFault(context, "invalid native audio callback buffer capacity"); + fallback(inputFrames, inputFrameCount, outputFrames, outputFrameCount, + frameChannelCount); + return; + } + lua_State* state = context->reference.state(); + LuaStateTryExecutionScope execution(state); + if (!execution.active()) { + fallback(inputFrames, inputFrameCount, outputFrames, outputFrameCount, + frameChannelCount); + return; + } + try { + AutomaticGcPause gcPause(state); + lua_glue::StackGuard stackRestore(state); + if (!context->reference.pushUnderExecutionScope()) { + fallback(inputFrames, inputFrameCount, outputFrames, + outputFrameCount, frameChannelCount); + return; + } + const int functionIndex = lua_absindex(state, -1); + const std::size_t inputSamples = sampleCount( + inputFrames == nullptr ? 0 : originalInput, frameChannelCount); + const std::size_t outputSamples = + sampleCount(originalOutput, frameChannelCount); + const int inputTableIndex = + pushCachedDenseTable(state, context->inputFrames, inputSamples, + context->inputSampleCount, inputFrames); + const int outputTableIndex = + pushCachedDenseTable(state, context->outputFrames, outputSamples, + context->outputSampleCount, nullptr); + + lua_pushvalue(state, functionIndex); + if (inputFrames == nullptr) { + lua_pushnil(state); + } else { + lua_pushvalue(state, inputTableIndex); + } + lua_pushinteger(state, originalInput); + lua_pushvalue(state, outputTableIndex); + lua_pushinteger(state, originalOutput); + lua_pushinteger(state, frameChannelCount); + lua_glue::ProtectedStackCall(state, 5, 1); + const int resultIndex = lua_absindex(state, -1); + if (lua_type(state, resultIndex) != LUA_TTABLE) { + throw std::invalid_argument( + "audio callback must return a result table"); + } + validateResultFields(state, resultIndex); + const unsigned int consumed = readRequiredFrameCount( + state, resultIndex, "inputFrameCount", originalInput); + const unsigned int produced = readRequiredFrameCount( + state, resultIndex, "outputFrameCount", originalOutput); + const std::size_t producedSamples = + sampleCount(produced, frameChannelCount); + lua_glue::Push(state, std::string_view("outputFrames")); + lua_rawget(state, resultIndex); + if (lua_isnil(state, -1)) { + lua_pop(state, 1); + lua_pushvalue(state, outputTableIndex); + } + copyDenseFloatArray(state, -1, producedSamples, outputSamples, + outputFrames, "outputFrames"); + inputFrameCount = consumed; + outputFrameCount = produced; + } catch (const std::exception& error) { + recordFault(context, error.what()); + inputFrameCount = originalInput; + outputFrameCount = originalOutput; + fallback(inputFrames, inputFrameCount, outputFrames, outputFrameCount, + frameChannelCount); + } catch (...) { + recordFault(context, "unknown Lua audio callback failure"); + inputFrameCount = originalInput; + outputFrameCount = originalOutput; + fallback(inputFrames, inputFrameCount, outputFrames, outputFrameCount, + frameChannelCount); + } } -void invokeGlyphPreProcessor(const std::shared_ptr &context, - const sf::Text::ShapedGlyph &glyph, - std::uint32_t &style, sf::Color &fill, - sf::Color &outline, float &thickness) { - withBlockingCallback(context, [&](sol::protected_function &function, - sol::state_view) { - sol::protected_function_result result = function( - std::ref(glyph), style, std::ref(fill), std::ref(outline), thickness); - throw_on_lua_error(result); - const sol::object returned = result; - if (is_nil_object(returned) || !returned.is()) - return; - const sol::table table = returned.as(); - const sol::object styleValue = table["style"]; - const sol::object fillValue = table["fillColor"]; - const sol::object outlineValue = table["outlineColor"]; - const sol::object thicknessValue = table["outlineThickness"]; - if (!is_nil_object(styleValue)) - style = styleValue.as(); - if (!is_nil_object(fillValue)) - fill = fillValue.as(); - if (!is_nil_object(outlineValue)) - outline = outlineValue.as(); - if (!is_nil_object(thicknessValue)) - thickness = thicknessValue.as(); - }); +void invokeGlyphPreProcessor(const std::shared_ptr& context, + const sf::Text::ShapedGlyph& glyph, + std::uint32_t& style, sf::Color& fill, + sf::Color& outline, float& thickness) { + withBlockingCallback( + context, [&](lua_glue::Function& function, lua_glue::StateView) { + lua_glue::CallResult result = + function(std::ref(glyph), style, std::ref(fill), + std::ref(outline), thickness); + throw_on_lua_error(result); + const lua_glue::Object returned = result; + if (is_nil_object(returned) || !returned.is()) { + return; + } + const lua_glue::Table table = returned.as(); + const lua_glue::Object styleValue = table["style"]; + const lua_glue::Object fillValue = table["fillColor"]; + const lua_glue::Object outlineValue = table["outlineColor"]; + const lua_glue::Object thicknessValue = table["outlineThickness"]; + if (!is_nil_object(styleValue)) { + style = styleValue.as(); + } + if (!is_nil_object(fillValue)) { + fill = fillValue.as(); + } + if (!is_nil_object(outlineValue)) { + outline = outlineValue.as(); + } + if (!is_nil_object(thicknessValue)) { + thickness = thicknessValue.as(); + } + }); } -bool invokeSftpDownload(const std::shared_ptr &context, - const void *data, std::size_t size) { - return withBlockingCallback( - context, [&](sol::protected_function &function, sol::state_view) { - const char *bytes = static_cast(data); - const std::string buffer = bytes != nullptr && size != 0 - ? std::string(bytes, bytes + size) - : std::string{}; - sol::protected_function_result result = function(buffer, size); - throw_on_lua_error(result); - return result.get(); - }); +bool invokeSftpDownload(const std::shared_ptr& context, + const void* data, std::size_t size) { + return withBlockingCallback( + context, [&](lua_glue::Function& function, lua_glue::StateView) { + const char* bytes = static_cast(data); + const std::string buffer = bytes != nullptr && size != 0 + ? std::string(bytes, bytes + size) + : std::string{}; + lua_glue::CallResult result = function(buffer, size); + throw_on_lua_error(result); + return result.get(); + }); } -bool invokeSftpUpload(const std::shared_ptr &context, - void *data, std::size_t &size) { - return withBlockingCallback( - context, [&](sol::protected_function &function, sol::state_view lua) { - sol::protected_function_result result = function(size); +bool invokeSftpUpload(const std::shared_ptr& context, + void* data, std::size_t& size) { + return withBlockingCallback(context, [&](lua_glue::Function& function, + lua_glue::StateView lua) { + lua_glue::CallResult result = function(size); throw_on_lua_error(result); - const sol::object returned = result; + const lua_glue::Object returned = result; if (is_nil_object(returned)) { - size = 0; - return false; + size = 0; + return false; } bool keepGoing = true; - sol::object dataValue = returned; - if (returned.is()) { - const sol::table table = returned.as(); - const sol::object keep = table["keepGoing"]; - if (!is_nil_object(keep)) - keepGoing = keep.as(); - dataValue = table["data"]; + lua_glue::Object dataValue = returned; + if (returned.is()) { + const lua_glue::Table table = returned.as(); + const lua_glue::Object keep = table["keepGoing"]; + if (!is_nil_object(keep)) { + keepGoing = keep.as(); + } + dataValue = table["data"]; } else if (returned.is()) { - keepGoing = returned.as(); - dataValue = sol::make_object(lua, lua_sf::LUASF_SOL_NIL); + keepGoing = returned.as(); + dataValue = lua_glue::MakeObject(lua, lua_glue::nil); } if (!keepGoing || is_nil_object(dataValue)) { - size = 0; - return keepGoing; + size = 0; + return keepGoing; } std::vector bytes = array_from_object(dataValue); const std::size_t count = std::min(bytes.size(), size); std::memcpy(data, bytes.data(), count); size = count; return true; - }); + }); } -} // namespace lua_sf::callback::detail +} // namespace lua_sf::callback::detail namespace lua_sf::callback { template <> -sol::object +lua_glue::Object to_object( - sol::state_view lua, const sf::SoundSource::EffectProcessor &callable, + lua_glue::StateView lua, const sf::SoundSource::EffectProcessor& callable, CallbackOptions options) { - if (!callable) { - if (options.allowNil) - return sol::make_object(lua, lua_sf::LUASF_SOL_NIL); - throw std::invalid_argument(options.label.empty() - ? "native audio callback does not allow nil" - : options.label + " does not allow nil"); - } - const std::string label = options.label.empty() ? "native audio callback" - : std::move(options.label); - return sol::make_object( - lua, - sol::as_function([callable, label](sol::object inputValue, + if (!callable) { + if (options.allowNil) { + return lua_glue::MakeObject(lua, lua_glue::nil); + } + throw std::invalid_argument( + options.label.empty() ? "native audio callback does not allow nil" + : options.label + " does not allow nil"); + } + const std::string label = options.label.empty() ? "native audio callback" + : std::move(options.label); + return lua_glue::MakeObject(lua, [callable, label]( + lua_glue::Object inputValue, LuaIntegral inputCount, - sol::object outputValue, + lua_glue::Object outputValue, LuaIntegral outputCount, LuaIntegral channels) { try { - const unsigned int inputCapacity = inputCount.value(); - const unsigned int outputCapacity = outputCount.value(); - const unsigned int channelCount = channels.value(); - if (channelCount == 0) - throw std::invalid_argument( - "audio callback channel count must be positive"); - const std::size_t inputSamples = - detail::sampleCount(inputCapacity, channelCount); - const std::size_t outputSamples = - detail::sampleCount(outputCapacity, channelCount); - const bool endOfStream = is_nil_object(inputValue); - if (endOfStream && inputCapacity != 0) - throw std::invalid_argument( - "nil input requires a zero input count"); - std::vector inputValues; - if (!endOfStream) - inputValues = detail::readDenseFloatArray( - inputValue, inputSamples, inputSamples, "inputFrames"); - std::vector outputValues = detail::readDenseFloatArray( - outputValue, outputSamples, outputSamples, "outputFrames"); - unsigned int consumed = inputCapacity; - unsigned int produced = outputCapacity; - callable(endOfStream ? nullptr : inputValues.data(), consumed, - outputValues.data(), produced, channelCount); - if (consumed > inputCapacity || produced > outputCapacity) - throw std::out_of_range( - "native audio callback returned a count beyond capacity"); - const std::size_t producedSamples = - detail::sampleCount(produced, channelCount); - outputValues.resize(producedSamples); - sol::state_view state(inputValue.lua_state()); - sol::table result = state.create_table(0, 3); - result.raw_set("inputFrameCount", consumed); - result.raw_set("outputFrameCount", produced); - result.raw_set("outputFrames", vector_to_object(state, outputValues)); - return result; - } catch (const std::exception &error) { - throw std::runtime_error(label + ": " + error.what()); + const unsigned int inputCapacity = inputCount.value(); + const unsigned int outputCapacity = outputCount.value(); + const unsigned int channelCount = channels.value(); + if (channelCount == 0) { + throw std::invalid_argument( + "audio callback channel count must be positive"); + } + const std::size_t inputSamples = + detail::sampleCount(inputCapacity, channelCount); + const std::size_t outputSamples = + detail::sampleCount(outputCapacity, channelCount); + const bool endOfStream = is_nil_object(inputValue); + if (endOfStream && inputCapacity != 0) { + throw std::invalid_argument( + "nil input requires a zero input count"); + } + std::vector inputValues; + if (!endOfStream) { + inputValues = detail::readDenseFloatArray( + inputValue, inputSamples, inputSamples, "inputFrames"); + } + std::vector outputValues = detail::readDenseFloatArray( + outputValue, outputSamples, outputSamples, "outputFrames"); + unsigned int consumed = inputCapacity; + unsigned int produced = outputCapacity; + callable(endOfStream ? nullptr : inputValues.data(), consumed, + outputValues.data(), produced, channelCount); + if (consumed > inputCapacity || produced > outputCapacity) { + throw std::out_of_range( + "native audio callback returned a count beyond capacity"); + } + const std::size_t producedSamples = + detail::sampleCount(produced, channelCount); + outputValues.resize(producedSamples); + lua_glue::StateView state(inputValue.lua_state()); + lua_glue::Table result = state.create_table(0, 3); + result.raw_set("inputFrameCount", consumed); + result.raw_set("outputFrameCount", produced); + result.raw_set("outputFrames", + vector_to_object(state, outputValues)); + return result; + } catch (const std::exception& error) { + throw std::runtime_error(label + ": " + error.what()); } - })); + }); } -} // namespace lua_sf::callback +} // namespace lua_sf::callback diff --git a/src/LuaStateLifecycle.cpp b/src/LuaStateLifecycle.cpp index 894889c..e1de617 100644 --- a/src/LuaStateLifecycle.cpp +++ b/src/LuaStateLifecycle.cpp @@ -1,941 +1,37 @@ #include "LuaStateLifecycle.hpp" -extern "C" { -#include -#include +extern "C" LUASF_API int LuaSF_initialize_state(lua_State* state) { + return lua_glue::InitializeState(state); } -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace lua_sf::detail_internal { - -enum class LuaStatePhase { - running, - stopping, - stopped, -}; - -struct LuaStateSession; - -} // namespace lua_sf::detail_internal - -namespace lua_sf { - -struct LuaRegistryReferenceState { - LuaRegistryReferenceState( - std::weak_ptr session, - lua_State *state) noexcept - : session(std::move(session)), state(state) {} - - ~LuaRegistryReferenceState(); - - std::weak_ptr session; - lua_State *state{}; - int reference{LUA_NOREF}; -}; - -} // namespace lua_sf - -namespace lua_sf::detail_internal { - -class DeferredCallbackErrorQueue final { -public: - static constexpr std::size_t capacity = 64; - static constexpr std::size_t messageCapacity = 512; - - DeferredCallbackErrorQueue() noexcept { - for (std::size_t index = 0; index < slots_.size(); ++index) - slots_[index].sequence.store(index, std::memory_order_relaxed); - } - - bool enqueue(std::string_view label, std::string_view message) noexcept { - std::size_t position = enqueuePosition_.load(std::memory_order_relaxed); - Slot *slot = nullptr; - for (;;) { - slot = &slots_[position & (capacity - 1)]; - const std::size_t sequence = - slot->sequence.load(std::memory_order_acquire); - const std::intptr_t difference = static_cast(sequence) - - static_cast(position); - if (difference == 0) { - if (enqueuePosition_.compare_exchange_weak(position, position + 1, - std::memory_order_relaxed)) - break; - } else if (difference < 0) { - overflowPending_.store(1, std::memory_order_release); - return false; - } else { - position = enqueuePosition_.load(std::memory_order_relaxed); - } - } - - std::size_t offset = 0; - copyPart(slot->message, offset, label); - if (!label.empty() && !message.empty()) - copyPart(slot->message, offset, ": "); - copyPart(slot->message, offset, message); - slot->message[offset] = '\0'; - slot->sequence.store(position + 1, std::memory_order_release); - return true; - } - - bool dequeue(char *buffer, std::size_t bufferCapacity) noexcept { - std::size_t position = dequeuePosition_.load(std::memory_order_relaxed); - Slot *slot = nullptr; - for (;;) { - slot = &slots_[position & (capacity - 1)]; - const std::size_t sequence = - slot->sequence.load(std::memory_order_acquire); - const std::intptr_t difference = static_cast(sequence) - - static_cast(position + 1); - if (difference == 0) { - if (dequeuePosition_.compare_exchange_weak(position, position + 1, - std::memory_order_relaxed)) - break; - } else if (difference < 0) { - if (overflowPending_.exchange(0, std::memory_order_acq_rel) == 0) - return false; - copyToBuffer(buffer, bufferCapacity, - "Lua callback error queue overflow"); - return true; - } else { - position = dequeuePosition_.load(std::memory_order_relaxed); - } - } - - copyToBuffer(buffer, bufferCapacity, slot->message.data()); - slot->sequence.store(position + capacity, std::memory_order_release); - return true; - } - -private: - struct Slot { - std::atomic sequence{}; - std::array message{}; - }; - - static void copyPart(std::array &target, - std::size_t &offset, std::string_view value) noexcept { - const std::size_t available = messageCapacity - 1 - offset; - const std::size_t count = std::min(available, value.size()); - if (count != 0) - std::memcpy(target.data() + offset, value.data(), count); - offset += count; - } - - static void copyToBuffer(char *buffer, std::size_t bufferCapacity, - std::string_view value) noexcept { - const std::size_t count = std::min(bufferCapacity - 1, value.size()); - if (count != 0) - std::memcpy(buffer, value.data(), count); - buffer[count] = '\0'; - } - - static_assert((capacity & (capacity - 1)) == 0); - static_assert(std::atomic::is_always_lock_free); - std::array slots_{}; - alignas(64) std::atomic enqueuePosition_{}; - alignas(64) std::atomic dequeuePosition_{}; - std::atomic overflowPending_{}; -}; - -struct LuaStateSession { - explicit LuaStateSession(lua_State *value) noexcept : state(value) {} - - lua_State *state{}; - std::recursive_mutex fallbackExecutionMutex; - std::recursive_mutex metadataMutex; - std::atomic phase{LuaStatePhase::running}; - LuaSFStateEnterHook enterHook{}; - LuaSFStateTryEnterHook tryEnterHook{}; - LuaSFStateLeaveHook leaveHook{}; - void *hookContext{}; - std::unordered_set registryReferences; - std::unordered_map retainedObjects; - bool quiescing{}; - std::unordered_map quiesceCallbacks; - DeferredCallbackErrorQueue deferredCallbackErrors; -}; - -struct EnteredSession { - lua_State *state{}; - lua_State *requestedState{}; - std::shared_ptr session; - LuaSFStateLeaveHook leaveHook{}; - void *hookContext{}; - bool usesFallback{}; -}; - -std::mutex sessionsMutex; -std::unordered_map> sessions; -std::unordered_map stateAliases; -class EnteredSessionStack final { -public: - [[nodiscard]] bool empty() const noexcept { return size_ == 0; } - [[nodiscard]] std::size_t size() const noexcept { return size_; } - [[nodiscard]] constexpr std::size_t capacity() const noexcept { - return entries_.size(); - } - - EnteredSession &back() noexcept { return entries_[size_ - 1]; } - - void push_back(EnteredSession value) { - if (size_ == entries_.size()) - throw std::bad_alloc(); - entries_[size_++] = std::move(value); - } - - void pop_back() noexcept { - if (size_ == 0) - return; - entries_[--size_] = {}; - } - -private: - std::array entries_{}; - std::size_t size_{}; -}; - -thread_local EnteredSessionStack enteredSessions; - -lua_State *mainThreadFromRegistry(lua_State *state) noexcept { - if (state == nullptr || lua_checkstack(state, 1) == 0) - return nullptr; - - const int originalTop = lua_gettop(state); - lua_rawgeti(state, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD); - lua_State *mainState = lua_tothread(state, -1); - lua_settop(state, originalTop); - return mainState; -} - -std::shared_ptr findSessionByMainState(lua_State *mainState) { - if (mainState == nullptr) - return {}; - std::scoped_lock lock(sessionsMutex); - const auto item = sessions.find(mainState); - return item == sessions.end() ? std::shared_ptr{} - : item->second; -} - -struct ResolvedSession { - lua_State *mainState{}; - std::shared_ptr session; -}; - -ResolvedSession findResolvedSessionLocked(lua_State *state) { - const auto alias = stateAliases.find(state); - if (alias == stateAliases.end()) - return {}; - const auto session = sessions.find(alias->second); - return session == sessions.end() - ? ResolvedSession{} - : ResolvedSession{alias->second, session->second}; -} - -ResolvedSession findResolvedSession(lua_State *state) noexcept { - if (state == nullptr) - return {}; - try { - std::scoped_lock lock(sessionsMutex); - return findResolvedSessionLocked(state); - } catch (...) { - return {}; - } -} - -bool addStateAliasUnderExecution( - lua_State *state, lua_State *mainState, - const std::shared_ptr &session) noexcept { - if (state == nullptr || mainState == nullptr || session == nullptr) - return false; - - int threadReference = LUA_NOREF; - if (state != mainState) { - lua_pushthread(state); - threadReference = luaL_ref(state, LUA_REGISTRYINDEX); - if (threadReference < 0) - return false; - try { - std::scoped_lock lock(session->metadataMutex); - if (session->state != mainState || - session->phase.load(std::memory_order_acquire) != - LuaStatePhase::running || - !session->registryReferences.insert(threadReference).second) { - luaL_unref(state, LUA_REGISTRYINDEX, threadReference); - return false; - } - } catch (...) { - luaL_unref(state, LUA_REGISTRYINDEX, threadReference); - return false; - } - } - - bool aliasRegistered = false; - try { - std::scoped_lock lock(sessionsMutex); - const auto activeSession = sessions.find(mainState); - if (activeSession != sessions.end() && activeSession->second == session) { - stateAliases.insert_or_assign(state, mainState); - aliasRegistered = true; - } - } catch (...) { - } - if (aliasRegistered) - return true; - - if (threadReference >= 0) { - try { - std::scoped_lock lock(session->metadataMutex); - session->registryReferences.erase(threadReference); - } catch (...) { - } - luaL_unref(state, LUA_REGISTRYINDEX, threadReference); - } - return false; -} - -ResolvedSession resolveSession(lua_State *state) noexcept { - return findResolvedSession(state); -} - -ResolvedSession -learnRegistryReferenceSessionUnderExecution(lua_State *state) noexcept { - ResolvedSession resolved = findResolvedSession(state); - if (resolved.session != nullptr) - return resolved; - - // Registry-reference construction receives the currently executing Lua - // thread. The host therefore already owns the VM either through a LuaSF - // scope or through the same lock installed as its execution hooks. - lua_State *mainState = mainThreadFromRegistry(state); - if (mainState == nullptr) - return {}; - std::shared_ptr session; - if (!enteredSessions.empty()) { - const EnteredSession &entered = enteredSessions.back(); - if (mainState != entered.state) - return {}; - session = entered.session; - } else { - session = findSessionByMainState(mainState); - } - if (session == nullptr || - session->phase.load(std::memory_order_acquire) != - LuaStatePhase::running || - !addStateAliasUnderExecution(state, mainState, session)) - return {}; - return {mainState, std::move(session)}; -} - -ResolvedSession tryResolveSession(lua_State *state) noexcept { - if (state == nullptr) - return {}; - - std::unique_lock lock(sessionsMutex, std::try_to_lock); - if (!lock.owns_lock()) - return {}; - return findResolvedSessionLocked(state); -} - -std::vector> snapshotSessions() { - std::scoped_lock lock(sessionsMutex); - std::vector> result; - result.reserve(sessions.size()); - for (const auto &[state, session] : sessions) { - static_cast(state); - result.push_back(session); - } - return result; -} - -} // namespace lua_sf::detail_internal - -namespace lua_sf { - -LuaRegistryReferenceState::~LuaRegistryReferenceState() { - if (reference < 0) - return; - const std::shared_ptr activeSession = - session.lock(); - if (activeSession == nullptr || - activeSession->phase.load(std::memory_order_acquire) != - detail_internal::LuaStatePhase::running) - return; - LuaStateExecutionScope execution(state); - if (!execution.active()) - return; - std::scoped_lock lock(activeSession->metadataMutex); - if (activeSession->state != state || - activeSession->phase.load(std::memory_order_acquire) != - detail_internal::LuaStatePhase::running) - return; - if (activeSession->registryReferences.erase(reference) != 0) - luaL_unref(state, LUA_REGISTRYINDEX, reference); -} - -LuaStateExecutionScope::LuaStateExecutionScope(lua_State *state) noexcept - : active_(LuaSF_enter_state(state) != 0) { - if (active_ && !detail_internal::enteredSessions.empty()) - state_ = detail_internal::enteredSessions.back().state; -} - -LuaStateExecutionScope::~LuaStateExecutionScope() { - if (active_) - LuaSF_leave_state(state_); -} - -bool LuaStateExecutionScope::active() const noexcept { return active_; } - -LuaStateTryExecutionScope::LuaStateTryExecutionScope(lua_State *state) noexcept - : active_(LuaSF_try_enter_state(state) != 0) { - if (active_ && !detail_internal::enteredSessions.empty()) - state_ = detail_internal::enteredSessions.back().state; -} - -LuaStateTryExecutionScope::~LuaStateTryExecutionScope() { - if (active_) - LuaSF_leave_state(state_); -} - -bool LuaStateTryExecutionScope::active() const noexcept { return active_; } - -LuaRegistryReference::LuaRegistryReference(lua_State *state, int stackIndex) { - if (state == nullptr) - throw std::invalid_argument("Lua registry reference has no state"); - const detail_internal::ResolvedSession resolved = - detail_internal::resolveSession(state); - lua_State *mainState = resolved.mainState; - const std::shared_ptr &session = - resolved.session; - if (mainState == nullptr || session == nullptr) - throw std::logic_error("Lua registry reference has no main state"); - LuaStateExecutionScope execution(mainState); - if (!execution.active()) - throw std::logic_error("Lua state is stopping"); - const int absoluteStackIndex = lua_absindex(state, stackIndex); - std::scoped_lock lock(session->metadataMutex); - if (session->state != mainState || - session->phase.load(std::memory_order_acquire) != - detail_internal::LuaStatePhase::running) - throw std::logic_error("Lua state is stopping"); - std::shared_ptr reference = - std::make_shared(session, mainState); - lua_pushvalue(state, absoluteStackIndex); - reference->reference = luaL_ref(state, LUA_REGISTRYINDEX); - if (reference->reference >= 0) - session->registryReferences.insert(reference->reference); - reference_ = std::move(reference); -} - -lua_State *LuaRegistryReference::state() const noexcept { - if (reference_ == nullptr) - return nullptr; - const std::shared_ptr session = - reference_->session.lock(); - if (session == nullptr || session->phase.load(std::memory_order_acquire) != - detail_internal::LuaStatePhase::running) - return nullptr; - return reference_->state; -} - -bool LuaRegistryReference::push() const { - if (reference_ == nullptr) - return false; - const std::shared_ptr session = - reference_->session.lock(); - if (session == nullptr || session->phase.load(std::memory_order_acquire) != - detail_internal::LuaStatePhase::running) - return false; - LuaStateExecutionScope execution(reference_->state); - if (!execution.active()) - return false; - std::scoped_lock lock(session->metadataMutex); - if (session->state != reference_->state || - session->phase.load(std::memory_order_acquire) != - detail_internal::LuaStatePhase::running) - return false; - if (reference_->reference == LUA_REFNIL) - lua_pushnil(reference_->state); - else - lua_rawgeti(reference_->state, LUA_REGISTRYINDEX, reference_->reference); - return true; -} - -bool LuaRegistryReference::pushUnderExecutionScope() const noexcept { - if (reference_ == nullptr) - return false; - if (detail_internal::enteredSessions.empty()) - return false; - const std::shared_ptr session = - reference_->session.lock(); - if (session == nullptr || reference_->reference < 0) - return false; - const detail_internal::EnteredSession &entered = - detail_internal::enteredSessions.back(); - if (entered.state != reference_->state || entered.session != session || - session->state != reference_->state || - session->phase.load(std::memory_order_acquire) != - detail_internal::LuaStatePhase::running) - return false; - if (reference_->reference == LUA_REFNIL) - lua_pushnil(reference_->state); - else - lua_rawgeti(reference_->state, LUA_REGISTRYINDEX, reference_->reference); - return true; -} - -void LuaRegistryReference::deferCallbackError( - std::string_view label, std::string_view message) const noexcept { - if (reference_ == nullptr) - return; - const std::shared_ptr session = - reference_->session.lock(); - if (session != nullptr) - session->deferredCallbackErrors.enqueue(label, message); -} - -bool LuaRegistryReference::equals(const LuaRegistryReference &other) const { - if (reference_ == nullptr || other.reference_ == nullptr || - reference_->state != other.reference_->state) - return false; - const std::shared_ptr session = - reference_->session.lock(); - if (session == nullptr || session != other.reference_->session.lock() || - session->phase.load(std::memory_order_acquire) != - detail_internal::LuaStatePhase::running) - return false; - LuaStateExecutionScope execution(reference_->state); - if (!execution.active()) - return false; - std::scoped_lock lock(session->metadataMutex); - if (session->state != reference_->state || - session->phase.load(std::memory_order_acquire) != - detail_internal::LuaStatePhase::running) - return false; - if (reference_->reference == LUA_REFNIL) - lua_pushnil(reference_->state); - else - lua_rawgeti(reference_->state, LUA_REGISTRYINDEX, reference_->reference); - if (other.reference_->reference == LUA_REFNIL) - lua_pushnil(reference_->state); - else - lua_rawgeti(reference_->state, LUA_REGISTRYINDEX, - other.reference_->reference); - const bool result = lua_rawequal(reference_->state, -2, -1) != 0; - lua_pop(reference_->state, 2); - return result; -} - -LuaRegistryReference::operator bool() const noexcept { - return reference_ != nullptr; -} - -namespace detail { - -void registerLuaThreadForRegistryReference(lua_State *state) { - if (state == nullptr) - throw std::invalid_argument("Lua registry reference has no state"); - const detail_internal::ResolvedSession resolved = - detail_internal::learnRegistryReferenceSessionUnderExecution(state); - if (resolved.session == nullptr) - throw std::logic_error( - "Lua registry reference requires an entered main state"); -} - -void retainLuaRegistryReference(const void *owner, - const LuaRegistryReference &reference) { - if (owner == nullptr || !reference) - return; - lua_State *state = reference.state(); - if (state == nullptr) - return; - const std::shared_ptr session = - detail_internal::findSessionByMainState(state); - if (session == nullptr) - return; - LuaStateExecutionScope execution(state); - if (!execution.active()) - return; - std::scoped_lock lock(session->metadataMutex); - if (session->state != state || - session->phase.load(std::memory_order_acquire) != - detail_internal::LuaStatePhase::running) - return; - session->retainedObjects.insert_or_assign(owner, reference); -} - -void releaseLuaRegistryReference(const void *owner) { - if (owner == nullptr) - return; - for (const std::shared_ptr &session : - detail_internal::snapshotSessions()) { - lua_State *state = nullptr; - { - std::scoped_lock lock(session->metadataMutex); - state = session->state; - } - LuaStateExecutionScope execution(state); - if (!execution.active()) - continue; - std::scoped_lock lock(session->metadataMutex); - session->retainedObjects.erase(owner); - } -} - -void registerStateQuiesceCallback(lua_State *state, const void *owner, - LuaStateQuiesceCallback callback) { - if (state == nullptr || owner == nullptr || callback == nullptr) - throw std::invalid_argument("State quiesce callback is incomplete"); - const detail_internal::ResolvedSession resolved = - detail_internal::resolveSession(state); - lua_State *mainState = resolved.mainState; - const std::shared_ptr &session = - resolved.session; - if (session == nullptr) - throw std::logic_error("State quiesce callback has no active state"); - LuaStateExecutionScope execution(mainState); - if (!execution.active()) - throw std::logic_error("Lua state is stopping"); - std::scoped_lock lock(session->metadataMutex); - if (session->state != mainState || session->quiescing || - session->phase.load(std::memory_order_acquire) != - detail_internal::LuaStatePhase::running) - throw std::logic_error("Lua state is stopping"); - session->quiesceCallbacks.insert_or_assign(owner, callback); -} - -void unregisterStateQuiesceCallback(lua_State *state, - const void *owner) noexcept { - if (state == nullptr || owner == nullptr) - return; - const detail_internal::ResolvedSession resolved = - detail_internal::findResolvedSession(state); - lua_State *mainState = resolved.mainState; - const std::shared_ptr &session = - resolved.session; - if (session == nullptr) - return; - std::scoped_lock lock(session->metadataMutex); - if (session->state == mainState) - session->quiesceCallbacks.erase(owner); -} - -} // namespace detail - -} // namespace lua_sf - -extern "C" LUASF_API int LuaSF_initialize_state(lua_State *state) { - if (state == nullptr) - return 1; - const lua_sf::detail_internal::ResolvedSession existing = - lua_sf::detail_internal::findResolvedSession(state); - if (existing.session != nullptr) { - return existing.session->phase.load(std::memory_order_acquire) == - lua_sf::detail_internal::LuaStatePhase::running - ? 0 - : 1; - } - - // Initialization is called by the thread currently executing registration, - // before a LuaSF execution gate necessarily exists. It is the only public - // entry point that learns a VM identity without a pre-existing session. - lua_State *mainState = lua_sf::detail_internal::mainThreadFromRegistry(state); - if (mainState == nullptr) - return 1; - std::shared_ptr session; - bool createdSession = false; - try { - std::scoped_lock lock(lua_sf::detail_internal::sessionsMutex); - const auto item = lua_sf::detail_internal::sessions.find(mainState); - if (item != lua_sf::detail_internal::sessions.end()) { - if (item->second->phase.load(std::memory_order_acquire) != - lua_sf::detail_internal::LuaStatePhase::running) - return 1; - session = item->second; - } else { - session = - std::make_shared(mainState); - lua_sf::detail_internal::sessions.emplace(mainState, session); - createdSession = true; - } - try { - lua_sf::detail_internal::stateAliases.insert_or_assign(mainState, - mainState); - } catch (...) { - lua_sf::detail_internal::stateAliases.erase(mainState); - if (createdSession) - lua_sf::detail_internal::sessions.erase(mainState); - throw; - } - } catch (...) { - return 1; - } - - bool initialized = false; - { - lua_sf::LuaStateExecutionScope execution(mainState); - if (execution.active()) { - initialized = state == mainState || - lua_sf::detail_internal::addStateAliasUnderExecution( - state, mainState, session); - } - } - if (!initialized && createdSession) - LuaSF_shutdown_state(mainState); - return initialized ? 0 : 1; -} - -extern "C" LUASF_API int -LuaSF_set_state_execution_hooks(lua_State *state, LuaSFStateEnterHook enterHook, - LuaSFStateTryEnterHook tryEnterHook, - LuaSFStateLeaveHook leaveHook, void *context) { - if (state == nullptr || (enterHook == nullptr) != (tryEnterHook == nullptr) || - (enterHook == nullptr) != (leaveHook == nullptr)) - return 1; - const lua_sf::detail_internal::ResolvedSession resolved = - lua_sf::detail_internal::resolveSession(state); - lua_State *mainState = resolved.mainState; - const std::shared_ptr &session = - resolved.session; - if (session == nullptr) - return 1; - lua_sf::LuaStateExecutionScope execution(mainState); - if (!execution.active()) - return 1; - std::scoped_lock lock(session->metadataMutex); - if (session->state != mainState || - session->phase.load(std::memory_order_acquire) != - lua_sf::detail_internal::LuaStatePhase::running) - return 1; - session->enterHook = enterHook; - session->tryEnterHook = tryEnterHook; - session->leaveHook = leaveHook; - session->hookContext = context; - return 0; +extern "C" LUASF_API int LuaSF_set_state_execution_hooks( + lua_State* state, LuaSFStateEnterHook enter, + LuaSFStateTryEnterHook tryEnter, LuaSFStateLeaveHook leave, void* context) { + return lua_glue::SetStateExecutionHooks(state, enter, tryEnter, leave, + context); } -extern "C" LUASF_API int LuaSF_enter_state(lua_State *state) { - const lua_sf::detail_internal::ResolvedSession resolved = - lua_sf::detail_internal::resolveSession(state); - lua_State *mainState = resolved.mainState; - const std::shared_ptr &session = - resolved.session; - if (session == nullptr) - return 0; - LuaSFStateEnterHook enterHook = nullptr; - LuaSFStateLeaveHook leaveHook = nullptr; - void *hookContext = nullptr; - { - std::scoped_lock lock(session->metadataMutex); - if (session->state != mainState || - session->phase.load(std::memory_order_acquire) != - lua_sf::detail_internal::LuaStatePhase::running) - return 0; - enterHook = session->enterHook; - leaveHook = session->leaveHook; - hookContext = session->hookContext; - } - try { - lua_sf::detail_internal::enteredSessions.push_back( - {mainState, state, session, leaveHook, hookContext, - enterHook == nullptr}); - } catch (const std::bad_alloc &) { - return 0; - } - if (enterHook != nullptr) { - if (enterHook(mainState, hookContext) == 0) { - lua_sf::detail_internal::enteredSessions.pop_back(); - return 0; - } - } else { - session->fallbackExecutionMutex.lock(); - } - { - std::scoped_lock lock(session->metadataMutex); - if (session->state == mainState && - session->phase.load(std::memory_order_acquire) == - lua_sf::detail_internal::LuaStatePhase::running) - return 1; - } - lua_sf::detail_internal::EnteredSession entered = - std::move(lua_sf::detail_internal::enteredSessions.back()); - lua_sf::detail_internal::enteredSessions.pop_back(); - if (entered.usesFallback) - session->fallbackExecutionMutex.unlock(); - else - entered.leaveHook(mainState, entered.hookContext); - return 0; +extern "C" LUASF_API void LuaSF_quiesce_state(lua_State* state) noexcept { + lua_glue::QuiesceState(state); } -extern "C" LUASF_API int LuaSF_try_enter_state(lua_State *state) noexcept { - try { - const lua_sf::detail_internal::ResolvedSession resolved = - lua_sf::detail_internal::tryResolveSession(state); - lua_State *mainState = resolved.mainState; - const std::shared_ptr &session = - resolved.session; - if (session == nullptr) - return 0; - LuaSFStateTryEnterHook tryEnterHook = nullptr; - LuaSFStateLeaveHook leaveHook = nullptr; - void *hookContext = nullptr; - { - std::unique_lock lock(session->metadataMutex, std::try_to_lock); - if (!lock.owns_lock()) - return 0; - if (session->state != mainState || - session->phase.load(std::memory_order_acquire) != - lua_sf::detail_internal::LuaStatePhase::running) - return 0; - tryEnterHook = session->tryEnterHook; - leaveHook = session->leaveHook; - hookContext = session->hookContext; - } - if (lua_sf::detail_internal::enteredSessions.capacity() == - lua_sf::detail_internal::enteredSessions.size()) - return 0; - lua_sf::detail_internal::enteredSessions.push_back( - {mainState, state, session, leaveHook, hookContext, - tryEnterHook == nullptr}); - if (tryEnterHook != nullptr) { - if (tryEnterHook(mainState, hookContext) == 0) { - lua_sf::detail_internal::enteredSessions.pop_back(); - return 0; - } - } else { - bool locked = false; - try { - locked = session->fallbackExecutionMutex.try_lock(); - } catch (...) { - lua_sf::detail_internal::enteredSessions.pop_back(); - return 0; - } - if (!locked) { - lua_sf::detail_internal::enteredSessions.pop_back(); - return 0; - } - } - if (session->phase.load(std::memory_order_acquire) == - lua_sf::detail_internal::LuaStatePhase::running) - return 1; - lua_sf::detail_internal::EnteredSession entered = - std::move(lua_sf::detail_internal::enteredSessions.back()); - lua_sf::detail_internal::enteredSessions.pop_back(); - if (entered.usesFallback) - session->fallbackExecutionMutex.unlock(); - else - entered.leaveHook(mainState, entered.hookContext); - return 0; - } catch (...) { - return 0; - } +extern "C" LUASF_API void LuaSF_shutdown_state(lua_State* state) { + lua_glue::ShutdownState(state); } -extern "C" LUASF_API void LuaSF_leave_state(lua_State *state) noexcept { - if (lua_sf::detail_internal::enteredSessions.empty()) - return; - const lua_sf::detail_internal::EnteredSession &active = - lua_sf::detail_internal::enteredSessions.back(); - if (state != active.state && state != active.requestedState) - return; - lua_State *mainState = active.state; - lua_sf::detail_internal::EnteredSession entered = - std::move(lua_sf::detail_internal::enteredSessions.back()); - lua_sf::detail_internal::enteredSessions.pop_back(); - if (entered.usesFallback) - entered.session->fallbackExecutionMutex.unlock(); - else - entered.leaveHook(mainState, entered.hookContext); +extern "C" LUASF_API int LuaSF_enter_state(lua_State* state) { + return lua_glue::EnterState(state); } -extern "C" LUASF_API int -LuaSF_take_deferred_callback_error(lua_State *state, char *buffer, - std::size_t capacity) { - if (state == nullptr || buffer == nullptr || capacity == 0) - return 0; - const lua_sf::detail_internal::ResolvedSession resolved = - lua_sf::detail_internal::resolveSession(state); - const std::shared_ptr &session = - resolved.session; - if (session == nullptr) - return 0; - return session->deferredCallbackErrors.dequeue(buffer, capacity) ? 1 : 0; +extern "C" LUASF_API int LuaSF_try_enter_state(lua_State* state) noexcept { + return lua_glue::TryEnterState(state); } -extern "C" LUASF_API void LuaSF_quiesce_state(lua_State *state) noexcept { - const lua_sf::detail_internal::ResolvedSession resolved = - lua_sf::detail_internal::findResolvedSession(state); - lua_State *mainState = resolved.mainState; - const std::shared_ptr &session = - resolved.session; - if (session == nullptr) - return; - std::unordered_map callbacks; - { - std::scoped_lock lock(session->metadataMutex); - if (session->state != mainState || session->quiescing) - return; - session->quiescing = true; - callbacks.swap(session->quiesceCallbacks); - } - for (const auto &[owner, callback] : callbacks) { - static_cast(owner); - callback(); - } +extern "C" LUASF_API void LuaSF_leave_state(lua_State* state) noexcept { + lua_glue::LeaveState(state); } -extern "C" LUASF_API void LuaSF_shutdown_state(lua_State *state) { - const lua_sf::detail_internal::ResolvedSession resolved = - lua_sf::detail_internal::resolveSession(state); - lua_State *mainState = resolved.mainState; - const std::shared_ptr &session = - resolved.session; - if (session == nullptr) - return; - LuaSF_quiesce_state(mainState); - lua_sf::LuaStateExecutionScope execution(mainState); - if (!execution.active()) - return; - lua_sf::detail_internal::LuaStatePhase expected = - lua_sf::detail_internal::LuaStatePhase::running; - if (!session->phase.compare_exchange_strong( - expected, lua_sf::detail_internal::LuaStatePhase::stopping, - std::memory_order_acq_rel, std::memory_order_acquire)) - return; - { - std::scoped_lock lock(session->metadataMutex); - session->retainedObjects.clear(); - for (const int reference : session->registryReferences) - luaL_unref(mainState, LUA_REGISTRYINDEX, reference); - session->registryReferences.clear(); - session->state = nullptr; - session->phase.store(lua_sf::detail_internal::LuaStatePhase::stopped, - std::memory_order_release); - } - std::scoped_lock lock(lua_sf::detail_internal::sessionsMutex); - const auto item = lua_sf::detail_internal::sessions.find(mainState); - if (item != lua_sf::detail_internal::sessions.end() && - item->second == session) { - for (auto alias = lua_sf::detail_internal::stateAliases.begin(); - alias != lua_sf::detail_internal::stateAliases.end();) { - if (alias->second == mainState) - alias = lua_sf::detail_internal::stateAliases.erase(alias); - else - ++alias; - } - lua_sf::detail_internal::sessions.erase(item); - } +extern "C" LUASF_API int LuaSF_take_deferred_callback_error( + lua_State* state, char* buffer, std::size_t capacity) { + return lua_glue::TakeDeferredCallbackError(state, buffer, capacity); } diff --git a/src/bind_ClassSupport.cpp b/src/bind_ClassSupport.cpp index a0680d8..5dc4bb6 100644 --- a/src/bind_ClassSupport.cpp +++ b/src/bind_ClassSupport.cpp @@ -4,14 +4,15 @@ lua_sf::ClassSprite::ClassSprite() : sf::Sprite(texture) {} -void bind_ClassSupport(sol::state_view lua) { - sol::table sf = lua_sf::sf_table(lua); - sf.new_usertype( - "__ClassSprite", sol::no_constructor, sol::base_classes, - sol::bases()); - lua_sf::mark_shared_usertype(lua); - sf["Sprite"].get().set_function( - "__classFactory", [](const sol::table &) { - return lua_sf::makeLuaSharedObject(); - }); +void bind_ClassSupport(lua_glue::StateView lua) { + lua_glue::Table sf = lua_sf::sf_table(lua); + auto type = lua_glue::BindClass(sf, "__ClassSprite"); + lua_glue::BindBase(type); + lua_glue::BindBase(type); + lua_glue::BindBase(type); + lua_sf::mark_shared_usertype(lua); + sf["Sprite"].get().set_function( + "__classFactory", [](const lua_glue::Table&) { + return lua_sf::makeLuaSharedObject(); + }); } diff --git a/src/bind_Drawable.cpp b/src/bind_Drawable.cpp index aa742cc..300e1cb 100644 --- a/src/bind_Drawable.cpp +++ b/src/bind_Drawable.cpp @@ -6,42 +6,43 @@ lua_sf::LuaDrawable::LuaDrawable(DrawCallback drawCallback) : m_drawCallback(std::move(drawCallback)) {} -void lua_sf::LuaDrawable::draw(sf::RenderTarget &target, +void lua_sf::LuaDrawable::draw(sf::RenderTarget& target, sf::RenderStates states) const { - m_drawCallback(target, states); + m_drawCallback(target, states); } -void bind_Drawable(sol::state_view lua) { - sol::table sf = lua_sf::sf_table(lua); - LUASF_STUB_CLASS("sf.Drawable"); - sf.new_usertype("Drawable", sol::no_constructor); - sol::table drawableType = sf["Drawable"].get(); - sol::table callbacks = lua.create_table(); - callbacks.add("draw"); - drawableType.raw_set("__classCallbacks", callbacks); - drawableType.set_function( - "__classFactory", [](const sol::table &classCallbacks) { - return lua_sf::makeLuaSharedObject( - lua_sf::function_from_object( - classCallbacks["draw"].get())); - }); - LUASF_STUB_CLASS("sf.LuaDrawable", "sf.Drawable"); - LUASF_STUB_FUNCTION("sf.LuaDrawable", "new", - "fun(drawCallback: fun(target: sf.RenderTarget, states: " - "sf.RenderStates)): sf.LuaDrawable"); - auto luaDrawableType = sf.new_usertype( - "LuaDrawable", sol::no_constructor, sol::base_classes, - sol::bases()); - luaDrawableType.set_function( - "new", sol::factories([](const sol::object &drawCallback) { - return lua_sf::makeLuaSharedObject( - lua_sf::function_from_object(drawCallback)); - })); - lua_sf::mark_shared_usertype(lua); - sol::table luaDrawableBases = lua.create_table(); - luaDrawableBases.add(drawableType); - sf["LuaDrawable"].get().raw_set("__nativeBases", - luaDrawableBases); +void bind_Drawable(lua_glue::StateView lua) { + lua_glue::Table sf = lua_sf::sf_table(lua); + LUASF_STUB_CLASS("sf.Drawable"); + lua_glue::BindClass(sf, "Drawable"); + lua_glue::Table drawableType = sf["Drawable"].get(); + lua_glue::Table callbacks = lua.create_table(); + callbacks.add("draw"); + drawableType.raw_set("__classCallbacks", callbacks); + drawableType.set_function( + "__classFactory", [](const lua_glue::Table& classCallbacks) { + return lua_sf::makeLuaSharedObject( + lua_sf::function_from_object( + classCallbacks["draw"].get())); + }); + LUASF_STUB_CLASS("sf.LuaDrawable", "sf.Drawable"); + LUASF_STUB_FUNCTION( + "sf.LuaDrawable", "new", + "fun(drawCallback: fun(target: sf.RenderTarget, states: " + "sf.RenderStates)): sf.LuaDrawable"); + auto luaDrawableType = + lua_glue::BindClass(sf, "LuaDrawable"); + lua_glue::BindBase(luaDrawableType); + luaDrawableType.set_function( + "new", [](const lua_glue::Object& drawCallback) { + return lua_sf::makeLuaSharedObject( + lua_sf::function_from_object(drawCallback)); + }); + lua_sf::mark_shared_usertype(lua); + lua_glue::Table luaDrawableBases = lua.create_table(); + luaDrawableBases.add(drawableType); + sf["LuaDrawable"].get().raw_set("__nativeBases", + luaDrawableBases); } diff --git a/src/bind_Event.cpp b/src/bind_Event.cpp index 8fe6853..0bd6532 100644 --- a/src/bind_Event.cpp +++ b/src/bind_Event.cpp @@ -5,612 +5,601 @@ namespace { template -sol::object event_get_if(sol::state_view lua, sf::Event &event) { - if (auto *value = event.getIf()) - return sol::make_object(lua, std::ref(*value)); - return sol::make_object(lua, lua_sf::LUASF_SOL_NIL); +bool event_is(const sf::Event& event) { + return event.is(); } -template bool event_is(const sf::Event &event) { - return event.is(); +template +const char* event_type_name() { + if constexpr (std::is_same_v) { + return "Closed"; + } else if constexpr (std::is_same_v) { + return "Resized"; + } else if constexpr (std::is_same_v) { + return "FocusLost"; + } else if constexpr (std::is_same_v) { + return "FocusGained"; + } else if constexpr (std::is_same_v) { + return "TextEntered"; + } else if constexpr (std::is_same_v) { + return "KeyPressed"; + } else if constexpr (std::is_same_v) { + return "KeyReleased"; + } else if constexpr (std::is_same_v) { + return "MouseWheelScrolled"; + } else if constexpr (std::is_same_v) { + return "MouseButtonPressed"; + } else if constexpr (std::is_same_v) { + return "MouseButtonReleased"; + } else if constexpr (std::is_same_v) { + return "MouseMoved"; + } else if constexpr (std::is_same_v) { + return "MouseMovedRaw"; + } else if constexpr (std::is_same_v) { + return "MouseEntered"; + } else if constexpr (std::is_same_v) { + return "MouseLeft"; + } else if constexpr (std::is_same_v) { + return "JoystickButtonPressed"; + } else if constexpr (std::is_same_v) { + return "JoystickButtonReleased"; + } else if constexpr (std::is_same_v) { + return "JoystickMoved"; + } else if constexpr (std::is_same_v) { + return "JoystickConnected"; + } else if constexpr (std::is_same_v) { + return "JoystickDisconnected"; + } else if constexpr (std::is_same_v) { + return "TouchBegan"; + } else if constexpr (std::is_same_v) { + return "TouchMoved"; + } else if constexpr (std::is_same_v) { + return "TouchEnded"; + } else if constexpr (std::is_same_v) { + return "SensorChanged"; + } else { + return "Unknown"; + } } -template const char *event_type_name() { - if constexpr (std::is_same_v) - return "Closed"; - else if constexpr (std::is_same_v) - return "Resized"; - else if constexpr (std::is_same_v) - return "FocusLost"; - else if constexpr (std::is_same_v) - return "FocusGained"; - else if constexpr (std::is_same_v) - return "TextEntered"; - else if constexpr (std::is_same_v) - return "KeyPressed"; - else if constexpr (std::is_same_v) - return "KeyReleased"; - else if constexpr (std::is_same_v) - return "MouseWheelScrolled"; - else if constexpr (std::is_same_v) - return "MouseButtonPressed"; - else if constexpr (std::is_same_v) - return "MouseButtonReleased"; - else if constexpr (std::is_same_v) - return "MouseMoved"; - else if constexpr (std::is_same_v) - return "MouseMovedRaw"; - else if constexpr (std::is_same_v) - return "MouseEntered"; - else if constexpr (std::is_same_v) - return "MouseLeft"; - else if constexpr (std::is_same_v) - return "JoystickButtonPressed"; - else if constexpr (std::is_same_v) - return "JoystickButtonReleased"; - else if constexpr (std::is_same_v) - return "JoystickMoved"; - else if constexpr (std::is_same_v) - return "JoystickConnected"; - else if constexpr (std::is_same_v) - return "JoystickDisconnected"; - else if constexpr (std::is_same_v) - return "TouchBegan"; - else if constexpr (std::is_same_v) - return "TouchMoved"; - else if constexpr (std::is_same_v) - return "TouchEnded"; - else if constexpr (std::is_same_v) - return "SensorChanged"; - else - return "Unknown"; +template +auto bind_event_subtype(lua_glue::Table sf, const char* name) { + auto type = lua_glue::BindStruct(sf, name); + lua_glue::BindConstructor(type); + const std::string owner = "sf." + std::string(name); + const std::string signature = "fun(self: " + owner + "): " + owner; + lua_sf::stub::function(owner.c_str(), "copy", signature.c_str()); + lua_sf::stub::function(owner.c_str(), "deepcopy", signature.c_str()); + return type; } template -void bind_empty_event_subtype(sol::table sf, const char *name) { - sf.new_usertype(name, sol::constructors()); +void bind_empty_event_subtype(lua_glue::Table sf, const char* name) { + bind_event_subtype(sf, name); } template -void bind_event_get_if(UserType &type, sol::state_view lua, - const char *name) { - type.set_function( - name, - sol::policies( - [lua](sf::Event &self) { return event_get_if(lua, self); }, - sol::self_dependency{})); +void bind_event_get_if(UserType& type, lua_glue::StateView, const char* name) { + lua_glue::BindCallable( + type, name, + [](sf::Event& self) { + return self.getIf(); + }, + lua_glue::ReturnPolicy::ReferenceInternal); } template -void bind_event_field(UserType &type, const char *name, - FieldType EventType::*field) { - if constexpr (lua_sf::is_lua_integral_v) { - using LuaFieldType = - std::conditional_t, std::int64_t, - std::uint64_t>; - type.set( - name, - sol::property( - [field](const EventType &self) { - return static_cast(self.*field); +void bind_event_field(UserType& type, const char* name, + FieldType EventType::* field) { + if constexpr (lua_sf::is_lua_integral_v) { + using LuaFieldType = std::conditional_t, + std::int64_t, std::uint64_t>; + lua_glue::BindProperty( + type, name, + [field](const EventType& self) { + return static_cast(self.*field); }, - [field](EventType &self, lua_sf::LuaIntegral value) { - self.*field = value.value(); - })); - } else { - type[name] = sol::policies(field, sol::self_dependency{}); - } + [field](EventType& self, lua_sf::LuaIntegral value) { + self.*field = value.value(); + }); + } else { + lua_glue::BindAttr(type, name, field); + } } -} // namespace - -void bind_Event(sol::state_view lua) { - sol::table sf = lua_sf::sf_table(lua); - - LUASF_STUB_CLASS("sf.Event_Closed"); - LUASF_STUB_FUNCTION("sf.Event_Closed", "new", "fun(): sf.Event_Closed"); - bind_empty_event_subtype(sf, "Event_Closed"); - - LUASF_STUB_CLASS("sf.Event_FocusLost"); - LUASF_STUB_FUNCTION("sf.Event_FocusLost", "new", "fun(): sf.Event_FocusLost"); - bind_empty_event_subtype(sf, "Event_FocusLost"); - - LUASF_STUB_CLASS("sf.Event_FocusGained"); - LUASF_STUB_FUNCTION("sf.Event_FocusGained", "new", - "fun(): sf.Event_FocusGained"); - bind_empty_event_subtype(sf, "Event_FocusGained"); - - LUASF_STUB_CLASS("sf.Event_MouseEntered"); - LUASF_STUB_FUNCTION("sf.Event_MouseEntered", "new", - "fun(): sf.Event_MouseEntered"); - bind_empty_event_subtype(sf, "Event_MouseEntered"); - - LUASF_STUB_CLASS("sf.Event_MouseLeft"); - LUASF_STUB_FUNCTION("sf.Event_MouseLeft", "new", "fun(): sf.Event_MouseLeft"); - bind_empty_event_subtype(sf, "Event_MouseLeft"); - - LUASF_STUB_CLASS("sf.Event_Resized"); - LUASF_STUB_FIELD("size", "sf.Vector2u"); - LUASF_STUB_FUNCTION("sf.Event_Resized", "new", "fun(): sf.Event_Resized"); - auto resized = sf.new_usertype( - "Event_Resized", sol::constructors()); - bind_event_field(resized, "size", &sf::Event::Resized::size); - - LUASF_STUB_CLASS("sf.Event_TextEntered"); - LUASF_STUB_FIELD("unicode", "integer"); - LUASF_STUB_FUNCTION("sf.Event_TextEntered", "new", - "fun(): sf.Event_TextEntered"); - auto textEntered = sf.new_usertype( - "Event_TextEntered", sol::constructors()); - bind_event_field(textEntered, "unicode", &sf::Event::TextEntered::unicode); - - LUASF_STUB_CLASS("sf.Event_KeyPressed"); - LUASF_STUB_FIELD("code", "sf.Keyboard.Key"); - LUASF_STUB_FIELD("scancode", "sf.Keyboard.Scancode"); - LUASF_STUB_FIELD("alt", "boolean"); - LUASF_STUB_FIELD("control", "boolean"); - LUASF_STUB_FIELD("shift", "boolean"); - LUASF_STUB_FIELD("system", "boolean"); - LUASF_STUB_FUNCTION("sf.Event_KeyPressed", "new", - "fun(): sf.Event_KeyPressed"); - auto keyPressed = sf.new_usertype( - "Event_KeyPressed", sol::constructors()); - bind_event_field(keyPressed, "code", &sf::Event::KeyPressed::code); - bind_event_field(keyPressed, "scancode", - &sf::Event::KeyPressed::scancode); - bind_event_field(keyPressed, "alt", &sf::Event::KeyPressed::alt); - bind_event_field(keyPressed, "control", &sf::Event::KeyPressed::control); - bind_event_field(keyPressed, "shift", &sf::Event::KeyPressed::shift); - bind_event_field(keyPressed, "system", &sf::Event::KeyPressed::system); - - LUASF_STUB_CLASS("sf.Event_KeyReleased"); - LUASF_STUB_FIELD("code", "sf.Keyboard.Key"); - LUASF_STUB_FIELD("scancode", "sf.Keyboard.Scancode"); - LUASF_STUB_FIELD("alt", "boolean"); - LUASF_STUB_FIELD("control", "boolean"); - LUASF_STUB_FIELD("shift", "boolean"); - LUASF_STUB_FIELD("system", "boolean"); - LUASF_STUB_FUNCTION("sf.Event_KeyReleased", "new", - "fun(): sf.Event_KeyReleased"); - auto keyReleased = sf.new_usertype( - "Event_KeyReleased", sol::constructors()); - bind_event_field(keyReleased, "code", &sf::Event::KeyReleased::code); - bind_event_field(keyReleased, "scancode", - &sf::Event::KeyReleased::scancode); - bind_event_field(keyReleased, "alt", &sf::Event::KeyReleased::alt); - bind_event_field(keyReleased, "control", &sf::Event::KeyReleased::control); - bind_event_field(keyReleased, "shift", &sf::Event::KeyReleased::shift); - bind_event_field(keyReleased, "system", &sf::Event::KeyReleased::system); - - LUASF_STUB_CLASS("sf.Event_MouseWheelScrolled"); - LUASF_STUB_FIELD("wheel", "sf.Mouse.Wheel"); - LUASF_STUB_FIELD("delta", "number"); - LUASF_STUB_FIELD("position", "sf.Vector2i"); - LUASF_STUB_FUNCTION("sf.Event_MouseWheelScrolled", "new", - "fun(): sf.Event_MouseWheelScrolled"); - auto wheelScrolled = sf.new_usertype( - "Event_MouseWheelScrolled", - sol::constructors()); - bind_event_field(wheelScrolled, "wheel", - &sf::Event::MouseWheelScrolled::wheel); - bind_event_field(wheelScrolled, "delta", - &sf::Event::MouseWheelScrolled::delta); - bind_event_field(wheelScrolled, "position", - &sf::Event::MouseWheelScrolled::position); - - LUASF_STUB_CLASS("sf.Event_MouseButtonPressed"); - LUASF_STUB_FIELD("button", "sf.Mouse.Button"); - LUASF_STUB_FIELD("position", "sf.Vector2i"); - LUASF_STUB_FUNCTION("sf.Event_MouseButtonPressed", "new", - "fun(): sf.Event_MouseButtonPressed"); - auto buttonPressed = sf.new_usertype( - "Event_MouseButtonPressed", - sol::constructors()); - bind_event_field(buttonPressed, "button", - &sf::Event::MouseButtonPressed::button); - bind_event_field(buttonPressed, "position", - &sf::Event::MouseButtonPressed::position); - - LUASF_STUB_CLASS("sf.Event_MouseButtonReleased"); - LUASF_STUB_FIELD("button", "sf.Mouse.Button"); - LUASF_STUB_FIELD("position", "sf.Vector2i"); - LUASF_STUB_FUNCTION("sf.Event_MouseButtonReleased", "new", - "fun(): sf.Event_MouseButtonReleased"); - auto buttonReleased = sf.new_usertype( - "Event_MouseButtonReleased", - sol::constructors()); - bind_event_field(buttonReleased, "button", - &sf::Event::MouseButtonReleased::button); - bind_event_field(buttonReleased, "position", - &sf::Event::MouseButtonReleased::position); - - LUASF_STUB_CLASS("sf.Event_MouseMoved"); - LUASF_STUB_FIELD("position", "sf.Vector2i"); - LUASF_STUB_FUNCTION("sf.Event_MouseMoved", "new", - "fun(): sf.Event_MouseMoved"); - auto mouseMoved = sf.new_usertype( - "Event_MouseMoved", sol::constructors()); - bind_event_field(mouseMoved, "position", &sf::Event::MouseMoved::position); - - LUASF_STUB_CLASS("sf.Event_MouseMovedRaw"); - LUASF_STUB_FIELD("delta", "sf.Vector2i"); - LUASF_STUB_FUNCTION("sf.Event_MouseMovedRaw", "new", - "fun(): sf.Event_MouseMovedRaw"); - auto rawMoved = sf.new_usertype( - "Event_MouseMovedRaw", sol::constructors()); - bind_event_field(rawMoved, "delta", &sf::Event::MouseMovedRaw::delta); - - LUASF_STUB_CLASS("sf.Event_JoystickButtonPressed"); - LUASF_STUB_FIELD("joystickId", "integer"); - LUASF_STUB_FIELD("button", "integer"); - LUASF_STUB_FUNCTION("sf.Event_JoystickButtonPressed", "new", - "fun(): sf.Event_JoystickButtonPressed"); - auto joystickButtonPressed = - sf.new_usertype( - "Event_JoystickButtonPressed", - sol::constructors()); - bind_event_field(joystickButtonPressed, "joystickId", - &sf::Event::JoystickButtonPressed::joystickId); - bind_event_field(joystickButtonPressed, "button", - &sf::Event::JoystickButtonPressed::button); - - LUASF_STUB_CLASS("sf.Event_JoystickButtonReleased"); - LUASF_STUB_FIELD("joystickId", "integer"); - LUASF_STUB_FIELD("button", "integer"); - LUASF_STUB_FUNCTION("sf.Event_JoystickButtonReleased", "new", - "fun(): sf.Event_JoystickButtonReleased"); - auto joystickButtonReleased = - sf.new_usertype( - "Event_JoystickButtonReleased", - sol::constructors()); - bind_event_field(joystickButtonReleased, "joystickId", - &sf::Event::JoystickButtonReleased::joystickId); - bind_event_field(joystickButtonReleased, "button", - &sf::Event::JoystickButtonReleased::button); - - LUASF_STUB_CLASS("sf.Event_JoystickMoved"); - LUASF_STUB_FIELD("joystickId", "integer"); - LUASF_STUB_FIELD("axis", "sf.Joystick.Axis"); - LUASF_STUB_FIELD("position", "number"); - LUASF_STUB_FUNCTION("sf.Event_JoystickMoved", "new", - "fun(): sf.Event_JoystickMoved"); - auto joystickMoved = sf.new_usertype( - "Event_JoystickMoved", sol::constructors()); - bind_event_field(joystickMoved, "joystickId", - &sf::Event::JoystickMoved::joystickId); - bind_event_field(joystickMoved, "axis", &sf::Event::JoystickMoved::axis); - bind_event_field(joystickMoved, "position", - &sf::Event::JoystickMoved::position); - - LUASF_STUB_CLASS("sf.Event_JoystickConnected"); - LUASF_STUB_FIELD("joystickId", "integer"); - LUASF_STUB_FUNCTION("sf.Event_JoystickConnected", "new", - "fun(): sf.Event_JoystickConnected"); - auto joystickConnected = sf.new_usertype( - "Event_JoystickConnected", - sol::constructors()); - bind_event_field(joystickConnected, "joystickId", - &sf::Event::JoystickConnected::joystickId); - - LUASF_STUB_CLASS("sf.Event_JoystickDisconnected"); - LUASF_STUB_FIELD("joystickId", "integer"); - LUASF_STUB_FUNCTION("sf.Event_JoystickDisconnected", "new", - "fun(): sf.Event_JoystickDisconnected"); - auto joystickDisconnected = sf.new_usertype( - "Event_JoystickDisconnected", - sol::constructors()); - bind_event_field(joystickDisconnected, "joystickId", - &sf::Event::JoystickDisconnected::joystickId); - - LUASF_STUB_CLASS("sf.Event_TouchBegan"); - LUASF_STUB_FIELD("finger", "integer"); - LUASF_STUB_FIELD("position", "sf.Vector2i"); - LUASF_STUB_FUNCTION("sf.Event_TouchBegan", "new", - "fun(): sf.Event_TouchBegan"); - auto touchBegan = sf.new_usertype( - "Event_TouchBegan", sol::constructors()); - bind_event_field(touchBegan, "finger", &sf::Event::TouchBegan::finger); - bind_event_field(touchBegan, "position", &sf::Event::TouchBegan::position); - - LUASF_STUB_CLASS("sf.Event_TouchMoved"); - LUASF_STUB_FIELD("finger", "integer"); - LUASF_STUB_FIELD("position", "sf.Vector2i"); - LUASF_STUB_FUNCTION("sf.Event_TouchMoved", "new", - "fun(): sf.Event_TouchMoved"); - auto touchMoved = sf.new_usertype( - "Event_TouchMoved", sol::constructors()); - bind_event_field(touchMoved, "finger", &sf::Event::TouchMoved::finger); - bind_event_field(touchMoved, "position", &sf::Event::TouchMoved::position); - - LUASF_STUB_CLASS("sf.Event_TouchEnded"); - LUASF_STUB_FIELD("finger", "integer"); - LUASF_STUB_FIELD("position", "sf.Vector2i"); - LUASF_STUB_FUNCTION("sf.Event_TouchEnded", "new", - "fun(): sf.Event_TouchEnded"); - auto touchEnded = sf.new_usertype( - "Event_TouchEnded", sol::constructors()); - bind_event_field(touchEnded, "finger", &sf::Event::TouchEnded::finger); - bind_event_field(touchEnded, "position", &sf::Event::TouchEnded::position); - - LUASF_STUB_CLASS("sf.Event_SensorChanged"); - LUASF_STUB_FIELD("type", "sf.Sensor.Type"); - LUASF_STUB_FIELD("value", "sf.Vector3f"); - LUASF_STUB_FUNCTION("sf.Event_SensorChanged", "new", - "fun(): sf.Event_SensorChanged"); - auto sensorChanged = sf.new_usertype( - "Event_SensorChanged", sol::constructors()); - bind_event_field(sensorChanged, "type", &sf::Event::SensorChanged::type); - bind_event_field(sensorChanged, "value", &sf::Event::SensorChanged::value); - - LUASF_STUB_CLASS("sf.Event"); - LUASF_STUB_FUNCTION("sf.Event", "new", - "fun(value: sf.Event_Closed): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_Resized): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_FocusLost): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_FocusGained): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_TextEntered): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_KeyPressed): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_KeyReleased): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_MouseWheelScrolled): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_MouseButtonPressed): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_MouseButtonReleased): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_MouseMoved): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_MouseMovedRaw): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_MouseEntered): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_MouseLeft): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_JoystickButtonPressed): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_JoystickButtonReleased): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_JoystickMoved): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_JoystickConnected): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_JoystickDisconnected): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_TouchBegan): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_TouchMoved): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_TouchEnded): sf.Event"); - LUASF_STUB_OVERLOAD("sf.Event", "new", - "fun(value: sf.Event_SensorChanged): sf.Event"); - auto event = sf.new_usertype("Event", sol::no_constructor); - event.set_function( - "new", - sol::factories( - [](const sf::Event::Closed &value) { return sf::Event(value); }, - [](const sf::Event::Resized &value) { return sf::Event(value); }, - [](const sf::Event::FocusLost &value) { return sf::Event(value); }, - [](const sf::Event::FocusGained &value) { return sf::Event(value); }, - [](const sf::Event::TextEntered &value) { return sf::Event(value); }, - [](const sf::Event::KeyPressed &value) { return sf::Event(value); }, - [](const sf::Event::KeyReleased &value) { return sf::Event(value); }, - [](const sf::Event::MouseWheelScrolled &value) { - return sf::Event(value); - }, - [](const sf::Event::MouseButtonPressed &value) { - return sf::Event(value); - }, - [](const sf::Event::MouseButtonReleased &value) { - return sf::Event(value); - }, - [](const sf::Event::MouseMoved &value) { return sf::Event(value); }, - [](const sf::Event::MouseMovedRaw &value) { - return sf::Event(value); - }, - [](const sf::Event::MouseEntered &value) { return sf::Event(value); }, - [](const sf::Event::MouseLeft &value) { return sf::Event(value); }, - [](const sf::Event::JoystickButtonPressed &value) { - return sf::Event(value); - }, - [](const sf::Event::JoystickButtonReleased &value) { - return sf::Event(value); - }, - [](const sf::Event::JoystickMoved &value) { - return sf::Event(value); - }, - [](const sf::Event::JoystickConnected &value) { - return sf::Event(value); - }, - [](const sf::Event::JoystickDisconnected &value) { - return sf::Event(value); - }, - [](const sf::Event::TouchBegan &value) { return sf::Event(value); }, - [](const sf::Event::TouchMoved &value) { return sf::Event(value); }, - [](const sf::Event::TouchEnded &value) { return sf::Event(value); }, - [](const sf::Event::SensorChanged &value) { - return sf::Event(value); - })); - - LUASF_STUB_FUNCTION("sf.Event", "type", "fun(self: sf.Event): string"); - event.set_function("type", [](const sf::Event &self) { - return self.visit([](const auto &value) { - return event_type_name>(); +} // namespace + +void bind_Event(lua_glue::StateView lua) { + lua_glue::Table sf = lua_sf::sf_table(lua); + + LUASF_STUB_CLASS("sf.Event_Closed"); + LUASF_STUB_FUNCTION("sf.Event_Closed", "new", "fun(): sf.Event_Closed"); + bind_empty_event_subtype(sf, "Event_Closed"); + + LUASF_STUB_CLASS("sf.Event_FocusLost"); + LUASF_STUB_FUNCTION("sf.Event_FocusLost", "new", + "fun(): sf.Event_FocusLost"); + bind_empty_event_subtype(sf, "Event_FocusLost"); + + LUASF_STUB_CLASS("sf.Event_FocusGained"); + LUASF_STUB_FUNCTION("sf.Event_FocusGained", "new", + "fun(): sf.Event_FocusGained"); + bind_empty_event_subtype(sf, "Event_FocusGained"); + + LUASF_STUB_CLASS("sf.Event_MouseEntered"); + LUASF_STUB_FUNCTION("sf.Event_MouseEntered", "new", + "fun(): sf.Event_MouseEntered"); + bind_empty_event_subtype(sf, "Event_MouseEntered"); + + LUASF_STUB_CLASS("sf.Event_MouseLeft"); + LUASF_STUB_FUNCTION("sf.Event_MouseLeft", "new", + "fun(): sf.Event_MouseLeft"); + bind_empty_event_subtype(sf, "Event_MouseLeft"); + + LUASF_STUB_CLASS("sf.Event_Resized"); + LUASF_STUB_FIELD("size", "sf.Vector2u"); + LUASF_STUB_FUNCTION("sf.Event_Resized", "new", "fun(): sf.Event_Resized"); + auto resized = bind_event_subtype(sf, "Event_Resized"); + bind_event_field(resized, "size", &sf::Event::Resized::size); + + LUASF_STUB_CLASS("sf.Event_TextEntered"); + LUASF_STUB_FIELD("unicode", "integer"); + LUASF_STUB_FUNCTION("sf.Event_TextEntered", "new", + "fun(): sf.Event_TextEntered"); + auto textEntered = + bind_event_subtype(sf, "Event_TextEntered"); + bind_event_field(textEntered, "unicode", &sf::Event::TextEntered::unicode); + + LUASF_STUB_CLASS("sf.Event_KeyPressed"); + LUASF_STUB_FIELD("code", "sf.Keyboard.Key"); + LUASF_STUB_FIELD("scancode", "sf.Keyboard.Scancode"); + LUASF_STUB_FIELD("alt", "boolean"); + LUASF_STUB_FIELD("control", "boolean"); + LUASF_STUB_FIELD("shift", "boolean"); + LUASF_STUB_FIELD("system", "boolean"); + LUASF_STUB_FUNCTION("sf.Event_KeyPressed", "new", + "fun(): sf.Event_KeyPressed"); + auto keyPressed = + bind_event_subtype(sf, "Event_KeyPressed"); + bind_event_field(keyPressed, "code", &sf::Event::KeyPressed::code); + bind_event_field(keyPressed, "scancode", &sf::Event::KeyPressed::scancode); + bind_event_field(keyPressed, "alt", &sf::Event::KeyPressed::alt); + bind_event_field(keyPressed, "control", &sf::Event::KeyPressed::control); + bind_event_field(keyPressed, "shift", &sf::Event::KeyPressed::shift); + bind_event_field(keyPressed, "system", &sf::Event::KeyPressed::system); + + LUASF_STUB_CLASS("sf.Event_KeyReleased"); + LUASF_STUB_FIELD("code", "sf.Keyboard.Key"); + LUASF_STUB_FIELD("scancode", "sf.Keyboard.Scancode"); + LUASF_STUB_FIELD("alt", "boolean"); + LUASF_STUB_FIELD("control", "boolean"); + LUASF_STUB_FIELD("shift", "boolean"); + LUASF_STUB_FIELD("system", "boolean"); + LUASF_STUB_FUNCTION("sf.Event_KeyReleased", "new", + "fun(): sf.Event_KeyReleased"); + auto keyReleased = + bind_event_subtype(sf, "Event_KeyReleased"); + bind_event_field(keyReleased, "code", &sf::Event::KeyReleased::code); + bind_event_field(keyReleased, "scancode", + &sf::Event::KeyReleased::scancode); + bind_event_field(keyReleased, "alt", &sf::Event::KeyReleased::alt); + bind_event_field(keyReleased, "control", &sf::Event::KeyReleased::control); + bind_event_field(keyReleased, "shift", &sf::Event::KeyReleased::shift); + bind_event_field(keyReleased, "system", &sf::Event::KeyReleased::system); + + LUASF_STUB_CLASS("sf.Event_MouseWheelScrolled"); + LUASF_STUB_FIELD("wheel", "sf.Mouse.Wheel"); + LUASF_STUB_FIELD("delta", "number"); + LUASF_STUB_FIELD("position", "sf.Vector2i"); + LUASF_STUB_FUNCTION("sf.Event_MouseWheelScrolled", "new", + "fun(): sf.Event_MouseWheelScrolled"); + auto wheelScrolled = bind_event_subtype( + sf, "Event_MouseWheelScrolled"); + bind_event_field(wheelScrolled, "wheel", + &sf::Event::MouseWheelScrolled::wheel); + bind_event_field(wheelScrolled, "delta", + &sf::Event::MouseWheelScrolled::delta); + bind_event_field(wheelScrolled, "position", + &sf::Event::MouseWheelScrolled::position); + + LUASF_STUB_CLASS("sf.Event_MouseButtonPressed"); + LUASF_STUB_FIELD("button", "sf.Mouse.Button"); + LUASF_STUB_FIELD("position", "sf.Vector2i"); + LUASF_STUB_FUNCTION("sf.Event_MouseButtonPressed", "new", + "fun(): sf.Event_MouseButtonPressed"); + auto buttonPressed = bind_event_subtype( + sf, "Event_MouseButtonPressed"); + bind_event_field(buttonPressed, "button", + &sf::Event::MouseButtonPressed::button); + bind_event_field(buttonPressed, "position", + &sf::Event::MouseButtonPressed::position); + + LUASF_STUB_CLASS("sf.Event_MouseButtonReleased"); + LUASF_STUB_FIELD("button", "sf.Mouse.Button"); + LUASF_STUB_FIELD("position", "sf.Vector2i"); + LUASF_STUB_FUNCTION("sf.Event_MouseButtonReleased", "new", + "fun(): sf.Event_MouseButtonReleased"); + auto buttonReleased = bind_event_subtype( + sf, "Event_MouseButtonReleased"); + bind_event_field(buttonReleased, "button", + &sf::Event::MouseButtonReleased::button); + bind_event_field(buttonReleased, "position", + &sf::Event::MouseButtonReleased::position); + + LUASF_STUB_CLASS("sf.Event_MouseMoved"); + LUASF_STUB_FIELD("position", "sf.Vector2i"); + LUASF_STUB_FUNCTION("sf.Event_MouseMoved", "new", + "fun(): sf.Event_MouseMoved"); + auto mouseMoved = + bind_event_subtype(sf, "Event_MouseMoved"); + bind_event_field(mouseMoved, "position", &sf::Event::MouseMoved::position); + + LUASF_STUB_CLASS("sf.Event_MouseMovedRaw"); + LUASF_STUB_FIELD("delta", "sf.Vector2i"); + LUASF_STUB_FUNCTION("sf.Event_MouseMovedRaw", "new", + "fun(): sf.Event_MouseMovedRaw"); + auto rawMoved = + bind_event_subtype(sf, "Event_MouseMovedRaw"); + bind_event_field(rawMoved, "delta", &sf::Event::MouseMovedRaw::delta); + + LUASF_STUB_CLASS("sf.Event_JoystickButtonPressed"); + LUASF_STUB_FIELD("joystickId", "integer"); + LUASF_STUB_FIELD("button", "integer"); + LUASF_STUB_FUNCTION("sf.Event_JoystickButtonPressed", "new", + "fun(): sf.Event_JoystickButtonPressed"); + auto joystickButtonPressed = + bind_event_subtype( + sf, "Event_JoystickButtonPressed"); + bind_event_field(joystickButtonPressed, "joystickId", + &sf::Event::JoystickButtonPressed::joystickId); + bind_event_field(joystickButtonPressed, "button", + &sf::Event::JoystickButtonPressed::button); + + LUASF_STUB_CLASS("sf.Event_JoystickButtonReleased"); + LUASF_STUB_FIELD("joystickId", "integer"); + LUASF_STUB_FIELD("button", "integer"); + LUASF_STUB_FUNCTION("sf.Event_JoystickButtonReleased", "new", + "fun(): sf.Event_JoystickButtonReleased"); + auto joystickButtonReleased = + bind_event_subtype( + sf, "Event_JoystickButtonReleased"); + bind_event_field(joystickButtonReleased, "joystickId", + &sf::Event::JoystickButtonReleased::joystickId); + bind_event_field(joystickButtonReleased, "button", + &sf::Event::JoystickButtonReleased::button); + + LUASF_STUB_CLASS("sf.Event_JoystickMoved"); + LUASF_STUB_FIELD("joystickId", "integer"); + LUASF_STUB_FIELD("axis", "sf.Joystick.Axis"); + LUASF_STUB_FIELD("position", "number"); + LUASF_STUB_FUNCTION("sf.Event_JoystickMoved", "new", + "fun(): sf.Event_JoystickMoved"); + auto joystickMoved = + bind_event_subtype(sf, "Event_JoystickMoved"); + bind_event_field(joystickMoved, "joystickId", + &sf::Event::JoystickMoved::joystickId); + bind_event_field(joystickMoved, "axis", &sf::Event::JoystickMoved::axis); + bind_event_field(joystickMoved, "position", + &sf::Event::JoystickMoved::position); + + LUASF_STUB_CLASS("sf.Event_JoystickConnected"); + LUASF_STUB_FIELD("joystickId", "integer"); + LUASF_STUB_FUNCTION("sf.Event_JoystickConnected", "new", + "fun(): sf.Event_JoystickConnected"); + auto joystickConnected = bind_event_subtype( + sf, "Event_JoystickConnected"); + bind_event_field(joystickConnected, "joystickId", + &sf::Event::JoystickConnected::joystickId); + + LUASF_STUB_CLASS("sf.Event_JoystickDisconnected"); + LUASF_STUB_FIELD("joystickId", "integer"); + LUASF_STUB_FUNCTION("sf.Event_JoystickDisconnected", "new", + "fun(): sf.Event_JoystickDisconnected"); + auto joystickDisconnected = + bind_event_subtype( + sf, "Event_JoystickDisconnected"); + bind_event_field(joystickDisconnected, "joystickId", + &sf::Event::JoystickDisconnected::joystickId); + + LUASF_STUB_CLASS("sf.Event_TouchBegan"); + LUASF_STUB_FIELD("finger", "integer"); + LUASF_STUB_FIELD("position", "sf.Vector2i"); + LUASF_STUB_FUNCTION("sf.Event_TouchBegan", "new", + "fun(): sf.Event_TouchBegan"); + auto touchBegan = + bind_event_subtype(sf, "Event_TouchBegan"); + bind_event_field(touchBegan, "finger", &sf::Event::TouchBegan::finger); + bind_event_field(touchBegan, "position", &sf::Event::TouchBegan::position); + + LUASF_STUB_CLASS("sf.Event_TouchMoved"); + LUASF_STUB_FIELD("finger", "integer"); + LUASF_STUB_FIELD("position", "sf.Vector2i"); + LUASF_STUB_FUNCTION("sf.Event_TouchMoved", "new", + "fun(): sf.Event_TouchMoved"); + auto touchMoved = + bind_event_subtype(sf, "Event_TouchMoved"); + bind_event_field(touchMoved, "finger", &sf::Event::TouchMoved::finger); + bind_event_field(touchMoved, "position", &sf::Event::TouchMoved::position); + + LUASF_STUB_CLASS("sf.Event_TouchEnded"); + LUASF_STUB_FIELD("finger", "integer"); + LUASF_STUB_FIELD("position", "sf.Vector2i"); + LUASF_STUB_FUNCTION("sf.Event_TouchEnded", "new", + "fun(): sf.Event_TouchEnded"); + auto touchEnded = + bind_event_subtype(sf, "Event_TouchEnded"); + bind_event_field(touchEnded, "finger", &sf::Event::TouchEnded::finger); + bind_event_field(touchEnded, "position", &sf::Event::TouchEnded::position); + + LUASF_STUB_CLASS("sf.Event_SensorChanged"); + LUASF_STUB_FIELD("type", "sf.Sensor.Type"); + LUASF_STUB_FIELD("value", "sf.Vector3f"); + LUASF_STUB_FUNCTION("sf.Event_SensorChanged", "new", + "fun(): sf.Event_SensorChanged"); + auto sensorChanged = + bind_event_subtype(sf, "Event_SensorChanged"); + bind_event_field(sensorChanged, "type", &sf::Event::SensorChanged::type); + bind_event_field(sensorChanged, "value", &sf::Event::SensorChanged::value); + + LUASF_STUB_CLASS("sf.Event"); + LUASF_STUB_FUNCTION("sf.Event", "new", + "fun(value: sf.Event_Closed): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_Resized): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_FocusLost): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_FocusGained): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_TextEntered): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_KeyPressed): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_KeyReleased): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_MouseWheelScrolled): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_MouseButtonPressed): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_MouseButtonReleased): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_MouseMoved): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_MouseMovedRaw): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_MouseEntered): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_MouseLeft): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_JoystickButtonPressed): sf.Event"); + LUASF_STUB_OVERLOAD( + "sf.Event", "new", + "fun(value: sf.Event_JoystickButtonReleased): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_JoystickMoved): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_JoystickConnected): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_JoystickDisconnected): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_TouchBegan): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_TouchMoved): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_TouchEnded): sf.Event"); + LUASF_STUB_OVERLOAD("sf.Event", "new", + "fun(value: sf.Event_SensorChanged): sf.Event"); + auto event = lua_glue::BindStruct(sf, "Event"); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor( + event); + lua_glue::BindConstructor( + event); + lua_glue::BindConstructor( + event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor( + event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor( + event); + lua_glue::BindConstructor( + event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor(event); + lua_glue::BindConstructor( + event); + LUASF_STUB_FUNCTION("sf.Event", "copy", "fun(self: sf.Event): sf.Event"); + LUASF_STUB_FUNCTION("sf.Event", "deepcopy", + "fun(self: sf.Event): sf.Event"); + + LUASF_STUB_FUNCTION("sf.Event", "type", "fun(self: sf.Event): string"); + event.set_function("type", [](const sf::Event& self) { + return self.visit([](const auto& value) { + return event_type_name>(); + }); }); - }); - LUASF_STUB_FUNCTION("sf.Event", "get", "fun(self: sf.Event): any"); - event.set_function( - "get", sol::policies( - [lua](sf::Event &self) -> sol::object { - return self.visit([lua](auto &value) -> sol::object { - return sol::make_object(lua, std::ref(value)); - }); - }, - sol::self_dependency{})); - - LUASF_STUB_FUNCTION("sf.Event", "isClosed", "fun(self: sf.Event): boolean"); - event.set_function("isClosed", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isResized", "fun(self: sf.Event): boolean"); - event.set_function("isResized", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isFocusLost", - "fun(self: sf.Event): boolean"); - event.set_function("isFocusLost", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isFocusGained", - "fun(self: sf.Event): boolean"); - event.set_function("isFocusGained", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isTextEntered", - "fun(self: sf.Event): boolean"); - event.set_function("isTextEntered", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isKeyPressed", - "fun(self: sf.Event): boolean"); - event.set_function("isKeyPressed", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isKeyReleased", - "fun(self: sf.Event): boolean"); - event.set_function("isKeyReleased", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isMouseWheelScrolled", - "fun(self: sf.Event): boolean"); - event.set_function("isMouseWheelScrolled", - &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isMouseButtonPressed", - "fun(self: sf.Event): boolean"); - event.set_function("isMouseButtonPressed", - &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isMouseButtonReleased", - "fun(self: sf.Event): boolean"); - event.set_function("isMouseButtonReleased", - &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isMouseMoved", - "fun(self: sf.Event): boolean"); - event.set_function("isMouseMoved", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isMouseMovedRaw", - "fun(self: sf.Event): boolean"); - event.set_function("isMouseMovedRaw", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isMouseEntered", - "fun(self: sf.Event): boolean"); - event.set_function("isMouseEntered", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isMouseLeft", - "fun(self: sf.Event): boolean"); - event.set_function("isMouseLeft", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isJoystickButtonPressed", - "fun(self: sf.Event): boolean"); - event.set_function("isJoystickButtonPressed", - &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isJoystickButtonReleased", - "fun(self: sf.Event): boolean"); - event.set_function("isJoystickButtonReleased", - &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isJoystickMoved", - "fun(self: sf.Event): boolean"); - event.set_function("isJoystickMoved", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isJoystickConnected", - "fun(self: sf.Event): boolean"); - event.set_function("isJoystickConnected", - &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isJoystickDisconnected", - "fun(self: sf.Event): boolean"); - event.set_function("isJoystickDisconnected", - &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isTouchBegan", - "fun(self: sf.Event): boolean"); - event.set_function("isTouchBegan", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isTouchMoved", - "fun(self: sf.Event): boolean"); - event.set_function("isTouchMoved", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isTouchEnded", - "fun(self: sf.Event): boolean"); - event.set_function("isTouchEnded", &event_is); - LUASF_STUB_FUNCTION("sf.Event", "isSensorChanged", - "fun(self: sf.Event): boolean"); - event.set_function("isSensorChanged", &event_is); - - LUASF_STUB_FUNCTION("sf.Event", "getIfClosed", - "fun(self: sf.Event): sf.Event_Closed|nil"); - bind_event_get_if(event, lua, "getIfClosed"); - LUASF_STUB_FUNCTION("sf.Event", "getIfResized", - "fun(self: sf.Event): sf.Event_Resized|nil"); - bind_event_get_if(event, lua, "getIfResized"); - LUASF_STUB_FUNCTION("sf.Event", "getIfFocusLost", - "fun(self: sf.Event): sf.Event_FocusLost|nil"); - bind_event_get_if(event, lua, "getIfFocusLost"); - LUASF_STUB_FUNCTION("sf.Event", "getIfFocusGained", - "fun(self: sf.Event): sf.Event_FocusGained|nil"); - bind_event_get_if(event, lua, - "getIfFocusGained"); - LUASF_STUB_FUNCTION("sf.Event", "getIfTextEntered", - "fun(self: sf.Event): sf.Event_TextEntered|nil"); - bind_event_get_if(event, lua, - "getIfTextEntered"); - LUASF_STUB_FUNCTION("sf.Event", "getIfKeyPressed", - "fun(self: sf.Event): sf.Event_KeyPressed|nil"); - bind_event_get_if(event, lua, "getIfKeyPressed"); - LUASF_STUB_FUNCTION("sf.Event", "getIfKeyReleased", - "fun(self: sf.Event): sf.Event_KeyReleased|nil"); - bind_event_get_if(event, lua, - "getIfKeyReleased"); - LUASF_STUB_FUNCTION("sf.Event", "getIfMouseWheelScrolled", - "fun(self: sf.Event): sf.Event_MouseWheelScrolled|nil"); - bind_event_get_if( - event, lua, "getIfMouseWheelScrolled"); - LUASF_STUB_FUNCTION("sf.Event", "getIfMouseButtonPressed", - "fun(self: sf.Event): sf.Event_MouseButtonPressed|nil"); - bind_event_get_if( - event, lua, "getIfMouseButtonPressed"); - LUASF_STUB_FUNCTION("sf.Event", "getIfMouseButtonReleased", - "fun(self: sf.Event): sf.Event_MouseButtonReleased|nil"); - bind_event_get_if( - event, lua, "getIfMouseButtonReleased"); - LUASF_STUB_FUNCTION("sf.Event", "getIfMouseMoved", - "fun(self: sf.Event): sf.Event_MouseMoved|nil"); - bind_event_get_if(event, lua, "getIfMouseMoved"); - LUASF_STUB_FUNCTION("sf.Event", "getIfMouseMovedRaw", - "fun(self: sf.Event): sf.Event_MouseMovedRaw|nil"); - bind_event_get_if(event, lua, - "getIfMouseMovedRaw"); - LUASF_STUB_FUNCTION("sf.Event", "getIfMouseEntered", - "fun(self: sf.Event): sf.Event_MouseEntered|nil"); - bind_event_get_if(event, lua, - "getIfMouseEntered"); - LUASF_STUB_FUNCTION("sf.Event", "getIfMouseLeft", - "fun(self: sf.Event): sf.Event_MouseLeft|nil"); - bind_event_get_if(event, lua, "getIfMouseLeft"); - LUASF_STUB_FUNCTION( - "sf.Event", "getIfJoystickButtonPressed", - "fun(self: sf.Event): sf.Event_JoystickButtonPressed|nil"); - bind_event_get_if( - event, lua, "getIfJoystickButtonPressed"); - LUASF_STUB_FUNCTION( - "sf.Event", "getIfJoystickButtonReleased", - "fun(self: sf.Event): sf.Event_JoystickButtonReleased|nil"); - bind_event_get_if( - event, lua, "getIfJoystickButtonReleased"); - LUASF_STUB_FUNCTION("sf.Event", "getIfJoystickMoved", - "fun(self: sf.Event): sf.Event_JoystickMoved|nil"); - bind_event_get_if(event, lua, - "getIfJoystickMoved"); - LUASF_STUB_FUNCTION("sf.Event", "getIfJoystickConnected", - "fun(self: sf.Event): sf.Event_JoystickConnected|nil"); - bind_event_get_if( - event, lua, "getIfJoystickConnected"); - LUASF_STUB_FUNCTION("sf.Event", "getIfJoystickDisconnected", - "fun(self: sf.Event): sf.Event_JoystickDisconnected|nil"); - bind_event_get_if( - event, lua, "getIfJoystickDisconnected"); - LUASF_STUB_FUNCTION("sf.Event", "getIfTouchBegan", - "fun(self: sf.Event): sf.Event_TouchBegan|nil"); - bind_event_get_if(event, lua, "getIfTouchBegan"); - LUASF_STUB_FUNCTION("sf.Event", "getIfTouchMoved", - "fun(self: sf.Event): sf.Event_TouchMoved|nil"); - bind_event_get_if(event, lua, "getIfTouchMoved"); - LUASF_STUB_FUNCTION("sf.Event", "getIfTouchEnded", - "fun(self: sf.Event): sf.Event_TouchEnded|nil"); - bind_event_get_if(event, lua, "getIfTouchEnded"); - LUASF_STUB_FUNCTION("sf.Event", "getIfSensorChanged", - "fun(self: sf.Event): sf.Event_SensorChanged|nil"); - bind_event_get_if(event, lua, - "getIfSensorChanged"); + LUASF_STUB_FUNCTION("sf.Event", "get", "fun(self: sf.Event): any"); + lua_glue::BindCallable( + event, "get", + [lua](sf::Event& self) -> lua_glue::Object { + return self.visit([lua](auto& value) -> lua_glue::Object { + return lua_glue::MakeObject(lua, std::ref(value)); + }); + }, + lua_glue::ReturnPolicy::ReferenceInternal); + + LUASF_STUB_FUNCTION("sf.Event", "isClosed", "fun(self: sf.Event): boolean"); + event.set_function("isClosed", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isResized", + "fun(self: sf.Event): boolean"); + event.set_function("isResized", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isFocusLost", + "fun(self: sf.Event): boolean"); + event.set_function("isFocusLost", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isFocusGained", + "fun(self: sf.Event): boolean"); + event.set_function("isFocusGained", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isTextEntered", + "fun(self: sf.Event): boolean"); + event.set_function("isTextEntered", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isKeyPressed", + "fun(self: sf.Event): boolean"); + event.set_function("isKeyPressed", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isKeyReleased", + "fun(self: sf.Event): boolean"); + event.set_function("isKeyReleased", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isMouseWheelScrolled", + "fun(self: sf.Event): boolean"); + event.set_function("isMouseWheelScrolled", + &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isMouseButtonPressed", + "fun(self: sf.Event): boolean"); + event.set_function("isMouseButtonPressed", + &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isMouseButtonReleased", + "fun(self: sf.Event): boolean"); + event.set_function("isMouseButtonReleased", + &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isMouseMoved", + "fun(self: sf.Event): boolean"); + event.set_function("isMouseMoved", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isMouseMovedRaw", + "fun(self: sf.Event): boolean"); + event.set_function("isMouseMovedRaw", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isMouseEntered", + "fun(self: sf.Event): boolean"); + event.set_function("isMouseEntered", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isMouseLeft", + "fun(self: sf.Event): boolean"); + event.set_function("isMouseLeft", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isJoystickButtonPressed", + "fun(self: sf.Event): boolean"); + event.set_function("isJoystickButtonPressed", + &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isJoystickButtonReleased", + "fun(self: sf.Event): boolean"); + event.set_function("isJoystickButtonReleased", + &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isJoystickMoved", + "fun(self: sf.Event): boolean"); + event.set_function("isJoystickMoved", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isJoystickConnected", + "fun(self: sf.Event): boolean"); + event.set_function("isJoystickConnected", + &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isJoystickDisconnected", + "fun(self: sf.Event): boolean"); + event.set_function("isJoystickDisconnected", + &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isTouchBegan", + "fun(self: sf.Event): boolean"); + event.set_function("isTouchBegan", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isTouchMoved", + "fun(self: sf.Event): boolean"); + event.set_function("isTouchMoved", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isTouchEnded", + "fun(self: sf.Event): boolean"); + event.set_function("isTouchEnded", &event_is); + LUASF_STUB_FUNCTION("sf.Event", "isSensorChanged", + "fun(self: sf.Event): boolean"); + event.set_function("isSensorChanged", &event_is); + + LUASF_STUB_FUNCTION("sf.Event", "getIfClosed", + "fun(self: sf.Event): sf.Event_Closed|nil"); + bind_event_get_if(event, lua, "getIfClosed"); + LUASF_STUB_FUNCTION("sf.Event", "getIfResized", + "fun(self: sf.Event): sf.Event_Resized|nil"); + bind_event_get_if(event, lua, "getIfResized"); + LUASF_STUB_FUNCTION("sf.Event", "getIfFocusLost", + "fun(self: sf.Event): sf.Event_FocusLost|nil"); + bind_event_get_if(event, lua, "getIfFocusLost"); + LUASF_STUB_FUNCTION("sf.Event", "getIfFocusGained", + "fun(self: sf.Event): sf.Event_FocusGained|nil"); + bind_event_get_if(event, lua, "getIfFocusGained"); + LUASF_STUB_FUNCTION("sf.Event", "getIfTextEntered", + "fun(self: sf.Event): sf.Event_TextEntered|nil"); + bind_event_get_if(event, lua, "getIfTextEntered"); + LUASF_STUB_FUNCTION("sf.Event", "getIfKeyPressed", + "fun(self: sf.Event): sf.Event_KeyPressed|nil"); + bind_event_get_if(event, lua, "getIfKeyPressed"); + LUASF_STUB_FUNCTION("sf.Event", "getIfKeyReleased", + "fun(self: sf.Event): sf.Event_KeyReleased|nil"); + bind_event_get_if(event, lua, "getIfKeyReleased"); + LUASF_STUB_FUNCTION("sf.Event", "getIfMouseWheelScrolled", + "fun(self: sf.Event): sf.Event_MouseWheelScrolled|nil"); + bind_event_get_if(event, lua, + "getIfMouseWheelScrolled"); + LUASF_STUB_FUNCTION("sf.Event", "getIfMouseButtonPressed", + "fun(self: sf.Event): sf.Event_MouseButtonPressed|nil"); + bind_event_get_if(event, lua, + "getIfMouseButtonPressed"); + LUASF_STUB_FUNCTION( + "sf.Event", "getIfMouseButtonReleased", + "fun(self: sf.Event): sf.Event_MouseButtonReleased|nil"); + bind_event_get_if( + event, lua, "getIfMouseButtonReleased"); + LUASF_STUB_FUNCTION("sf.Event", "getIfMouseMoved", + "fun(self: sf.Event): sf.Event_MouseMoved|nil"); + bind_event_get_if(event, lua, "getIfMouseMoved"); + LUASF_STUB_FUNCTION("sf.Event", "getIfMouseMovedRaw", + "fun(self: sf.Event): sf.Event_MouseMovedRaw|nil"); + bind_event_get_if(event, lua, + "getIfMouseMovedRaw"); + LUASF_STUB_FUNCTION("sf.Event", "getIfMouseEntered", + "fun(self: sf.Event): sf.Event_MouseEntered|nil"); + bind_event_get_if(event, lua, "getIfMouseEntered"); + LUASF_STUB_FUNCTION("sf.Event", "getIfMouseLeft", + "fun(self: sf.Event): sf.Event_MouseLeft|nil"); + bind_event_get_if(event, lua, "getIfMouseLeft"); + LUASF_STUB_FUNCTION( + "sf.Event", "getIfJoystickButtonPressed", + "fun(self: sf.Event): sf.Event_JoystickButtonPressed|nil"); + bind_event_get_if( + event, lua, "getIfJoystickButtonPressed"); + LUASF_STUB_FUNCTION( + "sf.Event", "getIfJoystickButtonReleased", + "fun(self: sf.Event): sf.Event_JoystickButtonReleased|nil"); + bind_event_get_if( + event, lua, "getIfJoystickButtonReleased"); + LUASF_STUB_FUNCTION("sf.Event", "getIfJoystickMoved", + "fun(self: sf.Event): sf.Event_JoystickMoved|nil"); + bind_event_get_if(event, lua, + "getIfJoystickMoved"); + LUASF_STUB_FUNCTION("sf.Event", "getIfJoystickConnected", + "fun(self: sf.Event): sf.Event_JoystickConnected|nil"); + bind_event_get_if(event, lua, + "getIfJoystickConnected"); + LUASF_STUB_FUNCTION( + "sf.Event", "getIfJoystickDisconnected", + "fun(self: sf.Event): sf.Event_JoystickDisconnected|nil"); + bind_event_get_if( + event, lua, "getIfJoystickDisconnected"); + LUASF_STUB_FUNCTION("sf.Event", "getIfTouchBegan", + "fun(self: sf.Event): sf.Event_TouchBegan|nil"); + bind_event_get_if(event, lua, "getIfTouchBegan"); + LUASF_STUB_FUNCTION("sf.Event", "getIfTouchMoved", + "fun(self: sf.Event): sf.Event_TouchMoved|nil"); + bind_event_get_if(event, lua, "getIfTouchMoved"); + LUASF_STUB_FUNCTION("sf.Event", "getIfTouchEnded", + "fun(self: sf.Event): sf.Event_TouchEnded|nil"); + bind_event_get_if(event, lua, "getIfTouchEnded"); + LUASF_STUB_FUNCTION("sf.Event", "getIfSensorChanged", + "fun(self: sf.Event): sf.Event_SensorChanged|nil"); + bind_event_get_if(event, lua, + "getIfSensorChanged"); } diff --git a/src/bind_Handle.cpp b/src/bind_Handle.cpp index 0296a7f..b5c4f08 100644 --- a/src/bind_Handle.cpp +++ b/src/bind_Handle.cpp @@ -1,29 +1,31 @@ #include "bind_Handle.hpp" -void bind_Handle(sol::state_view lua) { - sol::table sf = lua_sf::sf_table(lua); - LUASF_STUB_CLASS("sf.WindowHandle"); - LUASF_STUB_FUNCTION("sf.WindowHandle", "new", - "fun(value: integer): sf.WindowHandle"); - LUASF_STUB_OVERLOAD("sf.WindowHandle", "new", "fun(): sf.WindowHandle"); - LUASF_STUB_FUNCTION("sf.WindowHandle", "fromInteger", - "fun(value: integer): sf.WindowHandle"); - LUASF_STUB_FUNCTION("sf.WindowHandle", "toInteger", - "fun(self: sf.WindowHandle): integer"); +void bind_Handle(lua_glue::StateView lua) { + lua_glue::Table sf = lua_sf::sf_table(lua); + LUASF_STUB_CLASS("sf.WindowHandle"); + LUASF_STUB_FUNCTION("sf.WindowHandle", "new", + "fun(value: integer): sf.WindowHandle"); + LUASF_STUB_OVERLOAD("sf.WindowHandle", "new", "fun(): sf.WindowHandle"); + LUASF_STUB_FUNCTION("sf.WindowHandle", "fromInteger", + "fun(value: integer): sf.WindowHandle"); + LUASF_STUB_FUNCTION("sf.WindowHandle", "toInteger", + "fun(self: sf.WindowHandle): integer"); - auto type_sf__WindowHandle = sf.new_usertype( - "WindowHandle", sol::no_constructor); - type_sf__WindowHandle.set_function( - "new", sol::factories([] { return lua_sf::WindowHandle(); }, - [](lua_sf::LuaIntegral value) { - return lua_sf::WindowHandle(value.value()); - })); - type_sf__WindowHandle.set_function( - "fromInteger", [](lua_sf::LuaIntegral value) { - return lua_sf::WindowHandle(value.value()); - }); - type_sf__WindowHandle.set_function( - "toInteger", [](const lua_sf::WindowHandle &self) -> std::uintptr_t { - return self.toInteger(); - }); + auto type_sf__WindowHandle = + lua_glue::BindClass(sf, "WindowHandle"); + lua_glue::BindCallable(type_sf__WindowHandle, "new", [] { + return lua_sf::WindowHandle(); + }); + lua_glue::BindCallable(type_sf__WindowHandle, "new", + [](lua_sf::LuaIntegral value) { + return lua_sf::WindowHandle(value.value()); + }); + type_sf__WindowHandle.set_function( + "fromInteger", [](lua_sf::LuaIntegral value) { + return lua_sf::WindowHandle(value.value()); + }); + type_sf__WindowHandle.set_function( + "toInteger", [](const lua_sf::WindowHandle& self) -> std::uintptr_t { + return self.toInteger(); + }); } diff --git a/tools/binding_config.py b/tools/binding_config.py index 82b3a6f..12eca99 100644 --- a/tools/binding_config.py +++ b/tools/binding_config.py @@ -10,7 +10,7 @@ 1. TypeLifecycle — which types need long-lived memory/stream tracking 2. TypeConversion — C++ ↔ Lua type conversion rules 3. BindingOwnership — which header/binding unit owns which type -4. OperatorMapping — C++ operator → sol meta_function +4. OperatorMapping — C++ operator → Lua metamethod 5. MethodOverrides — exact native method helper descriptors 6. Binding Templates — C++ code templates with placeholder substitution """ @@ -337,7 +337,7 @@ def qualified_name_for_token_from_canonical(token: str, canonical: str) -> str | def qualify_known_public_type_tokens(value: str, canonical: str) -> str: def replace(match: re.Match[str]) -> str: token = match.group(0) - if token in CPP_BUILTIN_TYPES or token.startswith(("sf::", "std::", "sol::")): + if token in CPP_BUILTIN_TYPES or token.startswith(("sf::", "std::", "lua_glue::")): return token alias = PUBLIC_TYPE_ALIASES.get(token) if alias: @@ -355,7 +355,7 @@ def qualify_public_spelling(source: str, canonical: str) -> str: canonical_core = core_cpp_type(canonical) if not source_core or source_core in CPP_BUILTIN_TYPES: return source - if source_core.startswith(("std::", "sol::", "lua_")): + if source_core.startswith(("std::", "lua_glue::", "lua_")): return source if canonical_core.startswith("sf::") and not source_core.startswith("sf::"): replacement = f"sf::{source_core}" @@ -368,7 +368,7 @@ def qualify_public_spelling(source: str, canonical: str) -> str: def qualify_sfml_template_aliases(value: str, canonical: str) -> str: def replace(match: re.Match[str]) -> str: token = match.group(0) - if token in CPP_BUILTIN_TYPES or token.startswith(("sf::", "std::", "sol::")): + if token in CPP_BUILTIN_TYPES or token.startswith(("sf::", "std::", "lua_glue::")): return token alias = PUBLIC_TYPE_ALIASES.get(token) if alias: @@ -875,7 +875,7 @@ def _matrix_array_constructor(element_count: int) -> ConfiguredBinding: # =========================================================================== -# 4. Operator → sol meta_function Mapping +# 4. Operator → Lua metamethod Mapping # =========================================================================== OPERATOR_META_FUNCTIONS: dict[str, str] = { @@ -998,26 +998,26 @@ def _t(name: str, *lines: str) -> None: _t("template_unpack", 'LUASF_STUB_FUNCTION("{lua_path}", "unpack", "fun(self: {lua_path}): {field_lua_returns}");', - '{var_name}.set_function("unpack", [](const {cpp_type}& self) {{', + 'lua_glue::BindCallable({var_name}, "unpack", [](const {cpp_type}& self) {{', ' return std::make_tuple({field_exprs});', '}});', ) _t("template_components_tostring", - '{var_name}[sol::meta_function::to_string] = [name = std::string("{lua_leaf}")](const {cpp_type}& self) {{', + 'lua_glue::BindMetamethod({var_name}, "__tostring", [name = std::string("{lua_leaf}")](const {cpp_type}& self) {{', ' std::ostringstream stream;', ' stream << name << "(" << {stream_components} << ")";', ' return stream.str();', - '}};', + '}});', ) _t("template_rect_tostring", - '{var_name}[sol::meta_function::to_string] = [name = std::string("{lua_leaf}")](const {cpp_type}& self) {{', + 'lua_glue::BindMetamethod({var_name}, "__tostring", [name = std::string("{lua_leaf}")](const {cpp_type}& self) {{', ' std::ostringstream stream;', ' stream << name << "(" << self.position.x << ", " << self.position.y << ", "', ' << self.size.x << ", " << self.size.y << ")";', ' return stream.str();', - '}};', + '}});', ) _t("template_rect_scalar_constructor", @@ -1028,7 +1028,7 @@ def _t(name: str, *lines: str) -> None: ) _t("template_matrix_array_constructor", - '[](sol::table values) {{', + '[](lua_glue::Table values) {{', ' auto buffer = lua_sf::array_from_object(values);', ' if (buffer.size() != {element_count})', ' throw std::runtime_error("matrix constructor expects exactly {element_count} float values");', @@ -1037,27 +1037,27 @@ def _t(name: str, *lines: str) -> None: ) _t("template_matrix_array_field", - '{var_name}.set("array", sol::property(', + 'lua_glue::BindProperty({var_name}, "array",', ' [](const {cpp_type}& self) {{', - ' return sol::as_table(std::vector(self.array.begin(), self.array.end()));', + ' return lua_glue::AsTable(std::vector(self.array.begin(), self.array.end()));', ' }},', - ' []({cpp_type}& self, sol::object values) {{', + ' []({cpp_type}& self, lua_glue::Object values) {{', ' auto buffer = lua_sf::array_from_object(values);', ' if (buffer.size() != self.array.size())', ' throw std::runtime_error("matrix array assignment has the wrong number of float values");', ' std::copy(buffer.begin(), buffer.end(), self.array.begin());', - ' }}));', + ' }});', ) _t("template_matrix_copy", 'LUASF_STUB_FUNCTION("{lua_path}", "copyMatrix", "fun(source: sf.Transform, dest: {lua_path})");', - '{var_name}.set_function("copyMatrix", [](const sf::Transform& source, {cpp_type}& dest) {{', + 'lua_glue::BindCallable({var_name}, "copyMatrix", [](const sf::Transform& source, {cpp_type}& dest) {{', ' sf::priv::copyMatrix(source, dest);', '}});', ) _t("template_matrix_tostring", - '{var_name}[sol::meta_function::to_string] = [name = std::string("{lua_leaf}")](const {cpp_type}& self) {{', + 'lua_glue::BindMetamethod({var_name}, "__tostring", [name = std::string("{lua_leaf}")](const {cpp_type}& self) {{', ' std::ostringstream stream;', ' stream << name << "(";', ' for (std::size_t index = 0; index < self.array.size(); ++index) {{', @@ -1067,14 +1067,14 @@ def _t(name: str, *lines: str) -> None: ' }}', ' stream << ")";', ' return stream.str();', - '}};', + '}});', ) # =========================================================================== # 7. Generator Configuration # -# Constants used by ``generate_sol2_bindings.py`` and +# Constants used by ``generate_glue_bindings.py`` and # ``generate_build_files.py`` for filtering, type classification, and # special-case handling. # =========================================================================== @@ -1294,7 +1294,7 @@ def _t(name: str, *lines: str) -> None: ), ) -# TYPE_DECL_KINDS (used by both generate_sol2_bindings and generate_build_files; +# TYPE_DECL_KINDS (used by both generate_glue_bindings and generate_build_files; # includes CLASS_TEMPLATE for the build-file scanner) TYPE_DECL_KINDS: frozenset[str] = frozenset({ "CLASS_DECL", "STRUCT_DECL", "CLASS_TEMPLATE", @@ -1327,3 +1327,23 @@ def _t(name: str, *lines: str) -> None: MODULE_ORDER: dict[str, int] = { "System": 0, "Window": 1, "Graphics": 2, "Audio": 3, "Network": 4, } + +# Complete native value semantics have been checked for these non-template types. +# Private storage is included by the native copy; no Lua/resource references occur. +INDEPENDENT_VALUE_TYPES = frozenset({ + "sf::Angle", "sf::Color", "sf::Time", "sf::Transform", "sf::Vertex", + "sf::VideoMode", "sf::ContextSettings", "sf::IpAddress", +}) +MANUAL_INDEPENDENT_VALUE_HEADERS = { + name: "SFML/Window/Event.hpp" + for name in ["sf::Event", *( + "sf::Event::" + payload for payload in ( + "Closed", "Resized", "FocusLost", "FocusGained", "TextEntered", + "KeyPressed", "KeyReleased", "MouseWheelScrolled", "MouseButtonPressed", + "MouseButtonReleased", "MouseMoved", "MouseMovedRaw", "MouseEntered", + "MouseLeft", "JoystickButtonPressed", "JoystickButtonReleased", + "JoystickMoved", "JoystickConnected", "JoystickDisconnected", "TouchBegan", + "TouchMoved", "TouchEnded", "SensorChanged", + ) + )] +} diff --git a/tools/extract_sfml_api.py b/tools/extract_sfml_api.py index 62ffcf9..bb81c6c 100644 --- a/tools/extract_sfml_api.py +++ b/tools/extract_sfml_api.py @@ -12,6 +12,11 @@ from clang import cindex +try: + from .output_files import write_text_if_changed +except ImportError: + from output_files import write_text_if_changed + DEFAULT_LIBCLANG = r"C:\Program Files\LLVM\bin\libclang.dll" DEFAULT_MODULES = ("Audio", "Graphics", "Network", "System", "Window") @@ -567,7 +572,7 @@ def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Extract SFML public C++ API declarations to JSON.") parser.add_argument("--project-root", default=Path(__file__).resolve().parents[1]) parser.add_argument("--include-dir", default="third_party/SFML/include") - parser.add_argument("--output", default="output/sfml_api.json") + parser.add_argument("--output", default="output/LuaSF/sfml_api.json") parser.add_argument("--libclang", default=default_libclang_path()) parser.add_argument("--standard", default="c++20") parser.add_argument("--modules", default=",".join(DEFAULT_MODULES)) @@ -592,7 +597,7 @@ def main() -> int: api = build_api(args) output = (project_root / args.output).resolve() output.parent.mkdir(parents=True, exist_ok=True) - output.write_text(json.dumps(api, ensure_ascii=False, indent=2), encoding="utf-8") + write_text_if_changed(output, json.dumps(api, ensure_ascii=False, indent=2), encoding="utf-8") print(f"Wrote {api['header_count']} headers to {output}") if api["diagnostics"]: print(f"Clang produced diagnostics for {len(api['diagnostics'])} headers.") diff --git a/tools/generate_build_files.py b/tools/generate_build_files.py index b04a6be..11af4e4 100644 --- a/tools/generate_build_files.py +++ b/tools/generate_build_files.py @@ -10,6 +10,7 @@ from typing import Any try: + from .output_files import copy_file_if_changed, write_text_if_changed from .replace_model import ( MANUAL_DEPENDENCIES, MANUAL_HEADER_DECLARATION_PREFIX_OWNERS, @@ -20,6 +21,7 @@ callback_codec_manifest, ) except ImportError: + from output_files import copy_file_if_changed, write_text_if_changed from replace_model import ( MANUAL_DEPENDENCIES, MANUAL_HEADER_DECLARATION_PREFIX_OWNERS, @@ -61,13 +63,12 @@ def rel(path: Path, root: Path) -> str: def copy_file(src: Path, dst: Path) -> None: - dst.parent.mkdir(parents=True, exist_ok=True) - shutil.copy2(src, dst) + copy_file_if_changed(src, dst) def write_callback_codec_manifest(path: Path) -> None: path.parent.mkdir(parents=True, exist_ok=True) - path.write_text( + write_text_if_changed(path, json.dumps(callback_codec_manifest(), ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) @@ -82,10 +83,12 @@ def copy_support_sources(project_root: Path, output_root: Path) -> None: tools_out.mkdir(parents=True, exist_ok=True) for path in include_out.glob("*"): - if path.is_file() and (path.name in {"utils.hpp", "utils.inl"} or path.name.startswith("bind_")): + if (path.is_file() + and (path.name in {"utils.hpp", "utils.inl"} or path.name.startswith("bind_")) + and not (project_root / "include" / path.name).is_file()): path.unlink() for path in src_out.glob("bind_*.cpp"): - if path.is_file(): + if path.is_file() and not (project_root / "src" / path.name).is_file(): path.unlink() for path in sorted((project_root / "include").glob("*")): @@ -100,6 +103,33 @@ def copy_support_sources(project_root: Path, output_root: Path) -> None: copy_file(stub_dumper, tools_out / stub_dumper.name) +def remove_legacy_generated_layout(project_root: Path, output_root: Path) -> bool: + """Migrate only the generator's original, positively identified output layout.""" + if output_root.resolve() != (project_root / "output" / "LuaSF").resolve(): + return False + legacy_root = output_root.parent + entrypoint = legacy_root / "LuaSF.cpp" + wrapper = legacy_root / "include" / "luasf_sol.hpp" + marker = "// This file is generated by tools/generate_build_files.py. Do not edit manually." + if (entrypoint.is_symlink() or wrapper.is_symlink() + or not entrypoint.is_file() or not wrapper.is_file()): + return False + if not entrypoint.read_text(encoding="utf-8").startswith(marker + "\n"): + return False + if not all((output_root / name).is_file() + for name in ("CMakeLists.txt", "LuaSF.cpp", "include/LuaSF.hpp")): + return False + + for name in ("CMakeLists.txt", "LuaSF.cpp", "binding_order.json", + "callback_codecs.json", "sfml_api.json", "include", "src", "tools"): + path = legacy_root / name + if path.is_symlink() or path.is_file(): + path.unlink() + elif path.is_dir(): + shutil.rmtree(path) + return True + + def load_api(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8")) @@ -574,7 +604,7 @@ def save_cached_order( cycle_breaks: list[dict[str, Any]], ) -> None: cache_path.parent.mkdir(parents=True, exist_ok=True) - cache_path.write_text( + write_text_if_changed(cache_path, json.dumps( { "schema_version": SCHEMA_VERSION, @@ -665,7 +695,7 @@ def render_outputs( includes = "\n".join(f'#include "{entries[name].include_path}"' for name in order) bind_calls = "\n".join(f" {name}(lua);" for name in order) - cmake_output.write_text( + write_text_if_changed(cmake_output, cmake_template.read_text(encoding="utf-8").format( project_name=project_name, target_name=target_name, @@ -679,7 +709,7 @@ def render_outputs( ) public_header_output.parent.mkdir(parents=True, exist_ok=True) - public_header_output.write_text( + write_text_if_changed(public_header_output, public_header_template.read_text(encoding="utf-8").format( module_name=module_name, ), @@ -687,7 +717,7 @@ def render_outputs( ) source_output.parent.mkdir(parents=True, exist_ok=True) - source_output.write_text( + write_text_if_changed(source_output, source_template.read_text(encoding="utf-8").format( module_name=module_name, includes=includes, @@ -703,20 +733,20 @@ def render_outputs( def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Generate CMakeLists.txt and LuaSF DLL entry files.") - parser.add_argument("--api-json", default="output/sfml_api.json") - parser.add_argument("--output-root", default="output") - parser.add_argument("--cache", default="output/binding_order.json") + parser.add_argument("--api-json", default="output/LuaSF/sfml_api.json") + parser.add_argument("--output-root", default="output/LuaSF") + parser.add_argument("--cache", default="output/LuaSF/binding_order.json") parser.add_argument("--cmake-template", default="CMakeLists.txt.in") parser.add_argument("--source-template", "--main-template", default="LuaSF.cpp.in", dest="source_template") parser.add_argument("--header-template", default="LuaSF.hpp.in") - parser.add_argument("--cmake-output", default="output/CMakeLists.txt") - parser.add_argument("--source-output", "--main-output", default="output/LuaSF.cpp", dest="source_output") - parser.add_argument("--header-output", default="output/include/LuaSF.hpp") - parser.add_argument("--callback-codecs-output", default="output/callback_codecs.json") + parser.add_argument("--cmake-output", default="output/LuaSF/CMakeLists.txt") + parser.add_argument("--source-output", "--main-output", default="output/LuaSF/LuaSF.cpp", dest="source_output") + parser.add_argument("--header-output", default="output/LuaSF/include/LuaSF.hpp") + parser.add_argument("--callback-codecs-output", default="output/LuaSF/callback_codecs.json") parser.add_argument("--project-name", default="LuaSF") parser.add_argument("--target-name", default="LuaSF") parser.add_argument("--module-name", default="LuaSF") - parser.add_argument("--force-sort", action="store_true", help="Ignore output/binding_order.json and recompute the order.") + parser.add_argument("--force-sort", action="store_true", help="Ignore output/LuaSF/binding_order.json and recompute the order.") return parser.parse_args() @@ -730,6 +760,13 @@ def main() -> int: api = load_api(api_path) copy_support_sources(project_root, output_root) + glue_root = project_root / "LuaGlue" + if not (glue_root / "CMakeLists.txt").is_file(): + raise FileNotFoundError("LuaGlue/CMakeLists.txt is required for source distribution") + shutil.copytree(glue_root, output_root.parent / "LuaGlue", dirs_exist_ok=True, + copy_function=copy_file_if_changed, + ignore=shutil.ignore_patterns("build", ".git", "__pycache__")) + copy_file(project_root / "LICENSE", output_root / "LICENSE") write_callback_codec_manifest(callback_codecs_output) entries = discover_entries(api, project_root, output_root) order, used_cache = sorted_order(project_root, api_path, cache_path, api, entries, args.force_sort) @@ -747,11 +784,13 @@ def main() -> int: target_name=args.target_name, module_name=args.module_name, ) + if remove_legacy_generated_layout(project_root, output_root): + print("Removed the previous generated layout from output/.") cache_note = "using cached order" if used_cache else "after topological sort" print( - f"Generated output/CMakeLists.txt, output/LuaSF.cpp, output/include/LuaSF.hpp, " - f"and output/callback_codecs.json " + f"Generated output/LuaSF/CMakeLists.txt, output/LuaSF/LuaSF.cpp, output/LuaSF/include/LuaSF.hpp, " + f"and output/LuaSF/callback_codecs.json " f"for {len(order)} binding units ({cache_note})." ) return 0 diff --git a/tools/generate_sol2_bindings.py b/tools/generate_glue_bindings.py similarity index 92% rename from tools/generate_sol2_bindings.py rename to tools/generate_glue_bindings.py index 398bef7..c0c965c 100644 --- a/tools/generate_sol2_bindings.py +++ b/tools/generate_glue_bindings.py @@ -9,6 +9,7 @@ from typing import Any try: + from .output_files import write_text_if_changed from .replace_model import ( BINDING_TEMPLATES, BYTE_TYPES, @@ -23,6 +24,8 @@ IGNORE_RETURN_TYPES, IGNORED_NAMESPACES, INTEGER_TYPES, + INDEPENDENT_VALUE_TYPES, + MANUAL_INDEPENDENT_VALUE_HEADERS, LifecycleCategory, LUA_NAMESPACE_PROJECTIONS, LUA_KEYWORDS, @@ -63,6 +66,7 @@ walk_declarations, ) except ImportError: + from output_files import write_text_if_changed from replace_model import ( BINDING_TEMPLATES, BYTE_TYPES, @@ -77,6 +81,8 @@ IGNORE_RETURN_TYPES, IGNORED_NAMESPACES, INTEGER_TYPES, + INDEPENDENT_VALUE_TYPES, + MANUAL_INDEPENDENT_VALUE_HEADERS, LifecycleCategory, LUA_NAMESPACE_PROJECTIONS, LUA_KEYWORDS, @@ -538,7 +544,7 @@ def generic_callback_value_issue(cpp_type: str, *, is_return: bool, path: str) - if base == "void": return None if is_return else f"{path} cannot have type void" - if callback_value_lua_type(base) == "any" and base not in {"sol::object", "sol::table"}: + if callback_value_lua_type(base) == "any" and base not in {"lua_glue::Object", "lua_glue::Table"}: return f"{path} has no ordinary Lua conversion ({value})" return None @@ -1024,13 +1030,13 @@ def lua_param_type(type_ref: TypeRef) -> str: if base in INTEGER_TYPES: return f"lua_sf::LuaIntegral<{base}>" if is_std_function(type_ref): - return "sol::object" + return "lua_glue::Object" if is_sf_string(cpp) or is_filesystem_path(cpp) or is_string_view(cpp) or is_std_string(cpp) or is_std_wstring(cpp): return "std::string" if vector_element(cpp): - return "sol::table" + return "lua_glue::Table" if optional_element(cpp): - return "sol::object" + return "lua_glue::Object" return cpp @@ -1098,9 +1104,9 @@ def return_expr(type_ref: TypeRef, expr: str, indent: str, function_name: str | return [ f"{indent}const auto* result = {expr};", f"{indent}if (!result)", - f"{indent} return sol::as_table(std::vector<{elem}>{{}});", + f"{indent} return lua_glue::AsTable(std::vector<{elem}>{{}});", f"{indent}std::vector<{elem}> result_values(result, result + static_cast({count_expr}));", - f"{indent}return sol::as_table(std::move(result_values));", + f"{indent}return lua_glue::AsTable(std::move(result_values));", ] if is_window_handle(type_ref): return [f"{indent}return lua_sf::WindowHandle::fromNative({expr});"] @@ -1236,7 +1242,7 @@ def lua_path_for_type(qualified_name: str) -> str: def lua_table_expression(qualified_name: str) -> str: path = lua_path_for_type(qualified_name) - return "lua" + "".join(f"[{cpp_string_literal(part)}]" for part in path.split(".")) + ".get()" + return "lua" + "".join(f"[{cpp_string_literal(part)}]" for part in path.split(".")) + ".get()" def lua_leaf_for_type(qualified_name: str) -> str: @@ -1269,9 +1275,9 @@ def cpp_type_to_lua_type(value: str) -> str: return "number" if base in STRING_TYPES or base == "sf::String": return "string" - if base in {"sol::object", "sol::variadic_args"}: + if base in {"lua_glue::Object", "lua_glue::Arguments"}: return "any" - if base == "sol::table": + if base == "lua_glue::Table": return "table" if base in {"lua_State*", "lua_State"}: return "any" @@ -1339,6 +1345,8 @@ def stub_signature_for_item( override = plan.stub_param_types.get(stub_param.name) if override: stub_param = StubParam(stub_param.name, override) + if any(sanitize_lua_identifier(p.get("name") or "") == stub_param.name and p.get("default") is not None for p in params): + stub_param = StubParam(stub_param.name + "?", stub_param.lua_type) stub_params_list.append(stub_param) stub_params = tuple(stub_params_list) if constructor_return: @@ -1408,7 +1416,7 @@ def stub_doc_lines(item: dict[str, Any] | None) -> list[str]: doc = item.get("doc") if not isinstance(doc, str) or not doc.strip(): return [] - return [f" LUASF_STUB_DOC({cpp_string_literal(doc)});"] + return [f" LUASF_STUB_DOC({docstring_ref(doc)});"] def first_stub_doc(items: list[str | None]) -> str | None: @@ -1468,18 +1476,7 @@ def direct_children(item: dict[str, Any], kind: str) -> list[dict[str, Any]]: def constructor_param_sets(params: list[dict[str, Any]]) -> list[list[dict[str, Any]]]: - if not params: - return [[]] - first_default = None - for index, param in enumerate(params): - if param.get("default") is not None: - first_default = index - break - if first_default is None: - return [params] - if any(param.get("default") is None for param in params[first_default:]): - return [params] - return [params[:count] for count in range(first_default, len(params) + 1)] + return [params] def planned_lua_param_type(lua_param: str) -> str: @@ -1489,8 +1486,8 @@ def planned_lua_param_type(lua_param: str) -> str: def overload_specificity_key(plan: PlannedCall, original_index: int) -> tuple[int, int, int, int]: param_types = [planned_lua_param_type(param) for param in plan.lua_params] - object_params = sum(1 for type_text in param_types if type_text in {"sol::object", "sol::variadic_args"}) - table_params = sum(1 for type_text in param_types if type_text == "sol::table") + object_params = sum(1 for type_text in param_types if type_text in {"lua_glue::Object", "lua_glue::Arguments"}) + table_params = sum(1 for type_text in param_types if type_text == "lua_glue::Table") return (object_params, table_params, -len(param_types), original_index) @@ -1553,7 +1550,7 @@ def plan_parameters( has_size_pair = next_param is not None and is_size_type(TypeRef.from_json(next_param.get("type"))) is_const_pointer = is_const_type(cpp) if is_const_pointer: - plan.lua_params.append(f"sol::object {name}") + plan.lua_params.append(f"lua_glue::Object {name}") plan.prelude.append(f"auto {name}_buffer = lua_sf::array_from_object<{element}>({name});") plan.call_args.append(f"{name}_buffer.data()") plan.signature_key += (f"inarray:{element}",) @@ -1569,7 +1566,7 @@ def plan_parameters( plan.lua_params.append(f"std::size_t {lua_size_name}") plan.prelude.append(f"std::vector<{element}> {name}_buffer({lua_size_name});") plan.call_args.append(f"{name}_buffer.data()") - plan.post_values.append(f"sol::as_table({name}_buffer)") + plan.post_values.append(f"lua_glue::AsTable({name}_buffer)") plan.output_arrays.append(OutputArray(index, f"{name}_buffer")) plan.signature_key += (f"outarray:{element}",) if has_size_pair: @@ -1617,7 +1614,7 @@ def plan_parameters( return plan -def make_lambda( +def _make_lambda( item: dict[str, Any], owner_type: str | None, call_name: str, @@ -1641,7 +1638,8 @@ def make_lambda( lua_args = ", ".join(plan.lua_params) if owner_type and not item.get("static", False) and not is_constructor: - lua_args = f"{owner_type}& self" + (f", {lua_args}" if lua_args else "") + self_type = f"const {owner_type}" if item.get("const") else owner_type + lua_args = f"{self_type}& self" + (f", {lua_args}" if lua_args else "") capture = "[lua]" if return_wrapper_uses_lua(return_type) else "[]" lines: list[str] = [] @@ -1683,7 +1681,8 @@ def make_lambda( if item.get("static", False) and call_owner: call_target = f"{call_owner}::{call_name}" elif dispatch_type: - call_target = f"static_cast<{dispatch_type}&>(self).{call_name}" + dispatch_qualifier = "const " if item.get("const") else "" + call_target = f"static_cast<{dispatch_qualifier}{dispatch_type}&>(self).{call_name}" else: call_target = f"self.{call_name}" if owner_type is None: @@ -1760,7 +1759,7 @@ def make_lambda( elif item.get("qualified_name") in SPECIAL_POINTER_RETURNS: trailing_return = "" elif vector_element(return_type.cpp): - trailing_return = " -> sol::object" + trailing_return = " -> lua_glue::Object" elif return_type.cpp and return_type.cpp != "void" and not plan.post_values and not return_needs_wrapper(return_type): trailing_return = f" -> {return_type.cpp}" elif is_window_handle(return_type): @@ -1775,7 +1774,7 @@ def make_lambda( ): trailing_return = " -> std::string" elif optional_element(return_type.cpp): - trailing_return = " -> sol::object" + trailing_return = " -> lua_glue::Object" else: trailing_return = "" @@ -1800,6 +1799,46 @@ def make_lambda( return "\n".join(lines), None +_DOCSTRINGS: list[str] = [] + + +def docstring_ref(doc: str | None) -> str: + doc = doc or "" + if doc not in _DOCSTRINGS: + _DOCSTRINGS.append(doc) + return f"docs[{_DOCSTRINGS.index(doc)}]" + + +def make_lambda(item, owner_type, call_name, is_constructor=False, value_constructor=False): + code, reason = _make_lambda(item, owner_type, call_name, is_constructor, value_constructor) + if code is None: + return code, reason + defaults = [] + for parameter in item.get("parameters", []): + expression = parameter.get("default") + if expression is None: + continue + type_ref = TypeRef.from_json(parameter.get("type")) + native_type = clean_cpp_type((type_ref.canonical_cpp or type_ref.cpp).rstrip("&").strip()) + if not native_type.endswith("*"): + native_type = re.sub(r"^(?:(?:const|volatile)\s+)+", "", native_type) + default_type = optional_element(native_type) or native_type + leaf = default_type.rsplit("::", 1)[-1] + if re.match(r"^[A-Za-z_]\w*::", expression) and expression.startswith(leaf + "::"): + expression = default_type + expression[len(leaf):] + elif re.fullmatch(r"[A-Za-z_]\w*", expression) and expression not in {"true", "false", "nullptr"}: + scope = (item.get("qualified_name") or call_name).rsplit("::", 1)[0] + expression = scope + "::" + expression + value = native_type + expression if expression.lstrip().startswith("{") else f"static_cast<{native_type}>({expression})" + default_ref = TypeRef(spelling=native_type, canonical=native_type) + body = return_expr(default_ref, value, " ") + defaults.append("lua_glue::DefaultFactory{[lua]() {\n using namespace sf;\n" + "\n".join(body) + "\n}}") + if defaults: + code += ",\nlua_glue::Defaults{" + ",\n".join(defaults) + "}" + code += ",\n" + docstring_ref(item.get("doc")) + return code, reason + + def overload_block( name: str, lambdas: list[str], @@ -1807,37 +1846,15 @@ def overload_block( target: str = "set_function", self_dependency: bool = False, ) -> list[str]: - if not lambdas: - return [] - if len(lambdas) == 1: - lines = [f'{indent}{target}("{name}",'] + lines: list[str] = [] + table = target.removesuffix(".set_function") + for callable_code in lambdas: + lines.append(f'{indent}lua_glue::BindCallable({table}, "{name}",') + append_indented_block(lines, callable_code, indent + " ", + "," if self_dependency else "") if self_dependency: - lines.append(f"{indent} sol::policies(") - append_indented_block(lines, lambdas[0], indent + " ", ",") - lines.append(f"{indent} sol::self_dependency{{}}") - lines.append(f"{indent} )") - else: - append_indented_block(lines, lambdas[0], indent + " ") + lines.append(f"{indent} lua_glue::ReturnPolicy::ReferenceInternal") lines.append(f"{indent});") - return lines - lines = [f'{indent}{target}("{name}",'] - if self_dependency: - lines.append(f"{indent} sol::policies(") - lines.append(f"{indent} sol::overload(") - lambda_indent = indent + " " - else: - lines.append(f"{indent} sol::overload(") - lambda_indent = indent + " " - for index, lambda_code in enumerate(lambdas): - suffix = "," if index + 1 < len(lambdas) else "" - append_indented_block(lines, lambda_code, lambda_indent, suffix) - if self_dependency: - lines.append(f"{indent} ),") - lines.append(f"{indent} sol::self_dependency{{}}") - lines.append(f"{indent} )") - else: - lines.append(f"{indent} )") - lines.append(f"{indent});") return lines @@ -1886,30 +1903,23 @@ def meta_assignment_block( indent: str = " ", self_dependency: bool = False, ) -> list[str]: - if not lambdas: - return [] - lines = [f"{indent}{var_name}[sol::meta_function::{meta_function}] ="] - value_indent = indent + " " - if self_dependency: - lines.append(f"{value_indent}sol::policies(") - value_indent += " " - if len(lambdas) == 1: - append_indented_block( - lines, - lambdas[0], - value_indent, - "," if self_dependency else "", - ) - else: - lines.append(f"{value_indent}sol::overload(") - for index, lambda_code in enumerate(lambdas): - suffix = "," if index + 1 < len(lambdas) else "" - append_indented_block(lines, lambda_code, value_indent + " ", suffix) - lines.append(f"{value_indent})" + ("," if self_dependency else "")) - if self_dependency: - lines.append(f"{value_indent}sol::self_dependency{{}}") - lines.append(f"{indent} )") - lines.append(f"{indent};") + meta_name = { + "to_string": "__tostring", "unary_minus": "__unm", "addition": "__add", + "subtraction": "__sub", "multiplication": "__mul", "division": "__div", + "equal_to": "__eq", "less_than": "__lt", "less_than_or_equal_to": "__le", + "bitwise_left_shift": "__shl", "bitwise_right_shift": "__shr", + "modulus": "__mod", "bitwise_and": "__band", "bitwise_or": "__bor", + "bitwise_xor": "__bxor", "bitwise_not": "__bnot", + "index": "__index", "new_index": "__newindex", "call": "__call", + }[meta_function] + lines: list[str] = [] + for callable_code in lambdas: + lines.append(f'{indent}lua_glue::BindMetamethod({var_name}, "{meta_name}",') + append_indented_block(lines, callable_code, indent + " ", + "," if self_dependency else "") + if self_dependency: + lines.append(f"{indent} lua_glue::ReturnPolicy::ReferenceInternal") + lines.append(f"{indent});") return lines @@ -1925,7 +1935,7 @@ def is_single_output_reference_operator(method: dict[str, Any]) -> bool: return is_output_reference(type_ref.cpp) -class Sol2Generator: +class GlueGenerator: def __init__(self, api: dict[str, Any], output_root: Path): self.api = api self.output_root = output_root @@ -2242,10 +2252,20 @@ def generate(self) -> None: self.src_root.mkdir(parents=True, exist_ok=True) self._remove_legacy_generated_utils() self._clean_previous_bindings() + self._value_type_headers = dict(MANUAL_INDEPENDENT_VALUE_HEADERS) for file_item in self.api.get("files", []): self._write_binding_file(file_item) + self._write_value_traits() + + def _write_value_traits(self) -> None: + lines = ["#pragma once", "", "#include "] + lines.extend(f"#include <{header}>" for header in sorted(set(self._value_type_headers.values()))) + lines.append("") + lines.extend(f"template <> struct lua_glue::StructTraits<{name}> : lua_glue::IndependentValue<{name}> {{}};" for name in sorted(self._value_type_headers)) + write_text_if_changed(self.include_root / "LuaSFValueTraits.hpp", "\n".join(lines) + "\n", encoding="utf-8") + def _remove_legacy_generated_utils(self) -> None: for path in (self.include_root / "sfml_lua_utils.hpp", self.src_root / "sfml_lua_utils.cpp"): if path.exists(): @@ -2253,10 +2273,13 @@ def _remove_legacy_generated_utils(self) -> None: def _clean_previous_bindings(self) -> None: for root, suffix in ((self.include_root, ".hpp"), (self.src_root, ".cpp")): - if not root.exists(): - continue + expected = { + root / item["module"] / f"bind_{Path(item['path']).stem}{suffix}" + for item in self.api.get("files", []) + } for path in root.rglob(f"bind_*{suffix}"): - path.unlink() + if path.parent != root and path not in expected: + path.unlink() def _write_binding_file(self, file_item: dict[str, Any]) -> None: sfml_path = Path(file_item["path"]) @@ -2272,7 +2295,7 @@ def _write_binding_file(self, file_item: dict[str, Any]) -> None: original_include = sfml_include_for_file(file_item) extra_includes = self._extra_includes_for_file(file_item, original_include) - hpp_path.write_text( + write_text_if_changed(hpp_path, "\n".join( [ "#pragma once", @@ -2280,19 +2303,26 @@ def _write_binding_file(self, file_item: dict[str, Any]) -> None: f'#include <{original_include}>', *[f"#include <{include}>" for include in extra_includes], '#include "utils.hpp"', + '#include "LuaSFValueTraits.hpp"', "", - f"void bind_{stem}(sol::state_view lua);", + f"void bind_{stem}(lua_glue::StateView lua);", "", ] ), encoding="utf-8", ) + _DOCSTRINGS.clear() + self._value_types_in_file = [] body_lines = self._emit_file_body(file_item, stem) + for value_type in self._value_types_in_file: + self._value_type_headers[value_type] = original_include cpp_lines = [ f'#include "{module}/bind_{stem}.hpp"', "", "#include ", + "#include ", + "#include ", "#include ", "#include ", "#include ", @@ -2301,13 +2331,17 @@ def _write_binding_file(self, file_item: dict[str, Any]) -> None: "#include ", "#include ", "", - f"void bind_{stem}(sol::state_view lua) {{", - " sol::table sf = lua_sf::sf_table(lua);", + f"namespace {{ constexpr std::array docs = {{", + *[f" {cpp_string_literal(doc)}," for doc in _DOCSTRINGS], + "}; }", + "", + f"void bind_{stem}(lua_glue::StateView lua) {{", + " lua_glue::Table sf = lua_sf::sf_table(lua);", *body_lines, "}", "", ] - cpp_path.write_text("\n".join(cpp_lines), encoding="utf-8") + write_text_if_changed(cpp_path, "\n".join(cpp_lines), encoding="utf-8") def _extra_includes_for_file(self, file_item: dict[str, Any], original_include: str) -> list[str]: includes: set[str] = set() @@ -2360,7 +2394,7 @@ def _emit_item(self, item: dict[str, Any], table_var: str, namespace_prefix: str if full_namespace in LUA_NAMESPACE_PROJECTIONS: return self._emit_children(item, table_var, f"{full_namespace}::") child_table = f"{table_var}_{sanitize_identifier(name)}" - lines = [f' sol::table {child_table} = {table_var}["{name}"].get_or_create();'] + lines = [f' lua_glue::Table {child_table} = {table_var}["{name}"].get_or_create();'] lines.extend(self._emit_children(item, child_table, f"{namespace_prefix}{name}::")) return lines if kind in {"CLASS_DECL", "STRUCT_DECL"}: @@ -2408,11 +2442,11 @@ def _emit_enum(self, enum_item: dict[str, Any], table_var: str) -> list[str]: f" LUASF_STUB_FIELD({cpp_string_literal(constant['name'])}, " f"{cpp_string_literal(field_type)});" ) - lines.append(f' {table_var}.new_enum("{lua_name}",') + lines.append(f' lua_glue::BindEnum<{full_name}>({table_var}, "{lua_name}", {{') for index, constant in enumerate(constants): suffix = "," if index + 1 < len(constants) else "" - lines.append(f' "{constant["name"]}", {full_name}::{constant["name"]}{suffix}') - lines.append(" );") + lines.append(f' {{"{constant["name"]}", {full_name}::{constant["name"]}}}{suffix}') + lines.append(" });") return lines def _emit_class(self, cls: dict[str, Any], table_var: str) -> list[str]: @@ -2434,23 +2468,22 @@ def _emit_class(self, cls: dict[str, Any], table_var: str) -> list[str]: var_name = f"type_{sanitize_identifier(full_name)}" direct_bases = self._direct_base_type_names(cls, full_name) bases = self._base_type_names_for_binding(cls, full_name) - if bases: - lines = [ - f' auto {var_name} = {table_var}.new_usertype<{full_name}>("{lua_name}",', - " sol::no_constructor,", - f" sol::base_classes, sol::bases<{', '.join(bases)}>()", - " );", - ] - else: - lines = [f' auto {var_name} = {table_var}.new_usertype<{full_name}>("{lua_name}", sol::no_constructor);'] + value_type = bool(cls.get("_value_type")) or full_name in INDEPENDENT_VALUE_TYPES + cls["_value_type"] = value_type + if value_type: + self._value_types_in_file.append(full_name) + binder = "BindStruct" if value_type else "BindClass" + lines = [f' auto {var_name} = lua_glue::{binder}<{full_name}>({table_var}, "{lua_name}");'] + for base in bases: + lines.append(f" lua_glue::BindBase<{full_name}, {base}>({var_name});") nested_table_var = f"table_{sanitize_identifier(full_name)}" - lines.append(f' sol::table {nested_table_var} = {table_var}["{lua_name}"].get();') + lines.append(f' lua_glue::Table {nested_table_var} = {table_var}["{lua_name}"].get();') if not cls.get("_value_type"): lines.append(f" lua_sf::mark_shared_usertype<{full_name}>(lua);") if direct_bases: native_bases_var = f"native_bases_{sanitize_identifier(full_name)}" - lines.append(f" sol::table {native_bases_var} = lua.create_table();") + lines.append(f" lua_glue::Table {native_bases_var} = lua.create_table();") for base in direct_bases: lines.append(f" {native_bases_var}.add({lua_table_expression(base)});") lines.append(f' {nested_table_var}.raw_set("__nativeBases", {native_bases_var});') @@ -2478,6 +2511,9 @@ def _emit_class(self, cls: dict[str, Any], table_var: str) -> list[str]: lines.extend(self._emit_var(child, nested_table_var, f"{full_name}::")) elif child.get("kind") in {"TYPE_ALIAS_DECL", "TYPEDEF_DECL"}: lines.extend(self._emit_type_alias(child, nested_table_var, full_name, lua_path)) + if value_type: + for method in ("copy", "deepcopy"): + lines.append(f' LUASF_STUB_FUNCTION("{lua_path}", "{method}", "fun(self: {lua_path}): {lua_path}");') return lines @staticmethod @@ -2702,8 +2738,8 @@ def _emit_free_template_operators( ) if name == "operator-" and shape == "unary": lines.append( - f" {var_name}[sol::meta_function::unary_minus] = " - f"[](const {full_name}& value) {{ return -value; }};" + f' lua_glue::BindMetamethod({var_name}, "__unm", ' + f"[](const {full_name}& value) {{ return -value; }});" ) lines.append(f' LUASF_STUB_OPERATOR({cpp_string_literal(lua_path)}, "unm: {lua_path}");') elif name in {"operator+", "operator-", "operator=="}: @@ -2714,8 +2750,8 @@ def _emit_free_template_operators( "operator==": "equal_to", }[name] lines.append( - f" {var_name}[sol::meta_function::{meta}] = " - f"[](const {full_name}& left, const {full_name}& right) {{ return left {symbol} right; }};" + f' lua_glue::BindMetamethod({var_name}, "{dict(addition="__add", subtraction="__sub", equal_to="__eq")[meta]}", ' + f"[](const {full_name}& left, const {full_name}& right) {{ return left {symbol} right; }});" ) result_type = "boolean" if name == "operator==" else lua_path annotation = "eq" if name == "operator==" else ("add" if name == "operator+" else "sub") @@ -2724,23 +2760,18 @@ def _emit_free_template_operators( f'{cpp_string_literal(f"{annotation}({lua_path}): {result_type}")});' ) elif name == "operator*": - lines.append(f" {var_name}[sol::meta_function::multiplication] = sol::overload(") - lines.append( - f" []({full_name} value, {scalar_param} scalar) {{ return value * {scalar_expr}; }}," - ) - left_expr = scalar_expr.replace("scalar", "scalar") - lines.append( - f" []({scalar_param} scalar, {full_name} value) {{ return {left_expr} * value; }}" - ) - lines.append(" );") + lines.extend(meta_assignment_block(var_name, "multiplication", [ + f"[]({full_name} value, {scalar_param} scalar) {{ return value * {scalar_expr}; }}", + f"[]({scalar_param} scalar, {full_name} value) {{ return {scalar_expr} * value; }}", + ])) lines.append( f' LUASF_STUB_OPERATOR({cpp_string_literal(lua_path)}, ' f'{cpp_string_literal(f"mul({scalar_lua}): {lua_path}")});' ) elif name == "operator/": lines.append( - f" {var_name}[sol::meta_function::division] = " - f"[]({full_name} value, {scalar_param} scalar) {{ return value / {scalar_expr}; }};" + f' lua_glue::BindMetamethod({var_name}, "__div", ' + f"[]({full_name} value, {scalar_param} scalar) {{ return value / {scalar_expr}; }});" ) lines.append( f' LUASF_STUB_OPERATOR({cpp_string_literal(lua_path)}, ' @@ -2796,10 +2827,10 @@ def _emit_type_alias( *stub_doc_lines(item), f' LUASF_STUB_ALIAS({cpp_string_literal(alias_lua)}, {cpp_string_literal(target_lua)});', " {", - f' const sol::object aliasValue = {table_var}.raw_get("{name}");', - f' const sol::object aliasTarget = {table_var}.raw_get("{target_leaf}");', - " if ((!aliasValue.valid() || aliasValue.get_type() == sol::type::lua_nil) &&", - " aliasTarget.valid() && aliasTarget.get_type() != sol::type::lua_nil)", + f' const lua_glue::Object aliasValue = {table_var}.raw_get("{name}");', + f' const lua_glue::Object aliasTarget = {table_var}.raw_get("{target_leaf}");', + " if ((!aliasValue.valid() || aliasValue.get_type() == lua_glue::Type::Nil) &&", + " aliasTarget.valid() && aliasTarget.get_type() != lua_glue::Type::Nil)", f' {table_var}.raw_set("{name}", aliasTarget);', " }", ] @@ -2985,20 +3016,7 @@ def _emit_constructors(self, cls: dict[str, Any], var_name: str, full_name: str) lambdas = [item[1] for item in overloads] lambdas.extend(lambda_code for lambda_code, _stub_type in configured_constructors) - if len(lambdas) == 1: - lines = stub_lines - lines.append(f' {var_name}.set_function("new", sol::factories(') - append_indented_block(lines, lambdas[0], " ") - lines.append(" ));") - return lines - - lines = stub_lines - lines.append(f' {var_name}.set_function("new", sol::factories(') - for index, lambda_code in enumerate(lambdas): - suffix = "," if index + 1 < len(lambdas) else "" - append_indented_block(lines, lambda_code, " ", suffix) - lines.append(" ));") - return lines + return stub_lines + overload_block("new", lambdas, " ", f"{var_name}.set_function") @staticmethod def _has_default_constructible_aggregate(cls: dict[str, Any]) -> bool: @@ -3050,19 +3068,19 @@ def _emit_fields(self, cls: dict[str, Any], var_name: str, full_name: str) -> li if is_window_handle(type_ref): getter_return = " -> lua_sf::WindowHandle" elif optional_element(type_ref.cpp): - getter_return = " -> sol::object" + getter_return = " -> lua_glue::Object" elif is_sf_string(type_ref.cpp) or is_filesystem_path(type_ref.cpp) or is_char_pointer(type_ref.cpp): getter_return = " -> std::string" else: getter_return = "" - getter_lines = [f"{getter_capture}({full_name}& self){getter_return} {{"] + getter_lines = [f"{getter_capture}(const {full_name}& self){getter_return} {{"] getter_lines.extend(return_expr(type_ref, f"self.{field_name}", " ")) getter_lines.append("}") getter = "\n".join(getter_lines) if field_item.get("readonly"): - lines.append(f' {var_name}.set("{field_name}", sol::property(') + lines.append(f' lua_glue::BindProperty({var_name}, "{field_name}",') append_indented_block(lines, getter, " ") - lines.append(" ));") + lines.append(" );") else: setter_type = lua_param_type(type_ref) prelude, expr = from_lua_expr(type_ref, "value") @@ -3071,14 +3089,13 @@ def _emit_fields(self, cls: dict[str, Any], var_name: str, full_name: str) -> li setter_lines.append(f" self.{field_name} = {expr};") setter_lines.append("}") setter = "\n".join(setter_lines) - lines.append(f' {var_name}.set("{field_name}", sol::property(') + lines.append(f' lua_glue::BindProperty({var_name}, "{field_name}",') append_indented_block(lines, getter, " ", ",") append_indented_block(lines, setter, " ") - lines.append(" ));") + lines.append(" );") else: lines.append( - f' {var_name}["{field_name}"] = sol::policies(' - f'&{full_name}::{field_name}, sol::self_dependency{{}});' + f' lua_glue::BindAttr<{type_ref.cpp}>({var_name}, "{field_name}", &{full_name}::{field_name});' ) return lines @@ -3098,7 +3115,7 @@ def _emit_methods(self, cls: dict[str, Any], var_name: str, full_name: str) -> l ] = {} skipped_lines: list[str] = [] selected: dict[ - tuple[str, tuple[str, ...], bool], + tuple[str, tuple[str, ...], bool, bool], tuple[ int, int, @@ -3110,7 +3127,7 @@ def _emit_methods(self, cls: dict[str, Any], var_name: str, full_name: str) -> l bool, ], ] = {} - selected_order: list[tuple[str, tuple[str, tuple[str, ...], bool]]] = [] + selected_order: list[tuple[str, tuple[str, tuple[str, ...], bool, bool]]] = [] emission_order: list[tuple[str, str]] = [] emitted_method_names: set[str] = set() configured_overrides: dict[str, ConfiguredMethodOverride] = {} @@ -3160,7 +3177,7 @@ def _emit_methods(self, cls: dict[str, Any], var_name: str, full_name: str) -> l if planned.unsupported: skipped_lines.append(f" // Skipped {full_name}::{method.get('displayname')}: {planned.unsupported}.") continue - key = (name, planned.signature_key, bool(method.get("static"))) + key = (name, planned.signature_key, bool(method.get("static")), bool(method.get("const"))) lambda_code, reason = make_lambda(method_item, full_name, name) if reason: skipped_lines.append(f" // Skipped {full_name}::{method.get('displayname')}: {reason}.") @@ -3307,7 +3324,7 @@ def _emit_operator_methods( meta_function = OPERATOR_META_FUNCTIONS.get(name) if not meta_function: - skipped_lines.append(f" // Skipped operator {full_name}::{name}; no sol meta mapping is available.") + skipped_lines.append(f" // Skipped operator {full_name}::{name}; no Lua metamethod mapping is available.") continue declared_params = method.get("parameters", []) @@ -3423,7 +3440,7 @@ def _emit_output_reference_read_operator( dispatch_name = f"read_operator_{sanitize_identifier(full_name)}" lines = [ - f" auto {dispatch_name} = [](sol::this_state state, {full_name}& self, std::string type) -> sol::object {{", + f" auto {dispatch_name} = [](lua_glue::ThisState state, {full_name}& self, std::string type) -> lua_glue::Object {{", ] for cpp_type, io_info in read_infos.values(): checks = " || ".join(f'type == "{alias}"' for alias in io_info["aliases"]) @@ -3433,7 +3450,7 @@ def _emit_output_reference_read_operator( " {", f" {cpp_type} value{{}};", " self.operator>>(value);", - f" return sol::make_object(state, {io_info['result']});", + f" return lua_glue::MakeObject(state.value, {io_info['result']});", " }", ] ) @@ -3448,7 +3465,7 @@ def _emit_output_reference_read_operator( var_name, "bitwise_right_shift", [ - f"""[{dispatch_name}](sol::this_state state, {full_name}& self, std::string type) -> sol::object {{ + f"""[{dispatch_name}](lua_glue::ThisState state, {full_name}& self, std::string type) -> lua_glue::Object {{ return {dispatch_name}(state, self, std::move(type)); }}""" ], @@ -3514,8 +3531,8 @@ def _emit_index_operator( call_target = f"static_cast<{dispatch_type}&>(self)" lines = [ - f" auto {index_fn} = [](sol::object key) -> {index_type} {{", - " if (key.get_type() != sol::type::number || !key.is())", + f" auto {index_fn} = [](lua_glue::Object key) -> {index_type} {{", + " if (key.get_type() != lua_glue::Type::Number || !key.is())", f' throw std::invalid_argument("{lua_path_for_type(full_name)} index must be an integer");', " const lua_Integer index = key.as();", ] @@ -3530,20 +3547,18 @@ def _emit_index_operator( [ f" return static_cast<{index_type}>(index);", " };", - f" {var_name}[sol::meta_function::index] =", + f' lua_glue::BindMetamethod({var_name}, "__index",', ] ) append_indented_block( lines, - f"""sol::policies( - [{table_var}, {index_fn}](sol::this_state state, {full_name}& self, sol::object key) -> sol::object {{ - if (key.get_type() != sol::type::number) - return {table_var}.get(key); + f"""[{table_var}, {index_fn}](lua_glue::ThisState state, {full_name}& self, lua_glue::Object key) -> lua_glue::Object {{ + if (key.get_type() != lua_glue::Type::Number) + return {table_var}.get(key); const {index_type} index = {index_fn}(key); - return sol::make_object(state, std::ref({call_target}.operator[](index))); + return lua_glue::MakeObject(state.value, std::ref({call_target}.operator[](index))); }}, - sol::self_dependency{{}} -)""", + lua_glue::ReturnPolicy::ReferenceInternal)""", " ", ) lines.append(" ;") @@ -3556,13 +3571,13 @@ def _emit_index_operator( f'{cpp_string_literal(index_lua_type)}, {cpp_string_literal(value_lua_type)});' ) if not is_const_type(return_type.cpp): - lines.append(f" {var_name}[sol::meta_function::new_index] =") + lines.append(f' lua_glue::BindMetamethod({var_name}, "__newindex",') append_indented_block( lines, - f"""[{index_fn}]({full_name}& self, sol::object key, const {value_type}& value) {{ + f"""[{index_fn}]({full_name}& self, lua_glue::Object key, const {value_type}& value) {{ const {index_type} index = {index_fn}(key); {call_target}.operator[](index) = value; -}}""", +}})""", " ", ) lines.append(" ;") @@ -3670,6 +3685,7 @@ def _emit_var(self, item: dict[str, Any], table_var: str, namespace_prefix: str) if not name or name in IGNORE_NAMES: return [] full_name = item.get("qualified_name") or f"{namespace_prefix}{name}" + enum_constant = is_anonymous_cpp_name(full_name) and "value" in item if is_anonymous_cpp_name(full_name): full_name = f"{namespace_prefix}{name}" type_ref = TypeRef.from_json(item.get("type")) @@ -3679,6 +3695,8 @@ def _emit_var(self, item: dict[str, Any], table_var: str, namespace_prefix: str) f'{cpp_string_literal(type_ref_to_lua_type(type_ref))});' ) stub_lines = [*stub_doc_lines(item), stub_line] + if enum_constant: + return [*stub_lines, f' {table_var}.raw_set("{name}", static_cast<{type_ref.cpp}>({full_name}));'] if is_window_handle(type_ref): _prelude, expr = result_value_expr(type_ref, full_name) return [*stub_lines, f' {table_var}["{name}"] = {expr};'] @@ -3686,17 +3704,15 @@ def _emit_var(self, item: dict[str, Any], table_var: str, namespace_prefix: str) return [*stub_lines, f' {table_var}["{name}"] = lua_sf::to_utf8_string({full_name});'] if is_filesystem_path(type_ref.cpp): return [*stub_lines, f' {table_var}["{name}"] = {full_name}.string();'] - if vector_element(type_ref.cpp): - return [*stub_lines, f' {table_var}["{name}"] = sol::as_table({full_name});'] - if optional_element(type_ref.cpp): - return [*stub_lines, f' {table_var}["{name}"] = lua_sf::optional_to_object(lua, {full_name});'] - return [*stub_lines, f' {table_var}["{name}"] = {full_name};'] + if should_skip_type(type_ref): + return stub_lines + return [*stub_lines, f' lua_glue::BindStaticAttr<{type_ref.cpp}>({table_var}, "{name}", &{full_name});'] def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Generate sol2 binding source files from output/sfml_api.json.") - parser.add_argument("--api-json", default="output/sfml_api.json") - parser.add_argument("--output-root", default="output") + parser = argparse.ArgumentParser(description="Generate LuaGlue binding source files from output/LuaSF/sfml_api.json.") + parser.add_argument("--api-json", default="output/LuaSF/sfml_api.json") + parser.add_argument("--output-root", default="output/LuaSF") return parser.parse_args() @@ -3705,9 +3721,9 @@ def main() -> int: api_path = Path(args.api_json).resolve() output_root = Path(args.output_root).resolve() api = json.loads(api_path.read_text(encoding="utf-8")) - generator = Sol2Generator(api, output_root) + generator = GlueGenerator(api, output_root) generator.generate() - print(f"Generated sol2 bindings for {len(api.get('files', []))} headers into {output_root}") + print(f"Generated LuaGlue bindings for {len(api.get('files', []))} headers into {output_root}") if generator.skipped: print(f"Skipped {len(generator.skipped)} signatures; see generated comments for details.") return 0 diff --git a/tools/output_files.py b/tools/output_files.py new file mode 100644 index 0000000..b5004d6 --- /dev/null +++ b/tools/output_files.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +import shutil +from pathlib import Path + + +def write_text_if_changed(path: Path, content: str, encoding: str = "utf-8") -> None: + data = content.encode(encoding) + if path.is_file() and path.read_bytes() == data: + return + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(data) + + +def copy_file_if_changed(source: str | Path, destination: str | Path) -> str: + source = Path(source) + destination = Path(destination) + if not destination.is_file() or source.read_bytes() != destination.read_bytes(): + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(source, destination) + shutil.copymode(source, destination) + return str(destination) diff --git a/tools/replace_model.py b/tools/replace_model.py index 0a35675..4739070 100644 --- a/tools/replace_model.py +++ b/tools/replace_model.py @@ -2,7 +2,7 @@ Query and rendering API for LuaSF binding code generation. Imports pure data from ``binding_config`` and exposes functions that -``generate_sol2_bindings.py`` and ``generate_build_files.py`` call. +``generate_glue_bindings.py`` and ``generate_build_files.py`` call. Sections -------- @@ -31,6 +31,8 @@ CONVERSION_REGISTRY, CPP_BUILTIN_TYPES, INTEGER_TYPES, + INDEPENDENT_VALUE_TYPES, + MANUAL_INDEPENDENT_VALUE_HEADERS, LIFECYCLE_REGISTRY, LUA_NAMESPACE_PROJECTIONS, LifecycleCategory, @@ -84,6 +86,8 @@ CONVERSION_REGISTRY, CPP_BUILTIN_TYPES, INTEGER_TYPES, + INDEPENDENT_VALUE_TYPES, + MANUAL_INDEPENDENT_VALUE_HEADERS, LIFECYCLE_REGISTRY, LUA_NAMESPACE_PROJECTIONS, LifecycleCategory, @@ -228,6 +232,8 @@ IGNORE_RETURN_TYPES, IGNORED_NAMESPACES, INTEGER_TYPES, + INDEPENDENT_VALUE_TYPES, + MANUAL_INDEPENDENT_VALUE_HEADERS, LUA_NAMESPACE_PROJECTIONS, LUA_KEYWORDS, MANUAL_DEPENDENCIES, @@ -272,6 +278,8 @@ IGNORE_RETURN_TYPES, IGNORED_NAMESPACES, INTEGER_TYPES, + INDEPENDENT_VALUE_TYPES, + MANUAL_INDEPENDENT_VALUE_HEADERS, LUA_NAMESPACE_PROJECTIONS, LUA_KEYWORDS, MANUAL_DEPENDENCIES, @@ -731,7 +739,7 @@ def _indent(body: str, prefix: str = " ") -> str: return prefix + body.replace("\n", "\n" + prefix) -# -- public entry points called from generate_sol2_bindings.make_lambda -- +# -- public entry points called from generate_glue_bindings.make_lambda -- def render_ll_memory_ctor( @@ -788,7 +796,7 @@ def render_ll_stream_ctor( if stream_name is None: raise ValueError("long-lived stream constructor requires an sf::InputStream reference") body = render_template("ll_stream_ctor", stream_name=stream_name, owner_type=owner_type) - return f"[](sol::object {stream_name}) {{\n{_indent(body)}\n}}" + return f"[](lua_glue::Object {stream_name}) {{\n{_indent(body)}\n}}" def render_ll_stream_open( @@ -801,7 +809,7 @@ def render_ll_stream_open( if stream_name is None: raise ValueError("long-lived stream open requires an sf::InputStream reference") body = render_template("ll_stream_open", stream_name=stream_name, call_target=call_target) - return f"[]({owner_type}& self, sol::object {stream_name}) -> bool {{\n{_indent(body)}\n}}" + return f"[]({owner_type}& self, lua_glue::Object {stream_name}) -> bool {{\n{_indent(body)}\n}}" def render_ll_reset( diff --git a/versions.conf b/versions.conf index 50b2992..77c0008 100644 --- a/versions.conf +++ b/versions.conf @@ -7,4 +7,3 @@ SFML_ME_OH_REPOSITORY=JasonLeon01/SFML-ME SFML_ME_OH_TAG=310-ME-OH-GLESVER LUA_VERSION=5.5.0 LUA_SHA256=57ccc32bbbd005cab75bcc52444052535af691789dba2b9016d5c50640d68b3d -SOL2_VERSION=3.3.0