diff --git a/res/example.lua b/res/example.lua index ab01f0d..80dda8e 100644 --- a/res/example.lua +++ b/res/example.lua @@ -1,17 +1,19 @@ -- [[ --- This is an example of using LuaJIT to create a custom animation in Ly, in this case a --- bouncing square that changes colors. +-- This is an example of using LuaJIT to create a custom animation in Ly, in this case +-- bouncing squares that change colors. -- -- You are given the following `ly` table: -- { -- height: number -- The height of the terminal -- width: number -- The width of the terminal --- putCell(byte, fg, bg, x, y) -- Put the cell a buffer. This buffer will be drawn --- on screen after draw() is called. It will not clear --- itself automatically, see `ly.clear()`. --- All arguments to this function are numbers, and --- must be in the unsigned 32-bit integer range: 0 to 2^32-1. --- clear() -- Clear the buffer. +-- putCell(byte, fg, bg, x, y) -- Draw a cell. +-- All arguments to this function are integers, and +-- must be in the unsigned 32-bit integer range: 0 to 2^32-1. +-- +-- For arguments fg and bg: they are colors in the format +-- 0xSSRRGGBB, where SS is for styling. See your +-- config.ini for more details. +-- -- clock() -- The time, in microseconds. -- } -- @@ -19,16 +21,21 @@ -- -- ]] +-- You should probably copy FPS and FPS_COUNT into any future LuaJIT animations you create. +local FPS_COUNT = 40 +local function FPS() + return (1/FPS_COUNT)*1000000 +end + + local SQUARE_WIDTH = 10 local SQUARE_HEIGHT = 5 local SQUARE_COUNT = 25 -local FPS = 60 - local squares = {} -for i = 0, SQUARE_COUNT-1 do +for i = 1, SQUARE_COUNT do local vx = 1 local vy = 1 if math.random(1, 2) == 2 then vx = -vx end @@ -46,17 +53,13 @@ local timer = ly.clock() function draw() -- Rather than progressing the animation by frame, do it based on - -- seconds, like from ly.clock(). - if timer + ((1/FPS)*1000000)< ly.clock() then - ly.clear() + -- seconds, via ly.clock(). In this timeframe, you can update the animation state. + -- DO NOT DRAW CELLS IN THIS TIMEFRAME. You will get flickering. + + if timer + FPS() < ly.clock() then -- if this check passes, we can update the animation for i, v in ipairs(squares) do v.x = v.x + v.vx v.y = v.y + v.vy - for x = v.x, v.x + SQUARE_WIDTH do - for y = v.y, v.y + SQUARE_HEIGHT do - ly.putCell(string.byte(' '), 0, v.color, x, y) - end - end if v.x <= 0 then v.vx = 1; v.color = math.random(0xFFFFFF) end if v.x + SQUARE_WIDTH >= ly.width-1 then v.vx = -1; v.color = math.random(0xFFFFFF) end if v.y <= 0 then v.vy = 1; v.color = math.random(0xFFFFFF) end @@ -64,4 +67,13 @@ function draw() end timer = ly.clock() end + + + for i, v in ipairs(squares) do + for x = v.x, v.x + SQUARE_WIDTH do + for y = v.y, v.y + SQUARE_HEIGHT do + ly.putCell(string.byte(' '), 0, v.color, x, y) + end + end + end end diff --git a/src/animations/Lua.zig b/src/animations/Lua.zig index 4d3efff..cfcfb47 100644 --- a/src/animations/Lua.zig +++ b/src/animations/Lua.zig @@ -74,31 +74,33 @@ pub fn init( file_loading: { const zf = std.mem.concatWithSentinel(alloc, u8, &[1][]const u8{file}, 0) catch |e| { - self.log.err(self.io, "lua", "failed to allocate file path: {}", .{e}) catch unreachable; + try self.log.err(self.io, "lua", "failed to allocate file path: {}", .{e}); self.lua_str = "failed to allocate file path!"; self.info_line.addMessage(lang.err_alloc, self.bg, self.fg) catch {}; - self.lua_error = true; - break :file_loading; + return e; }; defer alloc.free(zf); - self.lua.doString(ly_lua) catch { - const errorStr = self.lua.toString(-1) catch unreachable; - self.lua_str = try self.allocator.dupeSentinel(u8, errorStr, 0); - self.log.err(self.io, "lua", "lua error: {s}", .{errorStr}) catch unreachable; - self.lua_error = true; - break :file_loading; - }; - self.propogateTerminalBounds(); + // create the ly table + self.lua.newTable(); + self.lua.setGlobal("ly"); + + // create ly.width and ly.height from TerminalBuffer width/height + self.propagateTerminalBounds(); + _ = self.lua.getGlobal("ly"); _ = self.lua.pushString("clock"); self.lua.pushFunction(luaLyClock); self.lua.setTable(-3); + _ = self.lua.pushString("putCell"); + self.lua.pushFunction(luaPutCell); + self.lua.setTable(-3); self.lua.setGlobal("ly"); + self.lua.doFile(zf) catch { const errorStr = self.lua.toString(-1) catch unreachable; self.lua_str = try self.allocator.dupeSentinel(u8, errorStr, 0); - self.log.err(self.io, "lua", "lua error: {s}", .{errorStr}) catch unreachable; + try self.log.err(self.io, "lua", "lua error: {s}", .{errorStr}); self.lua_error = true; break :file_loading; }; @@ -108,7 +110,7 @@ pub fn init( } fn draw(self: *Lua) void { - self.propogateTerminalBounds(); + self.propagateTerminalBounds(); if (self.lua_error) { // Ly's Red Screen of Omega-Death:tm: const cell = Cell.init(0x2588, 0x00ff0000, 0x00ff0000); @@ -135,94 +137,12 @@ fn draw(self: *Lua) void { self.lua_str = std.mem.concatWithSentinel( self.allocator, u8, - &[_][]const u8{ "Cannot call draw(): ", errorStr }, + &.{ "Cannot call draw(): ", errorStr }, 0, ) catch unreachable; self.log.err(self.io, "Lua", "Error (Cannot call draw()): {s}", .{errorStr}) catch unreachable; self.lua_error = true; }; - const bufferType = self.lua.getGlobal("_BUFFER"); - if (bufferType != .table) { - self.log.err(self.io, "Lua", "global '_BUFFER' must be a table: got {}", .{bufferType}) catch {}; - self.lua_str = std.fmt.allocPrintSentinel( - self.allocator, - "global '_BUFFER' must be a table: got {}", - .{bufferType}, - 0, - ) catch unreachable; - self.lua_error = true; - return; - } - self.lua.pushNil(); - while (self.lua.next(-2)) { - // expect the key to be an int - // expect the value to be a table - const pos = self.lua.toInteger(-2) catch { - self.log.err(self.io, "Lua", "pos: cannot convert to integer", .{}) catch {}; - self.lua_str = std.fmt.allocPrintSentinel( - self.allocator, - "pos: cannot convert to integer", - .{}, - 0, - ) catch unreachable; - self.lua_error = true; - return; - }; - - _ = self.lua.pushString("char"); - _ = self.lua.getTable(-2); - const char = self.lua.toInteger(-1) catch { - self.log.err(self.io, "Lua", "char: cannot convert to integer", .{}) catch {}; - self.lua_str = std.fmt.allocPrintSentinel( - self.allocator, - "char: cannot convert to integer", - .{}, - 0, - ) catch unreachable; - self.lua_error = true; - return; - }; - self.lua.pop(1); - _ = self.lua.pushString("fg"); - _ = self.lua.getTable(-2); - const fg = self.lua.toInteger(-1) catch { - self.log.err(self.io, "Lua", "fg: cannot convert to integer", .{}) catch {}; - self.lua_str = std.fmt.allocPrintSentinel( - self.allocator, - "fg: cannot convert to integer", - .{}, - 0, - ) catch unreachable; - self.lua_error = true; - return; - }; - self.lua.pop(1); - _ = self.lua.pushString("bg"); - _ = self.lua.getTable(-2); - const bg = self.lua.toInteger(-1) catch { - self.log.err(self.io, "Lua", "bg: cannot convert to integer", .{}) catch {}; - self.lua_str = std.fmt.allocPrintSentinel( - self.allocator, - "bg: cannot convert to integer", - .{}, - 0, - ) catch unreachable; - self.lua_error = true; - return; - }; - self.lua.pop(1); - self.lua.pop(1); - if (pos < 0) continue; // do not draw cells that underflow - TerminalBuffer.setCell( - @rem(@as(usize, @intCast(pos)), self.width), - @divFloor(@as(usize, @bitCast(pos)), self.width), - .init( - @intCast(char), - @intCast(@as(i32, @truncate(fg))), - @intCast(@as(i32, @truncate(bg))), - ), - ) catch {}; - } } fn calculateTimeout(self: *Lua, _: *anyopaque) !?usize { @@ -250,7 +170,7 @@ pub fn widget(self: *Lua) *Widget { return &self.instance.?; } -fn propogateTerminalBounds(self: *Lua) void { +fn propagateTerminalBounds(self: *Lua) void { if (self.terminal_buffer.height != self.height or self.terminal_buffer.width != self.width) { @@ -273,3 +193,34 @@ fn luaLyClock(state: ?*zlua.LuaState) callconv(.c) c_int { lua.pushInteger(std.Io.Timestamp.now(threaded.io(), .real).toMicroseconds()); return 1; } + +fn luaPutCell(state: ?*zlua.LuaState) callconv(.c) c_int { + const lua: *zlua.Lua = @ptrCast(@alignCast(state orelse unreachable)); + const MSG = "ly.putCell: Cannot convert %s-typed "; + const byte = lua.toNumeric(u32, 1) catch { + const t = lua.typeName(lua.typeOf(1)); + lua.raiseErrorStr(MSG ++ "byte to u32", .{t.ptr}); + }; + const fg = lua.toNumeric(u32, 2) catch { + const t = lua.typeName(lua.typeOf(2)); + lua.raiseErrorStr(MSG ++ "fg to u32", .{t.ptr}); + }; + const bg = lua.toNumeric(u32, 3) catch { + const t = lua.typeName(lua.typeOf(3)); + lua.raiseErrorStr(MSG ++ "bg to u32", .{t.ptr}); + }; + const x = lua.toNumeric(usize, 4) catch { + const t = lua.typeName(lua.typeOf(4)); + lua.raiseErrorStr(MSG ++ "x to usize", .{t.ptr}); + }; + const y = lua.toNumeric(usize, 5) catch { + const t = lua.typeName(lua.typeOf(5)); + lua.raiseErrorStr(MSG ++ "y to usize", .{t.ptr}); + }; + TerminalBuffer.setCell(x, y, .{ + .fg = fg, + .bg = bg, + .ch = byte, + }) catch {}; + return 0; +} diff --git a/src/animations/ly.lua b/src/animations/ly.lua deleted file mode 100644 index c45812b..0000000 --- a/src/animations/ly.lua +++ /dev/null @@ -1,17 +0,0 @@ -ly = { - width = 0, - height = 0, -} -_BUFFER = {} - -function ly.putCell(byte, fg, bg, x, y) - _BUFFER[x + (y * ly.width)] = { - char = math.floor(byte), - fg = math.floor(fg), - bg = math.floor(bg), - } -end - -function ly.clear() - _BUFFER = {} -end