Thanks to visit codestin.com
Credit goes to lib.rs

#timing #ui #timer #no-std #understory

no-std understory_timing

Host-agnostic timer queue primitives

3 releases

Uses new Rust 2024

0.1.2 May 17, 2026
0.1.1 May 17, 2026
0.1.0 May 14, 2026

#638 in GUI

Apache-2.0 OR MIT

39KB
446 lines

Understory Timing

Host-agnostic timer queue primitives

Latest published version. Documentation build status. Apache 2.0 or MIT license.
GitHub Actions CI status.

Understory Timing: host-agnostic timer queue primitives.

This crate provides a small, deterministic core for ordering timers by monotonic deadlines. It is intended for UI toolkits, event loops, and other host runtimes that want timer bookkeeping without taking on clocks, threads, async reactors, callbacks, or platform wakeups.

The core concepts are:

  • TimerInstant and TimerDuration: host-provided integer ticks. Most hosts use nanoseconds, but the queue treats them as opaque monotonic labels.
  • TimerQueue: a deadline-ordered queue of pending timers.
  • TimerId: the queue-assigned id used for queue-local cancellation and delivery recognition.
  • TimerRepeat: the policy for calculating a repeating timer's next deadline.
  • ExpiredTimer: an owned record returned to the host once a timer is due.

This crate deliberately does not know about wall-clock time, sleeping, wakeup registration, async tasks, widgets, rendering, or redraw policy. Host runtimes are responsible for:

The queue is backed by a sorted alloc::collections::VecDeque. It favors a small dependency-free core and explicit ordering rules over high-volume timer scheduling machinery. It is a good fit for the modest timer counts common in UI toolkits and small runtime loops; hosts with very large timer sets can layer a heap or timing wheel above a different scheduling core.

Invariants

  • Pending timers are stored in deadline order.
  • Timers with equal deadlines fire in scheduling order.
  • Timer ids are stable for the timer record, including after expiration.
  • Cancellation is idempotent, applies to pending timers, and reports whether a pending timer was removed.
  • Expired timers are removed before they are returned to the host.
  • Relative deadline arithmetic saturates at u64::MAX.

Cancellation and delivery

TimerQueue::cancel removes pending timers only. Once a timer has been returned by TimerQueue::pop_expired, it is no longer pending; hosts that batch expired timers before dispatch may still deliver that already-drained record. Owners should compare the delivered TimerId with their current stored id or token and ignore stale deliveries.

Repeating timers make rearm explicit for the same reason. To cancel a repeating timer that has already expired, drop the ExpiredTimer instead of passing it to TimerQueue::rearm.

Target payloads

Timer targets are host-defined owner handles. Use ids such as element, widget, task, connection, or request ids when possible. Expired timers own their target handle so the queue is not borrowed while the host dispatches the timer.

Minimal example

use understory_timing::TimerQueue;

let mut timers = TimerQueue::new();
let button = 42_u32;
let id = timers.schedule_once(button, 1_000, 250);

assert_eq!(timers.next_deadline(), Some(1_250));

let timer = timers.pop_expired(1_250).expect("timer is due");

assert_eq!(timer.id(), id);
assert_eq!(*timer.target(), button);
assert!(timers.is_empty());

External timer tokens

Host runtimes that already expose their own timer token type can store that token in the target payload. The queue's TimerId remains useful for diagnostics or queue-local cancellation, while the host token remains the value returned from higher-level APIs:

use understory_timing::TimerQueue;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
struct AppTimerToken(u64);

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
struct TimerTarget {
    token: AppTimerToken,
    window: u32,
}

let mut timers = TimerQueue::new();
let delivered = AppTimerToken(7);
let cancelled = AppTimerToken(8);

timers.schedule_once(
    TimerTarget {
        token: delivered,
        window: 1,
    },
    1_000,
    250,
);
timers.schedule_once(
    TimerTarget {
        token: cancelled,
        window: 1,
    },
    1_000,
    500,
);

let removed = timers.retain_pending(|timer| timer.target().token != cancelled);
assert_eq!(removed, 1);

let expired = timers.pop_expired(1_250).expect("timer is due");
let target = expired.into_target();

assert_eq!(target.token, delivered);
assert_eq!(target.window, 1);

Minimum supported Rust Version (MSRV)

This crate has been verified to compile with Rust 1.88 and later.

License

Licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Contribution

Contributions are welcome by pull request. The Rust code of conduct applies. Please feel free to add your name to the AUTHORS file in any substantive pull request.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

No runtime deps