https://github.com/rosslight/darp-luau
Darp.Luau is a .NET wrapper around Luau focused on native AOT compatibility, typed value access, and explicit ownership for Luau-backed references.
https://github.com/rosslight/darp-luau
csharp dotnet lua luau nativeaot
Last synced: 3 months ago
JSON representation
Darp.Luau is a .NET wrapper around Luau focused on native AOT compatibility, typed value access, and explicit ownership for Luau-backed references.
- Host: GitHub
- URL: https://github.com/rosslight/darp-luau
- Owner: rosslight
- License: apache-2.0
- Created: 2026-01-26T10:58:42.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-03-23T16:24:36.000Z (3 months ago)
- Last Synced: 2026-03-24T09:13:28.439Z (3 months ago)
- Topics: csharp, dotnet, lua, luau, nativeaot
- Language: C#
- Homepage: https://rosslight.github.io/darp-luau/
- Size: 334 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
Awesome Lists containing this project
README
# Darp.Luau
[](https://www.nuget.org/packages/Darp.Luau)
[](https://www.nuget.org/packages/Darp.Luau)


`Darp.Luau` is a .NET wrapper around [Luau](https://luau.org/) focused on native AOT compatibility, typed value access, and explicit ownership for Luau-backed references.
## Why another lua library
- NativeAOT first
- Typed reads and writes for tables, functions, userdata, strings, and buffers
- Clear lifetime guarantees both stability and performance
- Simple API through source-generated interceptors
- Custom libraries and managed userdata
- Support for `linux`,`windows`,`macos` on both `x64`,`arm64`
## Quick start
```csharp
using Darp.Luau;
using var lua = new LuauState();
using LuauFunction log = lua.CreateFunction((string message) => Console.WriteLine(message));
lua.Globals.Set("log", log);
using LuauTable config = lua.CreateTable();
config.Set("name", "Ada");
config.Set("enabled", true);
lua.Globals.Set("config", config);
lua.Load(
"""
function add(a, b)
return a + b
end
log(config.name)
result = add(20, 22)
"""
).Execute();
double result = lua.Globals.GetNumber("result");
```
## Call Lua functions from C#
```csharp
using LuauFunction add = lua.Globals.GetLuauFunction("add");
double sum = add.Invoke(1, 2);
using LuauFunction pair = lua.Globals.GetLuauFunction("pair");
(int total, int delta) = pair.Invoke(20, 4);
```
`Invoke(...)` converts a single Luau return value to the managed type you ask for and ignores extras. Use `Invoke(...)`, ... for typed multi-return calls, and `InvokeMulti(...)` for raw `LuauValue[]` access. The current argument buffer accepts up to 4 arguments per call.
`Load(...).Execute(...)` follows the same return-shaping pattern for chunk execution: use `Load(...).Execute()` for the first typed return value, `Load(...).Execute()`, ... for typed multi-return calls, and `Load(...).ExecuteMulti()` for raw `LuauValue[]` access.
```csharp
(int total, int delta) = lua.Load("return 20, 4").Execute();
```
## Expose managed callbacks
Use `CreateFunction(...)` for supported fixed delegate signatures:
```csharp
using LuauFunction sum = lua.CreateFunction((int a, int b) => a + b);
lua.Globals.Set("sum", sum);
using LuauFunction pair = lua.CreateFunction((int a, int b) => (a + b, a - b));
lua.Globals.Set("pair", pair);
```
Use `CreateFunctionBuilder(...)` when you need manual argument parsing, explicit user-facing errors, or a callback shape that the generator-backed path does not support:
```csharp
using LuauFunction pair = lua.CreateFunctionBuilder(static args =>
{
if (!args.TryValidateArgumentCount(2, out string? error))
return LuauReturn.Error(error);
if (!args.TryReadNumber(1, out int a, out error) || !args.TryReadNumber(2, out int b, out error))
return LuauReturn.Error(error);
if (a <= b)
return LuauReturn.Error("Expected a to be greater than b");
return LuauReturn.Ok(a + b, a - b);
});
```
`CreateFunction(...)` must be called directly at the call site so the generator can intercept it. It supports fixed delegate signatures, including supported top-level tuple returns. If you need a shape that is not supported there, use `CreateFunctionBuilder(...)`.
## Work with tables
```csharp
using LuauTable settings = lua.CreateTable();
settings.Set("volume", 0.8);
settings.Set("muted", false);
settings.Set("blob", new byte[] { 1, 2, 3 });
lua.Globals.Set("settings", settings);
using LuauTable roundTripped = lua.Globals.GetLuauTable("settings");
double volume = roundTripped.GetNumber("volume");
bool muted = roundTripped.GetBoolean("muted");
byte[] blob = roundTripped.GetBuffer("blob");
```
Use `Get*` for required values, `TryGet*` for optional or external data, and `*OrNil` when `nil` is part of the contract.
## Work with userdata
```csharp
var player = new PlayerUserdata { Name = "Ada" };
lua.Globals.Set("player", IntoLuau.FromUserdata(player));
PlayerUserdata samePlayer = lua.Globals.GetUserdata("player");
using LuauUserdata playerRef = lua.Globals.GetLuauUserdata("player");
_ = playerRef.TryGetManaged(out PlayerUserdata? resolvedPlayer, out string? error);
```
Managed userdata types implement `ILuauUserData` to expose script-facing fields, setters, and methods. See [Userdata](docs/features/userdata.md) for the full hook model.
`CreateFunction(...)` also supports managed userdata parameters and returns for types that implement `ILuauUserData`.
## Register custom libraries
```csharp
lua.OpenLibrary("game", static (state, in LuauTable lib) =>
{
lib.Set("answer", 42);
using LuauFunction add = state.CreateFunction((int a, int b) => a + b);
lib.Set("add", add);
});
```
`OpenLibrary(...)` registers a global table. It is a convenient way to expose host-provided APIs, but it is not a `require(...)`-style module loader by itself.
## Ownership and lifetime
- `LuauTable`, `LuauFunction`, `LuauString`, `LuauBuffer`, `LuauUserdata`, and reference-backed `LuauValue` are owned references and should be disposed.
- `LuauTableView`, `LuauFunctionView`, `LuauStringView`, `LuauBufferView`, `LuauUserdataView`, and `LuauArgs` are borrowed callback-scoped values.
- Reference-backed values belong to one `LuauState`; cross-state usage is invalid.
## Current boundaries
- `Load(...).Execute(...)` is the script execution API today. If you want file-based execution, read the file yourself and pass its contents in.
- `CreateFunction(...)` is generator-backed and has no runtime fallback.
- `LuauState` is not thread-safe.
- A documented module system and higher-level async/thread orchestration are not part of the current surface yet.