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}