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}