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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//! See [JobHandle]

use async_task::Task;
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};

/// Reference to a [Future] that has been scheduled via [send](crate::ScreepsRuntime::spawn)
///
/// Dropping a [`JobHandle`] cancels it, which means its future won't be polled again.
/// To drop the [`JobHandle`] handle without canceling it, use [detach()](JobHandle::detach) instead.
/// To cancel a task gracefully and wait until it is fully destroyed, use the [cancel()](JobHandle::cancel) method
///
/// This type implements [Future] to allow awaiting on the result of the spawned task
pub struct JobHandle<T> {
    pub(crate) fut_res: Rc<RefCell<Option<T>>>,
    task: Task<()>,
    complete: bool,
}

impl<T> JobHandle<T> {
    pub(crate) fn new(fut_res: Rc<RefCell<Option<T>>>, task: Task<()>) -> Self {
        Self {
            fut_res,
            task,
            complete: false,
        }
    }

    /// Cancels the task and waits for it to stop running.
    ///
    /// Returns the task's output if it was completed just before it got canceled, or [`None`] if
    /// it didn't complete.
    ///
    /// While it's possible to simply drop the [`JobHandle`] to cancel it, this is a cleaner way of
    /// canceling because it also waits for the task to stop running.
    pub async fn cancel(self) -> Option<T> {
        let _ = self.task.cancel().await;
        self.fut_res.take()
    }

    /// Detaches the task to let it keep running in the background.
    pub fn detach(self) {
        self.task.detach()
    }

    /// Check whether this job has completed
    pub fn is_complete(&self) -> bool {
        self.complete || self.fut_res.borrow().is_some()
    }
}

impl<T> Future for JobHandle<T> {
    type Output = T;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if self.complete {
            panic!("Cannot await on a JobHandle that has already completed")
        }
        let res = self.fut_res.borrow_mut().take();

        match res {
            Some(res) => {
                self.complete = true;
                Poll::Ready(res)
            }
            None => {
                cx.waker().wake_by_ref();
                Poll::Pending
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::spawn;
    use crate::tests::init_test;

    #[test]
    fn test_cancel() {
        init_test();

        let result = crate::block_on(async move {
            let task = spawn(async move { true });

            task.cancel().await
        })
        .unwrap();

        assert_eq!(None, result);
    }

    #[test]
    fn test_await() {
        init_test();

        let result = crate::block_on(async move { spawn(async move { true }).await }).unwrap();

        assert!(result, "Failed to await spawned future");
    }
}