Git Product home page Git Product logo

luabridge3's Introduction


LuaBridge 3.0

LuaBridge3 is a lightweight and dependency-free library for mapping data, functions, and classes back and forth between C++ and Lua (a powerful, fast, lightweight, embeddable scripting language). LuaBridge has been tested and works with Lua 5.1.5, 5.2.4, 5.3.6 and 5.4.6 as well as LuaJit 2.x onwards and for the first time also with Luau 0.556 onwards and Ravi 1.0-beta11.

Features

LuaBridge3 is usable from a compliant C++17 compiler and offers the following features:

  • MIT Licensed, no usage restrictions!
  • Headers-only: No Makefile, no .cpp files, just one #include and one header file (optional) !
  • Works with ANY lua version out there (PUC-Lua, LuaJIT, Luau, Ravi, you name it).
  • Simple, light, and nothing else needed.
  • Fast to compile (even in release mode), scaling linearly with the size of your binded code.
  • No macros, settings, or configuration scripts needed.
  • Supports different object lifetime management models.
  • Convenient, type-safe access to the Lua stack.
  • Automatic function parameter type binding.
  • Functions and constructors overloading support.
  • Easy access to Lua objects like tables and functions.
  • Expose C++ classes allowing them to use the flexibility of lua property lookup.
  • Interoperable with most common c++ standard library container types.
  • Written in a clear and easy to debug style.

Improvements Over Vanilla LuaBridge

LuaBridge3 offers a set of improvements compared to vanilla LuaBridge:

  • The only binder library that works with PUC-Lua as well as LuaJIT, Luau and Ravi, wonderful for game development !
  • Can work with both c++ exceptions and without (Works with -fno-exceptions and /EHsc-).
  • Can safely register and use classes exposed across shared library boundaries.
  • Full support for capturing lambdas in all namespace and class methods.
  • Overloaded function support in Namespace functions, Class constructors, functions and static functions.
  • Supports placement allocation or custom allocations/deallocations of C++ classes exposed to lua.
  • Lightweight object creation: allow adding lua tables on the stack and register methods and metamethods in them.
  • Allows for fallback __index and __newindex metamethods in exposed C++ classes, to support flexible and dynamic C++ classes !
  • Added std::shared_ptr to support shared C++/Lua lifetime for types deriving from std::enable_shared_from_this.
  • Supports conversion to and from std::nullptr_t, std::byte, std::pair, std::tuple and std::reference_wrapper.
  • Supports conversion to and from C style arrays of any supported type.
  • Transparent support of all signed and unsigned integer types up to int64_t.
  • Consistent numeric handling and conversions (signed, unsigned and floats) across all lua versions.
  • Simplified registration of enum types via the luabridge::Enum stack wrapper.
  • Opt-out handling of safe stack space checks (automatically avoids exhausting lua stack space when pushing values!).

Status

Build MacOS Build Windows Build Linux

Code Coverage

Coverage Status

Documentation

Please read the LuaBridge3 Reference Manual for more details on the API.

Release Notes

Plase read the LuaBridge3 Release Notes for more details

Installing LuaBridge3 (vcpkg)

You can download and install LuaBridge3 using the vcpkg dependency manager:

git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh # The name of the script should be "./bootstrap-vcpkg.bat" for Powershell
./vcpkg integrate install
./vcpkg install luabridge3

The LuaBridge3 port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please create an issue or pull request on the vcpkg repository.

Update vcpkg

To update the vcpkg port, we need to know the hash of the commit and the sha512 of its downloaded artifact. Starting from the commit hash that needs to be published, download the archived artifact and get the sha512 of it:

COMMIT_HASH="0e17140276d215e98764813078f48731125e4784"

wget https://github.com/kunitoki/LuaBridge3/archive/${COMMIT_HASH}.tar.gz

shasum -a 512 ${COMMIT_HASH}.tar.gz
# fbdf09e3bd0d4e55c27afa314ff231537b57653b7c3d96b51eac2a41de0c302ed093500298f341cb168695bae5d3094fb67e019e93620c11c7d6f8c86d3802e2 0e17140276d215e98764813078f48731125e4784.tar.gz

Now update the version in https://github.com/microsoft/vcpkg/blob/master/ports/luabridge3/vcpkg.json and the commit hash and sha512 in https://github.com/microsoft/vcpkg/blob/master/ports/luabridge3/portfile.cmake then commit the changes. Enter into vcpkg folder and issue:

./vcpkg x-add-version --all

Commit the changed files and create a Pull Request for vcpkg.

Unit Tests

Unit test build requires a CMake and C++17 compliant compiler.

There are 11 unit test flavors:

  • LuaBridgeTests51 - uses Lua 5.1
  • LuaBridgeTests51Noexcept - uses Lua 5.1 without exceptions enabled
  • LuaBridgeTests52 - uses Lua 5.2
  • LuaBridgeTests52Noexcept - uses Lua 5.2 without exceptions enabled
  • LuaBridgeTests53 - uses Lua 5.3
  • LuaBridgeTests53Noexcept - uses Lua 5.3 without exceptions enabled
  • LuaBridgeTests54 - uses Lua 5.4
  • LuaBridgeTests54Noexcept - uses Lua 5.4 without exceptions enabled
  • LuaBridgeTestsLuaJIT - uses LuaJIT 2.1
  • LuaBridgeTestsLuaJITNoexcept - uses LuaJIT 2.1 without exceptions enabled
  • LuaBridgeTestsLuau - uses Luau
  • LuaBridgeTestsRavi - uses Ravi

