timer.h — Callback-Style Timer

Introduction

timer.h provides xpp::Timer, a move-only RAII wrapper around xTimerStart / xTimerStop. It supports both one-shot and repeating timers via a callback API, with stop() / start() for pause/resume.

Timer complements Promise<void>::after(ms):

Promise<void>::after(ms)xpp::Timer
StylePromise-based (poll/wait)Callback-based
ModesOne-shot onlyOne-shot + repeating
Composition.then(), .await()None (just fires callback)
Use caseDelayed computation in a Promise chainPeriodic tasks, heartbeats, simple delayed callbacks

API Reference

Construction

ExpressionBehavior
Timer(ms, cb)Repeating: fires every ms (timeout = repeat = ms)
Timer(timeout, repeat, cb)Explicit: first fire after timeout, then every repeat. repeat == 0 = one-shot

cb is a callable with signature void(). It is stored by value (decay-copy) inside a heap-allocated State.

Methods

MethodReturnsDescription
stop()voidCancel the timer. Idempotent.
start()boolResume after stop(). false if already active or no live loop.
is_active()boolTrue if timer is currently scheduled.
operator bool()boolEquivalent to is_active().
handle()xTimerUnderlying handle for C interop, or nullptr.

Lifetime

  • Move-only: copy is deleted. Move transfers ownership of the internal State.
  • RAII: destructor calls stop() if the timer is active.
  • WaitScope contract: must be constructed within a WaitScope. The callback runs on the WaitScope thread.

Usage Examples

Repeating timer

xpp::EventLoop loop;
xpp::WaitScope scope(loop);

int ticks = 0;
xpp::Timer t(100, [&]() {
  if (++ticks >= 5) loop.stop();
});

loop.run();
// 5 ticks, ~500ms

One-shot delayed callback

xpp::Timer t(1000, 0, [&]() {
  printf("1 second elapsed\n");
});

Asymmetric repeating (fast first fire, slow subsequent)

// First fire at 10ms, then every 5s
xpp::Timer t(10, 5000, [&]() { poll_device(); });

Pause and resume

xpp::Timer t(100, [&]() { heartbeat(); });

// ... later, pause ...
t.stop();

// ... even later, resume ...
t.start();  // next fire is 100ms from now (not from when we paused)

Self-stop from callback

int n = 0;
xpp::Timer *t_ptr = nullptr;
xpp::Timer t(100, [&]() {
  if (++n >= 3) t_ptr->stop();
});
t_ptr = &t;

Calling stop() from inside the callback is safe — libx re-arms repeating timers before invoking the callback, so xTimerStop finds a valid timer in the heap and removes it.

Notes

Callback exceptions

If the callback throws, the exception propagates through xEventLoopRun to the caller of loop.run(). Throwing callbacks are the user's responsibility.

No deadline preservation on resume

stop() discards the original deadline. start() schedules a fresh timer with the original timeout_ms / repeat_ms. The next fire is timeout_ms away, regardless of when stop() was called.

This matches libuv's uv_timer_stop / uv_timer_start semantics.

Loop-destroy cleanup

When the host event loop is destroyed with the timer still pending, libx invokes the on_cancel hook (added in the x-timer-on-cancel change). The hook nulls the stored handle, so ~Timer skips xTimerStop. The user callback is NOT invoked on this path.

Comparison with Promise<void>::after(ms)

// Promise-based (use for Promise composition):
Promise<void>::after(100).then([]() {
  return compute_result();
}).await();

// Callback-based (use for periodic tasks or simple callbacks):
xpp::Timer t(100, []() {
  heartbeat();
});

Use after(ms) when you need .then() / .await() composition. Use Timer when you need a periodic callback or a simple one-shot callback without Promise overhead.