Skip to content

Commit c2f7954

Browse files
committed
Expose Luau built-in float vector support
The Luau VM supports native f32 3- or 4-vectors so that typical linear algebra operations are fast in game code. Both the 3- and 4-vector flavors are supported. Use -Dluau_vector_size=N to choose which. This must be configured at build time, as the native Luau VM must be re-compiled for this setting.
1 parent 848190f commit c2f7954

File tree

4 files changed

+113
-4
lines changed

4 files changed

+113
-4
lines changed

build.zig

+5-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub fn build(b: *Build) void {
2020

2121
const lang = b.option(Language, "lang", "Lua language version to build") orelse .lua54;
2222
const shared = b.option(bool, "shared", "Build shared library instead of static") orelse false;
23-
23+
const luau_vector_size = b.option(usize, "luau_vector_size", "Luau built-in vector size: 3-vector or 4-vector") orelse 3;
2424
const upstream = b.dependency(@tagName(lang), .{});
2525

2626
// Zig module
@@ -37,10 +37,11 @@ pub fn build(b: *Build) void {
3737
// Expose build configuration to the ziglua module
3838
const config = b.addOptions();
3939
config.addOption(Language, "lang", lang);
40+
config.addOption(usize, "luau_vector_size", luau_vector_size);
4041
ziglua.addOptions("config", config);
4142

4243
const lib = switch (lang) {
43-
.luau => buildLuau(b, target, optimize, upstream, shared),
44+
.luau => buildLuau(b, target, optimize, upstream, shared, luau_vector_size),
4445
else => buildLua(b, target, optimize, upstream, lang, shared),
4546
};
4647

@@ -152,7 +153,7 @@ fn buildLua(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.Optim
152153
}
153154

154155
/// Luau has diverged enough from Lua (C++, project structure, ...) that it is easier to separate the build logic
155-
fn buildLuau(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, upstream: *Build.Dependency, shared: bool) *Step.Compile {
156+
fn buildLuau(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, upstream: *Build.Dependency, shared: bool, luau_vector_len: usize) *Step.Compile {
156157
const lib_opts = .{
157158
.name = "luau",
158159
.target = target,
@@ -174,6 +175,7 @@ fn buildLuau(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.Opti
174175
"-DLUA_API=extern\"C\"",
175176
"-DLUACODE_API=extern\"C\"",
176177
"-DLUACODEGEN_API=extern\"C\"",
178+
if (luau_vector_len == 4) "-DLUA_VECTOR_SIZE=4" else "-DLUA_VECTOR_SIZE=3",
177179
};
178180

179181
for (luau_source_files) |file| {

src/libluau.zig

+39
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@ const c = @cImport({
1111

1212
const config = @import("config");
1313
pub const lang = config.lang;
14+
/// True if Luau built for 4-vectors
15+
pub const luau_vector_size = config.luau_vector_size;
1416

1517
/// This function is defined in luau.cpp and must be called to define the assertion printer
1618
extern "c" fn zig_registerAssertionHandler() void;
1719

1820
/// This function is defined in luau.cpp and ensures Zig uses the correct free when compiling luau code
1921
extern "c" fn zig_luau_free(ptr: *anyopaque) void;
2022

23+
// Workaround for Zig apparently always choosing the 3-vector variant of lua_pushvector(lua_State* L, float x, float y, float z)
24+
// despite compiling Luau with -DLUA_VECTOR_SIZE=4.
25+
extern "c" fn zig_lua_pushvector4(L: ?*LuaState, x: f32, y: f32, z: f32, w: f32) void;
26+
2127
const Allocator = std.mem.Allocator;
2228

2329
// Types
@@ -139,6 +145,7 @@ pub const LuaType = enum(i5) {
139145
boolean = c.LUA_TBOOLEAN,
140146
light_userdata = c.LUA_TLIGHTUSERDATA,
141147
number = c.LUA_TNUMBER,
148+
vector = c.LUA_TVECTOR,
142149
string = c.LUA_TSTRING,
143150
table = c.LUA_TTABLE,
144151
function = c.LUA_TFUNCTION,
@@ -520,6 +527,11 @@ pub const Lua = struct {
520527
return c.lua_isuserdata(lua.state, index) != 0;
521528
}
522529

530+
/// Returns true if the value at the given index is a vector
531+
pub fn isVector(lua: *Lua, index: i32) bool {
532+
return c.lua_isvector(lua.state, index);
533+
}
534+
523535
/// Returns true if the value at index1 is smaller than the value at index2, following the
524536
/// semantics of the Lua < operator.
525537
/// See https://www.lua.org/manual/5.4/manual.html#lua_lessthan
@@ -713,6 +725,17 @@ pub const Lua = struct {
713725
c.lua_pushvalue(lua.state, index);
714726
}
715727

728+
fn pushVector3(lua: *Lua, x: f32, y: f32, z: f32) void {
729+
c.lua_pushvector(lua.state, x, y, z);
730+
}
731+
732+
fn pushVector4(lua: *Lua, x: f32, y: f32, z: f32, w: f32) void {
733+
zig_lua_pushvector4(lua.state, x, y, z, w);
734+
}
735+
736+
/// Pushes a floating point 3-vector (or 4-vector if configured) `v` onto the stack
737+
pub const pushVector = if (luau_vector_size == 3) pushVector3 else pushVector4;
738+
716739
/// Returns true if the two values in indices `index1` and `index2` are primitively equal
717740
/// Bypasses __eq metamethods
718741
/// Returns false if not equal, or if any index is invalid
@@ -927,6 +950,22 @@ pub const Lua = struct {
927950
return error.Fail;
928951
}
929952

953+
/// Converts the Lua value at the given `index` to a 3- or 4-vector.
954+
/// The Lua value must be a vector.
955+
pub fn toVector(lua: *Lua, index: i32) ![luau_vector_size]f32 {
956+
const res = c.lua_tovector(lua.state, index);
957+
if (res) |r| {
958+
if (luau_vector_size == 3) {
959+
return [_]f32{ r[0], r[1], r[2] };
960+
} else if (luau_vector_size == 4) {
961+
return [_]f32{ r[0], r[1], r[2], r[3] };
962+
} else {
963+
@compileError("luau_vector_size must be 3 or 4");
964+
}
965+
}
966+
return error.Fail;
967+
}
968+
930969
/// Returns the `LuaType` of the value at the given index
931970
/// Note that this is equivalent to lua_type but because type is a Zig primitive it is renamed to `typeOf`
932971
/// See https://www.lua.org/manual/5.1/manual.html#lua_type

src/luau.cpp

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#include "Luau/Common.h"
2-
2+
#include "lua.h"
33
#include <cstdio>
44
#include <cstdlib>
55

@@ -16,3 +16,9 @@ extern "C" void zig_registerAssertionHandler() {
1616
extern "C" void zig_luau_free(void *ptr) {
1717
free(ptr);
1818
}
19+
20+
#if LUA_VECTOR_SIZE == 4
21+
extern "C" void zig_lua_pushvector4(lua_State* L, float x, float y, float z, float w) {
22+
lua_pushvector(L, x, y, z, w);
23+
}
24+
#endif

src/tests.zig

+62
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ test "typenames" {
381381
try expectEqualStrings("function", lua.typeName(.function));
382382
try expectEqualStrings("userdata", lua.typeName(.userdata));
383383
try expectEqualStrings("thread", lua.typeName(.thread));
384+
try expectEqualStrings("vector", lua.typeName(.vector));
384385
}
385386

386387
test "unsigned" {
@@ -2249,3 +2250,64 @@ test "tagged userdata" {
22492250
lua.pushInteger(13);
22502251
try expectError(error.Fail, lua.userdataTag(-1));
22512252
}
2253+
2254+
fn vectorCtor(l: *Lua) i32 {
2255+
const x = l.toNumber(1) catch unreachable;
2256+
const y = l.toNumber(2) catch unreachable;
2257+
const z = l.toNumber(3) catch unreachable;
2258+
if (ziglua.luau_vector_size == 4) {
2259+
const w = l.optNumber(4, 0);
2260+
l.pushVector(@floatCast(x), @floatCast(y), @floatCast(z), @floatCast(w));
2261+
} else {
2262+
l.pushVector(@floatCast(x), @floatCast(y), @floatCast(z));
2263+
}
2264+
return 1;
2265+
}
2266+
2267+
test "luau vectors" {
2268+
var lua = try Lua.init(testing.allocator);
2269+
defer lua.deinit();
2270+
lua.openLibs();
2271+
lua.register("vector", ziglua.wrap(vectorCtor));
2272+
2273+
try lua.doString(
2274+
\\function test()
2275+
\\ local a = vector(1, 2, 3)
2276+
\\ local b = vector(4, 5, 6)
2277+
\\ local c = (a + b) * vector(2, 2, 2)
2278+
\\ return c
2279+
\\end
2280+
);
2281+
_ = try lua.getGlobal("test");
2282+
try lua.protectedCall(0, 1, 0);
2283+
var v = try lua.toVector(-1);
2284+
try testing.expectEqualSlices(f32, &[3]f32{ 10, 14, 18 }, v[0..3]);
2285+
2286+
if (ziglua.luau_vector_size == 3) lua.pushVector(1, 2, 3) else lua.pushVector(1, 2, 3, 4);
2287+
try expect(lua.isVector(-1));
2288+
v = try lua.toVector(-1);
2289+
const expected = if (ziglua.luau_vector_size == 3) [3]f32{ 1, 2, 3 } else [4]f32{ 1, 2, 3, 4 };
2290+
try expectEqual(expected, v);
2291+
try expectEqualStrings("vector", lua.typeNameIndex(-1));
2292+
2293+
lua.pushInteger(5);
2294+
try expect(!lua.isVector(-1));
2295+
}
2296+
2297+
test "luau 4-vectors" {
2298+
var lua = try Lua.init(testing.allocator);
2299+
defer lua.deinit();
2300+
lua.openLibs();
2301+
lua.register("vector", ziglua.wrap(vectorCtor));
2302+
2303+
// More specific 4-vector tests
2304+
if (ziglua.luau_vector_size == 4) {
2305+
try lua.doString(
2306+
\\local a = vector(1, 2, 3, 4)
2307+
\\local b = vector(5, 6, 7, 8)
2308+
\\return a + b
2309+
);
2310+
const vec4 = try lua.toVector(-1);
2311+
try expectEqual([4]f32{ 6, 8, 10, 12 }, vec4);
2312+
}
2313+
}

0 commit comments

Comments
 (0)