templar_curator_primitives/policy/cooldown/
mod.rs1use core::num::NonZeroU64;
8
9use templar_vault_kernel::{DurationNs, TimeGate, TimestampNs};
10
11#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
17#[derive(Clone, Copy, PartialEq, Eq)]
18pub struct Cooldown {
19 last_event_ns: Option<u64>,
21 interval_ns: Option<NonZeroU64>,
23}
24
25impl Cooldown {
26 #[must_use]
27 fn normalized(last_event_ns: Option<u64>, interval_ns: Option<NonZeroU64>) -> Self {
28 Self {
29 last_event_ns,
30 interval_ns,
31 }
32 }
33
34 fn gate(&self) -> TimeGate {
35 if self.is_unlimited() {
36 return TimeGate::ready_now();
37 }
38
39 match (self.last_event_ns, self.interval_ns) {
40 (Some(last), Some(interval)) => {
41 TimeGate::schedule_from(TimestampNs(last), DurationNs(interval.get()))
42 }
43 _ => TimeGate::ready_now(),
44 }
45 }
46
47 #[must_use]
48 pub fn new(interval_ns: NonZeroU64) -> Self {
49 Self::normalized(None, Some(interval_ns))
50 }
51
52 #[must_use]
53 pub fn unlimited() -> Self {
54 Self {
55 last_event_ns: None,
56 interval_ns: None,
57 }
58 }
59
60 #[must_use]
61 pub fn is_unlimited(&self) -> bool {
62 self.interval_ns.is_none()
63 }
64
65 #[must_use]
66 pub fn last_event_ns(&self) -> Option<u64> {
67 self.last_event_ns
68 }
69
70 #[must_use]
71 pub fn interval_ns(&self) -> Option<NonZeroU64> {
72 self.interval_ns
73 }
74
75 #[must_use]
86 pub fn is_ready(&self, current_ns: u64) -> bool {
87 self.gate().is_ready(TimestampNs(current_ns))
88 }
89
90 pub fn try_acquire(self, current_ns: u64) -> Result<Self, CooldownError> {
91 match self.ready_at() {
92 Some(ready_at_ns) if current_ns < ready_at_ns => Err(CooldownError::OnCooldown {
93 ready_at_ns,
94 remaining_ns: ready_at_ns - current_ns,
95 }),
96 _ => Ok(self.recorded_at(current_ns)),
97 }
98 }
99
100 pub fn check(&self, current_ns: u64) -> Result<(), CooldownError> {
102 self.try_acquire(current_ns).map(|_| ())
103 }
104
105 #[must_use]
106 pub fn recorded_at(self, timestamp_ns: u64) -> Self {
107 Self::normalized(Some(timestamp_ns), self.interval_ns)
108 }
109
110 #[must_use]
111 pub fn with_last_event_ns(self, last_event_ns: Option<u64>) -> Self {
112 Self::normalized(last_event_ns, self.interval_ns)
113 }
114
115 #[must_use]
116 pub fn ready_at(&self) -> Option<u64> {
117 self.gate().ready_at_ns().map(Into::into)
118 }
119
120 #[must_use]
121 pub fn remaining(&self, current_ns: u64) -> u64 {
122 self.gate().remaining(TimestampNs(current_ns)).into()
123 }
124}
125
126#[templar_vault_macros::vault_derive]
128#[derive(Clone, Copy, PartialEq, Eq)]
129pub enum CooldownError {
130 OnCooldown { ready_at_ns: u64, remaining_ns: u64 },
132}