1#![warn(missing_docs)]
2
3#![doc=include_str!("../README.md")]
4
5
6mod system_util;
7mod waiting;
8
9pub use system_util::*;
10pub use waiting::*;
11
12use std::ops::ControlFlow;
13
14use bevy::ecs::system::{BoxedSystem, RegisteredSystemError, SystemId};
15use bevy::prelude::*;
16use variadics_please::all_tuples;
17
18pub mod prelude
20{
21 pub use crate::{
22 Coroutine,
23 CoResult,
24 co_continue,
25 co_break,
26 CoroutinePlugin,
27 with_input,
28 launch_coroutine,
29 wait,
30 };
31}
32
33pub struct CoroutinePlugin;
35
36impl Plugin for CoroutinePlugin
37{
38 fn build(&self, app: &mut App)
39 {
40 app
41 .init_resource::<Coroutines>()
42 .add_systems(Update, update_coroutines.in_set(CoroutineUpdateSystem))
43 ;
44 }
45}
46
47#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, SystemSet)]
49pub struct CoroutineUpdateSystem;
50
51pub type BoxedCoroutine = BoxedSystem<(), CoResult>;
53
54#[must_use]
56pub struct CoResult
57{
58 pub control_flow: ControlFlow<()>,
60 pub subroutines: Vec<BoxedCoroutine>,
62}
63
64pub fn co_continue() -> CoResult
66{
67 CoResult
68 {
69 control_flow: ControlFlow::Continue(()),
70 subroutines: Vec::new(),
71 }
72}
73
74pub fn co_break() -> CoResult
76{
77 CoResult
78 {
79 control_flow: ControlFlow::Break(()),
80 subroutines: Vec::new(),
81 }
82}
83
84impl CoResult
85{
86 pub fn add_subroutines<M>(&mut self, subroutines: impl IntoCoroutines<M>)
88 {
89 self.add_boxed_subroutines(subroutines.into_coroutines())
90 }
91
92 pub fn add_boxed_subroutines(&mut self, subroutines: Vec<BoxedCoroutine>)
94 {
95 self.subroutines.extend(subroutines);
96 }
97
98 pub fn with_subroutines<M>(&self, subroutines: impl IntoCoroutines<M>) -> Self
100 {
101 self.with_boxed_subroutines(subroutines.into_coroutines())
102 }
103
104 pub fn with_boxed_subroutines(&self, subroutines: Vec<BoxedCoroutine>) -> Self
106 {
107 CoResult
108 {
109 control_flow: self.control_flow,
110 subroutines,
111 }
112 }
113}
114
115
116struct CoroutineStack
117{
118 stack: Vec<SystemId<(), CoResult>>,
119}
120
121impl CoroutineStack
122{
123 fn resume(&mut self, world: &mut World) -> Result<(), RegisteredSystemError<(), CoResult>>
124 {
125 if let Some(co_system) = self.stack.last().copied()
126 {
127 let output = world.run_system(co_system)?;
128
129 if output.control_flow.is_break()
130 {
131 world.unregister_system(co_system)?;
132 let pop = self.stack.pop();
133 debug_assert_eq!(pop.unwrap(), co_system);
134 }
135
136 for subroutine in output.subroutines.into_iter().rev()
138 {
139 self.stack.push(world.register_boxed_system(subroutine));
140 }
141 }
142
143 Ok(())
144 }
145}
146
147#[derive(Resource, Default)]
148struct Coroutines(Vec<CoroutineStack>);
149
150pub struct Coroutine(Vec<BoxedCoroutine>);
168
169impl Coroutine
170{
171 pub fn new<M>(coroutines: impl IntoCoroutines<M>) -> Self
173 {
174 Coroutine(coroutines.into_coroutines())
175 }
176}
177
178impl Command for Coroutine
179{
180 fn apply(self, world: &mut World)
181 {
182 let Self(coroutines) = self;
183 let stack = coroutines.into_iter().rev().map(|coroutine| world.register_boxed_system(coroutine)).collect();
184
185 world.resource_mut::<Coroutines>().0.push(
186 CoroutineStack
187 {
188 stack
189 }
190 );
191 }
192}
193
194fn update_coroutines(
195 world: &mut World
196)
197{
198 let mut coroutines = std::mem::take(&mut world.resource_mut::<Coroutines>().0);
200
201 coroutines.retain_mut(|stack|
203 {
204 stack.resume(world).unwrap();
205 !stack.stack.is_empty()
206 });
207
208 world.resource_mut::<Coroutines>().0.append(&mut coroutines);
210}
211
212pub fn launch_coroutine<M, C: IntoCoroutines<M> + Clone>(coroutines: C) -> impl Fn(Commands) + Clone
227{
228 move |mut commands: Commands|
229 {
230 commands.queue(Coroutine::new(coroutines.clone()));
231 }
232}
233
234pub trait IntoCoroutines<Marker>: Sized
236{
237 fn collect_coroutines(self, coroutines: &mut Vec<BoxedCoroutine>);
239
240 fn into_coroutines(self) -> Vec<BoxedCoroutine>
242 {
243 let mut coroutines = Vec::new();
244 self.collect_coroutines(&mut coroutines);
245 coroutines
246 }
247}
248
249impl<S, M> IntoCoroutines<M> for S
250where
251 S: IntoSystem<(), CoResult, M> + 'static
252{
253 fn collect_coroutines(self, coroutines: &mut Vec<BoxedCoroutine>)
254 {
255 coroutines.push(Box::new(IntoSystem::into_system(self)));
256 }
257}
258
259
260#[doc(hidden)]
261pub struct CoroutineTupleMarker;
262
263macro_rules! impl_coroutine_collection
264{
265 ($(($sys: ident, $sys_var: ident, $marker: ident)),*) =>
266 {
267 impl<$($sys, $marker),*> IntoCoroutines<(CoroutineTupleMarker, $($marker,)*)> for ($($sys,)*)
268 where
269 $($sys: IntoCoroutines<$marker>),*
270 {
271 fn collect_coroutines(self, coroutines: &mut Vec<BoxedCoroutine>)
272 {
273 let ($($sys_var,)*) = self;
274 $(
275 $sys_var.collect_coroutines(coroutines);
276 )*
277 }
278 }
279 }
280}
281
282all_tuples!(impl_coroutine_collection, 1, 20, S, s, M);
283
284
285#[cfg(test)]
286mod test
287{
288 use crate::prelude::*;
289 use bevy::prelude::*;
290
291 #[test]
292 fn coroutine_in_coroutine()
293 {
294 let mut app = App::new();
295 app.add_plugins(CoroutinePlugin);
296
297 #[derive(Debug, Default, Resource, PartialEq, Eq)]
298 struct TestEvents(Vec<&'static str>);
299
300 app.init_resource::<TestEvents>();
301
302 app.add_systems(Startup, launch_coroutine(|mut events: ResMut<TestEvents>, mut commands: Commands|
303 {
304 events.0.push("COROUTINE_1");
305 commands.queue(Coroutine::new(|mut events: ResMut<TestEvents>|
306 {
307 events.0.push("COROUTINE_2");
308 co_break()
309 }));
310 co_break()
311 }));
312
313 app.update();
314 app.update();
315 app.update();
316
317 assert_eq!(app.world().resource::<TestEvents>(), &TestEvents(vec!["COROUTINE_1", "COROUTINE_2"]));
318 }
319}