sequoia-async 0.1.0

Sequoia GUI framework — async tasks bridged to the UI thread (futures without tokio)
Documentation
#![forbid(unsafe_code)]

use crate::task::{TaskHandle, TaskId};

pub struct ScopedTask {
    handle: Option<TaskHandle>,
    cancelled: bool,
}

impl ScopedTask {
    pub fn from_handle(handle: TaskHandle) -> Self {
        Self {
            handle: Some(handle),
            cancelled: false,
        }
    }

    pub fn new(id: TaskId, cancel: impl Fn(TaskId) -> bool + 'static) -> Self {
        Self {
            handle: Some(TaskHandle::new(id, cancel)),
            cancelled: false,
        }
    }

    pub fn id(&self) -> Option<TaskId> {
        self.handle.as_ref().map(|h| h.id())
    }

    pub fn cancel(&mut self) -> bool {
        if self.cancelled {
            return false;
        }
        self.cancelled = true;
        match &self.handle {
            Some(h) => h.cancel(),
            None => false,
        }
    }

    pub fn detach(mut self) -> Option<TaskId> {
        let id = self.id();
        self.cancelled = true;
        self.handle.take();

        id
    }

    pub fn is_active(&self) -> bool {
        !self.cancelled && self.handle.is_some()
    }
}

impl Drop for ScopedTask {
    fn drop(&mut self) {
        if !self.cancelled
            && let Some(h) = &self.handle
        {
            h.cancel();
        }
    }
}

impl std::fmt::Debug for ScopedTask {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ScopedTask")
            .field("id", &self.handle.as_ref().map(|h| h.id().get()))
            .field("cancelled", &self.cancelled)
            .finish()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::Cell;
    use std::rc::Rc;

    #[test]
    fn drop_cancels_once() {
        let n = Rc::new(Cell::new(0));
        let probe = Rc::clone(&n);
        {
            let _guard = ScopedTask::new(crate::task::TaskId::new(1), move |_| {
                probe.set(probe.get() + 1);
                true
            });
        }
        assert_eq!(n.get(), 1);
    }

    #[test]
    fn detach_skips_cancel() {
        let n = Rc::new(Cell::new(0));
        let probe = Rc::clone(&n);
        {
            let guard = ScopedTask::new(crate::task::TaskId::new(2), move |_| {
                probe.set(probe.get() + 1);
                true
            });
            let _ = guard.detach();
        }
        assert_eq!(n.get(), 0);
    }

    #[test]
    fn explicit_cancel_is_idempotent() {
        let mut guard = ScopedTask::new(crate::task::TaskId::new(3), |_| true);
        assert!(guard.cancel());
        assert!(!guard.cancel());
        assert!(!guard.is_active());
    }

    #[test]
    fn from_handle_cancels_on_drop() {
        let n = Rc::new(Cell::new(0));
        let probe = Rc::clone(&n);
        let handle = TaskHandle::new(crate::task::TaskId::new(4), move |_| {
            probe.set(probe.get() + 1);
            true
        });
        {
            let _guard = ScopedTask::from_handle(handle);
        }
        assert_eq!(n.get(), 1);
    }
}