Skip to main content

euv_ui/hook/throttled_value/
impl.rs

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