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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
use async_executor::Task;
use bevy::ecs::prelude::Resource;
use bevy::log::error;
use bevy::state::prelude::{State, States};
use rustc_hash::FxHashMap;
use std::any::type_name;
use std::{future::Future, marker::PhantomData};
use crate::executor::{with_world_mut, with_world_ref};
use crate::{executor::SPAWNER, AccessError, AccessResult, AsyncWorld};
/// A list of tasks constrained by [`States`].
#[derive(Debug, Resource)]
pub struct ScopedTasks<T: States> {
pub(crate) tasks: FxHashMap<T, Vec<Task<AccessResult<()>>>>,
p: PhantomData<T>,
}
impl<T: States> Default for ScopedTasks<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: States> ScopedTasks<T> {
pub fn new() -> Self {
ScopedTasks {
tasks: FxHashMap::default(),
p: PhantomData,
}
}
/// Drop tasks bound to scope.
pub fn drain(&mut self, state: &T) {
if let Some(v) = self.tasks.get_mut(state) {
v.clear();
}
}
}
impl AsyncWorld {
/// Spawn a `bevy_defer` compatible future.
///
/// The spawned future will not be dropped until finished.
///
/// # Panics
///
/// If used outside a `bevy_defer` future.
///
/// # Panic Handling
///
/// Due to the internals of `AsyncExecutor` this function will fail silently on panic.
/// Use `spawn_log` or `panic=abort` for better panic handling.
pub fn spawn_any<T: 'static>(&self, fut: impl Future<Output = T> + 'static) {
if !SPAWNER.is_set() {
panic!("AsyncWorld::spawn_any can only be used in a bevy_defer future.")
}
SPAWNER.with(|s| s.spawn(fut).detach());
}
/// Spawn a `bevy_defer` compatible future with a handle.
///
/// # Handle
///
/// The handle can be used to obtain the result,
/// if dropped, the associated future will be dropped by the executor.
///
/// # Panics
///
/// * If used outside a `bevy_defer` future.
/// * If the task has panicked.
pub fn spawn_task<T: 'static>(&self, fut: impl Future<Output = T> + 'static) -> Task<T> {
if !SPAWNER.is_set() {
panic!("AsyncWorld::spawn_scoped can only be used in a bevy_defer future.")
}
SPAWNER.with(|s| s.spawn(fut))
}
/// Spawn a `bevy_defer` compatible future with a handle.
///
/// # Handle
///
/// The handle can be used to obtain the result,
/// if dropped, the associated future will be dropped by the executor.
///
/// # Panics
///
/// * If used outside a `bevy_defer` future.
/// * If the task has panicked.
#[deprecated = "Use `spawn_task`."]
pub fn spawn_scoped<T: 'static>(
&self,
fut: impl Future<Output = T> + 'static,
) -> impl Future<Output = T> {
if !SPAWNER.is_set() {
panic!("AsyncWorld::spawn_scoped can only be used in a bevy_defer future.")
}
SPAWNER.with(|s| s.spawn(fut))
}
/// Spawn a `bevy_defer` compatible future, the future is constrained to a [`States`]
/// and will be cancelled upon exiting the state.
///
/// # Errors
///
/// If not in the specified state.
///
/// # Panics
///
/// * If used outside a `bevy_defer` future.
pub fn spawn_state_scoped<S: States>(
&self,
state: S,
fut: impl Future<Output = AccessResult> + 'static,
) -> AccessResult {
if !SPAWNER.is_set() {
panic!("AsyncWorld::spawn_state_scoped can only be used in a bevy_defer future.")
}
with_world_ref(|world| match world.get_resource::<State<S>>() {
Some(s) if s.get() == &state => Ok(()),
_ => Err(AccessError::NotInState {
ty: type_name::<S>(),
}),
})?;
with_world_mut(|world| {
if let Some(mut res) = world.get_resource_mut::<ScopedTasks<S>>() {
res.tasks
.entry(state)
.or_default()
.push(SPAWNER.with(|s| s.spawn(fut)));
} else {
error!(
"Cannot spawn state scoped futures without `react_to_state::<{}>`.",
type_name::<S>()
)
}
});
Ok(())
}
/// Spawn a `bevy_defer` compatible future and logs errors.
///
/// The spawned future will not be dropped until finished.
///
/// # Panics
///
/// If used outside a `bevy_defer` future.
///
/// # Panic Handling
///
/// Due to the internals of `AsyncExecutor` we currently cannot report error messages of panics.
/// Use `panic=abort` to avoid unwinding or choose an error based approach.
pub fn spawn<T: 'static>(&self, fut: impl Future<Output = AccessResult<T>> + 'static) {
if !SPAWNER.is_set() {
panic!("AsyncWorld::spawn can only be used in a bevy_defer future.")
}
SPAWNER.with(|s| {
let task = s.spawn(fut).fallible();
s.spawn(async move {
match task.await {
Some(Err(e)) => error!("{e}"),
None => error!("Task panicked!"),
Some(_) => (),
}
})
.detach();
});
}
}