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 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 TerminalBuffer = @import("../tui/TerminalBuffer.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]);
}
start_time: TimeOfDay,
terminal_buffer: *TerminalBuffer,
timeout: *bool,
animate: *bool,
timeout_sec: u12,
frames: u64,
pattern_cos_mod: f32,
pattern_sin_mod: f32,
@@ -28,11 +34,14 @@ pub fn init(
col1: u32,
col2: u32,
col3: u32,
timeout: *bool,
) ColorMix {
animate: *bool,
timeout_sec: u12,
) !ColorMix {
return .{
.start_time = try interop.getTimeOfDay(),
.terminal_buffer = terminal_buffer,
.timeout = timeout,
.animate = animate,
.timeout_sec = timeout_sec,
.frames = 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,
@@ -60,13 +69,13 @@ pub fn widget(self: *ColorMix) Widget {
null,
null,
draw,
null,
update,
null,
);
}
fn draw(self: *ColorMix) void {
if (self.timeout.*) return;
if (!self.animate.*) return;
self.frames +%= 1;
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 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 TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Widget = @import("../tui/Widget.zig");
@@ -11,9 +15,11 @@ pub const STEPS = 12;
pub const HEIGHT_MAX = 9;
pub const SPREAD_MAX = 4;
start_time: TimeOfDay,
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
timeout: *bool,
animate: *bool,
timeout_sec: u12,
buffer: []u8,
height: u8,
spread: u8,
@@ -27,7 +33,8 @@ pub fn init(
bottom_color: u32,
fire_height: u8,
fire_spread: u8,
timeout: *bool,
animate: *bool,
timeout_sec: u12,
) !Doom {
const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height);
initBuffer(buffer, terminal_buffer.width);
@@ -50,9 +57,11 @@ pub fn init(
};
return .{
.start_time = try interop.getTimeOfDay(),
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.timeout = timeout,
.animate = animate,
.timeout_sec = timeout_sec,
.buffer = buffer,
.height = @min(HEIGHT_MAX, fire_height),
.spread = @min(SPREAD_MAX, fire_spread),
@@ -67,7 +76,7 @@ pub fn widget(self: *Doom) Widget {
deinit,
realloc,
draw,
null,
update,
null,
);
}
@@ -83,7 +92,7 @@ fn realloc(self: *Doom) !void {
}
fn draw(self: *Doom) void {
if (self.timeout.*) return;
if (!self.animate.*) return;
for (0..self.terminal_buffer.width) |x| {
// 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_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 ly_core = @import("ly-core");
const interop = ly_core.interop;
const TimeOfDay = interop.TimeOfDay;
const LogFile = ly_core.LogFile;
const enums = @import("../enums.zig");
@@ -300,6 +302,7 @@ const VEC_Y = 1;
const DurFile = @This();
start_time: TimeOfDay,
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
dur_movie: DurFormat,
@@ -307,7 +310,8 @@ frames: u64,
frame_size: UVec2,
start_pos: IVec2,
full_color: bool,
timeout: *bool,
animate: *bool,
timeout_sec: u12,
frame_time: u32,
time_previous: i64,
is_color_format_16: bool,
@@ -367,7 +371,8 @@ pub fn init(
x_offset: i32,
y_offset: i32,
full_color: bool,
timeout: *bool,
animate: *bool,
timeout_sec: u12,
) !DurFile {
var dur_movie: DurFormat = .init(allocator);
@@ -399,6 +404,7 @@ pub fn init(
const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?);
return .{
.start_time = try interop.getTimeOfDay(),
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.frames = 0,
@@ -406,7 +412,8 @@ pub fn init(
.frame_size = frame_size,
.start_pos = start_pos,
.full_color = full_color,
.timeout = timeout,
.animate = animate,
.timeout_sec = timeout_sec,
.dur_movie = dur_movie,
.frame_time = frame_time,
.is_color_format_16 = eql(u8, dur_movie.colorFormat.?, "16"),
@@ -422,7 +429,7 @@ pub fn widget(self: *DurFile) Widget {
deinit,
realloc,
draw,
null,
update,
null,
);
}
@@ -438,7 +445,7 @@ fn realloc(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];
@@ -493,3 +500,11 @@ fn draw(self: *DurFile) void {
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 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 TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Widget = @import("../tui/Widget.zig");
@@ -16,6 +20,7 @@ const NEIGHBOR_DIRS = [_][2]i8{
.{ 1, 0 }, .{ 1, 1 },
};
start_time: TimeOfDay,
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
current_grid: []bool,
@@ -26,7 +31,8 @@ fg_color: u32,
entropy_interval: usize,
frame_delay: usize,
initial_density: f32,
timeout: *bool,
animate: *bool,
timeout_sec: u12,
dead_cell: Cell,
width: usize,
height: usize,
@@ -38,7 +44,8 @@ pub fn init(
entropy_interval: usize,
frame_delay: usize,
initial_density: f32,
timeout: *bool,
animate: *bool,
timeout_sec: u12,
) !GameOfLife {
const width = terminal_buffer.width;
const height = terminal_buffer.height;
@@ -48,6 +55,7 @@ pub fn init(
const next_grid = try allocator.alloc(bool, grid_size);
var game = GameOfLife{
.start_time = try interop.getTimeOfDay(),
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.current_grid = current_grid,
@@ -58,7 +66,8 @@ pub fn init(
.entropy_interval = entropy_interval,
.frame_delay = frame_delay,
.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 },
.width = width,
.height = height,
@@ -77,7 +86,7 @@ pub fn widget(self: *GameOfLife) Widget {
deinit,
realloc,
draw,
null,
update,
null,
);
}
@@ -105,7 +114,7 @@ fn realloc(self: *GameOfLife) !void {
}
fn draw(self: *GameOfLife) void {
if (self.timeout.*) return;
if (!self.animate.*) return;
// Update game state at controlled frame rate
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 {
// Conway's Game of Life rules with optimized neighbor counting
for (0..self.height) |y| {

View File

@@ -2,6 +2,10 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
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 TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Widget = @import("../tui/Widget.zig");
@@ -24,6 +28,7 @@ pub const Line = struct {
update: usize,
};
start_time: TimeOfDay,
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
dots: []Dot,
@@ -34,7 +39,8 @@ fg: u32,
head_col: u32,
min_codepoint: u16,
max_codepoint: u16,
timeout: *bool,
animate: *bool,
timeout_sec: u12,
default_cell: Cell,
pub fn init(
@@ -44,7 +50,8 @@ pub fn init(
head_col: u32,
min_codepoint: u16,
max_codepoint: u16,
timeout: *bool,
animate: *bool,
timeout_sec: u12,
) !Matrix {
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
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);
return .{
.start_time = try interop.getTimeOfDay(),
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.dots = dots,
@@ -62,7 +70,8 @@ pub fn init(
.head_col = head_col,
.min_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 },
};
}
@@ -74,7 +83,7 @@ pub fn widget(self: *Matrix) Widget {
deinit,
realloc,
draw,
null,
update,
null,
);
}
@@ -95,7 +104,7 @@ fn realloc(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_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 {
var y: usize = 0;
while (y <= height) : (y += 1) {

View File

@@ -76,7 +76,6 @@ const UiState = struct {
active_tty: u8,
buffer: TerminalBuffer,
labels_max_length: usize,
animation_timed_out: bool,
shutdown_label: Label,
restart_label: Label,
sleep_label: Label,
@@ -152,9 +151,6 @@ pub fn main() !void {
state.allocator = gpa.allocator();
// Allows stopping an animation after some time
const animation_time_start = try interop.getTimeOfDay();
// Load arguments
const params = comptime clap.parseParamsComptime(
\\-h, --help Shows all commands.
@@ -884,7 +880,8 @@ pub fn main() !void {
state.config.doom_bottom_color,
state.config.doom_fire_height,
state.config.doom_fire_spread,
&state.animation_timed_out,
&state.animate,
state.config.animation_timeout_sec,
);
animation = doom.widget();
},
@@ -896,17 +893,19 @@ pub fn main() !void {
state.config.cmatrix_head_col,
state.config.cmatrix_min_codepoint,
state.config.cmatrix_max_codepoint,
&state.animation_timed_out,
&state.animate,
state.config.animation_timeout_sec,
);
animation = matrix.widget();
},
.colormix => {
var color_mix = ColorMix.init(
var color_mix = try ColorMix.init(
&state.buffer,
state.config.colormix_col1,
state.config.colormix_col2,
state.config.colormix_col3,
&state.animation_timed_out,
&state.animate,
state.config.animation_timeout_sec,
);
animation = color_mix.widget();
},
@@ -918,7 +917,8 @@ pub fn main() !void {
state.config.gameoflife_entropy_interval,
state.config.gameoflife_frame_delay,
state.config.gameoflife_initial_density,
&state.animation_timed_out,
&state.animate,
state.config.animation_timeout_sec,
);
animation = game_of_life.widget();
},
@@ -932,7 +932,8 @@ pub fn main() !void {
state.config.dur_x_offset,
state.config.dur_y_offset,
state.config.full_color,
&state.animation_timed_out,
&state.animate,
state.config.animation_timeout_sec,
);
animation = dur.widget();
},
@@ -949,7 +950,6 @@ pub fn main() !void {
state.auth_fails = 0;
state.run = true;
state.update = true;
state.animation_timed_out = false;
state.animate = state.config.animation != .none;
state.insert_mode = !state.config.vi_mode or state.config.vi_default_mode == .insert;
state.edge_margin = Position.init(
@@ -1148,15 +1148,8 @@ pub fn main() !void {
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
if (state.animate and !state.animation_timed_out) {
if (state.animate) {
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) {
const time = try interop.getTimeOfDay();