Make animation timeout independent of event loop

Signed-off-by: AnErrupTion <anerruption@disroot.org>
This commit is contained in:
AnErrupTion
2026-02-11 23:51:32 +01:00
parent 57c96a3478
commit 7c7aed9cb2
6 changed files with 121 additions and 45 deletions

View File

@@ -1,6 +1,10 @@
const std = @import("std"); const std = @import("std");
const math = std.math; const math = std.math;
const ly_core = @import("ly-core");
const interop = ly_core.interop;
const TimeOfDay = interop.TimeOfDay;
const Cell = @import("../tui/Cell.zig"); const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Widget = @import("../tui/Widget.zig"); const Widget = @import("../tui/Widget.zig");
@@ -16,8 +20,10 @@ fn length(vec: Vec2) f32 {
return math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]); return math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]);
} }
start_time: TimeOfDay,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
timeout: *bool, animate: *bool,
timeout_sec: u12,
frames: u64, frames: u64,
pattern_cos_mod: f32, pattern_cos_mod: f32,
pattern_sin_mod: f32, pattern_sin_mod: f32,
@@ -28,11 +34,14 @@ pub fn init(
col1: u32, col1: u32,
col2: u32, col2: u32,
col3: u32, col3: u32,
timeout: *bool, animate: *bool,
) ColorMix { timeout_sec: u12,
) !ColorMix {
return .{ return .{
.start_time = try interop.getTimeOfDay(),
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
.timeout = timeout, .animate = animate,
.timeout_sec = timeout_sec,
.frames = 0, .frames = 0,
.pattern_cos_mod = terminal_buffer.random.float(f32) * math.pi * 2.0, .pattern_cos_mod = terminal_buffer.random.float(f32) * math.pi * 2.0,
.pattern_sin_mod = terminal_buffer.random.float(f32) * math.pi * 2.0, .pattern_sin_mod = terminal_buffer.random.float(f32) * math.pi * 2.0,
@@ -60,13 +69,13 @@ pub fn widget(self: *ColorMix) Widget {
null, null,
null, null,
draw, draw,
null, update,
null, null,
); );
} }
fn draw(self: *ColorMix) void { fn draw(self: *ColorMix) void {
if (self.timeout.*) return; if (!self.animate.*) return;
self.frames +%= 1; self.frames +%= 1;
const time: f32 = @as(f32, @floatFromInt(self.frames)) * time_scale; const time: f32 = @as(f32, @floatFromInt(self.frames)) * time_scale;
@@ -99,3 +108,11 @@ fn draw(self: *ColorMix) void {
} }
} }
} }
fn update(self: *ColorMix, _: *anyopaque) !void {
const time = try interop.getTimeOfDay();
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
self.animate.* = false;
}
}

View File