(Luau compiler needs exceptions, so there are no test targets on Luau without exceptions) (Ravi doesn't fully work without exceptions, so there are no test targets on Ravi without exceptions)

Generate Unix Makefiles and build on Linux:

git clone --recursive [email protected]:kunitoki/LuaBridge3.git

mkdir -p LuaBridge3/build
pushd LuaBridge3/build
cmake -G "Unix Makefiles" ../
cmake --build . -DCMAKE_BUILD_TYPE=Debug
# or cmake --build . -DCMAKE_BUILD_TYPE=Release
# or cmake --build . -DCMAKE_BUILD_TYPE=RelWithDebInfo
popd

Generate XCode project and build on MacOS:

git clone --recursive [email protected]:kunitoki/LuaBridge3.git

mkdir -p LuaBridge3/build
pushd LuaBridge3/build
cmake -G Xcode ../ # Generates XCode project build/LuaBridge.xcodeproj
cmake --build . -DCMAKE_BUILD_TYPE=Debug
# or cmake --build . -DCMAKE_BUILD_TYPE=Release
# or cmake --build . -DCMAKE_BUILD_TYPE=RelWithDebInfo
popd

Generate VS2019 solution on Windows:

git clone --recursive git@github.com:kunitoki/LuaBridge3.git

mkdir LuaBridge3/build
pushd LuaBridge3/build
cmake -G "Visual Studio 16" ../ # Generates MSVS solution build/LuaBridge.sln
popd

Official Repository

LuaBridge3 is published under the terms of the MIT License.

The original version of LuaBridge3 was written by Nathan Reed. The project has been taken over by Vinnie Falco, who added new functionality, wrote the new documentation, and incorporated contributions from Nigel Atkinson. Then it has been forked from the original https://github.com/vinniefalco/LuaBridge into its own LuaBridge3 repository by Lucio Asnaghi, and development continued there.

For questions, comments, or bug reports feel free to open a Github issue or contact Lucio Asnaghi directly at the email address indicated below.

Copyright 2020, Lucio Asnaghi ([email protected])
Copyright 2019, Dmitry Tarakanov
Copyright 2012, Vinnie Falco ([email protected])
Copyright 2008, Nigel Atkinson
Copyright 2007, Nathan Reed

luabridge3's People

Contributors

asomfai avatar chernikovdmitry avatar d-led avatar dmitry-t avatar fulgen301 avatar github-actions[bot] avatar grandmother avatar kunitoki avatar matrix47 avatar progschj avatar redbaron avatar reedbeta avatar ricanteja avatar rpatters1 avatar ryanel avatar scribam avatar shenyalei avatar skabyy avatar tobiasfunk avatar toski avatar vinniefalco avatar xrl1 avatar zeromus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

luabridge3's Issues

Weird issue with inheritance tree (LB3 only)

Intro

I have begun to investigate migrating my project from LB2 to LB3, and my regression test script (which is extensive) has surfaced exactly two issues that are extremely puzzling and random. One of them I haven't even thought of how to investigate, so I am tackling the other first. (Perhaps they have the same root cause: a man can dream.) I don't have a reproducible test case, but I was hoping @kunitoki or anyone else might have an idea as to where to start digging.

Executive summary

I have a class that (apparently) due to its name is generating a runtime error in LB3 but not in LB2. All my efforts to identify this problem have failed. Any ideas for how to proceed would be most welcome.

Sample class for context (does not fail)

My problem class is in an inheritance tree similar to the following. (The following works. I am just providing it for context so that I can describe the technical details of the problem.)

struct basebase {};

struct barbase : basebase
{
   virtual int foobar(int a, int b) { return a + b; }
};

struct foobase : basebase
{
   virtual int foobar(int a) { return a; }
};

struct foo : foobase {};

And it is registered like this:

   luabridge::getGlobalNamespace(L)
      .beginNamespace("foobar")
         .beginClass<basebase>("basebase")
         .endClass()
         .deriveClass<barbase, basebase>("barbase")
            .addFunction("foobar", &barbase::foobar)
         .endClass()
         .deriveClass<foobase, basebase>("foobase")
            .addFunction("foobar", &foobase::foobar)
         .endClass()
         .deriveClass<foo, foobase>("foo")
            .addConstructor<void(*) (void)>()
         .endClass()
      .endNamespace();

With my class (not the above example), when I run this Lua code, it fails as shown:

local foo = foobar.foo()
local rslt = foo:foobar(3)
--runtime error: Error decoding argument #3: The lua object can't be cast to desired type

What I know

I have done extensive debug tracing, and here is what I know:

  • The problem occurs on both platforms I use: macOS XCode and Windows Visual Studio 2022.
  • foobar() is failing because when Lua retrieves its function pointer from the Lua stack, the pointer has been miscast to barbase::foobar which takes two arguments instead of one.
  • Though miscast, the function pointer address is correct. If I supply two integer arguments in Lua, LB3 lets the function call go through, and the correct function (foobase::foobar) is called (which only sees the first argument).
  • The function is registered with the correct type. (I have traced into addFunction to confirm this.)
  • None of this happens when compiled with LB2. Lua retrieves the correctly cast function pointer from the Lua stack, and the function executes without issues.

What I tried

I have dozens of other classes registered in exactly this same manner. None of them (that I've found) have this problem. It is something specific to this class.

  • I have removed all the function registrations from my class and still see the problem.
  • I have bypassed my registration function and registered it with direct calls to the luabridge API and still see the problem.
  • I even cannibalized the class by removing as much of it as I could without a major rewrite and still saw the problem.
  • I created a superclass of my class and the superclass has the same problem.
  • I made a copy of the class and replaced all internal references to my class to the replacement copy (or as many as the compiler reported) . The copy does not fail.

What finally made a (very weird) difference

  • I went back to a clean version of my code and simply renamed the class using global search and replace. The renamed class does not fail. (!)
  • I named the class back to the original name (with global search and replace, not with git) and it fails again. (!)
  • Simply changing the registration name in LuaBridge made no difference. I had to change the actual class name.
  • The reverse is also true. With the renamed class that works, I registered it in LB under the original class name. That also works.
  • A new minimal class with my problem class's name (FCStaffSystem) does not exhibit the problem.

Conclusion

This problem is related to the actual class name and not its registered name in LB. But why this particular class name is causing this failure has stumped me.

C++ Builder complains redefinition of stack

I would like to use LuaBride3 in a c++ 32-bit windows project with the Embarcadero C++ Builder (Community).

Just including "LuaBridge3.h" stops compilation in line 2176: redefinition of 'Stack<char,void>'

template <>
struct Stack<int8_t>
{

Previous line here: 2128

template <>
struct Stack<char>
{

Example code (VCL application):

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "mainform.h"

#include "lua5.4/lua.hpp"
#include "LuaBridge/LuaBridge.h"

#include <iostream>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
//
}

I assume C++ Builder treats int8_t the same as char.
Do you know of a flag or project setting I can change to fix the error?

Question about lifetime

So from the manual it reads as if any pointers returned to Lua will never be collected by Lua (delete will not be called on them), but copy-constructed objects will be. Is there any better way to control this? I want to be able to define two functions, and both should return a pointer to a newly constructed object. But one of the two objects is expected to be deleted by the C++ code later, and the other should be deleted when Lua decides to collect it. Is that possible, or am I thinking about this wrong?

Compare `LuaRef`s referencing Lua functions in C++ code

Here I am again, and this time it's more of a question rather than an issue.
Let's get straight to practical example with this one. So, there may be various needs to store Lua functions as LuaRefs on the C++ side, but I guess one of most common reasons is to establish some event subscription from scripts to events happening in C++.
A naive example may look like this:

static std::set<LuaRef> eventHandlers;

static void subscribe(LuaRef handler)
{
  if (handler.isFunction())
  {
    eventHandlers.insert(handler);
  }
}

static void trigger()
{
  for (auto handler : eventHandlers)
  {
    handler();
  }
}

int main()
{
  const auto L = getLuaState();
  registerMainThread(L);
  enableExceptions(L);

  getGlobalNamespace(L)
    .addFunction("subscribe", subscribe)
    .addFunction("trigger", trigger);

  loadScript("test.lua");

  trigger();

  return 0;
}

And then in Lua:

subscribe(function()
  print('Hello')
end)
subscribe(function()
  print('World')
end)

And here first function is added okay, but as soon as another one is attempted to be added, I get this error: attempt to compare two function values. As I understand, that's because internally std::set attempts to check function uniqueness compared to already added to that set and that gets to comparison of LuaRefs and somewhere inside LuaBridge code this is determined as incorrect operation.

Ability to compare functions is important in this concept, because as soon as you need to remove a function from event handlers collection, you need some way to identify this function. In Lua functions may be compared easily because I assume they're some kind of pointers, i.e. function variable can be printed as function: 0000024a923ade90 and thus same such values may be considered equal. So it's possible to store event handlers in Lua in form of table where function itself may even be a table key, so it can be like:

handlers = {}
local func = function()
  print('Hello')
end
-- subscribe
handlers[func] = func
-- unsubscribe
handlers[func] = nil

In C++, as I understand, we don't have a simple possibility to compare LuaRefs which are actually referencing function, is this correct?
In my code I already implemented a workaround where I have special Function class that wraps LuaRef, has unique ID per instance, and provides operator== that compares IDs of passed instances, so it can be used in std::set.
But this means in scripts I have to additionally wrap Lua function into that Function class I bind. So it's like:

local handler = Function(function()
  print('Hello')
end))
-- then I can pass `handler` to C++

But this means that wrapping everywhere in scripts. Maybe more elegant solution is possible?

Functions overload arity error

I am trying to bind these functions:

Vector3 Vector3::operator * ( float f ) const{
    return Vector3(x * f, y * f, z * f);
}

Vector3 Vector3::operator * ( const Vector3& v ) const{
    return Vector3(x * v.x, y * v.y, z * v.z);
}

Using overload feature in __mul metamethod:

luabridge::getGlobalNamespace(L)
    ...
    .addFunction("__mul", (Vector3 (Vector3::*)(const Vector3&) const)&Vector3::operator*, (Vector3 (Vector3::*)(float) const)&Vector3::operator*)
    ...
    .endClass();

Testing this Lua code:

vec1 = Vector3(3,3,3)
vec2 = Vector3(2,2,2)

print(vec1 * 3)
print(vec1 * vec2)

And I'm getting this error only in print(vec1 * vec2) line:

( ERROR ): Lua Error: All 2 overloads of mul returned an error:
1: Skipped overload #0 with unmatched arity of 0 instead of 1
2: Error decoding argument #2: The lua object can't be casted to desired type
stack traceback:
[C]: in metamethod 'mul'
[string "lua://main.lua"]:19: in main chunk

I think function arity should be 1 but is registered with 0.

Migrating project from LuaBridge2

I took a stab at migrating my project from LuaBridge2 to LuaBridge3. I ran into a couple of roadblocks:

  • After reading the fine manual, I do not see a non-intrusive option for a shared lifetime pointer of a class. The closest thing on offer appears to be std::shared_ptr, but it is not, in fact, non-intrusive. Per the manual, each such class has to derive std::enable_shared_from_this, unless I am missing something. Is there any reason I can't use RefCountedPtr from LuaBridge2? It seems like it should work without any changes.
  • After a brief mention in the executive summary at the top, CFunctions have no further mention in the manual. How do I register them in a namespace? The Namespace::addCFunction appears to be missing. (Or at least that's the error message I'm getting.)
  • When pushing return values on the Lua stack in a CFunction, I get an "ignoring return value of nodiscard function" warning. Ex.:
luabridge::Stack<bool>::push(L, returnVal);

What should I be doing with the return value? Or is there a more proper way to push values?

.addConstructor ([] (void* ptr, lua_State* L) in Lua coroutine

I have started experimenting with .addConstructor ([] (void* ptr, lua_State* L) proxy constructors. This feature is one of the principal factors driving me to migrate to LB3 from LB2.

I ran into a problem that sometimes my proxy constructor works and sometimes it doesn't. (When it doesn't, the program often crashes. Basically my constructed Lua userdata is pointing at garbage, so the results are unpredictable.)

I'm fairly certain the cause is that I am constructing the instance inside a Lua coroutine. If this is enough information to go on, I'll leave it at that. Otherwise, I'll dig further. Let me know.

EDIT

See below for isolated test case. The coroutine seems to be an aggravating factor, but this test fails nearly consistently in the main routine.

Add more addStaticPropery overload

Should be interesting more addStaticPropery overload like these:

template <class Getter, typename = std::enable_if_t<!std::is_pointer_v<Getter>>>
Class<T> addStaticProperty(const char* name, Getter get)
{
    assert(name != nullptr);
    assertStackState(); // Stack: const table (co), class table (cl), static table (st)

    using GetType = decltype(get);

    lua_newuserdata_aligned<GetType>(L, std::move(get)); // Stack: co, cl, st, function userdata (ud)
    lua_pushcclosure_x(L, &detail::invoke_proxy_functor<GetType>, 1); // Stack: co, cl, st, function
    detail::add_property_getter(L, name, -2); // Stack: co, cl, st


    return *this;
}

template <class Getter, class Setter, typename = std::enable_if_t<!std::is_pointer_v<Getter> && !std::is_pointer_v<Setter>>>
Class<T> addStaticProperty(const char* name, Getter get, Setter set)
{
    assert(name != nullptr);
    assertStackState(); // Stack: const table (co), class table (cl), static table (st)

    using GetType = decltype(get);
    using SetType = decltype(set);

    lua_newuserdata_aligned<GetType>(L, std::move(get)); // Stack: co, cl, st, function userdata (ud)
    lua_pushcclosure_x(L, &detail::invoke_proxy_functor<GetType>, 1); // Stack: co, cl, st, function
    detail::add_property_getter(L, name, -2); // Stack: co, cl, st

    lua_newuserdata_aligned<SetType>(L, std::move(set)); // Stack: co, cl, st, function userdata (ud)
    lua_pushcclosure_x(L, &detail::invoke_proxy_functor<SetType>, 1); // Stack: co, cl, st, function
    detail::add_property_setter(L, name, -2); // Stack: co, cl, st


    return *this;
}

These will work with:

.addStaticProperty("onUpdate", [] () { return &Engine::onUpdate; }, [] (lua_State* L) { Engine::onUpdate.add("luaFunction", L); })

Thanks

Using enum in function argument

I have this enum:

    enum class Scaling{
        FITWIDTH,
        FITHEIGHT,
        LETTERBOX,
        CROP,
        STRETCH
    };

And these C++ functions:

static void setScalingMode(Scaling scalingMode);
static Scaling getScalingMode();

Lua bindings:

    luabridge::getGlobalNamespace(L)
        .beginNamespace("Scaling")
        .addProperty("FITWIDTH", Scaling::FITWIDTH)
        .addProperty("FITHEIGHT", Scaling::FITHEIGHT)
        .addProperty("LETTERBOX", Scaling::LETTERBOX)
        .addProperty("CROP", Scaling::CROP)
        .addProperty("STRETCH", Scaling::STRETCH)
        .endNamespace();

    luabridge::getGlobalNamespace(L)
        .beginClass<Engine>("Engine")
        .addStaticFunction("setScalingMode", &Engine::setScalingMode)
        .addStaticProperty("scalingMode", &Engine::getScalingMode, &Engine::setScalingMode)
        .endClass();

Code in Lua:

Engine.setScalingMode(Scaling.CROP)   -- not working
Engine.scalingMode = Scaling.CROP  -- not working

None of them are working. Is there anyway to get these enum working in function argument?

Passing LuaRef to a different lua thread issue

Hello im trying to execute a lua function when a value some_val is changed

function a(some_val)
 print(some_val.val)
end

I export a Hook function, that i can call like this:

Hook("somehook", a)
-- can do other code here after

c++ full code:

#include <chrono>
#include <iostream>
extern "C" {
#include <lua/lauxlib.h>
#include <lua/lua.h>
#include <lua/lualib.h>
}
#include <LuaBridge3/LuaBridge.h>

namespace utils {
    template <typename T = std::chrono::milliseconds>
    long long GetTime() {
        return std::chrono::duration_cast<T>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
    }
}

lua_State* L = nullptr;
long long unpause_at = 0;
bool stop_resuming_main = false;

struct LUAThread {
    lua_State* co = nullptr;
    luabridge::LuaRef cb;

    LUAThread(lua_State*&& co, luabridge::LuaRef&& cb) : co(std::move(co)), cb(std::move(cb)) {}

    void KillExecution() noexcept {
        lua_close(co);
    }
};

std::unordered_map <std::string, LUAThread> hooks;

int some_val = 7; // just for testing, in real world it has the ability to change.

void Execute(const std::string_view& script) noexcept {
    L =  luaL_newstate();
    luaL_openlibs(L);

    // todo: make this work in any lua thread.
    auto Sleep = [&](long long ms) {
        unpause_at = utils::GetTime() + ms;
    };

    auto Hook = [&](const std::string& name, luabridge::LuaRef cb) noexcept {
        lua_State* co = lua_newthread(L);
        LUAThread hook = LUAThread(std::move(co), std::move(cb));
        hooks.emplace(name, std::move(hook));
    };
    auto UnHook = [](const std::string& name) noexcept { hooks.erase(name); };

    luabridge::getGlobalNamespace(L)
    .addFunction("Hook", Hook)
    .addFunction("UnHook", UnHook)
    .addFunction("Sleep", Sleep);

    luaL_loadstring(L, script.data());

    auto hook_function = [](lua_State* L, lua_Debug* ar) {
        lua_yield(L, 0);
        return;
    };
    lua_sethook(L, hook_function, LUA_MASKCOUNT, 1);
}

void KillExecution() noexcept {
    for (auto& hook : hooks) {
        hook.second.KillExecution();
        hooks.erase(hook.first);
    }
    lua_close(L);
}

bool HandleLUA() noexcept {
    // hooks
    for (auto& hook : hooks) {
        auto& name = hook.first;
        auto& luahook = hook.second;
        luahook.cb.push(luahook.co);

        lua_newtable(luahook.co);
        lua_pushstring(luahook.co, "val");
        lua_pushinteger(luahook.co, some_val);
        lua_settable(luahook.co, -3);
        int nres = 0;
        int status = lua_resume(luahook.co, 0, 1, &nres);
        if (status != LUA_YIELD) {
            if (status == LUA_OK) {
                std::cout << "ok";
                luahook.KillExecution();
            }
            else if (lua_isstring(luahook.co, -1)) {
                std::cout << "[LUA][corout] error: " << lua_tostring(luahook.co, -1) << '\n';
                luahook.KillExecution();
            }
        }
    }
    // end hooks
    // main
    if (unpause_at <= utils::GetTime() && !stop_resuming_main) {
        int nres = 0;
        int status = lua_resume(L, nullptr, 0, &nres);
        if (status != LUA_YIELD) {
            if (status == LUA_OK) {
                if (hooks.size() == 0) {
                    KillExecution();
                    return true;
                }
                else {
                    stop_resuming_main = true;
                }
            }
            else if (lua_isstring(L, -1)) {
                std::cout << "[LUA][main] error: " << lua_tostring(L, -1) << '\n';
                KillExecution();
                return true;
            }
        }
    }
    return false;
}

int main() {
#ifdef _DEBUG
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

    Execute("function a(some_val)\n  print(some_val.val)\nend\n\nHook(\"somehook\", a)");

    while (!HandleLUA()) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    return 0;
}

But I am getting error [LUA][corout] error: cannot resume dead coroutine when it tries to resume the hook thread.

probably because LuaRef is on main lua_State* L

so my question is, how do I move the LuaRef callback from lua_State* L to LUAThread.co?

Enums

And here I come with another issue =)
This one was actually discovered by me some time ago, but I wasn't sure it's worth posting, now decided it probably is, because maybe it can be improved.

So, I started using LuaBridge 3 after LuaBridge 2.8, and in 2.8 version it was supposed to add Stack specializations for enums this way vinniefalco/LuaBridge#204
When I started using LuaBridge 3, it was stated in the docs that now it's done this way:
"Automatic handling of enum types by communicating with lua through std::underlying_type_t".
And indeed quite soon I found out that you can bind enum values as namespace properties and in Lua they turn into numbers basically. And I'm talking about both usual enum and and enum class. So I just wrote an example to double-check that, goes this way:

enum Enum
{
	ONE,
	TWO,
	THREE
};

enum class EnumClass
{
	One,
	Two,
	Three
};

// Then in bindings:

getGlobalNamespace(L)
  .beginNamespace("Enum")
    .addProperty("ONE", ONE)
    .addProperty("TWO", TWO)
    .addProperty("THREE", THREE)
  .endNamespace()
  .beginNamespace("EnumClass")
    .addProperty("One", EnumClass::One)
    .addProperty("Two", EnumClass::Two)
    .addProperty("Three", EnumClass::Three)
  .endNamespace()

And then in Lua you can just do print(Enum.ONE) or print(EnumClass.One) and it will print 0.
Though here along the way I may express a wish that instead of beginNamespace() maybe it would be better to have beginEnum() to have more descriptive syntax, but that's minor thing.

So the problem appears when, for example, you try to pass this enum value to some function like this:

// C++
void testEnumAsArg(Enum value)
{
	printf("Enum value: %i.\n", value);
}
-- Lua
testEnumAsArg(Enum.One)

Doesn't matter whether it's enum or enum class, it will fail with this error:
bad argument #1 to 'testEnumAsArg' (unregistered class expected, got number).

Then turns out that Stack specialization works, only it needs to be updated to LuaBridge 3 specifics like Result / TypeResult stuff etc. I think I'll omit code example here, I will share a link to full example again. With that wrapper I can do it like this:

template <>
struct Stack<Enum> : LuaEnumClassWrapper<Enum>
{
};

And then passing enum as argument starts working as expected.

A quite frustrating thing may happen when you don't even get any meaningful error and it just doesn't work.
That's the case when you create std::map when enum is used as key, like this:

std::map<Enum, std::string> testEnumMap = {
	{ ONE, "ONE" },
	{ TWO, "TWO" },
	{ THREE, "THREE" }
};

Then, if you bind this map (with the help of #include "LuaBridge/Map.h", of course), and try to access it in Lua, you just get an error without any description.
Though if instead you change map declaration to this std::map<int, std::string> it will work just fine.

So, initially I thought "well, I'm just going to use these wrappers", but now I started to think it's probably possible to do some automatic conversion here without using them. Because if it basically works C++ => Lua way, it should work the other way around.
If there are some complications, then I can continue use these wrappers just fine.

Full code example is here https://www.dropbox.com/s/ew7fahendv2z6xr/EnumClassTest.zip?dl=0. Wrappers are on line 30 of main.cpp, if you comment them, you get errors I'm talking about.

There's a small problem

// cpp code
// UserdataPtr Can't call
luabridge::LuaRef func = luabridge::getGlobal(L, func_name);
luabridge::LuaResult result = func("helloworld");

// const char* yes
luabridge::LuaRef func = luabridge::getGlobal(L, func_name);
const char* str = "helloworld";
luabridge::LuaResult result = func(name);

// lua code
function test(args)
print(args)
end

Can't add function in table

When i try add function in table, give me null error ('C:\scripts\Example.lua:11:' and no more)

getGlobalNamespace(L)
.beginNamespace("Entities")
	.addFunction("GetLocalHero", [&]() { 
		auto table = luabridge::newTable(L);
		table["index"] = 150;
		table["Health"] = [&]() {
		return 500;
		};
		return table;
	})
.endNamespace();

When i delete table["health"], everything is okay, but when i try to bind function in table, something is wrong

in lua:
cout(tostring(Entities.GetLocalHero().Health()))

lua version 5.4.2, luabridge: "LuaBridge3-master.zip"

Invalid noexcept operator

The following function in Expected.h is marked as noexcept but throws if LUABRIDGE_HAS_EXCEPTIONS is true.

constexpr const T&& value() const&& noexcept
{
#if LUABRIDGE_HAS_EXCEPTIONS
	if (!hasValue())
		throw BadExpectedAccess<E>(error());
#endif

	return std::move(base_type::value());
}

Incorrect type alignment

There seems to be a problem with custom memory alignment.

It is definitely a problem with LuaBridge3. It used to work in the vanilla LuaBridge with modifications to UserdataValue that I made. (See code)

Code taken from:
SteveKChiu/lua-intf#38
https://github.com/SteveKChiu/lua-intf/blob/master/LuaIntf/impl/CppObject.h#L255

Code to reproduce issue below

typedef __m128d SSEType;

struct alignas(16) OtherClass
{
public:
	SSEType XY;
	SSEType ZW;
};

OtherClass __LoadAligned(const double* Ptr)
{
	OtherClass Result;
	Result.XY = _mm_load_pd((const double*)(Ptr));
	Result.ZW = _mm_load_pd((const double*)(Ptr + 2));
	return Result;
}

OtherClass __VectorCompareEQ(const OtherClass& Vec1, const OtherClass& Vec2)
{
	OtherClass Result;
	Result.XY = _mm_cmpeq_pd(Vec1.XY, Vec2.XY);
	Result.ZW = _mm_cmpeq_pd(Vec1.ZW, Vec2.ZW);
	return Result;
}

int __VectorMaskBits(const OtherClass& VecMask)
{
	const int MaskXY = _mm_movemask_pd(VecMask.XY);
	const int MaskZW = _mm_movemask_pd(VecMask.ZW);
	return (MaskZW << 2) | (MaskXY);
}

template<typename T>
struct alignas(16) Vec
{
public:
	Vec() { }

	Vec(T InA, T InB, T InC, T InD)
	{
		X = InA;
		Y = InB;
		Z = InC;
		W = InD;
	}

public:

	T X;
	T Y;
	T Z;
	T W;

	bool operator==(const Vec<T>& Q) const
	{
		const OtherClass A = __LoadAligned((const double*)this);
		const OtherClass B = __LoadAligned((const double*)&Q);
		return __VectorMaskBits(__VectorCompareEQ(A, B)) == 0x0F;
	}
};

using FVec = Vec<double>;

Register in Lua:

.beginClass<FVec>("Vec")
	.addConstructor<void(*) ()>()
	.addConstructor<void(*) (double, double, double, double)>()
	.addProperty("A", &FVec::X)
	.addProperty("B", &FVec::Y)
	.addProperty("C", &FVec::Z)
	.addProperty("D", &FVec::W)
	.addFunction("__eq", &FVec::operator==)
.endClass()

Trigger crash:

assert(Vec(3.0, 2.0, 1.0, 0.5) == Vec(3.0, 2.0, 1.0, 0.5))

The C++ code works just fine. When you run this without the Lua part it does not crash.

FVec aa(1, 2, 3, 4);
FVec bb(1, 2, 3, 4.2);
auto r = aa.operator==(bb);

Callstack:

Vec<double>::operator==()
luabridge::detail::function<bool,std::tuple<Vec<double> const &>,2>::call<Vec<double> const ,bool (__cdecl Vec<double>::*)(Vec<double> const &)const >() [LuaBridge.h:5792]
lua_yieldk()
luaD_precall()
luaM_toobig()
luaT_callTMres()
luaV_equalobj()
luaV_execute()
luaM_toobig()
luaD_rawrunprotected()
luaD_pcall()
lua_pcallk()

Calling wrong function in Lua is getting only generic error "Error while calling ..."

When I call some class method (or any function) in Lua with wrong arguments I always get this generic error:

[string "lua://main.lua"]:89: Error while calling method
stack traceback:
[C]: in method 'getSide'
[string "lua://main.lua"]:89: in main chunk

But If I comment this catch clause in FuncTraits.h:

catch (...)
{
    luaL_error(L, "Error while calling method");
}

I get a more specific error:

[string "lua://main.lua"]:89: bad argument #1 to 'getSide' (Vector3 expected, got table)
stack traceback:
[C]: in method 'getSide'
[string "lua://main.lua"]:89: in main chunk

Which has much more information.

Is this intentional or am I doing something wrong?

LuaHelpers.h error

error happen if i include <LuaBridge/LuaBridge.h> in b.cpp
image

lua.hpp

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

a.h

#pragma once
#include <lua.hpp>

class b; // forward declaration

a.cpp

#include <a.h> 
#include <LuaBridge/LuaBridge.h> // fine
#include <b.h>
// #include <LuaBridge/LuaBridge.h> // if include at here error too

b.h

#pragma once
#include <lua.hpp>
class a; // forward declaration

b.cpp

#include <b.h> 
#include <a.h> 
#include <LuaBridge/LuaBridge.h> // error appear if i include

unregistered class expected, got nil

Using the current 0970f74 LuaBridge.h distribution file I get the error.

[string "testscript"]:10: bad argument #2 to 'Equals' (unregistered class expected, got nil)
Q1 = Quat(10, 90, 200, 1)
T = Transform.Identity
T:SetTranslation(Vector(10.0, 20.0, 30.0))
T:SetRotation(Q1)

R1 = T * T
R2 = Transform(Quat(3.0, 6.0, 9.0, -11.75), Vector(-71.0, 128.0, 27.0), Vector(1.690, 0.640, 0.810));
print("R1", R1)
print("R2", R2)

assert(R1:Equals(R2) == true);

I am sorry but I can't give any more C++ code to reproduce. All I can say is that R1 and R2 are valid Transform types and Equals expects a transform. These are test files that all worked with vanilla Luabridge.

Func signature:

.addFunction("Equals", [](const Transform* c, const Transform& Other, std::optional<double> Tolerance) -> bool {} )

New release/tag

Please, add a new release or tag for project, theres a considerable number of changes since 3.0-rc1.
Asking for that due vcpkg and conan.

Update to modern Luau version (0.55x)?

Hello!

Why you don't update luau to new versions? I need to use a modern luau with bug fixes and new features, but LuaBridge written with using Luau 0.50x.

Thanks!

Why no addConstant?

Hi,

I saw that addConstant was renamed to addVariable. I was wondering if there's a particular reason not to have an addConstant (or maybe addValue). My use case is to expose some global #define and enum values as names to Lua. Since those will never change, I was wondering if it wouldn't be more efficient to just have this available at least for primitive types:

    template <class T>
    Namespace& addConstant(char const* name, T value)
    {
        assert(name != nullptr);
        assert(lua_istable(L, -1)); // Stack: namespace table (ns)

        Stack::push(L, name); // Stack: ns, key
        Stack::push(L, value); // Stack: ns, key, value
        lua_settable(L, -3);

        return *this;
    }

problem with `std::function<void(int, std::string)>` argument

trying to make a function exposed to lua that can add custom code to function

std::map<string, std::function<void(int, string)>> hooklist;
void Hook(std::string name, std::function<void(int, std::string)> cb) {
    hooklist.emplace(name, cb);
}
    lua_State* luaState = luaL_newstate();
    luaL_openlibs(luaState);
    luabridge::getGlobalNamespace(luaState).addFunction("Hook", &Hook);
    luaL_dofile(luaState, get_path(fileToEdit).string().c_str());

then in the function i'm "hooking"

    for (auto& func : hooklist) {
        func.second(type, packet);
    }

Lua script i did for test:

function hook1(type, packet)
log("lol")
end


Hook("hook1", hook1)

But if i try add debug statement in Hook() even it doesn't print, but if i remove std::function<void(int, string)> argument it works, but the whole purpose of code loses value. How could I make this work?

Any objections to adding proxying of arrays?

In my use case, I would like to proxy a fixed-size C-style array both for reading and writing. I'm not sure how hard this would be. I experimented a bit something like addArray(char const* name, TG (*get), TEG (*getIndex)(TG, int), void (*setIndex)(TG, int, TES) = 0) with creating a lua_newuserdata on the fly when get is called, putting the array pointer in there, and setting __index and __newindex to use getIndex and setIndex callbacks, but I'm afraid I don't know enough C++ to get it working properly.

Just putting this out here in case you have an idea and find it easy to implement, I will try to dig into this when I have time.

Wrong type in arg error message

  1. Expose a function to Lua from C++ with 1 argument of a userdata type.
  2. Call the function from Lua but do not pass any value.
  3. Error message says it got table.

It should rather print that it got no value.

[string "testscript"]:X: bad argument #1 to 'MyFunction' (SOMETYPE expected, got table)

Problem lies around Userdata::throwBadArg, it prints table because the metatable of the userdata is on the stack.

How can I get lua script errors.

test.cpp

int main(){
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    try {
        luabridge::LuaException::enableExceptions(L);
        luaL_dofile(L, "/mnt/d/code/lua_cpp/bin/test.lua");
        auto func = luabridge::getGlobal(L, "test");
        auto res = func();
        std::cout << res.errorMessage() << std::endl;
    } catch (const luabridge::LuaException &e) {
        // e.waht() is empty string
        std::cerr << e.what() << std::endl;
    }
    lua_close(L);
}

test.lua

function test()
    print("=============>>>>", i + i)  -- I want to get error message 
end

Lua randomly gcs my variable in LB3 but not in LB2

I have made a LuaBridge registration of the tinyxml2 framework. The following code executes perfectly every time on LB2. But on LB3 the xml variable is garbage collected, usually before the first iteration of the inner loop. Both the LB2 and LB3 versions are built with Lua 5.4.4, so a difference in Lua version would not seem to be the issue.

The Lua function is:

function xmlelements(node, nodename)
    local child = node and node:FirstChildElement(nodename) or nil
    if child then child = child:ToElement() end -- in case node is XMLHandle
    return function()
        local current_child = child
        if child then
            child = child:NextSiblingElement(nodename)
        end
        return current_child
    end
end

local function do_xml_parse()
    local xml = tinyxml2.XMLDocument()
    local result = xml:LoadFile(finenv.RunningLuaFolderPath() .. "/jwluatagfile.xml")
    print("load result "..tostring(result))
    if result ~= tinyxml2.XML_SUCCESS then
        error("Unable to find jwluatagfile.xml. Is it in the same folder with this script?")
    end
    local class_collection = {}
    local tagfile = tinyxml2.XMLHandle(xml):FirstChildElement("tagfile"):ToNode()
    for compound in xmlelements(tagfile, "compound") do
        print("compound", compound:FirstChildElement("name"):GetText())
        if compound:Attribute("kind", "class") then
            local class_info = { _attr = { kind = 'class' }, __members = {} }
            class_info.name = compound:FirstChildElement("name"):GetText()
            class_info.filename = compound:FirstChildElement("filename"):GetText()
            class_info.base = compound:FirstChildElement("filename"):GetText()
            for member in xmlelements(compound, "member") do
                print("   member", member:FirstChildElement("name"):GetText())
                if member:Attribute("kind", "function") then
                    local member_info = { _attr = { kind = 'function' } }
                    member_info.type = member:FirstChildElement("type"):GetText()
                    member_info.name = member:FirstChildElement("name"):GetText()
                    member_info.anchorfile = member:FirstChildElement("anchorfile"):GetText()
                    member_info.anchor = member:FirstChildElement("anchor"):GetText()
                    member_info._attr.protection = member:Attribute("protection", nil)
                    member_info._attr.static = member:Attribute("static", nil)
                    member_info._attr.virtualness = member:Attribute("virtualness", nil)
                    member_info.arglist = member:FirstChildElement("arglist"):GetText()
                    class_info.__members[member_info.name] = member_info
                end
            end
            class_collection[class_info.name] = class_info
        end
    end
    xml:Clear()
    print("********** exiting function *************")
    return class_collection
end

An abbreviated version of the registration of XMLDocument is here. (XMLNode is registered before this.)

   luabridge::getGlobalNamespace(L)
      .beginNamespace("tinyxml2")
         .deriveClass<tinyxml2::XMLDocument, tinyxml2::XMLNode>("XMLDocument")
#if USE_PROXY
            .addConstructor([] (void* ptr, lua_State *L)
               {
                  const int numArgs = lua_gettop(L);
                  const bool processEntities = (numArgs > 2) ? luabridge::Stack<bool>::get(L, 2).value() : true;
                  const tinyxml2::Whitespace whitespaceMode = (numArgs > 3)
                                       ? luabridge::Stack<tinyxml2::Whitespace>::get(L, 3).value()
                                       : tinyxml2::PRESERVE_WHITESPACE;
                  return new(ptr) tinyxml2::XMLDocument(processEntities, whitespaceMode);
               })
#else
            .addConstructor<void (*) ()>()
#endif
            // plus function registrations
         .endClass()
      .endNamespace();

It doesn't matter which version of the constructor I use. Lua is garbage-collecting xml after only two print statements. When I run this same code with LB2, it prints 100s of print statements.

I can reproduce the problem with a smaller less complicated loop, but only if I run it in a coroutine. (Providing a small coroutine example is a challenge, because to execute a coroutine in the Finale Lua environment you have to set up a callback function, usually a timer on a dialog box, which introduces a lot of code specific to Lua on Finale.) This code is failing consistently even in the main execution stream without a coroutine.

I would have thought Lua would always keep xml alive until the function completed, especially since there is a xml:Clear() at the bottom.

Support for Lua 4 _close variables

I asked this on the LB2 repo, and I will ask it again here. Any plans to support the new Lua 4 <close> variables?

I've noticed that a number of Lua environments, after sitting on Lua 5.1 or 5.2 for a long time, are finally making the jump to Lua 5.4. (Mine included.) It would be nice to be able to add _close to some of my classes.

Question

Thank you for your work on this project.

I have some questions and would be very thankful to have some answers.

Let's assume I have exposed multiple classes to Lua with LuaBridge3. They include simple structs like Vectors where lifetime is managed by Lua but also pointers to objects where the host manages lifetime.

When pushing a C++ pointer to Lua is it possible to dynamically choose what exposed class to use?

I often do not know the exact type at compile time.

class A
class B : A // B inherits A

Let's say I have a pointer of type A but it's actually an instance of class B and I want to push the pointer as if it was B.

I think it would require a dynamic database or something of the class keys in ClassInfo.h.
Is something like this possible with LuaBridge3?

Can C++ exposed classes be extended in Lua?

I would like to add functions to C++ exposed classes in Lua. Ideally one could even override existing C++ functions and also call the function of the parent class.
I know that userdata types cannot be extended in Lua but is there some other way to achieve that? Or am I wrong?

Security on possible malicious Lua scripts.

Let's assume we only expose known safe functions to Lua and forbid bytecode. Did you do some research or tests on LuaBridge3 whether it is safe to run unknown Lua code.

What are your future plans with LuaBridge3?

Would be nice if you could share your plan on features etc. you might be working on in the future.

Thank you again for your work and time.

Failed to bind class function with noexcept qualifier

img
img2
img3

Error

In file included from /media/dezlow/Drive/Dev/C++/Oneiro/SandBox/Source/SandBoxApp.cpp:6:
In file included from /media/dezlow/Drive/Dev/C++/Oneiro/SandBox/Source/SandBoxApp.hpp:8:
In file included from /media/dezlow/Drive/Dev/C++/Oneiro/Engine/Include/Oneiro/Runtime/Application.hpp:8:
In file included from /media/dezlow/Drive/Dev/C++/Oneiro/Engine/Include/Oneiro/Core/Event.hpp:10:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/functional:54:
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/tuple:1852:14: error: no matching function for call to '__invoke'
      return std::__invoke(std::forward<_Fn>(__f),
             ^~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/tuple:1863:19: note: in instantiation of function template specialization 'std::__apply_impl<oe::MainCameraComponent *(oe::World::Entity::*&)() noexcept, std::tuple<>>' requested here
      return std::__apply_impl(std::forward<_Fn>(__f),
                  ^
/media/dezlow/Drive/Dev/C++/Oneiro/ThirdParty/luabridge3/Source/./LuaBridge/detail/CFunctions.h:545:25: note: in instantiation of function template specialization 'std::apply<oe::MainCameraComponent *(oe::World::Entity::*&)() noexcept, std::tuple<>>' requested here
                L, std::apply(func, make_arguments_list<ArgsPack, Start>(L)));
                        ^
/media/dezlow/Drive/Dev/C++/Oneiro/ThirdParty/luabridge3/Source/./LuaBridge/detail/CFunctions.h:753:92: note: in instantiation of function template specialization 'luabridge::detail::function<oe::MainCameraComponent *, std::tuple<>, 1>::call<oe::MainCameraComponent *(oe::World::Entity::*)() noexcept>' requested here
    return function<typename FnTraits::result_type, typename FnTraits::argument_types, 1>::call(
                                                                                           ^
/media/dezlow/Drive/Dev/C++/Oneiro/ThirdParty/luabridge3/Source/./LuaBridge/detail/CFunctions.h:918:28: note: in instantiation of function template specialization 'luabridge::detail::invoke_proxy_functor<oe::MainCameraComponent *(oe::World::Entity::*)() noexcept>' requested here
    lua_pushcclosure_x(L, &invoke_proxy_functor<F>, 1);
                           ^
/media/dezlow/Drive/Dev/C++/Oneiro/ThirdParty/luabridge3/Source/./LuaBridge/detail/Namespace.h:841:29: note: in instantiation of function template specialization 'luabridge::detail::push_member_function<oe::World::Entity, oe::MainCameraComponent *(oe::World::Entity::*)() noexcept, std::enable_if<false>>' requested here
                    detail::push_member_function<T>(L, std::move(functions));
                            ^
/media/dezlow/Drive/Dev/C++/Oneiro/SandBox/Source/SandBoxApp.cpp:62:14: note: in instantiation of function template specialization 'luabridge::Namespace::Class<oe::World::Entity>::addFunction<oe::MainCameraComponent *(oe::World::Entity::*)() noexcept>' requested here
            .addFunction("AddMainCameraComponent", &World::Entity::AddComponent<MainCameraComponent>)
             ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/invoke.h:90:5: note: candidate template ignored: substitution failure [with _Callable = oe::MainCameraComponent *(oe::World::Entity::*&)() noexcept, _Args = <>]: no type named 'type' in 'std::__invoke_result<oe::MainCameraComponent *(oe::World::Entity::*&)() noexcept>'
    __invoke(_Callable&& __fn, _Args&&... __args)

Inheritance and shared_ptr as container.

I was implementing constructors for classes with shared_ptr as container as described here https://kunitoki.github.io/LuaBridge3/Manual#343---shared_ptr-as-container
Problem is that I have two classes, one inherits from another, and this is where things go wrong, unless I misunderstand something.
I created separate test example to verify how things work, so the code may be as follows:

class Base : public std::enable_shared_from_this<Base>
{
public:
  Base(unsigned int id);
// ...
};

class Derived : public Base
{
public:
  Derived(unsigned int id, const std::string& name);
// ...
};

Now, when I create binding like this for Base:

getGlobalNamespace(L)
  .beginClass<Base>("Base")
  .addConstructorFrom<std::shared_ptr<Base>, void(unsigned int)>()
  .endClass();

everything's okay, but when I do

getGlobalNamespace(L)
  .deriveClass<Derived, Base>("Derived")
  .addConstructorFrom<shared_ptr<Derived>, void(unsigned int, const string&)>()
  .endClass();

I get this error:

luabridge\detail\TypeTraits.h(63,5): error G56916779: static assertion failed due to requirement 'std::is_base_of_v<std::enable_shared_from_this<Derived>, Derived>' static_assert(std::is_base_of_v<std::enable_shared_from_this<T>, T>);

First thing I tried is to make multiple inheritance for Derived, i.e.
class Derived : public Base, public std::enable_shared_from_this<Derived>.

Then I get this error

luabridge\detail\TypeTraits.h(69,19): error G417914D0: member 'shared_from_this' found in multiple base classes of different types return t->shared_from_this();

I google about std::enable_shared_from_this and inheritance in general, this seems to be good discussion https://stackoverflow.com/questions/657155/how-to-enable-shared-from-this-of-both-parent-and-derived
So there I found that simplest solution could be to override shared_from_this() in Derived like this:

shared_ptr<Derived> Derived::shared_from_this()
{
  return static_pointer_cast<Derived>(Base::shared_from_this());
}

but when I do it like this, I get std::bad_weak_ptr thrown in runtime when I try to pass instance of Derived into C++ function bound to Lua, more specifically, right when Base::shared_from_this() is called.

After some experimentation, turned that that I don't need to inherit public std::enable_shared_from_this<Derived>, but then I have to comment std::is_base_of_v check at TypeTraits.h(63). And then it works.

Full source of my example is here https://www.dropbox.com/s/qplxkfmmtpbhyla/SharedFromThisTest.zip?dl=0
With TypeTraits.h(63) line commented it builds and runs no problem. As soon as you uncomment the line, you get the static_assert error I posted above.
I'm not sure whether I'm doing everything correct and as intended in this example. But, if compared to RefCountedObjectPtr, it works no problem out of the box with only Base inheriting from it. So I assume something similar should work for shared_ptr as well.

bug:std::numeric_limits<U>::max C4003/min...

File:detail/LuaHelpers.h

func:
is_integral_representable_by
is_floating_point_representable_by

Error example:

std::numeric_limits<U>::max()
std::numeric_limits<U>::min()

fix:

(std::numeric_limits<U>::max)()
(std::numeric_limits<U>::min)()

Reflecting C++ classes in Lua scripts

Before 2019 or so, it was possible to get the _class, _propget, and _propset tables for a class and for a namespace. This allowed Lua scripts to reflect classes and instances, which I used for diagnostic and regression testing. (Also for a class browser to facility easier programming.) However, this access was closed down due (apparently) to security concerns. I now build in extra tables that allow for reflection when I register the classes, but this complicates my registration process when I want to add in a new C++ framework that has challenges (such as overloaded functions) that make it difficult or impossible to include in my standard registration process.

I can imagine that in many contexts (I am thinking games) the Lua environment needs to be relatively locked down. But I am working in a non-hostile environment where the user has to opt in to the Lua environment by installing the Lua plugin, and even then there is little motivation to cheat the system. So I am wondering if LuaBridge3 has an option to expose the class tables and if not, would you consider adding it?

overload template fails

Consider this piece of code

struct Vec
{
public:
	Vec operator+(const Vec& v) const
	{
		Vec a;
		return a;
	}

	template<typename FArg, typename std::enable_if_t<std::is_arithmetic<FArg>::value, int> = 0>
	Vec operator+(FArg v) const
	{
		Vec a;
		return a;
	}
};

luabridge::getGlobalNamespace(L)
.beginNamespace("test")
	.beginClass<Vec>("Vec")
		.addFunction("__add",
			luabridge::constOverload<double>(&Vec::operator+<double>),
			luabridge::constOverload<const Vec&>(&Vec::operator+))
	.endClass()
.endNamespace()
;

Gives this compiler output:

error C3889: call to object of class type 'luabridge::detail::const_overload<const Vec &>': no matching call operator found
note: could be 'unknown-type luabridge::detail::const_overload<const Vec &>::operator ()(R (__cdecl T::* )(const Vec &) const) noexcept const'
note: 'unknown-type luabridge::detail::const_overload<const Vec &>::operator ()(R (__cdecl T::* )(const Vec &) const) noexcept const': could not deduce template argument for 'R'
note: 'unknown-type luabridge::detail::const_overload<const Vec &>::operator ()(R (__cdecl T::* )(const Vec &) const) noexcept const': could not deduce template argument for 'T'

Compiler
Visual Studio 2022 14.35.32215 toolchain
Windows 10.0.18362.0 SDK

double overload succeeds but const Vec& spec fails.

Workaround fix:

.addFunction("__add", (Vec(Vec::*)(const Vec&) const)(&Vec::operator+), (Vec(Vec::*)(double) const)(&Vec::operator+))

Handling of LUA_TNIL and LUA_TNONE with boolean parameters

This a follow-on to issue #82.

Suppose you have this C method registered in Lua:

int my_method(bool arg);

With LB2, any of the following calls in Lua leads to my_method being called with arg == false in C++.

my_method(false)
my_method(nil)
my_method()

I currently have a constructor function with a boolean parameter that is omitted by the Lua code. LB3 is calling the constructor with true rather than false! (!!!!) This is perhaps the most wrong of any possible choice. I see only two possible acceptable behaviors if a boolean parameter is missing or nil:

  1. Pass false to the C function like LB2 does.
  2. Throw (or return) a run-time error.

If I were starting from scratch I might vote for option 2. But my project has been going a long time with the assumption of option 1. Many of the constructors and methods have trailing optional boolean parameters that C++ defaults to false. The option 1 behavior allows those trailing booleans also to be optional in Lua for free.

For my project, switching to LuaBridge3 (which I would very much like to do) is contingent on no breaking changes to the many thousands of lines of code written for Lua on Finale over the last nearly 20 years. Option 2 would be an unacceptable breaking change. I'm a believer in flexibility and options, so perhaps the choice between 1 & 2 should be both well-defined and documented (which I don't think it is at the moment) and selectable by means of a compile-time macro.

Userdata::get triggers the lua panic handler when called from C++

The method will trigger the panic handler if a cast cannot be made, this is fine when running in lua, but when executed from C++ it aborts the application when building without exceptions.

Suggestion is to have Userdata::get to return a TypeResult<T*> instead of T* (and eventually making TypeResult to hold an error string).

PANIC: unprotected error in call to Lua API (bad light userdata pointer)

Hi!
I ran into a problem when recently I upgraded my project setup to use LuaJIT + LuaBridge 3.
To isolate it, I wrote a simple test:

#include "lua.hpp"
#include "LuaBridge.h"

class Test
{
public:
  static int sum(int x, int y)
  {
    return x + y;
  }
};

int main()
{
  auto L = luaL_newstate();
  luaL_openlibs(L);

  luabridge::getGlobalNamespace(L)
    .beginClass<Test>("Test")
      .addStaticFunction("sum", &Test::sum)
    .endClass();
}

And then I get the error stated in the title. It occurs as soon as beginClass() gets called.
Might be important to note here, that it doesn't occur with regular Lua and also it doesn't occur with LuaJIT + LuaBridge 2.8.
Also might be important that any other stuff I do works great, i.e. I can do .beginNamespace(), addFunction() etc. no problem. The error caused ONLY by beginClass() according to what I have tested so far.
LuaJIT version I used is 2.1.0-beta3, and I also noticed LuaBridge 3 tests running on that same LuaJIT version pass just fine, so I added this test code as a separate test in ClassTests.cpp and it worked correctly. So I suppose I'm not doing something important in Lua initialization, or maybe it's some #define's I need, I'm not sure.

How to compare two LuaRefs with the same content ?

c++ code:

LuaRef function_ref=luabridge::getGlobal(lua_state,"compare_game_object");
if(function_ref.isFunction()){
    auto game_object=new GameObject();
    function_ref(game_object,game_object);
}

lua code:

function compare_game_object(a,b)
    print(a,b)
    print(a==b)
end

result:

userdata: 000002790b546a28      userdata: 000002790b546a78
false

An easy way to overload functions

I recently migrated from Sol2 to Luabridge3 because this library is very lightweight, specially in compiling time. But Sol2 overload feature is fantastic.

In Luabridge3 I can do overload functions all manually, like these:

.addFunction("addVertex", +[](Polygon* self, lua_State* L) -> void { 
            if (lua_gettop(L) != 2 && lua_gettop(L) != 3) throw luaL_error(L, "incorrect argument number");
            if (lua_isnumber(L, 2) && lua_isnumber(L, 3)) self->addVertex(lua_tonumber(L, 2), lua_tonumber(L, 3));
            else if (luabridge::Stack<Vector3>::isInstance(L, -1)) self->addVertex(luabridge::Stack<Vector3>::get(L, -1));
            else throw luaL_error(L, "incorrect argument type");
            })
.addFunction("getBone", +[](Model* self, lua_State* L) -> Bone { 
            if (lua_gettop(L) != 2) throw luaL_error(L, "incorrect argument number");
            if (lua_isinteger(L, -1)) return self->getBone(lua_tointeger(L, -1));
            if (lua_isstring(L, -1)) return self->getBone(lua_tostring(L, -1));
            throw luaL_error(L, "incorrect argument type");
            })

It does the job but it is not easy as sol2.

I don't have much experience in modern C++ and I do not know if it is really possible. But would be interesting a template function like this pseudocode:

template<Ret, Func1, Func2, ...>
Ret overload(lua_State *L){
    if (lua_tointeger(L, 1))
        Func1(lua_tointeger(L, 1));

    if (lua_tostring(L, 1))
        Func2(lua_tostring(L, 1));
}

And use this in Luabridge to overload:

luabridge::getGlobalNamespace (L)
  .beginNamespace ("test")
    .addFunction ("foo", overload<(void (*)(int))&foo, (void (*)(std::string))&foo>)
  .endNamespace ();

Also I opened a question in Stackoverflow (https://stackoverflow.com/questions/73799816/how-overload-c-functions-in-lua-using-generic-function-template) about this.

No arg error thrown for bool stack get

Stack<bool>::get does not check whether an actual value exists on stack. This results in LuaBridge silently treating a no value as a valid bool and not throwing any error.

See for example Stack<float> which has a check and reports if a non-float value (or no value) is on stack.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.