diff --git a/build.zig b/build.zig index ff3406c..08100be 100644 --- a/build.zig +++ b/build.zig @@ -19,11 +19,16 @@ pub fn build(b: *Build) void { const library_name = b.option([]const u8, "library_name", "Library name for lua linking, default is `lua`") orelse "lua"; const shared = b.option(bool, "shared", "Build shared library instead of static") orelse false; const luau_use_4_vector = b.option(bool, "luau_use_4_vector", "Build Luau to use 4-vectors instead of the default 3-vector.") orelse false; + const lua_user_h = b.option(Build.LazyPath, "lua_user_h", "Lazy path to user supplied c header file") orelse null; if (lang == .luau and shared) { std.debug.panic("Luau does not support compiling or loading shared modules", .{}); } + if (lua_user_h != null and (lang == .luajit or lang == .luau)) { + std.debug.panic("Only basic lua supports a user provided header file", .{}); + } + // Zig module const zlua = b.addModule("zlua", .{ .root_source_file = b.path("src/lib.zig"), @@ -44,7 +49,7 @@ pub fn build(b: *Build) void { const lib = switch (lang) { .luajit => luajit_setup.configure(b, target, optimize, upstream, shared), .luau => luau_setup.configure(b, target, optimize, upstream, luau_use_4_vector), - else => lua_setup.configure(b, target, optimize, upstream, lang, shared, library_name), + else => lua_setup.configure(b, target, optimize, upstream, lang, shared, library_name, lua_user_h), }; // Expose the Lua artifact, and get an install step that header translation can refer to @@ -103,6 +108,7 @@ pub fn build(b: *Build) void { var common_examples = [_]struct { []const u8, []const u8 }{ .{ "interpreter", "examples/interpreter.zig" }, .{ "zig-function", "examples/zig-fn.zig" }, + .{ "multithreaded", "examples/multithreaded.zig" }, }; const luau_examples = [_]struct { []const u8, []const u8 }{ .{ "luau-bytecode", "examples/luau-bytecode.zig" }, diff --git a/build/lua.zig b/build/lua.zig index 1131553..3df9dcd 100644 --- a/build/lua.zig +++ b/build/lua.zig @@ -12,7 +12,7 @@ pub const Language = enum { luau, }; -pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, upstream: *Build.Dependency, lang: Language, shared: bool, library_name: []const u8) *Step.Compile { +pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, upstream: *Build.Dependency, lang: Language, shared: bool, library_name: []const u8, lua_user_h: ?Build.LazyPath) *Step.Compile { const version: std.SemanticVersion = switch (lang) { .lua51 => .{ .major = 5, .minor = 1, .patch = 5 }, .lua52 => .{ .major = 5, .minor = 2, .patch = 4 }, @@ -38,6 +38,8 @@ pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin. lib.addIncludePath(upstream.path("src")); + const user_header = "user.h"; + const flags = [_][]const u8{ // Standard version used in Lua Makefile "-std=gnu99", @@ -55,6 +57,8 @@ pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin. // Build as DLL for windows if shared if (target.result.os.tag == .windows and shared) "-DLUA_BUILD_AS_DLL" else "", + + if (lua_user_h) |_| b.fmt("-DLUA_USER_H=\"{s}\"", .{user_header}) else "", }; const lua_source_files = switch (lang) { @@ -87,6 +91,11 @@ pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin. lib.installHeader(upstream.path("src/lauxlib.h"), "lauxlib.h"); lib.installHeader(upstream.path("src/luaconf.h"), "luaconf.h"); + if (lua_user_h) |user_h| { + lib.addIncludePath(user_h.dirname()); + lib.installHeader(user_h, user_header); + } + return lib; } diff --git a/examples/multithreaded.zig b/examples/multithreaded.zig new file mode 100644 index 0000000..271b96a --- /dev/null +++ b/examples/multithreaded.zig @@ -0,0 +1,81 @@ +//! Multi threaded lua program +//! The additional header must be passed to the build using `-Dlua_user_h=examples/user.h` +//! Checkout http://lua-users.org/wiki/ThreadsTutorial for more info + +const std = @import("std"); +const zlua = @import("zlua"); + +var mutex = std.Thread.Mutex{}; + +export fn lua_zlock(L: *zlua.LuaState) callconv(.C) void { + _ = L; + mutex.lock(); +} + +export fn lua_zunlock(L: *zlua.LuaState) callconv(.C) void { + _ = L; + mutex.unlock(); +} + +fn add_to_x(lua: *zlua.Lua, num: usize) void { + for (0..num) |_| { + // omit error handling for brevity + lua.loadString("x = x + 1\n") catch return; + lua.protectedCall(.{}) catch return; + } + + const size = 256; + var buf = [_:0]u8{0} ** size; + _ = std.fmt.bufPrint(&buf, "print(\"{}: \", x)", .{std.Thread.getCurrentId()}) catch return; + + // The printing from different threads does not always work nicely + // There seems to be a separate sterr lock on each argument to print + lua.loadString(&buf) catch return; + lua.protectedCall(.{}) catch return; +} + +pub fn main() anyerror!void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + defer _ = gpa.deinit(); + + // Initialize The Lua vm and get a reference to the main thread + var lua = try zlua.Lua.init(allocator); + defer lua.deinit(); + + lua.openLibs(); + + // create a global variable accessible by all threads + // omit error handling for brevity + try lua.loadString("_G.x = 0\n"); + try lua.protectedCall(.{}); + + const num = 1_000; + const n_jobs = 5; + var subs: [n_jobs]*zlua.Lua = undefined; + + // create a thread pool to run all the functions + var pool: std.Thread.Pool = undefined; + try pool.init(.{ .allocator = allocator, .n_jobs = n_jobs }); + defer pool.deinit(); + + var wg: std.Thread.WaitGroup = .{}; + + for (0..n_jobs) |i| { + subs[i] = lua.newThread(); + pool.spawnWg(&wg, add_to_x, .{ subs[i], num }); + } + + // also do the thing from the main thread + add_to_x(lua, num); + + wg.wait(); + + for (subs) |sub| { + try lua.closeThread(sub); + } + + // print the final value + try lua.loadString("print(x)\n"); + try lua.protectedCall(.{}); +} diff --git a/examples/user.h b/examples/user.h new file mode 100644 index 0000000..59d89a5 --- /dev/null +++ b/examples/user.h @@ -0,0 +1,6 @@ +#define lua_lock(L) lua_zlock(L) +#define lua_unlock(L) lua_zunlock(L) + +void lua_zlock(lua_State* L); +void lua_zunlock(lua_State* L); + diff --git a/readme.md b/readme.md index 5a2e810..a4199ad 100644 --- a/readme.md +++ b/readme.md @@ -66,6 +66,7 @@ There are currently three additional options that can be passed to `b.dependency * `.lang`: Set the Lua language to build and embed. Defaults to `.lua54`. Possible values are `.lua51`, `.lua52`, `.lua53`, `.lua54`, and `luau`. * `.shared`: Defaults to `false` for embedding in a Zig program. Set to `true` to dynamically link the Lua source code (useful for creating shared modules). * `luau_use_4_vector`: defaults to false. Set to true to use 4-vectors instead of the default 3-vector in Luau. +* `lua_user_h`: defaults to null. Provide a path to an additional header file for the Lua compilation. Must be set to to `examples/user.h` in order to run the multithreaded example. For example, here is a `b.dependency()` call that and links against a shared Lua 5.2 library: