Skip to content

Commit 334821b

Browse files
committed
feat: add userdata slice functions
The C API for full userdata allows both single and many-item allocations. This expands the Zig interface to allow for slices of full userdata to be allocated. Also exposes the name of the userdata metatables as strings (typename is not flexible enough)
1 parent 00abb7d commit 334821b

File tree

8 files changed

+334
-52
lines changed

8 files changed

+334
-52
lines changed

src/ziglua-5.1/lib.zig

+39-5
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,7 @@ pub const Lua = struct {
627627
}
628628

629629
/// This function allocates a new userdata of the given type.
630+
/// Returns a pointer to the Lua-owned data
630631
/// See https://www.lua.org/manual/5.1/manual.html#lua_newuserdata
631632
pub fn newUserdata(lua: *Lua, comptime T: type) *T {
632633
// safe to .? because this function throws a Lua error on out of memory
@@ -635,6 +636,15 @@ pub const Lua = struct {
635636
return opaqueCast(T, ptr);
636637
}
637638

639+
/// This function creates and pushes a slice of full userdata onto the stack.
640+
/// Returns a slice to the Lua-owned data.
641+
/// See https://www.lua.org/manual/5.1/manual.html#lua_newuserdata
642+
pub fn newUserdataSlice(lua: *Lua, comptime T: type, size: usize) []T {
643+
// safe to .? because this function throws a Lua error on out of memory
644+
const ptr = c.lua_newuserdata(lua.state, @sizeOf(T) * size).?;
645+
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
646+
}
647+
638648
/// Pops a key from the stack, and pushes a key-value pair from the table at the given index.
639649
/// See https://www.lua.org/manual/5.1/manual.html#lua_next
640650
pub fn next(lua: *Lua, index: i32) bool {
@@ -933,14 +943,27 @@ pub const Lua = struct {
933943
return error.Fail;
934944
}
935945

936-
/// Returns a pointer of the given type to the userdata at the given index.
937-
/// Works for both full and light userdata. Otherwise returns an error.
946+
/// Returns a Lua-owned userdata pointer of the given type at the given index.
947+
/// Works for both light and full userdata.
948+
/// Returns an error if the value is not a userdata.
938949
/// See https://www.lua.org/manual/5.1/manual.html#lua_touserdata
939950
pub fn toUserdata(lua: *Lua, comptime T: type, index: i32) !*T {
940951
if (c.lua_touserdata(lua.state, index)) |ptr| return opaqueCast(T, ptr);
941952
return error.Fail;
942953
}
943954

955+
/// Returns a Lua-owned userdata slice of the given type at the given index.
956+
/// Returns an error if the value is not a userdata.
957+
/// See https://www.lua.org/manual/5.1/manual.html#lua_touserdata
958+
pub fn toUserdataSlice(lua: *Lua, comptime T: type, index: i32) ![]T {
959+
if (c.lua_touserdata(lua.state, index)) |ptr| {
960+
const size = lua.objectLen(index) / @sizeOf(T);
961+
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
962+
}
963+
return error.Fail;
964+
}
965+
966+
944967
/// Returns the `LuaType` of the value at the given index
945968
/// Note that this is equivalent to lua_type but because type is a Zig primitive it is renamed to `typeOf`
946969
/// See https://www.lua.org/manual/5.1/manual.html#lua_type
@@ -1197,14 +1220,25 @@ pub const Lua = struct {
11971220
c.luaL_checktype(lua.state, arg, @enumToInt(t));
11981221
}
11991222

1200-
/// Checks whether the function argument `arg` is a userdata of the type `type_name`
1223+
/// Checks whether the function argument `arg` is a userdata of the type `name`
12011224
/// Returns the userdata's memory-block address
12021225
/// See https://www.lua.org/manual/5.1/manual.html#luaL_checkudata
1203-
pub fn checkUserdata(lua: *Lua, comptime T: type, arg: i32) *T {
1226+
pub fn checkUserdata(lua: *Lua, comptime T: type, arg: i32, name: [:0]const u8) *T {
12041227
// the returned pointer will not be null
1205-
return opaqueCast(T, c.luaL_checkudata(lua.state, arg, @typeName(T)).?);
1228+
return opaqueCast(T, c.luaL_checkudata(lua.state, arg, name.ptr).?);
12061229
}
12071230

1231+
/// Checks whether the function argument `arg` is a userdata of the type `name`
1232+
/// Returns a Lua-owned userdata slice
1233+
/// See https://www.lua.org/manual/5.1/manual.html#luaL_checkudata
1234+
pub fn checkUserdataSlice(lua: *Lua, comptime T: type, arg: i32, name: [:0]const u8) []T {
1235+
// the returned pointer will not be null
1236+
const ptr = c.luaL_checkudata(lua.state, arg, name.ptr).?;
1237+
const size = lua.objectLen(arg) / @sizeOf(T);
1238+
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
1239+
}
1240+
1241+
12081242
/// Loads and runs the given file
12091243
/// See https://www.lua.org/manual/5.1/manual.html#luaL_dofile
12101244
pub fn doFile(lua: *Lua, file_name: [:0]const u8) !void {

src/ziglua-5.1/tests.zig

+36-3
Original file line numberDiff line numberDiff line change
@@ -1159,11 +1159,11 @@ test "userdata" {
11591159
defer lua.deinit();
11601160

11611161
const Type = struct { a: i32, b: f32 };
1162-
try lua.newMetatable(@typeName(Type));
1162+
try lua.newMetatable("Type");
11631163

11641164
const checkUdata = ziglua.wrap(struct {
11651165
fn inner(l: *Lua) i32 {
1166-
const ptr = l.checkUserdata(Type, 1);
1166+
const ptr = l.checkUserdata(Type, 1, "Type");
11671167
if (ptr.a != 1234) {
11681168
l.pushBytes("error!");
11691169
l.raiseError();
@@ -1180,7 +1180,7 @@ test "userdata" {
11801180

11811181
{
11821182
var t = lua.newUserdata(Type);
1183-
lua.getField(ziglua.registry_index, @typeName(Type));
1183+
lua.getField(ziglua.registry_index, "Type");
11841184
lua.setMetatable(-2);
11851185
t.a = 1234;
11861186
t.b = 3.14;
@@ -1191,6 +1191,39 @@ test "userdata" {
11911191
}
11921192
}
11931193

1194+
test "userdata slices" {
1195+
var lua = try Lua.init(testing.allocator);
1196+
defer lua.deinit();
1197+
1198+
try lua.newMetatable("FixedArray");
1199+
1200+
// create an array of 10
1201+
const slice = lua.newUserdataSlice(Integer, 10);
1202+
lua.getField(ziglua.registry_index, "FixedArray");
1203+
lua.setMetatable(-2);
1204+
1205+
for (slice, 1..) |*item, index| {
1206+
item.* = @intCast(Integer, index);
1207+
}
1208+
1209+
const udataFn = struct {
1210+
fn inner(l: *Lua) i32 {
1211+
_ = l.checkUserdataSlice(Integer, 1, "FixedArray");
1212+
const arr = l.toUserdataSlice(Integer, 1) catch unreachable;
1213+
for (arr, 1..) |item, index| {
1214+
if (item != index) l.raiseErrorStr("something broke!", .{});
1215+
}
1216+
1217+
return 0;
1218+
}
1219+
}.inner;
1220+
1221+
lua.pushFunction(ziglua.wrap(udataFn));
1222+
lua.pushValue(2);
1223+
1224+
try lua.protectedCall(1, 0, 0);
1225+
}
1226+
11941227
test "refs" {
11951228
// tests for functions that aren't tested or will not be tested in ziglua
11961229
// but ensures that the signatures are at least type checked

src/ziglua-5.2/lib.zig

+48-7
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,7 @@ pub const Lua = struct {
711711
}
712712

713713
/// This function allocates a new userdata of the given type
714+
/// Returns a pointer to the Lua-owned data
714715
/// See https://www.lua.org/manual/5.2/manual.html#lua_newuserdata
715716
pub fn newUserdata(lua: *Lua, comptime T: type) *T {
716717
// safe to .? because this function throws a Lua error on out of memory
@@ -719,6 +720,15 @@ pub const Lua = struct {
719720
return opaqueCast(T, ptr);
720721
}
721722

723+
/// This function creates and pushes a slice of full userdata onto the stack.
724+
/// Returns a slice to the Lua-owned data.
725+
/// See https://www.lua.org/manual/5.2/manual.html#lua_newuserdata
726+
pub fn newUserdataSlice(lua: *Lua, comptime T: type, size: usize) []T {
727+
// safe to .? because this function throws a Lua error on out of memory
728+
const ptr = c.lua_newuserdata(lua.state, @sizeOf(T) * size).?;
729+
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
730+
}
731+
722732
/// Pops a key from the stack, and pushes a key-value pair from the table at the given index
723733
/// See https://www.lua.org/manual/5.2/manual.html#lua_next
724734
pub fn next(lua: *Lua, index: i32) bool {
@@ -1084,14 +1094,26 @@ pub const Lua = struct {
10841094
return result;
10851095
}
10861096

1087-
/// Returns a pointer of the given type to the userdata at the given index.
1088-
/// Works for both full and light userdata. Otherwise returns an error.
1097+
/// Returns a Lua-owned userdata pointer of the given type at the given index.
1098+
/// Works for both light and full userdata.
1099+
/// Returns an error if the value is not a userdata.
10891100
/// See https://www.lua.org/manual/5.2/manual.html#lua_touserdata
10901101
pub fn toUserdata(lua: *Lua, comptime T: type, index: i32) !*T {
10911102
if (c.lua_touserdata(lua.state, index)) |ptr| return opaqueCast(T, ptr);
10921103
return error.Fail;
10931104
}
10941105

1106+
/// Returns a Lua-owned userdata slice of the given type at the given index.
1107+
/// Returns an error if the value is not a userdata.
1108+
/// See https://www.lua.org/manual/5.2/manual.html#lua_touserdata
1109+
pub fn toUserdataSlice(lua: *Lua, comptime T: type, index: i32) ![]T {
1110+
if (c.lua_touserdata(lua.state, index)) |ptr| {
1111+
const size = lua.rawLen(index) / @sizeOf(T);
1112+
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
1113+
}
1114+
return error.Fail;
1115+
}
1116+
10951117
/// Returns the `LuaType` of the value at the given index
10961118
/// Note that this is equivalent to lua_type but because type is a Zig primitive it is renamed to `typeOf`
10971119
/// See https://www.lua.org/manual/5.2/manual.html#lua_type
@@ -1389,12 +1411,22 @@ pub const Lua = struct {
13891411
c.luaL_checktype(lua.state, arg, @enumToInt(t));
13901412
}
13911413

1392-
/// Checks whether the function argument `arg` is a userdata of the type `type_name`
1414+
/// Checks whether the function argument `arg` is a userdata of the type `name`
13931415
/// Returns the userdata's memory-block address
13941416
/// See https://www.lua.org/manual/5.2/manual.html#luaL_checkudata
1395-
pub fn checkUserdata(lua: *Lua, comptime T: type, arg: i32) *T {
1417+
pub fn checkUserdata(lua: *Lua, comptime T: type, arg: i32, name: [:0]const u8) *T {
13961418
// the returned pointer will not be null
1397-
return opaqueCast(T, c.luaL_checkudata(lua.state, arg, @typeName(T)).?);
1419+
return opaqueCast(T, c.luaL_checkudata(lua.state, arg, name.ptr).?);
1420+
}
1421+
1422+
/// Checks whether the function argument `arg` is a userdata of the type `name`
1423+
/// Returns a Lua-owned userdata slice
1424+
/// See https://www.lua.org/manual/5.2/manual.html#luaL_checkudata
1425+
pub fn checkUserdataSlice(lua: *Lua, comptime T: type, arg: i32, name: [:0]const u8) []T {
1426+
// the returned pointer will not be null
1427+
const ptr = c.luaL_checkudata(lua.state, arg, name.ptr).?;
1428+
const size = lua.rawLen(arg) / @sizeOf(T);
1429+
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
13981430
}
13991431

14001432
/// Checks whether the function argument arg is a number and returns this number cast to an unsigned
@@ -1652,12 +1684,21 @@ pub const Lua = struct {
16521684

16531685
/// This function works like `Lua.checkUserdata()` except it returns a Zig error instead of raising a Lua error on fail
16541686
/// See https://www.lua.org/manual/5.2/manual.html#luaL_testudata
1655-
pub fn testUserdata(lua: *Lua, comptime T: type, arg: i32) !*T {
1656-
if (c.luaL_testudata(lua.state, arg, @typeName(T))) |ptr| {
1687+
pub fn testUserdata(lua: *Lua, comptime T: type, arg: i32, name: [:0]const u8) !*T {
1688+
if (c.luaL_testudata(lua.state, arg, name.ptr)) |ptr| {
16571689
return opaqueCast(T, ptr);
16581690
} else return error.Fail;
16591691
}
16601692

1693+
/// This function works like `Lua.checkUserdataSlice()` except it returns a Zig error instead of raising a Lua error on fail
1694+
/// See https://www.lua.org/manual/5.2/manual.html#luaL_checkudata
1695+
pub fn testUserdataSlice(lua: *Lua, comptime T: type, arg: i32, name: [:0]const u8) ![]T {
1696+
if (c.luaL_testudata(lua.state, arg, name.ptr)) |ptr| {
1697+
const size = lua.rawLen(arg) / @sizeOf(T);
1698+
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
1699+
} else return error.Fail;
1700+
}
1701+
16611702
/// Converts any Lua value at the given index into a string in a reasonable format
16621703
/// See https://www.lua.org/manual/5.2/manual.html#luaL_tolstring
16631704
pub fn toBytesFmt(lua: *Lua, index: i32) [:0]const u8 {

src/ziglua-5.2/tests.zig

+37-5
Original file line numberDiff line numberDiff line change
@@ -1339,11 +1339,11 @@ test "userdata" {
13391339
defer lua.deinit();
13401340

13411341
const Type = struct { a: i32, b: f32 };
1342-
try lua.newMetatable(@typeName(Type));
1342+
try lua.newMetatable("Type");
13431343

13441344
const checkUdata = ziglua.wrap(struct {
13451345
fn inner(l: *Lua) i32 {
1346-
const ptr = l.checkUserdata(Type, 1);
1346+
const ptr = l.checkUserdata(Type, 1, "Type");
13471347
if (ptr.a != 1234) {
13481348
_ = l.pushBytes("error!");
13491349
l.raiseError();
@@ -1360,7 +1360,7 @@ test "userdata" {
13601360

13611361
{
13621362
var t = lua.newUserdata(Type);
1363-
lua.setMetatableRegistry(@typeName(Type));
1363+
lua.setMetatableRegistry("Type");
13641364
t.a = 1234;
13651365
t.b = 3.14;
13661366

@@ -1371,7 +1371,7 @@ test "userdata" {
13711371

13721372
const testUdata = ziglua.wrap(struct {
13731373
fn inner(l: *Lua) i32 {
1374-
const ptr = l.testUserdata(Type, 1) catch {
1374+
const ptr = l.testUserdata(Type, 1, "Type") catch {
13751375
_ = l.pushBytes("error!");
13761376
l.raiseError();
13771377
};
@@ -1391,7 +1391,7 @@ test "userdata" {
13911391

13921392
{
13931393
var t = lua.newUserdata(Type);
1394-
lua.setMetatableRegistry(@typeName(Type));
1394+
lua.setMetatableRegistry("Type");
13951395
t.a = 1234;
13961396
t.b = 3.14;
13971397

@@ -1401,6 +1401,38 @@ test "userdata" {
14011401
}
14021402
}
14031403

1404+
test "userdata slices" {
1405+
var lua = try Lua.init(testing.allocator);
1406+
defer lua.deinit();
1407+
1408+
try lua.newMetatable("FixedArray");
1409+
1410+
// create an array of 10
1411+
const slice = lua.newUserdataSlice(Integer, 10);
1412+
lua.setMetatableRegistry("FixedArray");
1413+
for (slice, 1..) |*item, index| {
1414+
item.* = @intCast(Integer, index);
1415+
}
1416+
1417+
const udataFn = struct {
1418+
fn inner(l: *Lua) i32 {
1419+
_ = l.checkUserdataSlice(Integer, 1, "FixedArray");
1420+
_ = l.testUserdataSlice(Integer, 1, "FixedArray") catch unreachable;
1421+
const arr = l.toUserdataSlice(Integer, 1) catch unreachable;
1422+
for (arr, 1..) |item, index| {
1423+
if (item != index) l.raiseErrorStr("something broke!", .{});
1424+
}
1425+
1426+
return 0;
1427+
}
1428+
}.inner;
1429+
1430+
lua.pushFunction(ziglua.wrap(udataFn));
1431+
lua.pushValue(2);
1432+
1433+
try lua.protectedCall(1, 0, 0);
1434+
}
1435+
14041436
test "refs" {
14051437
// tests for functions that aren't tested or will not be tested in ziglua
14061438
// but ensures that the signatures are at least type checked

0 commit comments

Comments
 (0)