@@ -1,6 +1,10 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ly_core = @import("ly-core");
const interop = ly_core.interop;
const TimeOfDay = interop.TimeOfDay;
const Cell = @import("../tui/Cell.zig"); const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Widget = @import("../tui/Widget.zig"); const Widget = @import("../tui/Widget.zig");
@@ -11,9 +15,11 @@ pub const STEPS = 12;
pub const HEIGHT_MAX = 9; pub const HEIGHT_MAX = 9;
pub const SPREAD_MAX = 4; pub const SPREAD_MAX = 4;
start_time: TimeOfDay,
allocator: Allocator, allocator: Allocator,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
timeout: *bool, animate: *bool,
timeout_sec: u12,
buffer: []u8, buffer: []u8,
height: u8, height: u8,
spread: u8, spread: u8,
@@ -27,7 +33,8 @@ pub fn init(
bottom_color: u32, bottom_color: u32,
fire_height: u8, fire_height: u8,
fire_spread: u8, fire_spread: u8,
timeout: *bool, animate: *bool,
timeout_sec: u12,
) !Doom { ) !Doom {
const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height); const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height);
initBuffer(buffer, terminal_buffer.width); initBuffer(buffer, terminal_buffer.width);
@@ -50,9 +57,11 @@ pub fn init(
}; };
return .{ return .{
.start_time = try interop.getTimeOfDay(),
.allocator = allocator, .allocator = allocator,
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
.timeout = timeout, .animate = animate,
.timeout_sec = timeout_sec,
.buffer = buffer, .buffer = buffer,
.height = @min(HEIGHT_MAX, fire_height), .height = @min(HEIGHT_MAX, fire_height),
.spread = @min(SPREAD_MAX, fire_spread), .spread = @min(SPREAD_MAX, fire_spread),
@@ -67,7 +76,7 @@ pub fn widget(self: *Doom) Widget {
deinit, deinit,
realloc, realloc,
draw, draw,
null, update,
null, null,
); );
} }
@@ -83,7 +92,7 @@ fn realloc(self: *Doom) !void {
} }
fn draw(self: *Doom) void { fn draw(self: *Doom) void {
if (self.timeout.*) return; if (!self.animate.*) return;
for (0..self.terminal_buffer.width) |x| { for (0..self.terminal_buffer.width) |x| {
// We start from 1 so that we always have the topmost line when spreading fire // We start from 1 so that we always have the topmost line when spreading fire
@@ -130,3 +139,11 @@ fn initBuffer(buffer: []u8, width: usize) void {
@memset(slice_start, 0); @memset(slice_start, 0);
@memset(slice_end, STEPS); @memset(slice_end, STEPS);
} }
fn update(self: *Doom, _: *anyopaque) !void {
const time = try interop.getTimeOfDay();
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
self.animate.* = false;
}
}

View File

@@ -5,6 +5,8 @@ const eql = std.mem.eql;
const flate = std.compress.flate; const flate = std.compress.flate;
const ly_core = @import("ly-core"); const ly_core = @import("ly-core");
const interop = ly_core.interop;
const TimeOfDay = interop.TimeOfDay;
const LogFile = ly_core.LogFile; const LogFile = ly_core.LogFile;
const enums = @import("../enums.zig"); const enums = @import("../enums.zig");
@@ -300,6 +302,7 @@ const VEC_Y = 1;
const DurFile = @This(); const DurFile = @This();
start_time: TimeOfDay,
allocator: Allocator, allocator: Allocator,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
dur_movie: DurFormat, dur_movie: DurFormat,
@@ -307,7 +310,8 @@ frames: u64,
frame_size: UVec2, frame_size: UVec2,
start_pos: IVec2, start_pos: IVec2,
full_color: bool, full_color: bool,
timeout: *bool, animate: *bool,
timeout_sec: u12,
frame_time: u32, frame_time: u32,
time_previous: i64, time_previous: i64,
is_color_format_16: bool, is_color_format_16: bool,
@@ -367,7 +371,8 @@ pub fn init(
x_offset: i32, x_offset: i32,
y_offset: i32, y_offset: i32,
full_color: bool, full_color: bool,
timeout: *bool, animate: *bool,
timeout_sec: u12,
) !DurFile { ) !DurFile {
var dur_movie: DurFormat = .init(allocator); var dur_movie: DurFormat = .init(allocator);
@@ -399,6 +404,7 @@ pub fn init(
const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?); const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?);
return .{ return .{
.start_time = try interop.getTimeOfDay(),
.allocator = allocator, .allocator = allocator,
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
.frames = 0, .frames = 0,
@@ -406,7 +412,8 @@ pub fn init(
.frame_size = frame_size, .frame_size = frame_size,
.start_pos = start_pos, .start_pos = start_pos,
.full_color = full_color, .full_color = full_color,
.timeout = timeout, .animate = animate,
.timeout_sec = timeout_sec,
.dur_movie = dur_movie, .dur_movie = dur_movie,
.frame_time = frame_time, .frame_time = frame_time,
.is_color_format_16 = eql(u8, dur_movie.colorFormat.?, "16"), .is_color_format_16 = eql(u8, dur_movie.colorFormat.?, "16"),
@@ -422,7 +429,7 @@ pub fn widget(self: *DurFile) Widget {
deinit, deinit,
realloc, realloc,
draw, draw,
null, update,
null, null,
); );
} }
@@ -438,7 +445,7 @@ fn realloc(self: *DurFile) !void {
} }
fn draw(self: *DurFile) void { fn draw(self: *DurFile) void {
if (self.timeout.*) return; if (!self.animate.*) return;
const current_frame = self.dur_movie.frames.items[self.frames]; const current_frame = self.dur_movie.frames.items[self.frames];
@@ -493,3 +500,11 @@ fn draw(self: *DurFile) void {
self.frames = (self.frames + 1) % frame_count; self.frames = (self.frames + 1) % frame_count;
} }
} }
fn update(self: *DurFile, _: *anyopaque) !void {
const time = try interop.getTimeOfDay();
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
self.animate.* = false;
}
}

