bevy_async_system/runner/
once.rs

1use bevy::app::AppExit;
2use bevy::ecs::schedule::ScheduleLabel;
3use bevy::ecs::system::EntityCommands;
4use bevy::prelude::{Commands, Event, EventWriter, FromWorld, In, IntoSystem, IntoSystemConfigs, NextState, Query, ResMut, Resource, Schedules, States, World};
5
6use crate::async_schedules::TaskSender;
7use crate::prelude::{AsyncSchedule, AsyncScheduleCommand, IntoAsyncScheduleCommand};
8use crate::runner::{schedule_initialize, task_running};
9use crate::runner::config::AsyncSystemConfig;
10
11/// Run the system only once.
12///
13/// The system can use `Output`.
14/// If any output is returned, it becomes the task's return value.
15///
16/// ## Example
17///
18/// ```no_run
19/// use bevy::prelude::*;
20/// use bevy_async_system::prelude::*;
21///
22/// fn setup(mut commands: Commands){
23///     commands.spawn_async(|schedules| async move{
24///         schedules.add_system(Update, once::run(without_output)).await;
25///         let count: u32 = schedules.add_system(Update, once::run(with_output)).await;
26///         assert_eq!(count, 10);
27///     });
28/// }
29///
30/// #[derive(Resource)]
31/// struct Count(u32);
32///
33/// fn without_output(mut commands: Commands){
34///     commands.insert_resource(Count(10));
35/// }
36///
37///
38/// fn with_output(count: Res<Count>) -> u32{
39///     count.0
40/// }
41///
42/// ```
43///
44#[inline(always)]
45pub fn run<Out, Marker, Sys>(system: Sys) -> impl IntoAsyncScheduleCommand<Out>
46    where
47        Out: Send + Sync + 'static,
48        Marker: Send + Sync + 'static,
49        Sys: IntoSystem<(), Out, Marker> + Send + Sync + 'static
50{
51    OnceOnMain(AsyncSystemConfig::<Out, Marker, Sys>::new(system))
52}
53
54
55/// Set the next state.
56///
57/// ```no_run
58/// use bevy::prelude::*;
59/// use bevy_async_system::prelude::*;
60///
61/// #[derive(Debug, Default, Eq, PartialEq, Hash, Copy, Clone, States)]
62/// enum ExampleState{
63///     #[default]
64///     First,
65///     Second,
66/// }
67///
68/// fn setup(mut commands: Commands){
69///     commands.spawn_async(|scheduler|async move{
70///         scheduler.add_system(Update, once::set_state(ExampleState::Second)).await;
71///     });
72/// }
73/// ```
74///
75#[inline]
76pub fn set_state<S: States + Copy>(to: S) -> impl IntoAsyncScheduleCommand {
77    run(move |mut state: ResMut<NextState<S>>| {
78        state.set(to);
79    })
80}
81
82
83/// Send the event.
84///
85/// The event to be send must derive [`Clone`] in addition to [`Event`](bevy::prelude::Event).
86///
87/// ```
88/// use bevy::prelude::*;
89/// use bevy_async_system::prelude::*;
90///
91/// #[derive(Event, Clone)]
92/// struct ExampleEvent;
93///
94/// fn setup(mut commands: Commands){
95///     commands.spawn_async(|schedules|async move{
96///         schedules.add_system(Update, once::send(ExampleEvent)).await;
97///     });
98/// }
99/// ```
100#[inline]
101pub fn send<E: Event + Clone>(event: E) -> impl IntoAsyncScheduleCommand {
102    run(move |mut ew: EventWriter<E>| {
103        ew.send(event.clone());
104    })
105}
106
107
108/// Send [`AppExit`].
109#[inline(always)]
110pub fn app_exit() -> impl IntoAsyncScheduleCommand {
111    send(AppExit)
112}
113
114
115/// Insert a [`Resource`](bevy::prelude::Resource).
116///
117/// The resource is cloned inside the function.
118///
119/// If the resource derives [`Default`], we recommend using [`once::init_resource`](once::init_resource) instead.
120/// ```
121/// use bevy::prelude::*;
122/// use bevy_async_system::prelude::*;
123///
124/// #[derive(Resource, Clone)]
125/// struct ExampleResource;
126///
127/// fn setup(mut commands: Commands){
128///     commands.spawn_async(|schedules|async move{
129///         schedules.add_system(Update, once::insert_resource(ExampleResource)).await;
130///     });
131/// }
132/// ```
133#[inline]
134pub fn insert_resource<R: Resource + Clone>(resource: R) -> impl IntoAsyncScheduleCommand {
135    run(move |mut commands: Commands| {
136        commands.insert_resource(resource.clone());
137    })
138}
139
140
141/// Initialize a [`Resource`](bevy::prelude::Resource).
142///
143/// ```
144/// use bevy::prelude::*;
145/// use bevy_async_system::prelude::*;
146///
147/// #[derive(Resource, Default)]
148/// struct ExampleResource;
149///
150/// fn setup(mut commands: Commands){
151///     commands.spawn_async(|schedules|async move{
152///         schedules.add_system(Update, once::init_resource::<ExampleResource>()).await;
153///     });
154/// }
155/// ```
156#[inline]
157pub fn init_resource<R: Resource + Default>() -> impl IntoAsyncScheduleCommand {
158    run(|mut commands: Commands| {
159        commands.init_resource::<R>();
160    })
161}
162
163
164/// Init a non send resource.
165///
166/// The system runs on the main thread.
167/// ```
168/// use bevy::prelude::*;
169/// use bevy_async_system::prelude::*;
170///
171/// #[derive(Default)]
172/// struct ExampleResource;
173///
174/// fn setup(mut commands: Commands){
175///     commands.spawn_async(|schedules|async move{
176///         schedules.add_system(Update, once::init_non_send_resource::<ExampleResource>()).await;
177///     });
178/// }
179/// ```
180#[inline]
181pub fn init_non_send_resource<R: FromWorld + 'static>() -> impl IntoAsyncScheduleCommand {
182    run(move |world: &mut World| {
183        world.init_non_send_resource::<R>();
184    })
185}
186
187
188struct OnceOnMain<Out, Marker, Sys>(AsyncSystemConfig<Out, Marker, Sys>);
189
190
191impl<Out, Marker, Sys> IntoAsyncScheduleCommand<Out> for OnceOnMain<Out, Marker, Sys>
192    where
193        Out: Send + Sync + 'static,
194        Marker: Send + Sync + 'static,
195        Sys: IntoSystem<(), Out, Marker> + Send + Sync + 'static
196{
197    fn into_schedule_command(self, sender: TaskSender<Out>, schedule_label: impl ScheduleLabel + Clone) -> AsyncScheduleCommand {
198        AsyncScheduleCommand::new(OnceRunner {
199            config: self.0,
200            sender,
201            schedule_label,
202        })
203    }
204}
205
206
207struct OnceRunner<Out, Marker, Sys, Label> {
208    config: AsyncSystemConfig<Out, Marker, Sys>,
209    sender: TaskSender<Out>,
210    schedule_label: Label,
211}
212
213
214impl<Out, Marker, Sys, Label> AsyncSchedule for OnceRunner<Out, Marker, Sys, Label>
215    where
216        Out: Send + Sync + 'static,
217        Sys: IntoSystem<(), Out, Marker> + Send + Sync,
218        Marker: Send + Sync + 'static,
219        Label: ScheduleLabel + Clone
220{
221    fn initialize(self: Box<Self>, entity_commands: &mut EntityCommands, schedules: &mut Schedules) {
222        let schedule = schedule_initialize(schedules, &self.schedule_label);
223        entity_commands.insert(self.sender);
224        let entity = entity_commands.id();
225        schedule.add_systems(self
226            .config
227            .system
228            .pipe(move |In(input): In<Out>, mut senders: Query<&mut TaskSender<Out>>| {
229                if let Ok(mut sender) = senders.get_mut(entity) {
230                    let _ = sender.try_send(input);
231                    sender.close_channel();
232                }
233            })
234            .run_if(task_running::<Out>(entity)));
235    }
236}
237
238
239#[cfg(test)]
240mod tests {
241    use bevy::app::{PreUpdate, Startup, Update};
242    use bevy::ecs::event::ManualEventReader;
243    use bevy::prelude::{Commands, NonSendMut, Res, Resource};
244
245    use crate::ext::spawn_async_system::SpawnAsyncSystem;
246    use crate::runner::once;
247    use crate::test_util::{FirstEvent, is_first_event_already_coming, is_second_event_already_coming, new_app, SecondEvent, test_state_finished, TestState};
248
249    #[test]
250    fn set_state() {
251        let mut app = new_app();
252        app.add_systems(Startup, |mut commands: Commands| {
253            commands.spawn_async(|schedules| async move {
254                schedules.add_system(PreUpdate, once::set_state(TestState::Finished)).await;
255            });
256        });
257
258        app.update();
259
260        assert!(test_state_finished(&mut app));
261    }
262
263
264    #[test]
265    fn send_event() {
266        let mut app = new_app();
267        app.add_systems(Startup, |mut commands: Commands| {
268            commands.spawn_async(|schedules| async move {
269                schedules.add_system(Update, once::send(FirstEvent)).await;
270                schedules.add_system(Update, once::send(SecondEvent)).await;
271            });
272        });
273
274        let mut er_first = ManualEventReader::default();
275        let mut er_second = ManualEventReader::default();
276
277        app.update();
278
279        assert!(is_first_event_already_coming(&mut app, &mut er_first));
280        assert!(!is_second_event_already_coming(&mut app, &mut er_second));
281
282        app.update();
283        assert!(!is_first_event_already_coming(&mut app, &mut er_first));
284        assert!(is_second_event_already_coming(&mut app, &mut er_second));
285
286        app.update();
287        assert!(!is_first_event_already_coming(&mut app, &mut er_first));
288        assert!(!is_second_event_already_coming(&mut app, &mut er_second));
289    }
290
291
292    #[test]
293    fn output() {
294        let mut app = new_app();
295        app.add_systems(Startup, setup);
296
297        app.update();
298        app.update();
299    }
300
301
302    fn setup(mut commands: Commands) {
303        commands.spawn_async(|schedules| async move {
304            schedules.add_system(Update, once::run(without_output)).await;
305            let count: u32 = schedules.add_system(Update, once::run(with_output)).await;
306            assert_eq!(count, 10);
307        });
308    }
309
310    fn without_output(mut commands: Commands) {
311        commands.insert_resource(Count(10));
312    }
313
314
315    fn with_output(count: Res<Count>) -> u32 {
316        count.0
317    }
318
319    #[derive(Resource)]
320    struct Count(u32);
321
322
323    #[test]
324    fn init_non_send_resource() {
325        let mut app = new_app();
326        app.add_systems(Startup, |mut commands: Commands| {
327            commands.spawn_async(|schedules| async move {
328                schedules.add_system(Update, once::init_non_send_resource::<NonSendNum>()).await;
329                schedules.add_system(Update, once::run(|mut r: NonSendMut<NonSendNum>| {
330                    r.0 = 3;
331                })).await;
332            });
333        });
334
335        app.update();
336        assert_eq!(app.world.non_send_resource::<NonSendNum>().0, 0);
337        app.update();
338        assert_eq!(app.world.non_send_resource::<NonSendNum>().0, 3);
339
340        #[derive(Default)]
341        struct NonSendNum(usize);
342    }
343}