1use crate::{BackgroundExecutor, Task};
2use std::{
3 future::Future,
4 pin::Pin,
5 sync::atomic::{AtomicUsize, Ordering::SeqCst},
6 task,
7 time::Duration,
8};
9
10pub use util::*;
11
12pub trait FluentBuilder {
14 fn map<U>(self, f: impl FnOnce(Self) -> U) -> U
16 where
17 Self: Sized,
18 {
19 f(self)
20 }
21
22 fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
24 where
25 Self: Sized,
26 {
27 self.map(|this| if condition { then(this) } else { this })
28 }
29
30 fn when_else(
32 self,
33 condition: bool,
34 then: impl FnOnce(Self) -> Self,
35 else_fn: impl FnOnce(Self) -> Self,
36 ) -> Self
37 where
38 Self: Sized,
39 {
40 self.map(|this| if condition { then(this) } else { else_fn(this) })
41 }
42
43 fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
45 where
46 Self: Sized,
47 {
48 self.map(|this| {
49 if let Some(value) = option {
50 then(this, value)
51 } else {
52 this
53 }
54 })
55 }
56 fn when_none<T>(self, option: &Option<T>, then: impl FnOnce(Self) -> Self) -> Self
58 where
59 Self: Sized,
60 {
61 self.map(|this| if option.is_some() { this } else { then(this) })
62 }
63}
64
65pub trait FutureExt {
67 fn with_timeout(self, timeout: Duration, executor: &BackgroundExecutor) -> WithTimeout<Self>
70 where
71 Self: Sized;
72}
73
74impl<T: Future> FutureExt for T {
75 fn with_timeout(self, timeout: Duration, executor: &BackgroundExecutor) -> WithTimeout<Self>
76 where
77 Self: Sized,
78 {
79 WithTimeout {
80 future: self,
81 timer: executor.timer(timeout),
82 }
83 }
84}
85
86pub struct WithTimeout<T> {
87 future: T,
88 timer: Task<()>,
89}
90
91#[derive(Debug, thiserror::Error)]
92#[error("Timed out before future resolved")]
93pub struct Timeout;
95
96impl<T: Future> Future for WithTimeout<T> {
97 type Output = Result<T::Output, Timeout>;
98
99 fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> task::Poll<Self::Output> {
100 let this = unsafe { &raw mut *self.get_unchecked_mut() };
103 let future = unsafe { Pin::new_unchecked(&mut (*this).future) };
104 let timer = unsafe { Pin::new_unchecked(&mut (*this).timer) };
105
106 if let task::Poll::Ready(output) = future.poll(cx) {
107 task::Poll::Ready(Ok(output))
108 } else if timer.poll(cx).is_ready() {
109 task::Poll::Ready(Err(Timeout))
110 } else {
111 task::Poll::Pending
112 }
113 }
114}
115
116#[cfg(any(test, feature = "test-support"))]
117pub async fn smol_timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
120where
121 F: Future<Output = T>,
122{
123 let timer = async {
124 smol::Timer::after(timeout).await;
125 Err(())
126 };
127 let future = async move { Ok(f.await) };
128 smol::future::FutureExt::race(timer, future).await
129}
130
131pub(crate) fn atomic_incr_if_not_zero(counter: &AtomicUsize) -> usize {
134 let mut loaded = counter.load(SeqCst);
135 loop {
136 if loaded == 0 {
137 return 0;
138 }
139 match counter.compare_exchange_weak(loaded, loaded + 1, SeqCst, SeqCst) {
140 Ok(x) => return x + 1,
141 Err(actual) => loaded = actual,
142 }
143 }
144}
145
146#[inline]
148pub(crate) fn round_half_toward_zero(value: f32) -> f32 {
149 (value.abs() - 0.5).ceil().copysign(value)
150}
151
152#[inline]
153pub(crate) fn round_half_toward_zero_f64(value: f64) -> f64 {
154 (value.abs() - 0.5).ceil().copysign(value)
155}
156
157#[inline]
158pub(crate) fn round_to_device_pixel(logical: f32, scale_factor: f32) -> f32 {
159 round_half_toward_zero(logical * scale_factor)
160}
161
162#[inline]
163pub(crate) fn round_stroke_to_device_pixel(logical: f32, scale_factor: f32) -> f32 {
164 if logical == 0.0 {
165 0.0
166 } else {
167 round_to_device_pixel(logical.max(0.0), scale_factor).max(1.0)
168 }
169}
170
171#[inline]
172pub(crate) fn floor_to_device_pixel(logical: f32, scale_factor: f32) -> f32 {
173 (logical * scale_factor).floor()
174}
175
176#[inline]
177pub(crate) fn ceil_to_device_pixel(logical: f32, scale_factor: f32) -> f32 {
178 (logical * scale_factor).ceil()
179}
180
181#[cfg(test)]
182mod tests {
183 use crate::TestAppContext;
184
185 use super::*;
186
187 #[test]
188 fn test_round_half_toward_zero() {
189 assert_eq!(round_half_toward_zero(0.5), 0.0);
190 assert_eq!(round_half_toward_zero(1.5), 1.0);
191 assert_eq!(round_half_toward_zero(2.5), 2.0);
192 assert_eq!(round_half_toward_zero(-0.5), 0.0);
193 assert_eq!(round_half_toward_zero(-1.5), -1.0);
194 assert_eq!(round_half_toward_zero(-2.5), -2.0);
195
196 assert_eq!(round_half_toward_zero(1.5001), 2.0);
197 assert_eq!(round_half_toward_zero(1.4999), 1.0);
198 assert_eq!(round_half_toward_zero(-1.5001), -2.0);
199 assert_eq!(round_half_toward_zero(-1.4999), -1.0);
200
201 assert_eq!(round_half_toward_zero(0.0), 0.0);
202 assert_eq!(round_half_toward_zero(3.0), 3.0);
203 assert_eq!(round_half_toward_zero(-3.0), -3.0);
204 }
205
206 #[test]
207 fn test_device_pixel_helpers() {
208 assert_eq!(round_to_device_pixel(1.0, 1.5), 1.0);
209 assert_eq!(round_to_device_pixel(0.3, 2.0), 1.0);
210 assert_eq!(round_to_device_pixel(1.4, 1.0), 1.0);
211 assert_eq!(round_to_device_pixel(1.6, 1.0), 2.0);
212
213 assert_eq!(round_stroke_to_device_pixel(0.0, 1.0), 0.0);
214 assert_eq!(round_stroke_to_device_pixel(0.4, 1.0), 1.0);
215 assert_eq!(round_stroke_to_device_pixel(0.5, 1.0), 1.0);
216 assert_eq!(round_stroke_to_device_pixel(1.0, 1.5), 1.0);
217 assert_eq!(round_stroke_to_device_pixel(1.6, 1.0), 2.0);
218
219 assert_eq!(floor_to_device_pixel(0.3, 2.0), 0.0);
220 assert_eq!(ceil_to_device_pixel(0.3, 2.0), 1.0);
221 assert_eq!(floor_to_device_pixel(2.1, 1.0), 2.0);
222 assert_eq!(ceil_to_device_pixel(2.1, 1.0), 3.0);
223
224 assert_eq!(round_to_device_pixel(2.0, 2.0), 4.0);
225 assert_eq!(floor_to_device_pixel(2.0, 2.0), 4.0);
226 assert_eq!(ceil_to_device_pixel(2.0, 2.0), 4.0);
227 }
228
229 #[test]
230 fn test_round_half_toward_zero_f64() {
231 assert_eq!(round_half_toward_zero_f64(0.5), 0.0);
232 assert_eq!(round_half_toward_zero_f64(-0.5), 0.0);
233 assert_eq!(round_half_toward_zero_f64(1.5), 1.0);
234 assert_eq!(round_half_toward_zero_f64(-1.5), -1.0);
235 assert_eq!(round_half_toward_zero_f64(2.5001), 3.0);
236 }
237
238 #[gpui::test]
239 async fn test_with_timeout(cx: &mut TestAppContext) {
240 Task::ready(())
241 .with_timeout(Duration::from_secs(1), &cx.executor())
242 .await
243 .expect("Timeout should be noop");
244
245 let long_duration = Duration::from_secs(6000);
246 let short_duration = Duration::from_secs(1);
247 cx.executor()
248 .timer(long_duration)
249 .with_timeout(short_duration, &cx.executor())
250 .await
251 .expect_err("timeout should have triggered");
252
253 let fut = cx
254 .executor()
255 .timer(long_duration)
256 .with_timeout(short_duration, &cx.executor());
257 cx.executor().advance_clock(short_duration * 2);
258 futures::FutureExt::now_or_never(fut)
259 .unwrap_or_else(|| panic!("timeout should have triggered"))
260 .expect_err("timeout");
261 }
262}