LuaBridge3 Documentation

repository·master·Indexed 18 days ago

https://github.com/kunitoki/luabridge3

A high-performance, headers-only C++17 library for binding C++ data, functions, and classes to Lua and its derivatives, including PUC-Lua (5.1.5 to 5.5.0), LuaJIT 2.1, Luau 0.713, and Ravi 1.0-beta11. It provides type-safe access to the Lua stack, automatic binding for function overloading, and support for STL containers, smart pointers, and modern C++ features up to C++23.

Tokens
37.2K
Snippets
115
Records
148
Agent score
63%

What's inside LuaBridge3

  1. Overview of LuaBridge 3.0

    master

    LuaBridge3 is a lightweight, dependency-free, headers-only C++ library designed for mapping data, functions, and classes between C++ and Lua. It is highly optimized for performance and is compatible with a wide range of Lua implementations, making it suitable for game development and embedded scripting.

    Supported Lua Implementations:

    • PUC-Lua: 5.1.5, 5.2.4, 5.3.6, 5.4.8, and 5.5.0
    • LuaJIT: 2.1
    • Luau: 0.713
    • Ravi: 1.0-beta11

    Key Features:

    • Headers-only: No build system or .cpp files required; just #include the headers.
    • C++17 Compliant: Requires a C++17 compatible compiler.
    • Type-safe: Provides convenient, type-safe access to the Lua stack.
    • Automatic Binding: Supports automatic function parameter type binding and overloading (functions, constructors, and static functions).
    • Modern C++ Support: Interoperable with STL containers, smart pointers (std::shared_ptr, std::unique_ptr), and modern C++17/20/23 features.
  2. Overview of LuaBridge3 capabilities

    master

    LuaBridge3 provides a lightweight mechanism for mapping data, functions, and classes between C++ and Lua. The library's functionality is categorized into several core areas:

    • Accessing C++ from Lua: Registering namespaces, functions, properties, classes, constructors, and managing the Lua stack or coroutines.
    • Passing Objects: Managing object lifetime models between C++ and Lua (C++ lifetime, Lua lifetime, and shared lifetime).
    • Accessing Lua from C++: Using LuaRef, table proxies, calling Lua functions, and wrapping callables.
    • Security: Utilizing a metatable security system to control access, which can be relaxed for trusted scripts.
    • Configuration: Using compile-time configuration macros to tune library behavior.
  3. Accessing Lua from C++ with LuaBridge3

    master

    LuaBridge3 provides specialized classes and functions to bridge the gap between C++ and the dynamically typed Lua environment. Because Lua is dynamically typed, you must use specific LuaBridge3 abstractions to map Lua values to C++ types safely.

    Key areas of functionality include:

    • Value Referencing: Using LuaRef to manage the lifetime and type conversion of Lua values in C++.
    • Table Manipulation: Using Table Proxies to interact with Lua tables using C++ syntax.
    • Function Invocation: Calling Lua functions from C++ and handling exceptions or errors.
    • C++ to Lua Binding: Wrapping C++ callables (like lambdas and functions) so they can be invoked from within Lua.
  4. Register Static and Member Coroutines in Classes

    master

    You can attach coroutines to registered classes using addStaticCoroutine and addCoroutine.

    Static Coroutines

    Registered with addStaticCoroutine, these live in the class's static table. The factory lambda does not require an object instance as an argument.

    Member Coroutines

    Registered with addCoroutine, these bind to individual class instances.

    • Argument Requirement: The first argument of the factory lambda must be T* or const T* (where T is the class type). LuaBridge automatically passes the Lua object as this argument.
    • Const vs Non-Const:
      • A factory taking const T* is a const method: accessible on both const and non-const objects.
      • A factory taking T* is a non-const method: accessible on non-const objects only.
    // Static Coroutine
    .beginClass<Counter>("Counter")
        .addStaticCoroutine("range", [](int from, int count) -> luabridge::CppCoroutine<int>
        {
            for (int i = 0; i < count; ++i)
                co_yield from + i;
            co_return -1;
        })
    .endClass();
    
    // Member Coroutine (Non-const)
    .beginClass<Counter>("Counter")
        .addCoroutine("pop", [](Counter* obj) -> luabridge::CppCoroutine<int>
        {
            co_yield obj->value--;
            co_return obj->value;
        })
    .endClass();
  5. Understand object lifetime models in LuaBridge3

    master

    LuaBridge3 supports three primary lifetime models when passing C++ objects to Lua. Choosing the correct one is critical to avoid undefined behavior (e.g., accessing deleted memory or memory leaks).

    1. C++ Lifetime (Reference/Pointer)

    Controlled by C++ code. Lua holds a reference to the object, but Lua's garbage collector will not call the C++ destructor.

    • Use when: The object is owned by a C++ scope or manager.
    • Risk: If the C++ object is deleted while Lua still holds a reference, accessing it from Lua causes undefined behavior.
    • Passed via: T*, const T*, T&, or const T&.

    2. Lua Lifetime (Value)

    Controlled by Lua's garbage collector. A copy of the object is constructed inside Lua userdata.

    • Use when: You want Lua to own a copy of the data.
    • Risk: If C++ holds a reference to this object after Lua garbage collects it, accessing it from C++ causes undefined behavior.
    • Passed via: T or const T.

    3. Shared Lifetime (Reference Counted)

    Ownership is shared between Lua and C++. The object exists until both the C++ reference count and Lua's garbage collector release it.

    • Use when: Using smart pointers like std::shared_ptr or boost::shared_ptr.
    • Requirement: Classes must derive from std::enable_shared_from_this or boost::enable_shared_from_this to allow safe reconstruction from raw pointers.
    // C++ Lifetime (Pointer)
    A a;
    luabridge::push(L, &a); 
    
    // Lua Lifetime (Value/Copy)
    B b;
    luabridge::push(L, b); 
  6. How LuaBridge 3 security protects userdata and metatables

    master

    LuaBridge 3 implements a security system to prevent scripts from causing undefined behavior by manipulating the environment. The system relies on several mechanisms:

    • Table Proxies: Class and const class tables use the table proxy technique. Their metatables include __index and __newindex metamethods, making these tables immutable from Lua.
    • Metatable Hiding: Metatables have __metatable set to false, preventing scripts from retrieving them via getmetatable.
    • Registry Mapping: Classes are mapped to metatables via the Lua registry, which is inaccessible to Lua scripts.
    • Unique Tagging: Metatables are tagged with a unique lightuserdata key, preventing other libraries from forging LuaBridge metatables.
    • Type Checking: When calling member functions or accessing properties, LuaBridge type-checks the this pointer to prevent undefined behavior caused by Lua's dynamic typing.

    Security Bypasses:

    • Scripts with access to the debug library or a raw getmetatable can bypass these protections.
    • Host C code can defeat the system by revealing the unique lightuserdata key or placing a metatable in a script-accessible location.
  7. Use property member proxies to expose third-party class data

    master

    When working with third-party libraries where class declarations cannot be modified, you may encounter data that cannot be accessed via standard pointers-to-members (e.g., array elements or functions with incompatible signatures).

    LuaBridge allows you to use property member proxies to bridge this gap. A proxy consists of a pair of 'flat' functions (a getter and a setter) that take a pointer to the object as their first parameter. This allows you to expose complex or inaccessible C++ data as familiar object properties in Lua.

    // Example: Using a helper class to proxy array access
    struct Vec {
      float coord[3];
    };
    
    struct VecHelper {
      template <unsigned index>
      static float get(Vec const* vec) { return vec->coord[index]; }
    
      template <unsigned index>
      static void set(Vec* vec, float value) { vec->coord[index] = value; }
    };
    
    luabridge::getGlobalNamespace(L)
      .beginNamespace("test")
        .beginClass<Vec>("Vec")
          .addProperty("x", &VecHelper::get<0>, &VecHelper::set<0>)
          .addProperty("y", &VecHelper::get<1>, &VecHelper::set<1>)
          .addProperty("z", &VecHelper::get<2>, &VecHelper::set<2>)
        .endClass()
      .endNamespace();
  8. Manipulate Lua tables using Table Proxies

    master

    In LuaBridge, the luabridge::LuaRef class uses a mechanism called table proxies to allow C++ developers to interact with Lua tables using standard array indexing syntax ([]).

    When you apply the [] operator to a luabridge::LuaRef that represents a table, it returns a temporary proxy object. This proxy allows you to:

    1. Access values: Use any convertible type (string, integer, etc.) as a key to retrieve a value.
    2. Assign values: Assigning to a proxy (e.g., v[key] = value) modifies the underlying Lua table.
    3. Remove values: Assigning luabridge::LuaNil() to a key removes that entry from the table.

    Note that table proxies are compiler-created temporary objects; you should not attempt to store or work with them directly. They are designed to make Lua table manipulation conform to C++ idioms.

    luabridge::LuaRef v (L);
    v = luabridge::newTable (L);
    
    v ["name"] = "John Doe";             // string key, string value
    v [1] = 200;                         // integer key, integer value
    v [2] = luabridge::newTable (L);     // integer key, LuaRef value
    v [3] = v [1];                      // assign 200 to integer index 3
    v [1] = 100;                         // v[1] is 100, v[3] is still 200
    v [3] = v [2];                      // v[2] and v[3] reference the same table
    v [2] = luabridge::LuaNil ();        // Removes the value with key = 2.
  9. Use Constructor Proxies for complex or hidden arguments

    master

    If a constructor requires arguments that cannot be exposed to Lua (e.g., internal pointers) or requires manual stack manipulation, use the addConstructor overload that accepts a functor (lambda).

    This functor must use placement new to construct the object into the provided void* ptr.

    Key features:

    • Custom Arguments: You can capture C++ variables in the lambda to pass them to the constructor.
    • Lua Stack Access: You can include lua_State* as the last parameter in the functor to manually inspect the Lua stack (e.g., using lua_checkinteger).
    • Overloading: You can provide multiple functors to addConstructor. LuaBridge will attempt them in order until one succeeds.
    // Example: Using a proxy to pass a hidden pointer and manual stack checking
    luabridge::getGlobalNamespace (L)
      .beginNamespace ("test")
        .beginClass<HardToCreate> ("HardToCreate")
          .addConstructor ([] (void* ptr, lua_State* L) {
            return new (ptr) HardToCreate (shouldNotSeeMe, lua_checkinteger (L, 2));
          })
        .endClass ()
      .endNamespace ();
  10. Configure read-only and read-write properties

    master

    Properties allow Lua to interact with C++ variables or getter/setter pairs.

    Read-Only Properties

    • Direct Variables: Pass only the pointer to the variable: .addProperty("name", &variable).
    • Getter Functions: Pass only a getter function: .addProperty("name", getterFunc).
    • Explicit Read-Only: You can mark a direct variable as read-only by passing false as the second optional parameter: .addProperty("name", &variable, false).

    Read-Write Properties

    • Direct Variables: Pass the pointer twice (once for get, once for set): .addProperty("name", &variable, &variable).
    • Getter/Setter Pairs: Pass a getter and a setter function: .addProperty("name", getterFunc, setterFunc).
    • Tables/Tuples: C++ containers like std::tuple can be mapped to Lua tables. If a setter is provided, Lua tables can be converted back to the C++ type.
    // Read-only variable
    .addProperty ("var1", &globalVar)
    
    // Read-write variable
    .addProperty ("var2", &staticVar, &staticVar)
    
    // Read-only via getter
    .addProperty ("prop1", getString)
    
    // Read-write via getter/setter
    .addProperty ("prop2", getString, setString)
  11. How registration and LuaRef work in LuaBridge3

    master

    LuaBridge3 uses a process called registration to make C++ concepts like variables and classes available to Lua. This process uses C++ template metaprogramming to automatically generate the necessary Lua C API calls at compile-time.

    To access Lua objects from C++ (such as numbers, strings, or tables), use the luabridge::LuaRef class. LuaRef provides a clean way to interact with the Lua stack, making it easy to call Lua functions or access table values from your C++ code.

  12. What can be registered in LuaBridge 3

    master

    To expose C++ functionality to Lua, you must register specific types of objects. LuaBridge supports five primary registration types:

    1. Namespaces: Implemented as Lua tables that act as containers for other registrations (like classes or functions).
    2. Data: Exposes global or static variables, as well as data members and static data members.
    3. Functions: Exposes regular functions, member functions, and static member functions.
    4. CFunctions: Exposes functions that follow the lua_CFunction calling convention (regular, member, or static).
    5. Properties: Exposes global, member, or static properties. These behave like data in Lua but are implemented in C++ using getter and setter functions.

    Note on Read-Only access: Both Data and Properties can be marked as read-only during registration. This prevents Lua scripts from modifying the values while still allowing the C++ side to update them. This is distinct from C++ const semantics.