bevy_coroutine/
lib.rs

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
18/// `use bevy_coroutine::prelude::*;` to import the plugin, essential types and utility functions.
19pub 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
33/// Plugin to enable bevy_coroutine in your [`App`].
34pub 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/// The [`SystemSet`] for the system that updates coroutines.
48#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, SystemSet)]
49pub struct CoroutineUpdateSystem;
50
51/// A type alias for a boxed system that can be used in a coroutine.
52pub type BoxedCoroutine = BoxedSystem<(), CoResult>;
53
54/// The result of a coroutine resumption.
55#[must_use]
56pub struct CoResult
57{
58	/// Controls the execution of the coroutine.
59	pub control_flow: ControlFlow<()>,
60	/// The list of coroutines to be run sequentially before resuming this coroutine.
61	pub subroutines: Vec<BoxedCoroutine>,
62}
63
64/// Rerun the current coroutine next update.
65pub fn co_continue() -> CoResult
66{
67	CoResult
68	{
69		control_flow: ControlFlow::Continue(()),
70		subroutines: Vec::new(),
71	}
72}
73
74/// Stops the execution of the current coroutine.
75pub fn co_break() -> CoResult
76{
77	CoResult
78	{
79		control_flow: ControlFlow::Break(()),
80		subroutines: Vec::new(),
81	}
82}
83
84impl CoResult
85{
86	/// Adds one or more coroutine systems to run sequentially before resuming the current coroutine.
87	pub fn add_subroutines<M>(&mut self, subroutines: impl IntoCoroutines<M>)
88	{
89		self.add_boxed_subroutines(subroutines.into_coroutines())
90	}
91
92	/// Adds one or more coroutine systems to run sequentially before resuming the current coroutine.
93	pub fn add_boxed_subroutines(&mut self, subroutines: Vec<BoxedCoroutine>)
94	{
95		self.subroutines.extend(subroutines);
96	}
97
98	/// Builds a new CoResult with the same `control_flow` but the given sub coroutine systems.
99	pub fn with_subroutines<M>(&self, subroutines: impl IntoCoroutines<M>) -> Self
100	{
101		self.with_boxed_subroutines(subroutines.into_coroutines())
102	}
103
104	/// Builds a new CoResult with the same `control_flow` but the given sub coroutine systems.
105	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			// Last in the stack is always executed first, so the reverse order
137			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
150/// [`Command`] that launches a coroutine.
151/// 
152/// # Usage
153/// 
154/// ```
155/// # use bevy::prelude::*;
156/// # use bevy_coroutine::prelude::*;
157/// fn launch_coroutine(
158///     mut commands: Commands
159/// ) {
160///     commands.queue(Coroutine::new(my_coroutine));
161/// }
162/// 
163/// fn my_coroutine() -> CoResult {
164///     co_break()
165/// }
166/// ```
167pub struct Coroutine(Vec<BoxedCoroutine>);
168
169impl Coroutine
170{
171	/// Makes a `Coroutine` [`Command`] that launches the given coroutine systems in sequence.
172	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	// Take all registered coroutines
199	let mut coroutines = std::mem::take(&mut world.resource_mut::<Coroutines>().0);
200
201	// Execute the coroutines and remove the completed ones
202	coroutines.retain_mut(|stack|
203		{
204			stack.resume(world).unwrap();
205			!stack.stack.is_empty()
206		});
207	
208	// Put the coroutines back in the registry, we need to append because coroutines can register new coroutines
209	world.resource_mut::<Coroutines>().0.append(&mut coroutines);
210}
211
212/// Returns a system that launches the given coroutine when executed.
213/// 
214/// # Usage
215/// 
216/// ```
217/// # use bevy::prelude::*;
218/// # use bevy_coroutine::prelude::*;
219/// # let mut app = App::new();
220/// app.add_systems(Startup, launch_coroutine(my_coroutine));
221/// 
222/// fn my_coroutine() -> CoResult {
223///     co_break()
224/// }
225/// ```
226pub 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
234/// Conversion trait to turn something into a sequence of [`BoxedCoroutine`].
235pub trait IntoCoroutines<Marker>: Sized
236{
237	/// Turn this value into a sequence of [`BoxedCoroutine`] and put them in the given Vec.
238	fn collect_coroutines(self, coroutines: &mut Vec<BoxedCoroutine>);
239
240	/// Turn this value into a sequence of [`BoxedCoroutine`] and returns it as a Vec.
241	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}