const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const os = std.os; /// A timer fires a callback after a specified amount of time. A timer can /// repeat by returning "rearm" in the callback or by rescheduling the /// start within the callback. pub fn Timer(comptime xev: type) type { return struct { const Self = @This(); /// Create a new timer. pub fn init() !Self { return .{}; } pub fn deinit(self: *const Self) void { // Nothing for now. _ = self; } /// Start the timer. The timer will execute in next_ms milliseconds from /// now. /// /// This will use the monotonic clock on your system if available so /// this is immune to system clock changes or drift. The callback is /// guaranteed to fire NO EARLIER THAN "next_ms" milliseconds. We can't /// make any guarantees about exactness or time bounds because its possible /// for your OS to just... pause.. the process for an indefinite period of /// time. /// /// Like everything else in libxev, if you want something to repeat, you /// must then requeue the completion manually. This punts off one of the /// "hard" aspects of timers: it is up to you to determine what the semantic /// meaning of intervals are. For example, if you want a timer to repeat every /// 10 seconds, is it every 10th second of a wall clock? every 10th second /// after an invocation? every 10th second after the work time from the /// invocation? You have the power to answer these questions, manually. pub fn run( self: Self, loop: *xev.Loop, c: *xev.Completion, next_ms: u64, comptime Userdata: type, userdata: ?*Userdata, comptime cb: *const fn ( ud: ?*Userdata, l: *xev.Loop, c: *xev.Completion, r: RunError!void, ) xev.CallbackAction, ) void { _ = self; loop.timer(c, next_ms, userdata, (struct { fn callback( ud: ?*anyopaque, l_inner: *xev.Loop, c_inner: *xev.Completion, r: xev.Result, ) xev.CallbackAction { return @call(.always_inline, cb, .{ @as(?*Userdata, if (Userdata == void) null else @ptrCast(@alignCast(ud))), l_inner, c_inner, if (r.timer) |trigger| @as(RunError!void, switch (trigger) { .request, .expiration => {}, .cancel => error.Canceled, }) else |err| err, }); } }).callback); } /// Reset a timer to execute in next_ms milliseconds. If the timer /// is already started, this will stop it and restart it. If the /// timer has never been started, this is equivalent to running "run". /// In every case, the timer callback is updated to the given userdata /// and callback. /// /// This requires an additional completion c_cancel to represent /// the need to possibly cancel the previous timer. You can check /// if c_cancel was used by checking the state() after the call. /// /// VERY IMPORTANT: both c and c_cancel MUST NOT be undefined. They /// must be initialized to ".{}" if being used for the first time. pub fn reset( self: Self, loop: *xev.Loop, c: *xev.Completion, c_cancel: *xev.Completion, next_ms: u64, comptime Userdata: type, userdata: ?*Userdata, comptime cb: *const fn ( ud: ?*Userdata, l: *xev.Loop, c: *xev.Completion, r: RunError!void, ) xev.CallbackAction, ) void { _ = self; loop.timer_reset(c, c_cancel, next_ms, userdata, (struct { fn callback( ud: ?*anyopaque, l_inner: *xev.Loop, c_inner: *xev.Completion, r: xev.Result, ) xev.CallbackAction { return @call(.always_inline, cb, .{ @as(?*Userdata, if (Userdata == void) null else @ptrCast(@alignCast(ud))), l_inner, c_inner, if (r.timer) |trigger| @as(RunError!void, switch (trigger) { .request, .expiration => {}, .cancel => error.Canceled, }) else |err| err, }); } }).callback); } /// Cancel a previously started timer. The timer to cancel used the completion /// "c_cancel". A new completion "c" must be specified which will be called /// with the callback once cancellation is complete. /// /// The original timer will still have its callback fired but with the /// error "error.Canceled". pub fn cancel( self: Self, loop: *xev.Loop, c_timer: *xev.Completion, c_cancel: *xev.Completion, comptime Userdata: type, userdata: ?*Userdata, comptime cb: *const fn ( ud: ?*Userdata, l: *xev.Loop, c: *xev.Completion, r: CancelError!void, ) xev.CallbackAction, ) void { _ = self; c_cancel.* = switch (xev.backend) { .io_uring => .{ .op = .{ .timer_remove = .{ .timer = c_timer, }, }, .userdata = userdata, .callback = (struct { fn callback( ud: ?*anyopaque, l_inner: *xev.Loop, c_inner: *xev.Completion, r: xev.Result, ) xev.CallbackAction { return @call(.always_inline, cb, .{ @as(?*Userdata, if (Userdata == void) null else @ptrCast(@alignCast(ud))), l_inner, c_inner, if (r.timer_remove) |_| {} else |err| err, }); } }).callback, }, .epoll, .kqueue, .wasi_poll, .iocp, => .{ .op = .{ .cancel = .{ .c = c_timer, }, }, .userdata = userdata, .callback = (struct { fn callback( ud: ?*anyopaque, l_inner: *xev.Loop, c_inner: *xev.Completion, r: xev.Result, ) xev.CallbackAction { return @call(.always_inline, cb, .{ @as(?*Userdata, if (Userdata == void) null else @ptrCast(@alignCast(ud))), l_inner, c_inner, if (r.cancel) |_| {} else |err| err, }); } }).callback, }, }; loop.add(c_cancel); } /// Error that could happen while running a timer. pub const RunError = error{ /// The timer was canceled before it could expire Canceled, /// Some unexpected error. Unexpected, }; pub const CancelError = xev.CancelError; test "timer" { const testing = std.testing; var loop = try xev.Loop.init(.{}); defer loop.deinit(); var timer = try init(); defer timer.deinit(); // Add the timer var called = false; var c1: xev.Completion = undefined; timer.run(&loop, &c1, 1, bool, &called, (struct { fn callback( ud: ?*bool, _: *xev.Loop, _: *xev.Completion, r: RunError!void, ) xev.CallbackAction { _ = r catch unreachable; ud.?.* = true; return .disarm; } }).callback); // Wait try loop.run(.until_done); try testing.expect(called); } test "timer reset" { const testing = std.testing; var loop = try xev.Loop.init(.{}); defer loop.deinit(); var timer = try init(); defer timer.deinit(); var c_timer: xev.Completion = .{}; var c_cancel: xev.Completion = .{}; const cb = (struct { fn callback( ud: ?*bool, _: *xev.Loop, _: *xev.Completion, r: RunError!void, ) xev.CallbackAction { _ = r catch unreachable; ud.?.* = true; return .disarm; } }).callback; // Add the timer var canceled = false; timer.run(&loop, &c_timer, 100_000, bool, &canceled, cb); // Wait try loop.run(.no_wait); try testing.expect(!canceled); // Reset it timer.reset(&loop, &c_timer, &c_cancel, 1, bool, &canceled, cb); try loop.run(.until_done); try testing.expect(canceled); try testing.expect(c_timer.state() == .dead); try testing.expect(c_cancel.state() == .dead); } test "timer reset before tick" { const testing = std.testing; var loop = try xev.Loop.init(.{}); defer loop.deinit(); var timer = try init(); defer timer.deinit(); var c_timer: xev.Completion = .{}; var c_cancel: xev.Completion = .{}; const cb = (struct { fn callback( ud: ?*bool, _: *xev.Loop, _: *xev.Completion, r: RunError!void, ) xev.CallbackAction { _ = r catch unreachable; ud.?.* = true; return .disarm; } }).callback; // Add the timer var canceled = false; timer.run(&loop, &c_timer, 100_000, bool, &canceled, cb); // Reset it timer.reset(&loop, &c_timer, &c_cancel, 1, bool, &canceled, cb); try loop.run(.until_done); try testing.expect(canceled); try testing.expect(c_timer.state() == .dead); try testing.expect(c_cancel.state() == .dead); } test "timer reset after trigger" { const testing = std.testing; var loop = try xev.Loop.init(.{}); defer loop.deinit(); var timer = try init(); defer timer.deinit(); var c_timer: xev.Completion = .{}; var c_cancel: xev.Completion = .{}; const cb = (struct { fn callback( ud: ?*bool, _: *xev.Loop, _: *xev.Completion, r: RunError!void, ) xev.CallbackAction { _ = r catch unreachable; ud.?.* = true; return .disarm; } }).callback; // Add the timer var canceled = false; timer.run(&loop, &c_timer, 1, bool, &canceled, cb); try loop.run(.until_done); try testing.expect(canceled); canceled = false; // Reset it timer.reset(&loop, &c_timer, &c_cancel, 1, bool, &canceled, cb); try loop.run(.until_done); try testing.expect(canceled); try testing.expect(c_timer.state() == .dead); try testing.expect(c_cancel.state() == .dead); } test "timer cancel" { const testing = std.testing; var loop = try xev.Loop.init(.{}); defer loop.deinit(); var timer = try init(); defer timer.deinit(); // Add the timer var canceled = false; var c1: xev.Completion = undefined; timer.run(&loop, &c1, 100_000, bool, &canceled, (struct { fn callback( ud: ?*bool, _: *xev.Loop, _: *xev.Completion, r: RunError!void, ) xev.CallbackAction { ud.?.* = if (r) false else |err| err == error.Canceled; return .disarm; } }).callback); // Cancel var cancel_confirm = false; var c2: xev.Completion = undefined; timer.cancel(&loop, &c1, &c2, bool, &cancel_confirm, (struct { fn callback( ud: ?*bool, _: *xev.Loop, _: *xev.Completion, r: CancelError!void, ) xev.CallbackAction { _ = r catch unreachable; ud.?.* = true; return .disarm; } }).callback); // Wait try loop.run(.until_done); try testing.expect(canceled); try testing.expect(cancel_confirm); } }; }