Mani is an odin-to-lua exporter that generates Lua C API bindings and LSP code completion for your odin source files.
package main
import "core:fmt"
import "shared:lua"
import "shared:luaL"
import "shared:mani"
@(LuaExport = {
Name = "my_nice_fn",
})
test :: proc(var: int) -> (result1: int, result2: int) {
fmt.printf("Lua: Var is %d\n", var)
result1 = 60 + var
result2 = 40 + var
return
}
main :: proc() {
L := luaL.newstate()
mani.export_all(L, mani.global_state)
}
-- lua code
res1, res2 = my_nice_fn(3)
print("Results from Call: " .. res1 .. " " .. res2)
@(LuaExport = {
Name = "print_object", // optional
})
half_object_print :: proc(using v: HalfObject) {
fmt.printf("My value is %d, and my hidden is %d\n", value, hidden)
}
- Procedures support all primitive types (string, cstring, ints, floats) as results and parameters
- Pointers are only supported with exported structs/arrays
- Multiple return values are supported
- Code completion is automatically configured to work for the new struct type declared
- You must specify the userdata
Type
inLuaExport
to either beFull
,Light
or both of them - Mani converts automatically between light and full userdata, depending on the context. If the user needs a value, it will be a value.
- The term light userdata in mani's context is not fully accurate. Light userdatas cannot have metatables. What mani does is create a full userdata which is a wrapper around a single pointer. This allows to modify odin from within lua, while still allowing a metatable.
- Passing a struct by value or pointer in odin doesn't have any correlation with the
Type
supported. It will just work
@(LuaExport = {
Name = "half_object", // optional lua name
Type = { // Light or Full userdata. You must have at least one of these
Full, // The struct can be allocated in lua fully
Light // A wrapper to a pointer, usually from odin. You can modify odin from lua with this
},
// Optional field access
Fields = {
value = "val",
},
// Methods/metamethods assume that the first parameter of the proc is the object itself.
// The object can either be passed by value or by pointer
// All methods/metamethods require to be @LuaExport'ed
Methods = {
half_object_print = "print",
mod_object = "mod",
},
Metamethods = {
__tostring = half_object_tostring,
},
})
HalfObject :: struct {
value: int,
hidden: int, // Not in fields, therefore private
}
@(LuaExport)
make_object :: proc(v: int) -> (result: HalfObject) {
return {
value = v,
hidden = v + 1,
}
}
@(LuaExport)
mod_object :: proc(o: ^HalfObject, v: int) {
o.value = v
}
@(LuaExport)
half_object_tostring :: proc(using v: HalfObject) -> string {
// Lua will allocate a string when you push it, so this can be on the temp allocator
return fmt.tprintf("HalfObject {{%d, %d}}", value, hidden)
}
- Mani supports arrays up to 4 elements
- Swizzling support in lua!!! With intellisense!!!!
- Methods/metamethods support
- Because you can convert make conversions between different sizes of arrays via swizzling, mani requires to specify the conversions via
SwizzleTypes
, while also exporting every type defined in there. This is a bit of a headache, and I'm hoping to find a better solution to this - Distinct types not yet supported, because distinct and swizzling works rather weird in odin aswell
@(LuaExport = {
Name = "vec2f64",
Type = {Light, Full},
SwizzleTypes = {Vec3, Vec4}, // For type conversions when swizzling (local my_vec3 = v.xyy)
Fields = xyrg, // Needs to be divizible by len(array). It will treat x, r as arr[0] and y,g as arr[1]
Metamethods = {
__tostring = vec2_tostring,
},
})
Vec2 :: [2]int
// We will need to declare all our swizzles
@(LuaExport = {
Name = "vec3f64",
Type = {Light, Full},
SwizzleTypes = {Vec2, Vec4}, // A Vec3 can construct a Vec2 and a Vec4
Fields = xyzrgb,
Metamethods = {
__tostring = vec3_tostring,
},
})
Vec3 :: [3]int
@(LuaExport = {
Name = "vec4f64",
Type = {Light, Full},
SwizzleTypes = {Vec2, Vec3},
Fields = xyzwrgba,
Methods = {
vec4_tostring = "stringify",
},
Metamethods = {
__tostring = vec4_tostring,
},
})
Vec4 :: [4]int
@(LuaExport)
make_vec4 :: proc(x: int, y: int, z: int, w: int) -> Vec4 {
return {x, y, z, w}
}
@(LuaExport)
vec4_tostring :: proc(v: Vec4) -> string {
return fmt.tprintf("{{%d, %d, %d, %d}}", v.x, v.y, v.z, v.w)
}
// ... and so on
-- lua code
local v = make_vec3(2, 3, 4)
local v2 = v.xz -- v2 is of type Vec2
v2.xy = v.zz
LuaImport
can be used to link a global function to odin.- The wrapper code generated is available under the name
_mani_<your proc name>
. It accepts the same parameters and return values - Calling the wrapper procedure will call the lua function.
GlobalSymbol
is optional. If not specified, the wrapper code will just generate the Lua C API calls required to call any function on the top of the lua stack, with the parameters and results provided. This will require you to be familiar with how the C API works, so use with caution.
@(LuaImport = {
GlobalSymbol = "Update",
})
update :: proc(dt: f64) -> (int, int) {
val1, val2 := _mani_update(dt)
// do some for processing
return val1, val2
}
-- lua
function Update(dt)
return 1, 2
end
- Download odin-lua
- For lua code completion, download lua-language-server
- Place the contents of the shared directory in
%ODIN_ROOT%/shared
folder or a collection of your choice. IfODIN_ROOT
is configured, you can just callcopy_to_shared.bat
- Build
generator
directoryodin build generator -out:build/mani.exe
- Create a
.json
file with the configuration of the generator. See the example config for all the available options - The output executable accepts a json as parameter
mani config.json
. You can specify--show-timings
for benchmarking the performance of the generation. - The output of
mani
is a source file*.(config.odin_ext)
and a LSP file*(config.lua_ext)
for each package encountered in the directory and it's subdirectories. - When initializing the lua state, call
mani.export_all
in order to hook all the code generated to your lua state
import lua "shared:lua"
import luaL "shared:luaL"
import mani "shared:mani"
main :: proc() {
L := luaL.newstate()
mani.export_all(L, mani.global_state)
}
- Build your source with the
-ignore-unkown-attributes
flag
- See the example config for all the available options
dir
refers to the odin source folder you want to generate for.meta_dir
refers to the lua directory you want to put the LSP generation.- In order to prevent mani for looking to generate code for the already generated files, provide a longer extension for the
odin_ext
andlua_ext
. For example:.generated.odin
/.lsp.lua
types
accepts a dictionary of arrays. Each key will represent a lua type, while the array value will be a list of odin types to be converted to lua. This is used by the code completion generation. The types exported by your application will already be correctly configured, so you only need to worry about primitives. Because the parser doesn't do any package resolution,c.int
andint
both need to be there and will be treated as distinct types. Aliasing any one of them in your code means that you need to add the alias in your configuration aswell.