use "collections"
class Timer
"""
The `Timer` class represents a timer that fires after an expiration
time, and then fires at an interval. When a `Timer` fires, it calls
the `apply` method of the `TimerNotify` object that was passed to it
when it was created.
The following example waits 5 seconds and then fires every 2
seconds, and when it fires the `TimerNotify` object prints how many
times it has been called:
```pony
use "time"
actor Main
new create(env: Env) =>
let timers = Timers
let timer = Timer(Notify(env), 5_000_000_000, 2_000_000_000)
timers(consume timer)
class Notify is TimerNotify
let _env: Env
var _counter: U32 = 0
new iso create(env: Env) =>
_env = env
fun ref apply(timer: Timer, count: U64): Bool =>
_env.out.print(_counter.string())
_counter = _counter + 1
true
```
"""
var _expiration: U64
var _interval: U64
let _notify: TimerNotify
embed _node: ListNode[Timer]
new iso create(
notify: TimerNotify iso,
expiration: U64,
interval: U64 = 0)
=>
"""
Create a new timer. The expiration time should be a nanosecond count
until the first expiration. The interval should also be in nanoseconds.
"""
_expiration = expiration + Time.nanos()
_interval = interval
_notify = consume notify
_node = ListNode[Timer]
try _node()? = this end
new abs(notify: TimerNotify, expiration: (I64, I64), interval: U64 = 0) =>
"""
Creates a new timer with an absolute expiration time rather than a relative
time. The expiration time is wall-clock adjusted system time.
"""
_expiration = _abs_expiration_time(expiration)
_interval = interval
_notify = notify
_node = ListNode[Timer]
try _node()? = this end
fun ref _cancel() =>
"""
Remove the timer from any list.
"""
_node.remove()
_notify.cancel(this)
fun ref _get_node(): ListNode[Timer] =>
"""
Returns the list node pointing to the timer. Used to schedule the timer in
a queue.
"""
_node
fun ref _slop(bits: USize) =>
"""
Apply slop bits to the expiration time and interval. This reduces the
precision by the given number of bits, effectively quantizing time.
"""
_expiration = _expiration >> bits.u64()
if _interval > 0 then
_interval = (_interval >> bits.u64()).max(1)
end
fun ref _fire(current: U64): Bool =>
"""
A timer is fired if its expiration time is in the past. The notifier is
called with a count based on the elapsed time since expiration and the
timer interval. The expiration time is set to the next expiration. Returns
true if the timer should be rescheduled, false otherwise.
"""
let elapsed = current - _expiration
if elapsed < (1 << 63) then
let count = (elapsed / _interval) + 1
_expiration = _expiration + (count * _interval)
if not _notify(this, count) then
_notify.cancel(this)
return false
end
end
(_interval > 0) or ((_expiration - current) < (1 << 63))
fun _next(): U64 =>
"""
Returns the next expiration time.
"""
_expiration
fun tag _abs_expiration_time(wall: (I64, I64)): U64 =>
"""
Converts a wall-clock adjusted system time to absolute expiration time
"""
let wall_now = Time.now()
Time.nanos()
+ (((wall._1 * 1000000000) + wall._2)
- ((wall_now._1 * 1000000000) + wall_now._2)).u64()