fluxion_core/
fluxion_task.rs

1// Copyright 2025 Umberto Gotti <umberto.gotti@umbertogotti.dev>
2// Licensed under the Apache License, Version 2.0
3// http://www.apache.org/licenses/LICENSE-2.0
4
5//! Runtime-agnostic task spawning with cooperative cancellation.
6//!
7//! This module provides a unified abstraction for spawning background tasks
8//! that works across all async runtimes (Tokio, smol, async-std, WASM).
9
10use crate::CancellationToken;
11use core::future::Future;
12
13/// Runtime-agnostic task handle with automatic cancellation on drop.
14///
15/// `FluxionTask` spawns a background task using the configured runtime
16/// (Tokio, smol, async-std, or WASM) and provides cooperative cancellation
17/// through a `CancellationToken`.
18///
19/// The spawned task receives a `CancellationToken` that it should monitor
20/// to enable graceful shutdown. When the `FluxionTask` is dropped or manually
21/// cancelled, the token is signaled, allowing the task to clean up and exit.
22///
23/// # Runtime Support
24///
25/// - **Tokio**: `tokio::spawn` (default)
26/// - **smol**: `smol::spawn`
27/// - **async-std**: `async_core::task::spawn`
28/// - **WASM**: `wasm_bindgen_futures::spawn_local`
29///
30/// Select runtime via feature flags: `runtime-tokio`, `runtime-smol`,
31/// `runtime-async-std`, or automatic WASM detection.
32///
33/// # Example
34///
35/// ```rust
36/// use fluxion_core::FluxionTask;
37/// use futures::FutureExt;
38///
39/// # #[tokio::main]
40/// # async fn main() {
41/// let task = FluxionTask::spawn(|cancel| async move {
42///     loop {
43///         if cancel.is_cancelled() {
44///             println!("Graceful shutdown");
45///             break;
46///         }
47///         // Do work...
48///     }
49/// });
50///
51/// // Task automatically cancels on drop
52/// drop(task);
53/// # }
54/// ```
55#[derive(Debug)]
56pub struct FluxionTask {
57    cancel: CancellationToken,
58}
59
60impl FluxionTask {
61    /// Spawn a background task with cancellation support.
62    ///
63    /// The provided closure receives a `CancellationToken` that will be triggered
64    /// when the task is dropped or manually cancelled. The spawned future should
65    /// monitor this token and exit gracefully when cancellation is requested.
66    ///
67    /// # Arguments
68    ///
69    /// * `f` - A closure that receives a `CancellationToken` and returns a future
70    ///
71    /// # Example
72    ///
73    /// ```rust
74    /// use fluxion_core::FluxionTask;
75    /// use std::sync::atomic::{AtomicU32, Ordering};
76    /// use std::sync::Arc;
77    ///
78    /// # #[tokio::main]
79    /// # async fn main() {
80    /// let counter = Arc::new(AtomicU32::new(0));
81    /// let counter_clone = counter.clone();
82    ///
83    /// let task = FluxionTask::spawn(|cancel| async move {
84    ///     while !cancel.is_cancelled() {
85    ///         counter_clone.fetch_add(1, Ordering::SeqCst);
86    ///     }
87    ///     println!("Stopped at count: {}", counter_clone.load(Ordering::SeqCst));
88    /// });
89    ///
90    /// // Do other work...
91    ///
92    /// // Task cancels automatically on drop
93    /// drop(task);
94    /// # }
95    /// ```
96    #[cfg(not(target_arch = "wasm32"))]
97    pub fn spawn<F, Fut>(f: F) -> Self
98    where
99        F: FnOnce(CancellationToken) -> Fut + Send + 'static,
100        Fut: Future<Output = ()> + Send + 'static,
101    {
102        let cancel = CancellationToken::new();
103        let cancel_clone = cancel.clone();
104        let _future = f(cancel_clone);
105
106        #[cfg(all(
107            feature = "runtime-tokio",
108            not(all(feature = "runtime-smol", not(feature = "runtime-tokio"))),
109            not(all(
110                feature = "runtime-async-std",
111                not(feature = "runtime-tokio"),
112                not(feature = "runtime-smol")
113            ))
114        ))]
115        tokio::spawn(_future);
116
117        #[cfg(all(feature = "runtime-smol", not(feature = "runtime-tokio")))]
118        smol::spawn(_future).detach();
119
120        #[cfg(all(
121            feature = "runtime-async-std",
122            not(target_arch = "wasm32"),
123            not(feature = "runtime-tokio"),
124            not(feature = "runtime-smol")
125        ))]
126        async_std::task::spawn(_future);
127
128        Self { cancel }
129    }
130
131    /// Manually cancel the task.
132    ///Spawn a background task with cancellation support (WASM version without Send bounds).
133    ///
134    /// This is the WASM-specific version of `spawn` that does not require `Send` bounds
135    /// since WASM is single-threaded. The closure and future only need to be `'static`.
136    ///
137    /// # Arguments
138    ///
139    /// * `f` - A closure that receives a `CancellationToken` and returns a future
140    #[cfg(target_arch = "wasm32")]
141    pub fn spawn<F, Fut>(f: F) -> Self
142    where
143        F: FnOnce(CancellationToken) -> Fut + 'static,
144        Fut: Future<Output = ()> + 'static,
145    {
146        let cancel = CancellationToken::new();
147        let cancel_clone = cancel.clone();
148        let _future = f(cancel_clone);
149
150        wasm_bindgen_futures::spawn_local(_future);
151
152        Self { cancel }
153    }
154
155    ///
156    /// This signals the task to stop but doesn't wait for it to complete.
157    /// The task will stop at its next cancellation checkpoint.
158    ///
159    /// # Example
160    ///
161    /// ```rust
162    /// use fluxion_core::FluxionTask;
163    ///
164    /// # async fn example() {
165    /// let task = FluxionTask::spawn(|cancel| async move {
166    ///     loop {
167    ///         if cancel.is_cancelled() {
168    ///             break;
169    ///         }
170    ///         // Do work...
171    ///     }
172    /// });
173    ///
174    /// // Explicitly cancel before drop
175    /// task.cancel();
176    /// # }
177    /// ```
178    pub fn cancel(&self) {
179        self.cancel.cancel();
180    }
181
182    /// Check if cancellation has been requested.
183    ///
184    /// Returns `true` if the task has been cancelled via `cancel()` or drop.
185    ///
186    /// # Example
187    ///
188    /// ```rust
189    /// use fluxion_core::FluxionTask;
190    ///
191    /// # async fn example() {
192    /// let task = FluxionTask::spawn(|cancel| async move {
193    ///     // Task body...
194    /// });
195    ///
196    /// assert!(!task.is_cancelled());
197    /// task.cancel();
198    /// assert!(task.is_cancelled());
199    /// # }
200    /// ```
201    pub fn is_cancelled(&self) -> bool {
202        self.cancel.is_cancelled()
203    }
204}
205
206impl Drop for FluxionTask {
207    fn drop(&mut self) {
208        // Signal cancellation to allow graceful shutdown
209        self.cancel.cancel();
210    }
211}