euv_ui/hook/throttled_value/impl.rs
1use super::*;
2
3impl<T: Clone + PartialEq + Default + 'static> ThrottledValue<T> {
4 /// Sends `next` through the throttle.
5 ///
6 /// - If the throttle is idle (no cooldown active), the
7 /// value is emitted immediately, and a cooldown
8 /// window of `interval_ms` opens starting at `now`.
9 /// - If a cooldown is active, `next` is stored as the
10 /// next pending value. The pending value will be
11 /// committed on the next `tick` call once the
12 /// cooldown expires. Intermediate `set` calls during
13 /// the cooldown overwrite the pending slot — only
14 /// the most recent input wins.
15 ///
16 /// `interval_ms = 0` collapses to "every `set` is
17 /// immediately committed" — the cooldown branch is
18 /// never taken.
19 ///
20 /// # Arguments
21 ///
22 /// - `T: Clone + PartialEq + Default + 'static` - A generic type parameter.
23 /// - `Instant` - A monotonic instant in time (`Instant`).
24 pub fn set(&self, next: T, now: Instant) {
25 if self.interval_ms == 0 {
26 self.get_value().set(next);
27 self.get_pending().set(None);
28 self.get_state().set(ThrottleState::Idle);
29 return;
30 }
31 match self.get_state().get() {
32 ThrottleState::Idle => {
33 self.get_value().set(next);
34 self.get_state().set(ThrottleState::Cooldown(now));
35 }
36 ThrottleState::Cooldown(_) => {
37 self.get_pending().set(Some(next));
38 }
39 }
40 }
41
42 /// Drives the throttle forward.
43 ///
44 /// - If idle, no-op.
45 /// - If a cooldown is active and the cooldown
46 /// window has elapsed, any pending value is
47 /// committed and the state returns to `Idle`. If
48 /// nothing is pending, the state still returns to
49 /// `Idle`. The cooldown simply lapses in that case.
50 ///
51 /// Returns `true` when a pending value was committed,
52 /// `false` otherwise.
53 ///
54 /// # Arguments
55 ///
56 /// - `Instant` - A monotonic instant in time (`Instant`).
57 ///
58 /// # Returns
59 ///
60 /// - `bool` - A boolean.
61 pub fn tick(&self, now: Instant) -> bool {
62 match self.get_state().get() {
63 ThrottleState::Idle => false,
64 ThrottleState::Cooldown(start) => {
65 if now.duration_since(start).as_millis() < u128::from(self.interval_ms) {
66 return false;
67 }
68 let committed: bool = match self.get_pending().get() {
69 Some(pending) => {
70 self.get_value().set(pending);
71 self.get_pending().set(None);
72 true
73 }
74 None => false,
75 };
76 self.get_state().set(ThrottleState::Idle);
77 committed
78 }
79 }
80 }
81
82 /// Drops any pending value and ends the cooldown.
83 /// The emitted value is left untouched.
84 pub fn cancel(&self) {
85 self.get_pending().set(None);
86 self.get_state().set(ThrottleState::Idle);
87 }
88
89 /// Returns the currently emitted value as a snapshot.
90 ///
91 /// # Returns
92 ///
93 /// - `T` - The current value (or a snapshot thereof).
94 pub fn get(&self) -> T {
95 self.get_value().get()
96 }
97
98 /// Returns `true` when the throttle is in a cooldown
99 /// window (recent `set` that has not yet had a chance
100 /// to commit any pending follow-up).
101 ///
102 /// # Returns
103 ///
104 /// - `bool` - `true` when a throttle delay is active.
105 pub fn is_throttling(&self) -> bool {
106 matches!(self.get_state().get(), ThrottleState::Cooldown(_))
107 }
108}
109
110impl<T: Clone + PartialEq + Debug + Default + 'static> Display for ThrottledValue<T> {
111 /// Formats the [`ThrottledValue`] via the supplied formatter.
112 ///
113 /// # Arguments
114 ///
115 /// - `&mut Formatter<'_>` - The formatter receiving the formatted output.
116 ///
117 /// # Returns
118 ///
119 /// - `FmtResult` - Result of the formatting operation.
120 fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
121 match &self.get_state().get() {
122 ThrottleState::Idle => {
123 write!(formatter, "ThrottledValue({:?})", self.get_value().get())
124 }
125 ThrottleState::Cooldown(_) => {
126 write!(
127 formatter,
128 "ThrottledValue(cooldown={:?})",
129 self.get_value().get()
130 )
131 }
132 }
133 }
134}