1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
use std::{any::Any, path::PathBuf};

use ahash::HashMap;
use poll_promise::Promise;

const FILE_SAVER_PROMISE: &str = "file_saver";

/// Pending background tasks, e.g. files being saved.
#[derive(Default)]
pub struct BackgroundTasks {
    /// Pending background tasks, using `poll_promise`.
    promises: HashMap<String, Promise<Box<dyn Any + Send>>>,
}

impl BackgroundTasks {
    /// Creates a promise with the specified name that will run `f` on a background
    /// thread using the `poll_promise` crate.
    ///
    /// Names can only be re-used once the promise with that name has finished running,
    /// otherwise an other is returned.
    // TODO(cmc): offer `spawn_async_promise` once we open save_file to the web
    #[cfg(not(target_arch = "wasm32"))]
    pub fn spawn_threaded_promise<F, T>(
        &mut self,
        name: impl Into<String>,
        f: F,
    ) -> anyhow::Result<()>
    where
        F: FnOnce() -> T + Send + 'static,
        T: Send + 'static,
    {
        let name = name.into();

        if self.promises.contains_key(&name) {
            anyhow::bail!("there's already a promise {name:?} running!");
        }

        let f = move || Box::new(f()) as Box<dyn Any + Send>; // erase it
        let promise = Promise::spawn_thread(&name, f);

        self.promises.insert(name, promise);

        Ok(())
    }

    #[cfg(not(target_arch = "wasm32"))]
    pub fn spawn_file_saver<F>(&mut self, f: F) -> anyhow::Result<()>
    where
        F: FnOnce() -> anyhow::Result<PathBuf> + Send + 'static,
    {
        self.spawn_threaded_promise(FILE_SAVER_PROMISE, f)
    }

    /// Polls the promise with the given name.
    ///
    /// Returns `Some<T>` it it's ready, or `None` otherwise.
    ///
    /// Panics if `T` does not match the actual return value of the promise.
    pub fn poll_promise<T: Any>(&mut self, name: impl AsRef<str>) -> Option<T> {
        self.promises
            .remove(name.as_ref())
            .and_then(|promise| match promise.try_take() {
                Ok(any) => Some(*any.downcast::<T>().unwrap()),
                Err(promise) => {
                    self.promises.insert(name.as_ref().to_owned(), promise);
                    None
                }
            })
    }

    pub fn poll_file_saver_promise(&mut self) -> Option<anyhow::Result<PathBuf>> {
        self.poll_promise(FILE_SAVER_PROMISE)
    }

    pub fn is_file_save_in_progress(&self) -> bool {
        self.promises.contains_key(FILE_SAVER_PROMISE)
    }
}