feat: Lua Animations

Slap the ENTIRE LUA RUNTIME onto Ly
This commit is contained in:
RadsammyT 2026-05-18 13:53:51 -04:00
commit 1019acfa9e
No known key found for this signature in database
7 changed files with 179 additions and 0 deletions

View file

@ -71,6 +71,14 @@ pub fn build(b: *std.Build) !void {
}),
});
const zlua = b.dependency("zlua", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zlua", zlua.module("zlua"));
const ly_ui = b.dependency("ly_ui", .{
.target = target,
.optimize = optimize,

View file

@ -11,6 +11,10 @@
.url = "git+https://github.com/Hejsil/zig-clap#fc1e5cc3f6d9d3001112385ee6256d694e959d2f",
.hash = "clap-0.11.0-oBajB7foAQC3Iyn4IVCkUdYaOVVng5IZkSncySTjNig1",
},
.zlua = .{
.url = "git+https://github.com/natecraddock/ziglua?ref=zig-0.16#8f271c82baa5fc43aa02a72f6da020c2025d9436",
.hash = "zlua-0.1.0-hGRpC2aABQD4D9PBVH3wAW8k32-I4969MRQ0CpOwoley",
},
},
.paths = .{
"build.zig",

151
src/animations/Lua.zig Normal file
View file

@ -0,0 +1,151 @@
const std = @import("std");
const ly_ui = @import("ly-ui");
const LogFile = ly_ui.ly_core.LogFile;
const Widget = ly_ui.Widget;
const TerminalBuffer = ly_ui.TerminalBuffer;
const Cell = ly_ui.Cell;
const Allocator = std.mem.Allocator;
const zlua = @import("zlua");
const LyLua = @embedFile("ly.lua");
const Lua = @This();
instance: ?Widget = null,
lua: *zlua.Lua,
log: *LogFile,
terminal_buffer: *TerminalBuffer,
io: std.Io,
lua_error: bool = false,
pub fn init(
io: std.Io,
alloc: Allocator,
log: *LogFile,
buf: *TerminalBuffer,
file: ?[]const u8,
) !Lua {
var self: Lua = .{
.lua = try zlua.Lua.init(alloc),
.terminal_buffer = buf,
.instance = null,
.log = log,
.io = io,
};
self.lua.openLibs();
if (file) |f| {
const zf = std.mem.concatWithSentinel(alloc, u8, &[1][]const u8{f}, 0) catch unreachable;
defer alloc.free(zf);
self.lua.doString(LyLua) catch {
const errorStr = self.lua.toString(-1) catch unreachable;
self.log.err(self.io, "Lua", "Lua Error: {s}", .{errorStr}) catch unreachable;
self.lua.setGlobal("_ERROR");
self.lua_error = true;
};
self.lua.doFile(zf) catch {
const errorStr = self.lua.toString(-1) catch unreachable;
self.log.err(self.io, "Lua", "Lua Error: {s}", .{errorStr}) catch unreachable;
self.lua.setGlobal("_ERROR");
self.lua_error = true;
};
}
_ = self.lua.getGlobal("ly");
addFuncToTable(self.lua, "putCell", luaPutCell);
self.lua.setGlobal("ly");
return self;
}
fn draw(self: *Lua) void {
_ = self.lua.getGlobal("ly");
_ =self.lua.pushString("width");
self.lua.pushInteger(@intCast(self.terminal_buffer.width));
self.lua.setTable(-3);
_ =self.lua.pushString("height");
self.lua.pushInteger(@intCast(self.terminal_buffer.height));
self.lua.setTable(-3);
self.lua.setGlobal("ly");
if (!self.lua_error) {
self.lua.doString(
"draw()"
) catch {
const errorStr = self.lua.toString(-1) catch unreachable;
self.log.err(self.io, "Lua", "Lua Error: {s}", .{errorStr}) catch unreachable;
self.lua.setGlobal("_ERROR");
self.lua_error = true;
};
} else {
// set the entire background to red
const cell = Cell.init(0x2588, 0x00ff0000, 0x00ff0000);
for (0..self.terminal_buffer.height) |y|
for (0..self.terminal_buffer.width) |x|
cell.put(x, y) catch {};
}
}
fn calculateTimeout(self: *Lua, _: *anyopaque) !?usize {
const lyType = self.lua.getGlobal("ly"); // +1
_ = self.lua.pushString("frame_delay"); // + 1
if (lyType != .table)
std.debug.panic("Lua: global 'ly' must be a table: got {}", .{lyType});
const frameDelayType = self.lua.getTable(-2); // neut
if (frameDelayType != .number)
std.debug.panic("Lua: 'ly.frame_delay' must be a number: got {}", .{frameDelayType});
const timeout = try self.lua.toNumeric(usize, -1);
self.lua.pop(-1); // pop number from stack
self.lua.pop(-1); // pop table 'ly' from stack
return timeout;
}
fn deinit(self: *Lua) void {
self.lua.deinit();
}
pub fn widget(self: *Lua) *Widget {
if (self.instance) |*inst| return inst;
self.instance = Widget.init(
"Lua",
null,
self,
deinit, //deinit
null, //realloc
draw, //draw
null, //update
null,
calculateTimeout, //calculateTimeout
);
return &self.instance.?;
}
fn luaPutCell(lua: ?*zlua.LuaState) callconv(.c) c_int {
var vm = @as(*zlua.Lua, @ptrCast(lua));
const cell = vm.checkNumeric(u32, 1);
const fg = vm.checkNumeric(u32, 2);
const bg = vm.checkNumeric(u32, 3);
const x = vm.checkNumeric(usize, 4);
const y = vm.checkNumeric(usize, 5);
Cell.init(cell, fg, bg).put(x, y) catch {};
return 0; // This function doesnt return anything
}
/// Adds member function `name` to table at the top of the stack
///
/// Pushes: 0
/// Pops: 0, this function can be called continuously
/// Errors: unknown
fn addFuncToTable(
vm: *zlua.Lua,
comptime name: []const u8,
func: zlua.CFn,
) void
{
_ = vm.pushString(name);
vm.pushFunction(func);
vm.setTable(-3);
}

3
src/animations/ly.lua Normal file
View file

@ -0,0 +1,3 @@
ly = {
frame_delay = 0,
}

View file

@ -74,6 +74,7 @@ lang: []const u8 = "en",
login_cmd: ?[]const u8 = null,
login_defs_path: []const u8 = "/etc/login.defs",
logout_cmd: ?[]const u8 = null,
lua_animation_file: ?[]const u8 = null,
ly_log: ?[]const u8 = "/var/log/ly.log",
margin_box_h: u8 = 2,
margin_box_v: u8 = 1,

View file

@ -7,6 +7,7 @@ pub const Animation = enum {
colormix,
gameoflife,
dur_file,
lua,
};
pub const DisplayServer = enum {

View file

@ -29,6 +29,7 @@ const Doom = @import("animations/Doom.zig");
const DurFile = @import("animations/DurFile.zig");
const GameOfLife = @import("animations/GameOfLife.zig");
const Matrix = @import("animations/Matrix.zig");
const LuaAnim = @import("animations/Lua.zig");
const auth = @import("auth.zig");
const InfoLine = @import("components/InfoLine.zig");
const Session = @import("components/Session.zig");
@ -1098,6 +1099,16 @@ pub fn main(init: std.process.Init) !void {
);
animation = dur.widget();
},
.lua => {
var lua = try LuaAnim.init(
state.io,
state.allocator,
&state.log_file,
&state.buffer,
state.config.lua_animation_file,
);
animation = lua.widget();
},
}
defer if (animation) |a| a.deinit();