maycoon_core/tasks/
fetcher.rs

1use crate::tasks::runner;
2use crate::tasks::task::Task;
3
4/// A value factory that uses the result of an asynchronous task to compute an output value.
5///
6/// The fetcher takes a task and a factory function.
7/// It produces an output value `O` by taking an [Option] of an input value `I`.
8///
9/// If the inner [Task] is not ready yet, the factory will be called with `None`.
10/// Once the inner [Task] is ready, the factory will be called with `Some(I)`.
11///
12/// This is similar to the [FutureBuilder](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html) from Flutter.
13pub struct Fetcher<I: Send + 'static, O> {
14    task: Option<Box<dyn Task<I>>>,
15    factory: Box<dyn Fn(Option<I>) -> O>,
16    value: Option<O>,
17}
18
19impl<I: Send + 'static, O> Fetcher<I, O> {
20    /// Create a new task fetcher with the given task and factory function.
21    #[inline(always)]
22    pub const fn new(
23        task: Box<dyn Task<I>>,
24        factory: Box<dyn Fn(Option<I>) -> O + 'static>,
25    ) -> Self {
26        Self {
27            task: Some(task),
28            factory,
29            value: None,
30        }
31    }
32
33    /// Spawn a future using the [TaskRunner](runner::TaskRunner)
34    /// and create a new [Fetcher] with the resulting task and the given factory function.
35    ///
36    /// If you want to spawn a blocking task, use [Fetcher::spawn_blocking] instead.
37    #[inline(always)]
38    pub fn spawn(
39        future: impl Future<Output = I> + Send + 'static,
40        factory: impl Fn(Option<I>) -> O + 'static,
41    ) -> Self {
42        let runner = runner();
43        let task = runner.spawn(future);
44
45        Self::new(task, Box::new(factory))
46    }
47
48    /// Spawn a blocking function using the [TaskRunner](runner::TaskRunner)
49    /// and create a new [Fetcher] with the resulting task and the given factory function.
50    ///
51    /// If your task is non-blocking (a simple future), use [Fetcher::spawn] instead.
52    #[inline(always)]
53    #[cfg(native)]
54    pub fn spawn_blocking(
55        func: impl Fn() -> I + Send + 'static,
56        factory: impl Fn(Option<I>) -> O + 'static,
57    ) -> Self {
58        let runner = runner();
59        let task = runner.spawn_blocking(func);
60
61        Self::new(task, Box::new(factory))
62    }
63
64    /// Returns a mutable reference to the possible output value.
65    ///
66    /// This is mostly used internally and will return the cached output value.
67    #[inline(always)]
68    pub const fn value_mut(&mut self) -> Option<&mut O> {
69        self.value.as_mut()
70    }
71
72    /// Returns a reference to the possible output value.
73    ///
74    /// This is mostly used internally and will return the cached output value.
75    #[inline(always)]
76    pub const fn value_ref(&self) -> Option<&O> {
77        self.value.as_ref()
78    }
79
80    /// Returns whether the inner task is ready.
81    ///
82    /// **NOTE:** This will only return `true`, if the inner task has finished,
83    /// but the value has not been consumed yet.
84    #[inline(always)]
85    pub fn is_ready(&self) -> bool {
86        self.task.as_ref().map(|t| t.is_ready()).unwrap_or(false)
87    }
88
89    /// Returns whether the inner task has finished and the value has been computed.
90    #[inline(always)]
91    pub const fn is_fetched(&self) -> bool {
92        self.task.is_none() && self.value.is_some()
93    }
94
95    /// Computes the output value from the inner task.
96    ///
97    /// If the inner task is not ready, the factory function will be called with `None`.
98    /// Otherwise, it will be called with `Some(I)`.
99    #[inline(always)]
100    pub fn compute(&mut self) {
101        if self.is_ready() {
102            let mut task = self.task.take().unwrap();
103            let value = task.take().unwrap();
104
105            self.value = Some((self.factory)(Some(value)));
106        } else {
107            self.value = Some((self.factory)(None));
108        }
109    }
110
111    /// Fetches the output value and returns a mutable reference.
112    ///
113    /// This will only actually compute the value,
114    /// if the task is not fully finished yet,
115    /// so it's safe to call, even if the task is already done.
116    #[inline(always)]
117    pub fn fetch(&mut self) -> &mut O {
118        if !self.is_fetched() {
119            self.compute();
120        }
121
122        self.value_mut().unwrap()
123    }
124}
125
126#[cfg(all(test, feature = "test"))]
127mod tests {
128    use crate::tasks::fetcher::Fetcher;
129    use std::time::Duration;
130
131    #[test]
132    fn test_fetcher_spawn() {
133        init();
134
135        let mut fetcher = Fetcher::spawn(
136            async { tokio::time::sleep(Duration::from_millis(10)).await },
137            |_| 1,
138        );
139
140        std::thread::sleep(Duration::from_millis(20));
141
142        assert_eq!(fetcher.fetch(), &1);
143    }
144
145    #[test]
146    fn test_fetcher_spawn_blocking() {
147        init();
148
149        let mut fetcher =
150            Fetcher::spawn_blocking(|| std::thread::sleep(Duration::from_millis(10)), |_| 1);
151
152        std::thread::sleep(Duration::from_millis(20));
153
154        assert_eq!(fetcher.fetch(), &1);
155    }
156
157    fn init() {
158        crate::tasks::try_init(crate::tasks::TaskRunner::Tokio(
159            crate::tasks::runner::tokio::TaskRunner::new(
160                false, None, None, None, None, None, None, None,
161            ),
162        ));
163    }
164}