View File

@@ -1,6 +1,10 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ly_core = @import("ly-core");
const interop = ly_core.interop;
const TimeOfDay = interop.TimeOfDay;
const Cell = @import("../tui/Cell.zig"); const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Widget = @import("../tui/Widget.zig"); const Widget = @import("../tui/Widget.zig");
@@ -16,6 +20,7 @@ const NEIGHBOR_DIRS = [_][2]i8{
.{ 1, 0 }, .{ 1, 1 }, .{ 1, 0 }, .{ 1, 1 },
}; };
start_time: TimeOfDay,
allocator: Allocator, allocator: Allocator,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
current_grid: []bool, current_grid: []bool,
@@ -26,7 +31,8 @@ fg_color: u32,
entropy_interval: usize, entropy_interval: usize,
frame_delay: usize, frame_delay: usize,
initial_density: f32, initial_density: f32,
timeout: *bool, animate: *bool,
timeout_sec: u12,
dead_cell: Cell, dead_cell: Cell,
width: usize, width: usize,
height: usize, height: usize,
@@ -38,7 +44,8 @@ pub fn init(
entropy_interval: usize, entropy_interval: usize,
frame_delay: usize, frame_delay: usize,
initial_density: f32, initial_density: f32,
timeout: *bool, animate: *bool,
timeout_sec: u12,
) !GameOfLife { ) !GameOfLife {
const width = terminal_buffer.width; const width = terminal_buffer.width;
const height = terminal_buffer.height; const height = terminal_buffer.height;
@@ -48,6 +55,7 @@ pub fn init(
const next_grid = try allocator.alloc(bool, grid_size); const next_grid = try allocator.alloc(bool, grid_size);
var game = GameOfLife{ var game = GameOfLife{
.start_time = try interop.getTimeOfDay(),
.allocator = allocator, .allocator = allocator,
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
.current_grid = current_grid, .current_grid = current_grid,
@@ -58,7 +66,8 @@ pub fn init(
.entropy_interval = entropy_interval, .entropy_interval = entropy_interval,
.frame_delay = frame_delay, .frame_delay = frame_delay,
.initial_density = initial_density, .initial_density = initial_density,
.timeout = timeout, .animate = animate,
.timeout_sec = timeout_sec,
.dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg }, .dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg },
.width = width, .width = width,
.height = height, .height = height,
@@ -77,7 +86,7 @@ pub fn widget(self: *GameOfLife) Widget {
deinit, deinit,
realloc, realloc,
draw, draw,
null, update,
null, null,
); );
} }
@@ -105,7 +114,7 @@ fn realloc(self: *GameOfLife) !void {
} }
fn draw(self: *GameOfLife) void { fn draw(self: *GameOfLife) void {
if (self.timeout.*) return; if (!self.animate.*) return;
// Update game state at controlled frame rate // Update game state at controlled frame rate
self.frame_counter += 1; self.frame_counter += 1;
@@ -132,6 +141,14 @@ fn draw(self: *GameOfLife) void {
} }
} }
fn update(self: *GameOfLife, _: *anyopaque) !void {
const time = try interop.getTimeOfDay();
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
self.animate.* = false;
}
}
fn updateGeneration(self: *GameOfLife) void { fn updateGeneration(self: *GameOfLife) void {
// Conway's Game of Life rules with optimized neighbor counting // Conway's Game of Life rules with optimized neighbor counting
for (0..self.height) |y| { for (0..self.height) |y| {

View File

@@ -2,6 +2,10 @@ const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Random = std.Random; const Random = std.Random;
const ly_core = @import("ly-core");
const interop = ly_core.interop;
const TimeOfDay = interop.TimeOfDay;
const Cell = @import("../tui/Cell.zig"); const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Widget = @import("../tui/Widget.zig"); const Widget = @import("../tui/Widget.zig");
@@ -24,6 +28,7 @@ pub const Line = struct {
update: usize, update: usize,
}; };
start_time: TimeOfDay,
allocator: Allocator, allocator: Allocator,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
dots: []Dot, dots: []Dot,
@@ -34,7 +39,8 @@ fg: u32,
head_col: u32, head_col: u32,
min_codepoint: u16, min_codepoint: u16,
max_codepoint: u16, max_codepoint: u16,
timeout: *bool, animate: *bool,
timeout_sec: u12,
default_cell: Cell, default_cell: Cell,
pub fn init( pub fn init(
@@ -44,7 +50,8 @@ pub fn init(
head_col: u32, head_col: u32,
min_codepoint: u16, min_codepoint: u16,
max_codepoint: u16, max_codepoint: u16,
timeout: *bool, animate: *bool,
timeout_sec: u12,
) !Matrix { ) !Matrix {
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1)); const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
const lines = try allocator.alloc(Line, terminal_buffer.width); const lines = try allocator.alloc(Line, terminal_buffer.width);
@@ -52,6 +59,7 @@ pub fn init(
initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random); initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random);
return .{ return .{
.start_time = try interop.getTimeOfDay(),
.allocator = allocator, .allocator = allocator,
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
.dots = dots, .dots = dots,
@@ -62,7 +70,8 @@ pub fn init(
.head_col = head_col, .head_col = head_col,
.min_codepoint = min_codepoint, .min_codepoint = min_codepoint,
.max_codepoint = max_codepoint - min_codepoint, .max_codepoint = max_codepoint - min_codepoint,
.timeout = timeout, .animate = animate,
.timeout_sec = timeout_sec,
.default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg }, .default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg },
}; };
} }
@@ -74,7 +83,7 @@ pub fn widget(self: *Matrix) Widget {
deinit, deinit,
realloc, realloc,
draw, draw,
null, update,
null, null,
); );
} }
@@ -95,7 +104,7 @@ fn realloc(self: *Matrix) !void {
} }
fn draw(self: *Matrix) void { fn draw(self: *Matrix) void {
if (self.timeout.*) return; if (!self.animate.*) return;
const buf_height = self.terminal_buffer.height; const buf_height = self.terminal_buffer.height;
const buf_width = self.terminal_buffer.width; const buf_width = self.terminal_buffer.width;
@@ -188,6 +197,14 @@ fn draw(self: *Matrix) void {
} }
} }
fn update(self: *Matrix, _: *anyopaque) !void {
const time = try interop.getTimeOfDay();
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
self.animate.* = false;
}
}
fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void { fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void {
var y: usize = 0; var y: usize = 0;
while (y <= height) : (y += 1) { while (y <= height) : (y += 1) {

View File

@@ -76,7 +76,6 @@ const UiState = struct {
active_tty: u8, active_tty: u8,
buffer: TerminalBuffer, buffer: TerminalBuffer,
labels_max_length: usize, labels_max_length: usize,
animation_timed_out: bool,
shutdown_label: Label, shutdown_label: Label,
restart_label: Label, restart_label: Label,
sleep_label: Label, sleep_label: Label,
@@ -152,9 +151,6 @@ pub fn main() !void {
state.allocator = gpa.allocator(); state.allocator = gpa.allocator();
// Allows stopping an animation after some time
const animation_time_start = try interop.getTimeOfDay();
// Load arguments // Load arguments
const params = comptime clap.parseParamsComptime( const params = comptime clap.parseParamsComptime(
\\-h, --help Shows all commands. \\-h, --help Shows all commands.
@@ -884,7 +880,8 @@ pub fn main() !void {
state.config.doom_bottom_color, state.config.doom_bottom_color,
state.config.doom_fire_height, state.config.doom_fire_height,
state.config.doom_fire_spread, state.config.doom_fire_spread,
&state.animation_timed_out, &state.animate,
state.config.animation_timeout_sec,
); );
animation = doom.widget(); animation = doom.widget();
}, },
@@ -896,17 +893,19 @@ pub fn main() !void {
state.config.cmatrix_head_col, state.config.cmatrix_head_col,
state.config.cmatrix_min_codepoint, state.config.cmatrix_min_codepoint,
state.config.cmatrix_max_codepoint, state.config.cmatrix_max_codepoint,
&state.animation_timed_out, &state.animate,
state.config.animation_timeout_sec,
); );
animation = matrix.widget(); animation = matrix.widget();
}, },
.colormix => { .colormix => {
var color_mix = ColorMix.init( var color_mix = try ColorMix.init(
&state.buffer, &state.buffer,
state.config.colormix_col1, state.config.colormix_col1,
state.config.colormix_col2, state.config.colormix_col2,
state.config.colormix_col3, state.config.colormix_col3,
&state.animation_timed_out, &state.animate,
state.config.animation_timeout_sec,
); );
animation = color_mix.widget(); animation = color_mix.widget();
}, },
@@ -918,7 +917,8 @@ pub fn main() !void {
state.config.gameoflife_entropy_interval, state.config.gameoflife_entropy_interval,
state.config.gameoflife_frame_delay, state.config.gameoflife_frame_delay,
state.config.gameoflife_initial_density, state.config.gameoflife_initial_density,
&state.animation_timed_out, &state.animate,
state.config.animation_timeout_sec,
); );
animation = game_of_life.widget(); animation = game_of_life.widget();
}, },
@@ -932,7 +932,8 @@ pub fn main() !void {
state.config.dur_x_offset, state.config.dur_x_offset,
state.config.dur_y_offset, state.config.dur_y_offset,
state.config.full_color, state.config.full_color,
&state.animation_timed_out, &state.animate,
state.config.animation_timeout_sec,
); );
animation = dur.widget(); animation = dur.widget();
}, },
@@ -949,7 +950,6 @@ pub fn main() !void {
state.auth_fails = 0; state.auth_fails = 0;
state.run = true; state.run = true;
state.update = true; state.update = true;
state.animation_timed_out = false;
state.animate = state.config.animation != .none; state.animate = state.config.animation != .none;
state.insert_mode = !state.config.vi_mode or state.config.vi_default_mode == .insert; state.insert_mode = !state.config.vi_mode or state.config.vi_default_mode == .insert;
state.edge_margin = Position.init( state.edge_margin = Position.init(
@@ -1148,15 +1148,8 @@ pub fn main() !void {
var timeout: i32 = -1; var timeout: i32 = -1;
// Calculate the maximum timeout based on current animations, or the (big) clock. If there's none, we wait for the event indefinitely instead // Calculate the maximum timeout based on current animations, or the (big) clock. If there's none, we wait for the event indefinitely instead
if (state.animate and !state.animation_timed_out) { if (state.animate) {
timeout = state.config.animation_frame_delay; timeout = state.config.animation_frame_delay;
// Check how long we've been running so we can turn off the animation
const time = try interop.getTimeOfDay();
if (state.config.animation_timeout_sec > 0 and time.seconds - animation_time_start.seconds > state.config.animation_timeout_sec) {
state.animation_timed_out = true;
}
} else if (state.config.bigclock != .none and state.config.clock == null) { } else if (state.config.bigclock != .none and state.config.clock == null) {
const time = try interop.getTimeOfDay(); const time = try interop.getTimeOfDay();