Skip to main content

rig_ecs/
tasks.rs

1//! Work that runs off the main thread: a [`Running`] task on an entity
2//! becomes a [`Done`] component when it finishes, through one `poll_tasks`
3//! system, so observers of that component carry on; on an effect, such as
4//! an open tool call, it becomes the effect's
5//! [`Outcome`](super::stage::Outcome) in
6//! [`Stage::Arrive`](super::stage::Stage::Arrive). A long-lived source,
7//! such as a socket or a thread's channel, is a [`Running::stream`] whose
8//! every item becomes a [`Done`] on the entity, in order. Each finished
9//! task and each item also calls [`Wake`], so a loop that sleeps while
10//! nothing happens runs a frame for it.
11
12use std::sync::Arc;
13use std::sync::atomic::{AtomicBool, Ordering};
14
15use bevy_ecs::prelude::*;
16use bevy_ecs::query::QueryFilter;
17use bevy_tasks::futures::check_ready;
18use bevy_tasks::{ConditionalSend, ConditionalSendFuture, Task, TaskPool};
19use crossbeam_channel::Receiver;
20use futures::{Stream, StreamExt};
21
22use super::stage::{self, Effect};
23
24/// Frames run right after a woken one (see [`run_settle_frames`]).
25const SETTLE_FRAMES: u32 = 4;
26
27/// Wakes the app's loop from another thread: a finished call, a streamed
28/// fragment, terminal input or a signal. The runner that sleeps between
29/// frames inserts its own; the default does nothing, for a loop that never
30/// sleeps for long. A windowed app inserts its own too (rig-harness's
31/// PLUGINS.md, "A window").
32///
33/// A woken frame often leaves work for the next, such as a command's
34/// notice or a task that finished just after it woke the loop, so the
35/// kernel asks for a few more frames after each wake, in `Last`, whatever
36/// the loop.
37#[derive(Resource, Clone)]
38pub struct Wake {
39    wake: Arc<dyn Fn() + Send + Sync>,
40    woken: Arc<AtomicBool>,
41}
42
43impl Wake {
44    /// A wake that calls `wake`, which asks the loop for a frame from any
45    /// thread.
46    pub fn new(wake: impl Fn() + Send + Sync + 'static) -> Self {
47        Self {
48            wake: Arc::new(wake),
49            woken: Arc::new(AtomicBool::new(false)),
50        }
51    }
52
53    /// Asks the loop for a frame soon.
54    pub fn wake(&self) {
55        self.woken.store(true, Ordering::Release);
56        (self.wake)();
57    }
58}
59
60impl Default for Wake {
61    fn default() -> Self {
62        Self::new(|| {})
63    }
64}
65
66/// Runs [`SETTLE_FRAMES`] more frames after a frame some [`Wake`] woke,
67/// in `Last`, by asking the loop for each without counting it as a wake.
68pub(crate) fn run_settle_frames(wake: Res<Wake>, mut left: Local<u32>) {
69    if wake.woken.swap(false, Ordering::AcqRel) {
70        *left = SETTLE_FRAMES;
71    }
72    if *left > 0 {
73        *left -= 1;
74        (wake.wake)();
75    }
76}
77
78/// What a finished task does to its entity: inserts its output.
79type Finish = Box<dyn FnOnce(EntityWorldMut) + Send>;
80
81/// Work running off the main thread for its entity, such as a tool call's
82/// work: when
83/// it finishes, the kernel replaces it with its output in [`PollTasks`], as a
84/// [`Done<T>`] or the component [`Running::spawn_into`] names, and
85/// observers of that component carry on. Bevy's own pattern
86/// (`examples/async_tasks/async_compute.rs`): one task component for every
87/// kind of output, so no plugin registers a system for its own. Dropping
88/// it, or the entity, cancels the task; so does the app's exit.
89#[derive(Component)]
90pub struct Running(Work);
91
92/// What a [`Running`] waits for.
93enum Work {
94    /// One output, when the task finishes.
95    Task(Task<Finish>),
96    /// An output per item, as they come, until the task ends.
97    Stream {
98        items: Receiver<Finish>,
99        task: Task<()>,
100    },
101}
102
103impl Running {
104    /// Spawns `work` on `pool`; it ends as a [`Done<T>`] on the entity, and
105    /// wakes the loop.
106    pub fn spawn<T: Send + Sync + 'static>(
107        pool: &TaskPool,
108        wake: &Wake,
109        work: impl ConditionalSendFuture<Output = T> + 'static,
110    ) -> Self {
111        Self::spawn_into::<Done<T>, T>(pool, wake, work)
112    }
113
114    /// [`Running::spawn`], ending as the component `C` made from the
115    /// output.
116    pub fn spawn_into<C: Component + From<T>, T: Send + 'static>(
117        pool: &TaskPool,
118        wake: &Wake,
119        work: impl ConditionalSendFuture<Output = T> + 'static,
120    ) -> Self {
121        Self::finishing(pool, wake, work, |mut entity, output| {
122            entity.insert(C::from(output));
123        })
124    }
125
126    /// Spawns `work` on `pool` for the effect it goes on, such as an open
127    /// tool call's `ToolResult`; it ends as the effect's
128    /// [`stage::Outcome`], unless something answered it first.
129    pub fn answering<T: Into<stage::Outcome> + Send + 'static>(
130        pool: &TaskPool,
131        wake: &Wake,
132        work: impl ConditionalSendFuture<Output = T> + 'static,
133    ) -> Self {
134        Self::finishing(pool, wake, work, |mut entity, output| {
135            let outcome: stage::Outcome = output.into();
136            entity.insert_if_new(outcome);
137        })
138    }
139
140    /// Spawns a task on `pool` that runs `items` to their end, such as a
141    /// socket's messages or a thread's channel: each item is inserted on
142    /// the entity as a [`Done<T>`], in order, and wakes the loop; once the
143    /// last one is in, `Running` is removed. Several items may come in one
144    /// frame, so a reader observes `Insert<Done<T>>`, which sees each.
145    pub fn stream<T: Send + Sync + 'static>(
146        pool: &TaskPool,
147        wake: &Wake,
148        items: impl Stream<Item = T> + ConditionalSend + 'static,
149    ) -> Self {
150        let (sender, received) = crossbeam_channel::unbounded();
151        let wake = wake.clone();
152        let task = pool.spawn(async move {
153            let mut items = std::pin::pin!(items);
154            while let Some(item) = items.next().await {
155                let insert = move |mut entity: EntityWorldMut| {
156                    entity.insert(Done(item));
157                };
158                // Dropped with its entity: nothing reads the rest.
159                if sender.send(Box::new(insert) as Finish).is_err() {
160                    break;
161                }
162                wake.wake();
163            }
164            wake.wake();
165        });
166        Self(Work::Stream {
167            items: received,
168            task,
169        })
170    }
171
172    /// Spawns `work` on `pool`; `finish` puts its output on the entity.
173    pub(crate) fn finishing<T: Send + 'static>(
174        pool: &TaskPool,
175        wake: &Wake,
176        work: impl ConditionalSendFuture<Output = T> + 'static,
177        finish: fn(EntityWorldMut, T),
178    ) -> Self {
179        let wake = wake.clone();
180        Self(Work::Task(pool.spawn(async move {
181            let output = work.await;
182            wake.wake();
183            Box::new(move |entity: EntityWorldMut| finish(entity, output)) as Finish
184        })))
185    }
186
187    /// Whether it is a [`Running::stream`], a source that may never end,
188    /// rather than work that finishes.
189    pub fn is_stream(&self) -> bool {
190        matches!(self.0, Work::Stream { .. })
191    }
192
193    /// Cancels the task, once awaited.
194    pub(crate) async fn cancel(self) {
195        match self.0 {
196            Work::Task(task) => {
197                task.cancel().await;
198            }
199            Work::Stream { task, .. } => {
200                task.cancel().await;
201            }
202        }
203    }
204}
205
206/// A finished task's output, or a [`Running::stream`]'s latest item.
207#[derive(Component)]
208pub struct Done<T: Send + Sync + 'static>(pub T);
209
210impl<T: Send + Sync + 'static> From<T> for Done<T> {
211    fn from(output: T) -> Self {
212        Self(output)
213    }
214}
215
216/// The system polling running work, in `Update`: a system that reads what
217/// finished this frame runs after it.
218#[derive(SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
219pub struct PollTasks;
220
221/// Replaces each finished [`Running`] task with its output, but an
222/// effect's, which the kernel takes in [`Stage::Arrive`](stage::Stage::Arrive).
223pub(crate) fn poll_tasks(
224    tasks: Query<(Entity, &mut Running), Without<Effect>>,
225    commands: Commands,
226) {
227    finish(tasks, commands);
228}
229
230/// Replaces each finished effect's [`Running`] task with its outcome, in
231/// [`Stage::Arrive`](stage::Stage::Arrive).
232pub(crate) fn arrive(tasks: Query<(Entity, &mut Running), With<Effect>>, commands: Commands) {
233    finish(tasks, commands);
234}
235
236/// Replaces each finished task among `tasks` with its output, and puts a
237/// stream's items on its entity in order, removing it once it ended.
238fn finish<F: QueryFilter>(mut tasks: Query<(Entity, &mut Running), F>, mut commands: Commands) {
239    for (entity, mut running) in &mut tasks {
240        match &mut running.bypass_change_detection().0 {
241            Work::Task(task) => {
242                if let Some(finish) = check_ready(task) {
243                    commands.entity(entity).remove::<Running>().queue(finish);
244                }
245            }
246            Work::Stream { items, task } => {
247                // Every item was sent before the task ended.
248                let ended = check_ready(task).is_some();
249                let mut entity = commands.entity(entity);
250                for insert in items.try_iter() {
251                    entity.queue(insert);
252                }
253                if ended {
254                    entity.remove::<Running>();
255                }
256            }
257        }
258    }
259}