# LuaJIT Go Wrapper This is a Go wrapper for LuaJIT that makes it easy to embed Lua in your Go applications. We've focused on making it both performant and developer-friendly, with an API that feels natural to use. ## What's This For? This wrapper lets you run Lua code from Go and easily pass data back and forth between the two languages. You might want this if you're: - Adding scripting support to your application - Building a game engine - Creating a configuration system - Writing an embedded rules engine - Building test automation tools ## Get Started First, grab the package: ```bash go get git.sharkk.net/Sky/LuaJIT-to-Go ``` You'll need LuaJIT's development files, but don't worry - we include libraries for Windows and Linux in the vendor directory. Here's the simplest thing you can do: ```go L := luajit.New() defer L.Close() defer L.Cleanup() err := L.DoString(`print("Hey from Lua!")`) ``` ## Working with Bytecode Need even more performance? You can compile your Lua code to bytecode and reuse it: ```go // Compile once bytecode, err := L.CompileBytecode(` local function calculate(x) return x * x + x + 1 end return calculate(10) `, "calc") // Execute many times for i := 0; i < 1000; i++ { err := L.LoadAndRunBytecode(bytecode, "calc") } // Or do both at once err := L.CompileAndRun(`return "hello"`, "greeting") ``` ### When to Use Bytecode Bytecode execution is consistently faster than direct execution: ``` Benchmark Ops/sec Comparison ---------------------------------------------------------------------------- BenchmarkSimpleDoString 2,561,012 Base BenchmarkSimplePrecompiledBytecode 3,828,841 +49.5% faster BenchmarkFunctionCallDoString 2,021,098 Base BenchmarkFunctionCallPrecompiled 3,482,074 +72.3% faster BenchmarkLoopDoString 188,119 Base BenchmarkLoopPrecompiled 211,081 +12.2% faster BenchmarkTableOperationsDoString 84,086 Base BenchmarkTableOperationsPrecompiled 93,655 +11.4% faster BenchmarkComplexScript 33,133 Base BenchmarkComplexScriptPrecompiled 41,044 +23.9% faster ``` Use bytecode when you: - Have code that runs frequently - Need maximum performance - Want to precompile your Lua code - Are distributing Lua code to many instances ## Sandboxed Execution For more controlled execution, the sandbox provides isolation and environment management: ```go // Create a sandbox sandbox := luajit.NewSandbox() defer sandbox.Close() // Initialize the environment err := sandbox.Initialize() // Register a function adder := func(s *luajit.State) int { sum := s.ToNumber(1) + s.ToNumber(2) s.PushNumber(sum) return 1 } sandbox.RegisterFunction("add", adder) // Run code in the sandbox result, err := sandbox.Run(` local result = add(40, 2) return "The answer is: " .. result `) fmt.Println(result) // "The answer is: 42" // Add permanent functions to the environment sandbox.AddPermanentLua(` function square(x) return x * x end `) // Run bytecode for better performance bytecode, _ := sandbox.Compile(`return square(10)`) result, _ = sandbox.RunBytecode(bytecode) ``` ### When to Use the Sandbox Use the sandbox when you need: - Isolated execution environments - Persistent state between executions - Control over available libraries and functions - Enhanced security for untrusted code - Better resource management for long-running applications ## Registering Go Functions Want to call Go code from Lua? It's straightforward: ```go // This function adds two numbers and returns the result adder := func(s *luajit.State) int { sum := s.ToNumber(1) + s.ToNumber(2) s.PushNumber(sum) return 1 // we're returning one value } L.RegisterGoFunction("add", adder) ``` Now in Lua: ```lua result = add(40, 2) -- result = 42 ``` ## Working with Tables Lua tables are pretty powerful - they're like a mix of Go's maps and slices. We make it easy to work with them: ```go // Go → Lua stuff := map[string]any{ "name": "Arthur Dent", "age": 30, "items": []float64{1, 2, 3}, } L.PushTable(stuff) // Lua → Go L.GetGlobal("some_table") result, err := L.ToTable(-1) ``` ## Error Handling We provide useful errors instead of mysterious panics: ```go if err := L.DoString("this isn't valid Lua!"); err != nil { if luaErr, ok := err.(*luajit.LuaError); ok { fmt.Printf("Error: %s\n", luaErr.Message) } } ``` ## Memory Management The wrapper uses a custom table pooling system to reduce GC pressure when handling many tables: ```go // Tables are pooled and reused internally for better performance for i := 0; i < 1000; i++ { L.GetGlobal("table") table, _ := L.ToTable(-1) // Use table... L.Pop(1) // Table is automatically returned to pool } ``` The sandbox also manages its environment efficiently: ```go // Environment objects are pooled and reused for i := 0; i < 1000; i++ { result, _ := sandbox.Run("return i + 1") } ``` ## Best Practices ### State Management - Always use `defer L.Close()` and `defer L.Cleanup()` to prevent memory leaks - Each Lua state should stick to one goroutine - For concurrent operations, create multiple states - You can share functions between states safely - Keep an eye on your stack management - pop as many items as you push ### Bytecode Optimization - Use bytecode for frequently executed code paths - Consider compiling critical Lua code to bytecode at startup - For small scripts (< 1024 bytes), direct execution might be faster ### Sandbox Usage - Call `Initialize()` before first use of a sandbox - Use `defer sandbox.Close()` to ensure proper cleanup - For persistent objects, add them with `SetGlobal()` - Control available libraries with `AddModule()` - For frequently used functions, use `RegisterFunction()` - Reset the environment with `ResetEnvironment()` if state gets corrupted - For maximum performance, compile scripts to bytecode first, then run with `RunBytecode()` ## Advanced Features ### Bytecode Serialization You can serialize bytecode for distribution or caching: ```go // Compile once bytecode, _ := L.CompileBytecode(complexScript, "module") // Save to file ioutil.WriteFile("module.luac", bytecode, 0644) // Later, load from file bytecode, _ := ioutil.ReadFile("module.luac") L.LoadAndRunBytecode(bytecode, "module") ``` ### Closures and Upvalues Bytecode properly preserves closures and upvalues: ```go code := ` local counter = 0 return function() counter = counter + 1 return counter end ` bytecode, _ := L.CompileBytecode(code, "counter") L.LoadAndRunBytecodeWithResults(bytecode, "counter", 1) L.SetGlobal("increment") // Later... L.GetGlobal("increment") L.Call(0, 1) // Returns 1 L.Pop(1) L.GetGlobal("increment") L.Call(0, 1) // Returns 2 ``` ### Sandbox Environment Customization You can tailor the sandbox environment for specific needs: ```go // Add a custom module myModule := map[string]any{ "version": "1.0", "config": map[string]any{ "debug": true, "maxItems": 100, }, } sandbox.AddModule("myapp", myModule) // Add permanent Lua code sandbox.AddPermanentLua(` -- Utility functions available to all scripts function myapp.formatItem(item) return string.format("%s: %d", item.name, item.value) end `) // Use in script result, _ := sandbox.Run(` local item = {name="test", value=42} return myapp.formatItem(item) `) ``` ## Performance Considerations - The sandbox adds a small overhead to execution but provides better isolation - For maximum performance with the sandbox, use bytecode compilation - The wrapper optimizes for common operations with specialized C helpers - For small scripts (<1KB), direct execution with `DoString` is fastest - For larger scripts or repeated execution, bytecode provides better performance - Table operations are optimized for both array-like and map-like tables ## Need Help? Check out the tests in the repository - they're full of examples. If you're stuck, open an issue! We're here to help. ## License MIT Licensed - do whatever you want with it!