Skip to documentation
SLOP

tiny.reticulum.node.timer

Reference tiny.reticulum node timer

Defined in node.

API (25)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callsnodetimer
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callersprivate sourcelib.reticulum.src.node.timer.TableremoveAtnode.timer.Duenext
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsnode.Nodeactivatetest sourcelib.reticulum.src.node.timertest: timer admission accounts for on...test sourcelib.reticulum.src.node.timertest: timers admit maximum and reject...test sourcelib.reticulum.src.node.timertest: timers iterate due entries in d...test sourcelib.reticulum.src.node.timertest: timers keep one deadline per li...node.timer.Tableactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.reticulum.src.node.timertest: timer admission accounts for on...node.timer.Tablecontainsnode.timer.TablecanScheduleAfterCancel
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.reticulum.src.node.timertest: timers iterate due entries in d...test sourcelib.reticulum.src.node.timertest: timers keep one deadline per li...private sourcelib.reticulum.src.node.timer.TableremoveAtnode.timer.Tablecancel
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsnode.timer.TablecanScheduleAfterCanceltest sourcelib.reticulum.src.node.timertest: timers keep one deadline per li...node.timer.Tablecontains
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callstest sourcelib.reticulum.src.node.timertest: timers admit maximum and reject...test sourcelib.reticulum.src.node.timertest: timers keep one deadline per li...node.timer.Tablecount
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsnode.Nodedeinittest sourcelib.reticulum.src.node.timertest: timer admission accounts for on...test sourcelib.reticulum.src.node.timertest: timers admit maximum and reject...test sourcelib.reticulum.src.node.timertest: timers iterate due entries in d...test sourcelib.reticulum.src.node.timertest: timers keep one deadline per li...node.timer.Tabledeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.node.timertest: timers iterate due entries in d...node.timer.Tabledue
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.reticulum.src.node.ownerinitSubownerstest sourcelib.reticulum.src.node.timertest: timer admission accounts for on...test sourcelib.reticulum.src.node.timertest: timers admit maximum and reject...test sourcelib.reticulum.src.node.timertest: timers iterate due entries in d...test sourcelib.reticulum.src.node.timertest: timers keep one deadline per li...node.timer.Tableinit
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.node.timertest: timer admission accounts for on...test sourcelib.reticulum.src.node.timertest: timers admit maximum and reject...test sourcelib.reticulum.src.node.timertest: timers iterate due entries in d...test sourcelib.reticulum.src.node.timertest: timers keep one deadline per li...node.timer.Tableschedule
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callstest sourcelib.reticulum.src.node.timertest: timers keep one deadline per li...node.timer.TablescheduledAt
Static calls · unresolved targets: 0 · external targets: 1.

Source: lib/reticulum/src/node/root.zig:72

zig
pub const timer = @import("timer.zig");

Source: lib/reticulum/src/node/timer.zig

zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const packet = @import("../packet/root.zig");pub const Seconds = u64;pub const TimerId = union(enum) {    receipt: packet.Hash,    hashlist: void,    announces: void,    link: [16]u8,    link_entries: void,    pub fn eql(left: TimerId, right: TimerId) bool {        return switch (left) {            .receipt => |left_hash| switch (right) {                .receipt => |right_hash| std.mem.eql(u8, &left_hash, &right_hash),                .hashlist, .announces, .link, .link_entries => false,            },            .hashlist => right == .hashlist,            .announces => right == .announces,            .link_entries => right == .link_entries,            .link => |left_id| switch (right) {                .link => |right_id| std.mem.eql(u8, &left_id, &right_id),                .receipt, .hashlist, .announces, .link_entries => false,            },        };    }};pub const Timer = struct {    id: TimerId,    at: Seconds,};const TableLimits = struct {    timers_max: usize,};const TableCapacity = struct {    timers_max: usize,    storage_bytes: usize,    pub const DeriveError = error{ InvalidLimit, CapacityOverflow };    pub fn derive(limits: TableLimits) DeriveError!TableCapacity {        if (limits.timers_max == 0) return error.InvalidLimit;        const timers_max = limits.timers_max;        const storage_bytes = alloc_phase.capacity.mul(            usize,            timers_max,            @sizeOf(Timer),        ) catch return error.CapacityOverflow;        return .{ .timers_max = timers_max, .storage_bytes = storage_bytes };    }};pub const Table = struct {    phase: alloc_phase.capacity.Phase,    capacity: Capacity,    storage: Storage,    entries: []Timer,    len: usize = 0,    pub const storage_alignment: usize = 8;    pub const Storage = []align(storage_alignment) u8;    pub const Limits: type = TableLimits;    pub const Capacity: type = TableCapacity;    pub const Exhaustion = error{Full};    pub const InitError = Capacity.DeriveError || error{StorageLengthMismatch};    pub const work_limits: alloc_phase.capacity.WorkLimits = .{        .transition_steps_max = 131_072,        .cleanup_steps_per_call_max = 65_536,        .cleanup_calls_at_capacity_max = 1,    };    pub const claim: alloc_phase.capacity.Declaration = .{        .source = .{            .id = "reticulum.timers",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{.{                    .id = "caller_timer_table",                    .lifetime = .transferred,                    .detail = "caller storage for bounded node timers",                }},                .excluded = &.{                    "event timestamps",                    "operating-system clocks and scheduler state",                },            },            .capacity = .{                .inputs = &.{alloc_phase.capacity.bindInput(                    TableLimits,                    "timers_max",                    "timers_max",                )},                .type_selectors = &.{alloc_phase.capacity.bindType(Timer, "timer")},                .nodes = &.{                    .{ .input = 0 },                    .{ .scale = .{                        .node = 0,                        .coefficient = .{ .size_of_concrete_type = 0 },                    } },                },                .assertions = &.{.{                    .scope = .closure_total,                    .measure = .retained,                    .relation = .exact,                    .expression = 1,                }},            },            .overload = .{                .kind = .reject_before_mutation,                .detail = "full timer admission preserves scheduled timers",            },            .risks = .{                .transitive = .{                    .status = .excluded,                    .detail = "timer operations call no allocating owner",                },                .foreign = .{                    .status = .excluded,                    .detail = "the timer table reads no external clock",                },            },            .work = .{ .equation = "timer operations scan at most timers_max entries" },            .obligations = &.{                .{ .key = "reticulum_timers_capacity", .role = .capacity_model },                .{ .key = "reticulum_timers_overload", .role = .overload },                .{ .key = "reticulum_timers_work", .role = .work_bound },            },        },        .bindings = .{            .owner = @This(),            .seal = .{                .family = alloc_phase.capacity.selector(@This().activate),                .premise = .{ .class = .checked_semantic_fact, .authority = .checker },            },            .teardown = .{                .family = alloc_phase.capacity.selector(@This().deinit),                .premise = .{ .class = .checked_semantic_fact, .authority = .checker },            },        },    };    pub fn init(storage: Storage, limits: Limits) InitError!Table {        const capacity = try Capacity.derive(limits);        if (storage.len != capacity.storage_bytes) return error.StorageLengthMismatch;        return .{            .phase = .initialization,            .capacity = capacity,            .storage = storage,            .entries = std.mem.bytesAsSlice(Timer, storage),        };    }    pub fn activate(self: *Table) void {        std.debug.assert(self.phase == .initialization);        std.debug.assert(self.len == 0);        self.phase = .steady;    }    pub fn schedule(self: *Table, id: TimerId, at: Seconds) Exhaustion!void {        std.debug.assert(self.phase == .steady);        for (self.entries[0..self.len]) |*entry| {            if (!entry.id.eql(id)) continue;            entry.at = at;            return;        }        if (self.len == self.capacity.timers_max) return error.Full;        self.entries[self.len] = .{ .id = id, .at = at };        self.len += 1;    }    pub fn cancel(self: *Table, id: TimerId) bool {        std.debug.assert(self.phase == .steady);        for (self.entries[0..self.len], 0..) |entry, index| {            if (!entry.id.eql(id)) continue;            self.removeAt(index);            return true;        }        return false;    }    pub fn contains(self: *const Table, id: TimerId) bool {        std.debug.assert(self.phase == .steady);        for (self.entries[0..self.len]) |entry| {            if (entry.id.eql(id)) return true;        }        return false;    }    /// Gives the second one named timer comes due at, so a caller checks the deadline the node    /// armed for one timer. A name the table holds no timer for gives null.    pub fn scheduledAt(self: *const Table, id: TimerId) ?Seconds {        std.debug.assert(self.phase == .steady);        for (self.entries[0..self.len]) |entry| {            if (entry.id.eql(id)) return entry.at;        }        return null;    }    pub fn canScheduleAfterCancel(self: *const Table, id: TimerId, cancelled_id: ?TimerId) bool {        std.debug.assert(self.phase == .steady);        if (self.contains(id)) return true;        if (self.len < self.capacity.timers_max) return true;        const cancelled = cancelled_id orelse return false;        return self.contains(cancelled);    }    pub fn due(self: *Table, now: Seconds) Due {        std.debug.assert(self.phase == .steady);        return .{ .table = self, .now = now };    }    pub fn count(self: *const Table) usize {        std.debug.assert(self.phase == .steady);        return self.len;    }    pub fn deinit(self: *Table) Storage {        std.debug.assert(self.phase == .steady);        self.phase = .teardown;        const storage = self.storage;        self.* = undefined;        return storage;    }    fn removeAt(self: *Table, index: usize) void {        std.debug.assert(index < self.len);        std.mem.copyForwards(            Timer,            self.entries[index .. self.len - 1],            self.entries[index + 1 .. self.len],        );        self.len -= 1;    }};pub const Due = struct {    table: *Table,    now: Seconds,    pub fn next(self: *Due) ?Timer {        std.debug.assert(self.table.phase == .steady);        var earliest: ?usize = null;        for (0..self.table.capacity.timers_max) |index| {            if (index == self.table.len) break;            const candidate = self.table.entries[index];            if (candidate.at > self.now) continue;            if (earliest) |selected| {                if (candidate.at >= self.table.entries[selected].at) continue;            }            earliest = index;        }        const index = earliest orelse return null;        const value = self.table.entries[index];        self.table.removeAt(index);        return value;    }};comptime {    alloc_phase.capacity.requireProvisionedRejectingOwnerShape(Table);}test "timers admit maximum and reject maximum plus one" {    comptime {        @stardustClaim(alloc_phase.capacity.witness(            Table,            "reticulum_timers_capacity",        ), null, null, null, null, null, null);        @stardustClaim(alloc_phase.capacity.witness(            Table,            "reticulum_timers_overload",        ), null, null, null, null, null, null);        @stardustClaim(alloc_phase.capacity.witness(            Table,            "reticulum_timers_work",        ), null, null, null, null, null, null);    }    const capacity = comptime TableCapacity.derive(.{ .timers_max = 3 }) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined;    var table = try Table.init(&bytes, .{ .timers_max = 3 });    table.activate();    defer _ = table.deinit();    try table.schedule(.{ .receipt = @splat(1) }, 30);    try table.schedule(.{ .receipt = @splat(2) }, 10);    try table.schedule(.{ .receipt = @splat(3) }, 20);    try std.testing.expectError(error.Full, table.schedule(.hashlist, 40));    try std.testing.expectEqual(@as(usize, 3), table.count());}test "timers iterate due entries in deadline order" {    const capacity = comptime TableCapacity.derive(.{ .timers_max = 3 }) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined;    var table = try Table.init(&bytes, .{ .timers_max = 3 });    table.activate();    defer _ = table.deinit();    try table.schedule(.{ .receipt = @splat(1) }, 30);    try table.schedule(.{ .receipt = @splat(2) }, 10);    try table.schedule(.{ .receipt = @splat(3) }, 20);    var due = table.due(20);    try std.testing.expect(due.next().?.id.eql(.{ .receipt = @splat(2) }));    try std.testing.expect(due.next().?.id.eql(.{ .receipt = @splat(3) }));    try std.testing.expect(due.next() == null);    try std.testing.expect(table.cancel(.{ .receipt = @splat(1) }));}test "timer admission accounts for one cancelled receipt timer" {    const capacity = comptime TableCapacity.derive(.{ .timers_max = 1 }) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined;    var table = try Table.init(&bytes, .{ .timers_max = 1 });    table.activate();    defer _ = table.deinit();    const old = TimerId{ .receipt = @splat(1) };    const new = TimerId{ .receipt = @splat(2) };    try table.schedule(old, 10);    try std.testing.expect(table.canScheduleAfterCancel(new, old));    try std.testing.expect(!table.canScheduleAfterCancel(new, null));}test "timers keep one deadline per link id" {    const capacity = comptime TableCapacity.derive(.{ .timers_max = 2 }) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined;    var table = try Table.init(&bytes, .{ .timers_max = 2 });    table.activate();    defer _ = table.deinit();    const first = TimerId{ .link = @splat(1) };    const second = TimerId{ .link = @splat(2) };    try table.schedule(first, 10);    try table.schedule(second, 20);    try table.schedule(first, 30);    try std.testing.expectEqual(@as(usize, 2), table.count());    try std.testing.expectEqual(@as(?Seconds, 30), table.scheduledAt(first));    try std.testing.expect(!table.contains(.{ .receipt = @splat(1) }));    try std.testing.expect(table.cancel(second));    try std.testing.expect(!table.contains(second));}

Audit

Definitions26
Public names30
Members15
Version26.7.0
Revisiondaab053ee433