Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ run-name: Build LuaSF packages for ${{ github.ref_name }} @ ${{ github.sha }}

on:
workflow_call:
workflow_dispatch:

permissions:
contents: read
Expand Down Expand Up @@ -144,6 +145,7 @@ jobs:

$requiredRuntimeDlls = @(
'LuaSF.dll',
'LuaGlue.dll',
'lua.dll',
'msvcp140.dll',
'msvcp140_atomic_wait.dll',
Expand Down
93 changes: 28 additions & 65 deletions CMakeLists.txt.in
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ set(LUASF_EMBEDDED_ARCHIVE_DIR "${{LUASF_BINARY_ROOT}}/lib/$<CONFIG>/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()
Expand All @@ -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)
Expand Down Expand Up @@ -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 "$<TARGET_FILE_DIR:LuaGlue::LuaGlue>;$<TARGET_FILE_DIR:${{LUASF_LUA_TARGET}}>;$<TARGET_FILE_DIR:sfml-system>"
LIBRARY_OUTPUT_DIRECTORY "${{LUASF_BINARY_ROOT}}/bin"
ARCHIVE_OUTPUT_DIRECTORY "${{LUASF_BINARY_ROOT}}/lib"
)
Expand Down Expand Up @@ -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}
Expand All @@ -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
$<$<CONFIG:Debug>: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
)

Expand All @@ -279,9 +234,10 @@ function(luasf_configure_binding_target target_name output_dir archive_dir)
target_include_directories("${{target_name}}"
PUBLIC
"$<BUILD_INTERFACE:${{LUASF_ROOT}}/include>"
"$<BUILD_INTERFACE:${{LUASF_SFML_ROOT}}/include>"
)

target_link_libraries("${{target_name}}" PRIVATE
target_link_libraries("${{target_name}}" PUBLIC LuaGlue::LuaGlue PRIVATE
${{LUASF_LUA_TARGET}}
sfml-system
sfml-window
Expand Down Expand Up @@ -329,6 +285,7 @@ function(luasf_copy_target_runtime_files target_name include_lua_runtime)
list(APPEND _luasf_runtime_files "$<TARGET_FILE:{target_name}_lua_shared>")
endif()
foreach(_luasf_runtime_target IN ITEMS
LuaGlue::LuaGlue
sfml-system
sfml-window
sfml-graphics
Expand Down Expand Up @@ -363,6 +320,7 @@ function(luasf_copy_runtime_files target_name)
"$<TARGET_FILE:{target_name}_lua_shared>"
)
foreach(_luasf_runtime_target IN ITEMS
LuaGlue::LuaGlue
sfml-system
sfml-window
sfml-graphics
Expand Down Expand Up @@ -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
Expand All @@ -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 "$<TARGET_FILE_DIR:LuaGlue::LuaGlue>;$<TARGET_FILE_DIR:${{LUASF_LUA_TARGET}}>;$<TARGET_FILE_DIR:sfml-system>"
)

set(_luasf_stub_args "${{LUASF_LUA_STUB_OUTPUT}}")
Expand All @@ -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
"$<TARGET_FILE:${{LUASF_LUA_TARGET}}>"
"$<TARGET_FILE:LuaGlue::LuaGlue>"
"$<TARGET_FILE:sfml-system>"
"$<TARGET_FILE:sfml-window>"
"$<TARGET_FILE:sfml-graphics>"
Expand All @@ -443,14 +405,14 @@ function(luasf_add_lua_stub target_name)
if(APPLE)
set(_luasf_stub_command
"${{CMAKE_COMMAND}}" -E env
"DYLD_LIBRARY_PATH=$<TARGET_FILE_DIR:${{LUASF_LUA_TARGET}}>:$<TARGET_FILE_DIR:sfml-system>:$ENV{{DYLD_LIBRARY_PATH}}"
"DYLD_LIBRARY_PATH=$<TARGET_FILE_DIR:LuaGlue::LuaGlue>:$<TARGET_FILE_DIR:${{LUASF_LUA_TARGET}}>:$<TARGET_FILE_DIR:sfml-system>:$ENV{{DYLD_LIBRARY_PATH}}"
"$<TARGET_FILE:${{target_name}}_lua_stub_dump>"
${{_luasf_stub_args}}
)
else()
set(_luasf_stub_command
"${{CMAKE_COMMAND}}" -E env
"LD_LIBRARY_PATH=$<TARGET_FILE_DIR:${{LUASF_LUA_TARGET}}>:$<TARGET_FILE_DIR:sfml-system>:$ENV{{LD_LIBRARY_PATH}}"
"LD_LIBRARY_PATH=$<TARGET_FILE_DIR:LuaGlue::LuaGlue>:$<TARGET_FILE_DIR:${{LUASF_LUA_TARGET}}>:$<TARGET_FILE_DIR:sfml-system>:$ENV{{LD_LIBRARY_PATH}}"
"$<TARGET_FILE:${{target_name}}_lua_stub_dump>"
${{_luasf_stub_args}}
)
Expand All @@ -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
Expand Down
40 changes: 40 additions & 0 deletions LuaGlue/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:include>")
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()
21 changes: 21 additions & 0 deletions LuaGlue/LICENSE
Original file line number Diff line number Diff line change
@@ -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.
74 changes: 74 additions & 0 deletions LuaGlue/README.md
Original file line number Diff line number Diff line change
@@ -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 <LuaGlue/LuaGlue.hpp>
#include <array>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

struct MyClass {
int a{};
float b{};
std::string function(double c) { return std::to_string(c); }
inline static std::vector<int> d;
static std::unordered_map<int, std::string> StaticFunction(long f) {
return {{static_cast<int>(f), "value"}};
}
};

constexpr std::array<std::string_view, 1> docs{"Return the selected entry."};

void bindExample(const lua_glue::Table& module) {
auto type = lua_glue::BindClass<MyClass>(module, "MyClass");
lua_glue::BindConstructor<MyClass>(type);
lua_glue::BindAttr<int>(type, "a", &MyClass::a);
lua_glue::BindAttr<float>(type, "b", &MyClass::b);
lua_glue::BindStaticAttr<std::vector<int>>(type, "d", &MyClass::d);
lua_glue::BindMethod<std::string, double>(type, "function", &MyClass::function);
lua_glue::BindFunction<std::unordered_map<int, std::string>, 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<Derived, Base>` registers a base-pointer conversion and Lua member inheritance. Use `BindCast<Derived, Base>` for a native interface conversion that does not expose Lua inheritance; that interface needs no Lua class descriptor. `BindEnum<Enum>` 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<T>&`, receive `nil` when no explicit binding default exists. Missing non-nullable arguments still fail overload matching.

## Values and lifetime

`BindStruct<T>` requires an enabled `StructTraits<T>` and adds `copy()`, `deepcopy()`, `__copy` and `__deepcopy`. Derive that trait from `IndependentValue<T>` 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).
Loading