azul_core/
task.rs

1use std::{
2    sync::{Arc, Mutex, Weak, atomic::{AtomicUsize, Ordering}},
3    thread::{self, JoinHandle},
4    time::{Duration, Instant},
5};
6use crate::{
7    FastHashMap,
8    callbacks::{
9        Redraw, DontRedraw, TimerCallback, TimerCallbackInfo, RefAny,
10        TimerCallbackReturn, TimerCallbackType, UpdateScreen,
11    },
12    app_resources::AppResources,
13};
14
15/// Should a timer terminate or not - used to remove active timers
16#[derive(Debug, Copy, Clone, PartialEq, Eq)]
17pub enum TerminateTimer {
18    /// Remove the timer from the list of active timers
19    Terminate,
20    /// Do nothing and let the timers continue to run
21    Continue,
22}
23
24static MAX_DAEMON_ID: AtomicUsize = AtomicUsize::new(0);
25
26/// ID for uniquely identifying a timer
27#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub struct TimerId { id: usize }
29
30impl TimerId {
31    /// Generates a new, unique `TimerId`.
32    pub fn new() -> Self {
33        TimerId { id: MAX_DAEMON_ID.fetch_add(1, Ordering::SeqCst) }
34    }
35}
36
37/// A `Timer` is a function that is run on every frame.
38///
39/// There are often a lot of visual tasks such as animations or fetching the
40/// next frame for a GIF or video, etc. - that need to run every frame or every X milliseconds,
41/// but they aren't heavy enough to warrant creating a thread - otherwise the framework
42/// would create too many threads, which leads to a lot of context switching and bad performance.
43///
44/// The callback of a `Timer` should be fast enough to run under 16ms,
45/// otherwise running timers will block the main UI thread.
46#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
47pub struct Timer {
48    /// Stores when the timer was created (usually acquired by `Instant::now()`)
49    pub created: Instant,
50    /// When the timer was last called (`None` only when the timer hasn't been called yet).
51    pub last_run: Option<Instant>,
52    /// If the timer shouldn't start instantly, but rather be delayed by a certain timeframe
53    pub delay: Option<Duration>,
54    /// How frequently the timer should run, i.e. set this to `Some(Duration::from_millis(16))`
55    /// to run the timer every 16ms. If this value is set to `None`, (the default), the timer
56    /// will execute the timer as-fast-as-possible (i.e. at a faster framerate
57    /// than the framework itself) - which might be  performance intensive.
58    pub interval: Option<Duration>,
59    /// When to stop the timer (for example, you can stop the
60    /// execution after 5s using `Some(Duration::from_secs(5))`).
61    pub timeout: Option<Duration>,
62    /// Callback to be called for this timer
63    pub callback: TimerCallback,
64}
65
66impl Timer {
67
68    /// Create a new timer
69    pub fn new(callback: TimerCallbackType) -> Self {
70        Timer {
71            created: Instant::now(),
72            last_run: None,
73            delay: None,
74            interval: None,
75            timeout: None,
76            callback: TimerCallback(callback),
77        }
78    }
79
80    /// Delays the timer to not start immediately but rather
81    /// start after a certain time frame has elapsed.
82    #[inline]
83    pub fn with_delay(mut self, delay: Duration) -> Self {
84        self.delay = Some(delay);
85        self
86    }
87
88    /// Converts the timer into a timer, running the function only
89    /// if the given `Duration` has elapsed since the last run
90    #[inline]
91    pub fn with_interval(mut self, interval: Duration) -> Self {
92        self.interval = Some(interval);
93        self
94    }
95
96    /// Converts the timer into a countdown, by giving it a maximum duration
97    /// (counted from the creation of the Timer, not the first use).
98    #[inline]
99    pub fn with_timeout(mut self, timeout: Duration) -> Self {
100        self.timeout = Some(timeout);
101        self
102    }
103
104    /// Crate-internal: Invokes the timer if the timer and
105    /// the `self.timeout` allow it to
106    pub fn invoke<'a>(&mut self, info: TimerCallbackInfo<'a>) -> TimerCallbackReturn {
107
108        let instant_now = Instant::now();
109        let delay = self.delay.unwrap_or_else(|| Duration::from_millis(0));
110
111        // Check if the timers timeout is reached
112        if let Some(timeout) = self.timeout {
113            if instant_now - self.created > timeout {
114                return (DontRedraw, TerminateTimer::Terminate);
115            }
116        }
117
118        if let Some(interval) = self.interval {
119            let last_run = match self.last_run {
120                Some(s) => s,
121                None => self.created + delay,
122            };
123            if instant_now - last_run < interval {
124                return (DontRedraw, TerminateTimer::Continue);
125            }
126        }
127
128        let res = (self.callback.0)(info);
129
130        self.last_run = Some(instant_now);
131
132        res
133    }
134}
135
136/// Simple struct that is used by Azul internally to determine when the thread has finished executing.
137/// When this struct goes out of scope, Azul will call `.join()` on the thread (so in order to not
138/// block the main thread, simply let it go out of scope naturally.
139pub struct DropCheck(Arc<()>);
140
141/// A `Task` is a seperate thread that is owned by the framework.
142///
143/// In difference to a `Thread`, you don't have to `await()` the result of a `Task`,
144/// you can just hand the task to the framework (via `AppResources::add_task`) and
145/// the framework will automatically update the UI when the task is finished.
146/// This is useful to offload actions such as loading long files, etc. to a background thread.
147///
148/// Azul will join the thread automatically after it is finished (joining won't block the UI).
149pub struct Task {
150    // Thread handle of the currently in-progress task
151    join_handle: Option<JoinHandle<UpdateScreen>>,
152    dropcheck: Weak<()>,
153    /// Timer that will run directly after this task is completed.
154    pub after_completion_timer: Option<Timer>,
155}
156
157pub type TaskCallback<U> = fn(Arc<Mutex<U>>, DropCheck) -> UpdateScreen;
158
159impl Task {
160
161    /// Creates a new task from a callback and a set of input data - which has to be wrapped in an `Arc<Mutex<T>>>`.
162    pub fn new<U: Send + 'static>(data: Arc<Mutex<U>>, callback: TaskCallback<U>) -> Self {
163
164        let thread_check = Arc::new(());
165        let thread_weak = Arc::downgrade(&thread_check);
166        let thread_handle = thread::spawn(move || callback(data, DropCheck(thread_check)));
167
168        Self {
169            join_handle: Some(thread_handle),
170            dropcheck: thread_weak,
171            after_completion_timer: None,
172        }
173    }
174
175    /// Stores a `Timer` that will run after the task has finished.
176    ///
177    /// Often necessary to "clean up" or copy data from the background task into the UI.
178    #[inline]
179    pub fn then(mut self, timer: Timer) -> Self {
180        self.after_completion_timer = Some(timer);
181        self
182    }
183
184    /// Returns true if the task has been finished, false otherwise
185    pub fn is_finished(&self) -> bool {
186        self.dropcheck.upgrade().is_none()
187    }
188}
189
190impl Drop for Task {
191    fn drop(&mut self) {
192        if let Some(thread_handle) = self.join_handle.take() {
193            let _ = thread_handle.join().unwrap();
194        }
195    }
196}
197
198/// A `Thread` is a simple abstraction over `std::thread` that allows to offload a pure
199/// function to a different thread (essentially emulating async / await for older compilers).
200///
201/// # Warning
202///
203/// `Thread` panics if it goes out of scope before `.block()` was called.
204pub struct Thread<T> {
205    join_handle: Option<JoinHandle<T>>,
206}
207
208/// Error that can happen while calling `.block()`
209#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
210pub enum BlockError {
211    /// Arc::into_inner() failed
212    ArcUnlockError,
213    /// The background thread panicked
214    ThreadJoinError,
215    /// Mutex::into_inner() failed
216    MutexIntoInnerError,
217}
218
219impl<T> Thread<T> {
220
221    /// Creates a new thread that spawns a certain (pure) function on a separate thread.
222    /// This is a workaround until `await` is implemented. Note that invoking this function
223    /// will create an OS-level thread.
224    ///
225    /// **Warning**: You *must* call `.await()`, otherwise the `Thread` will panic when it is dropped!
226    ///
227    /// # Example
228    ///
229    /// ```rust
230    /// # extern crate azul_core;
231    /// # use azul_core::task::Thread;
232    /// #
233    /// fn pure_function(input: usize) -> usize { input + 1 }
234    ///
235    /// let thread_1 = Thread::new(5, pure_function);
236    /// let thread_2 = Thread::new(10, pure_function);
237    /// let thread_3 = Thread::new(20, pure_function);
238    ///
239    /// // thread_1, thread_2 and thread_3 run in parallel here...
240    ///
241    /// let result_1 = thread_1.block();
242    /// let result_2 = thread_2.block();
243    /// let result_3 = thread_3.block();
244    ///
245    /// assert_eq!(result_1, Ok(6));
246    /// assert_eq!(result_2, Ok(11));
247    /// assert_eq!(result_3, Ok(21));
248    /// ```
249    pub fn new<U>(initial_data: U, callback: fn(U) -> T) -> Self where T: Send + 'static, U: Send + 'static {
250        Self {
251            join_handle: Some(thread::spawn(move || callback(initial_data))),
252        }
253    }
254
255    /// Block until the internal thread has finished and return T
256    pub fn block(mut self) -> Result<T, BlockError> {
257        // .block() can only be called once, so these .unwrap()s are safe
258        let handle = self.join_handle.take().unwrap();
259        let data = handle.join().map_err(|_| BlockError::ThreadJoinError)?;
260        Ok(data)
261    }
262}
263
264impl<T> Drop for Thread<T> {
265    fn drop(&mut self) {
266        if self.join_handle.take().is_some() {
267            panic!("Thread has not been await()-ed correctly!");
268        }
269    }
270}
271
272/// Run all currently registered timers
273#[must_use]
274pub fn run_all_timers(
275    timers: &mut FastHashMap<TimerId, Timer>,
276    data: &mut RefAny,
277    resources: &mut AppResources,
278) -> UpdateScreen {
279
280    let mut should_update_screen = DontRedraw;
281    let mut timers_to_terminate = Vec::new();
282
283    for (key, timer) in timers.iter_mut() {
284        let (should_update, should_terminate) = timer.invoke(TimerCallbackInfo {
285            state: data,
286            app_resources: resources,
287        });
288
289        if should_update == Redraw {
290            should_update_screen = Redraw;
291        }
292
293        if should_terminate == TerminateTimer::Terminate {
294            timers_to_terminate.push(key.clone());
295        }
296    }
297
298    for key in timers_to_terminate {
299        timers.remove(&key);
300    }
301
302    should_update_screen
303}
304
305/// Remove all tasks that have finished executing
306#[must_use]
307pub fn clean_up_finished_tasks<T>(
308    tasks: &mut Vec<Task>,
309    timers: &mut FastHashMap<TimerId, Timer>,
310) -> UpdateScreen {
311
312    let old_count = tasks.len();
313    let mut timers_to_add = Vec::new();
314
315    tasks.retain(|task| {
316        if task.is_finished() {
317            if let Some(timer) = task.after_completion_timer {
318                timers_to_add.push((TimerId::new(), timer));
319            }
320            false
321        } else {
322            true
323        }
324    });
325
326    let timers_is_empty = timers_to_add.is_empty();
327    let new_count = tasks.len();
328
329    // Start all the timers that should run after the completion of the task
330    for (timer_id, timer) in timers_to_add {
331        timers.insert(timer_id, timer);
332    }
333
334    if old_count == new_count && timers_is_empty {
335        DontRedraw
336    } else {
337        Redraw
338    }
339}