LuaCpp

repository·main·Indexed 42 days ago

https://github.com/jordanvrtanoski/luacpp

A high-level C++ wrapper for the Lua liblua C API that simplifies embedding a Lua interpreter into C++ projects. It provides managed classes for context management via LuaContext, global variable synchronization, and object instrumentation using LuaMetaObject. The library includes a state pooling system to optimize performance for high-frequency script execution, supporting predefined pool colors (default, sandboxed, minimal, io) and custom PoolConfig.

Tokens
7.9K
Snippets
24
Records
27
Agent score
50%

What's inside LuaCpp

  1. Use State Pooling for high performance

    main

    For scenarios requiring frequent Lua script execution, use state pooling to avoid the overhead of repeated memory allocation and library loading. RunPooled() automatically acquires a state from the pool and returns it when finished.

    Predefined Pool Colors

    ColorLibrariesUse Case
    "default"All standardGeneral scripting
    "sandboxed"base, math, string, tableSafe execution
    "minimal"base onlyMaximum performance
    "io"base, io, osFile operations

    Custom Pools

    You can define your own pool configuration using PoolConfig:

    PoolConfig config;
    config.libraries = {"base", "math"};
    config.maxSize = 3;
    ctx.createPool("math_only", config);
    ctx.RunPooled("calculation", "math_only");
    #include <LuaCpp.hpp>
    #include <iostream>
    
    using namespace LuaCpp;
    
    int main(int argc, char **argv) {
    	LuaContext ctx;
    
    	// Compile a script
    	ctx.CompileString("hello", "print('Hello from pooled state!')");
    
    	// Execute using a pooled state
    	try {
    		ctx.RunPooled("hello");
    	}
    	catch (std::runtime_error& e) {
    		std::cout << e.what() << '\n';
    	}
    }
  2. Understand Lua State Pooling with Colored Pools

    main

    Lua State Pooling is an opt-in system designed to reduce the overhead of creating and destroying lua_State instances. Instead of the default behavior where every execution creates a new state, you can use "colored pools" to reuse states that share the same configuration (libraries, globals, and hooks).

    Key Concepts

    • Pool Color: A string identifier representing a specific configuration. Different colors allow you to have different pools (e.g., a sandboxed pool with limited libraries and a default pool with all standard libraries).
    • State Reuse: When a state is released back to a pool, it is reset (clearing the stack, resetting hooks, and restoring globals) before being made available for the next request.
    • Thread Safety: Pooling is not thread-safe by default. You must explicitly enable it via the PoolManager to avoid synchronization issues when multiple threads acquire/release states.

    Predefined Colors

    ColorLibrariesUse Case
    "default"all standardGeneral scripting
    "sandboxed"base, math, string, tableUntrusted code
    "minimal"base onlyMaximum performance
    "io"base, io, osFile/system operations
  3. How LuaTTable::getValue handles missing keys

    main

    The LuaTTable::getValue(Table::Key key) method is used to retrieve values from a Lua table.

    Behavior for missing keys: If the requested key does not exist in the table, the method returns a reference to a static LuaTNil placeholder. This ensures that missing keys return a nil value as documented, rather than causing undefined behavior through null pointer dereferencing.

    Implementation Detail: The method uses table.find(key) to check for existence. This avoids the side effect of std::map::operator[], which would otherwise insert a default-constructed entry into the map if the key was missing.

    LuaType &LuaTTable::getValue(Table::Key key) {
        auto it = table.find(key);
        if (it == table.end()) {
            static LuaTNil nilPlaceholder;
            return nilPlaceholder;
        }
        return *(it->second);
    }
  4. Use nullptr instead of NULL for modern C++ compatibility

    main

    LuaCpp follows modern C++ best practices by using nullptr instead of the deprecated NULL macro. This provides type safety (std::nullptr_t), avoids ambiguity with integer 0, and ensures correct template deduction.

    When to use nullptr:

    • When initializing std::shared_ptr (e.g., std::shared_ptr<LuaLibrary> lib = nullptr;).
    • When working with LuaTUserData (e.g., userdata(nullptr)).
    • In unit tests (e.g., EXPECT_NE(nullptr, pointer);).

    When to use NULL:

    • Only when interacting with the standard Lua C API where the API specifically requires NULL (for example, when defining luaL_Reg struct sentinel values or passing parameters to lua_load()).
  5. Instrument C++ objects with LuaMetaObject

    main

    To make existing C++ objects behave like native Lua types without moving all data into the Lua engine, you can use LuaMetaObject.

    To instrument a class, it must:

    1. Inherit from LuaMetaObject.
    2. Implement three methods: Exists(const std::string &name), getValue(std::string &key), and setValue(std::string &key, std::shared_ptr<LuaType> val).
    class MetaMap : public LuaMetaObject {
    	public:
    	        std::map<std::string, std::shared_ptr<LuaType>> values;
    		MetaMap() : values() {}
    		bool inline Exists(const std::string &name) {
    			return !(values.find( name ) == values.end());
    		}
    
    		std::shared_ptr<LuaType> getValue(std::string &key) {
    			if (Exists(key)) {
    				return values[key];
    			}
    			return std::make_shared<LuaTNil>();
    		}
    
    		void setValue(std::string &key, std::shared_ptr<LuaType> val) {
    			values[key] = val;
    		}
    };
  6. Instrument C++ objects using LuaMetaObject

    main

    To make existing C++ objects behave like native Lua types without moving their data into the Lua engine, inherit from LuaMetaObject and implement the following three methods:

    • bool Exists(const std::string &name)
    • std::shared_ptr<LuaType> getValue(std::string &key)
    • void setValue(std::string &key, std::shared_ptr<LuaType> val)

    This allows the Lua engine to interact with the object directly via these interface methods.

    class MetaMap : public LuaMetaObject {
    	public:
    		std::map<std::string, std::shared_ptr<LuaType>> values;
    		MetaMap() : values() {}
    		
    		bool inline Exists(const std::string &name) {
    			return !(values.find( name ) == values.end());
    		}
    
    		std::shared_ptr<LuaType> getValue(std::string &key) {
    			if (Exists(key)) {
    				return values[key];
    			}
    			return std::make_shared<LuaTNil>();
    		}
    
    		void setValue(std::string &key, std::shared_ptr<LuaType> val) {
    			values[key] = val;
    		}
    };
  7. How state pooling works in LuaCpp

    main

    State pooling is a performance optimization that reuses lua_State instances across multiple script executions. Instead of allocating memory and loading libraries for every execution, states are borrowed from a pool and returned after use. This is particularly beneficial for high-frequency script execution or when running the same script repeatedly, as it amortizes memory allocation and library loading costs.

    When to use pooling

    • High-frequency script execution: Use pooling.
    • Repeated script execution: Use pooling.
    • Multi-threaded environments: Use pooling with thread safety enabled.
    • One-off scripts: Regular Run() is sufficient.
    • Different library requirements: Use different colored pools.
    #include <LuaCpp.hpp>
    
    using namespace LuaCpp;
    
    int main() {
        LuaContext ctx;
        
        // Compile a script
        ctx.CompileString("hello", "print('Hello from pooled state!')");
        
        // Execute using a pooled state
        ctx.RunPooled("hello");
        
        // The state is automatically returned to the pool
        return 0;
    }
  8. Quickstart: Execute Lua code in C++

    main

    To quickly integrate Lua into your C++ project, use the LuaContext class. The simplest way to run code is via CompileStringAndRun(), which compiles and executes a Lua string immediately.

    Note on Thread Safety: LuaContext is not thread-safe because the underlying Lua engine is not thread-safe. In multithreaded environments, create a separate LuaContext instance for each thread.

    #include <LuaCpp.hpp>
    #include <iostream>
    
    using namespace LuaCpp;
    using namespace LuaCpp::Registry;
    using namespace LuaCpp::Engine;
    
    int main(int argc, char **argv) {
    	std::cout << "Hi from C++" << "\n";
    	LuaContext lua;
    
    	try {
    		lua.CompileStringAndRun("print('The fastest way to start using lua in a project')");
    	}
    	catch (std::runtime_error& e) {
    		std::cout << e.what() << '\n';
    	}
    }
  9. Build documentation (PDF)

    main

    To generate the full reference and user manual in PDF format, you must have texlive-latex-base, texlive-latex-recommended, texlive-latex-extra, and graphviz installed on your system.

    sudo apt-get install texlive-latex-base texlive-latex-recommended texlive-latex-extra graphviz
    
    mkdir build
    cd build
    cmake ../Source
    make doc_pdf
  10. Manage Lua states manually with Acquire and Release

    main

    For scenarios where you need to execute multiple scripts on the same state (to maintain stateful data between calls), use manual acquire/release.

    Warning: You must manually return the state to the pool using ReleasePooledState.

    For exception-safe management, use AcquirePooledStateRAII, which automatically returns the state to the pool when the wrapper goes out of scope.

    // Manual management
    auto state = ctx.AcquirePooledState("default");
    ctx.RunWithEnvironmentPooled("init", LuaEnvironment(), "default");
    ctx.ReleasePooledState(std::move(state), "default");
    
    // RAII management (Recommended for safety)
    {
        auto pooled = ctx.AcquirePooledStateRAII("default");
        ctx.RunPooled("work");
    }
  11. Execute Lua code in C++ using LuaContext

    main

    The LuaContext class is the primary high-level abstraction for interacting with the Lua engine. It allows you to execute Lua code from strings or files.

    To optimize performance, LuaCpp compiles and stores code in an internal registry for reuse. However, CompileStringAndRun() and CompileFileAndRun() will always recompile the provided code under the name default.

    Thread Safety Warning: Lua is not thread-safe, and consequently, LuaContext instances are not thread-safe. In multithreaded environments, you should create a separate LuaContext instance for each thread.

    #include <LuaCpp.hpp>
    #include <iostream>
    
    using namespace LuaCpp;
    using namespace LuaCpp::Registry;
    using namespace LuaCpp::Engine;
    
    int main(int argc, char **argv) {
    	LuaContext lua;
    
    	// The simplest way to start
    	try {
    		lua.CompileStringAndRun("print('The fastest way to start using lua in a project')");
    	}
    	catch (std::runtime_error& e) {
    		std::cout << e.what() << '\n';
    	}
    }