Skip to main content

bevy_app/
sub_app.rs

1use crate::{App, AppLabel, First, InternedAppLabel, Plugin, Plugins, PluginsState};
2use alloc::{boxed::Box, string::String, vec::Vec};
3use bevy_ecs::{
4    message::{message_update_system, MessageRegistry},
5    observer::IntoObserver,
6    prelude::*,
7    schedule::{
8        InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleCleanupPolicy,
9        ScheduleError, ScheduleLabel,
10    },
11    system::{ScheduleSystem, SystemId, SystemInput},
12};
13use bevy_platform::collections::{HashMap, HashSet};
14use core::fmt::Debug;
15
16#[cfg(feature = "trace")]
17use tracing::{info_span, warn};
18
19type ExtractFn = Box<dyn FnMut(&mut World, &mut World) + Send>;
20
21/// A secondary application with its own [`World`]. These can run independently of each other.
22///
23/// These are useful for situations where certain processes (e.g. a render thread) need to be kept
24/// separate from the main application.
25///
26/// # Example
27///
28/// ```
29/// # use bevy_app::{App, SubApp, Main};
30/// # use bevy_derive::AppLabel;
31/// # use bevy_ecs::prelude::*;
32/// # use bevy_ecs::schedule::ScheduleLabel;
33///
34/// #[derive(Resource, Default)]
35/// struct Val(pub i32);
36///
37/// #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel, Default)]
38/// struct ExampleApp;
39///
40/// // Create an app with a certain resource.
41/// let mut app = App::new();
42/// app.insert_resource(Val(10));
43///
44/// // Create a sub-app with the same resource and a single schedule.
45/// let mut sub_app = SubApp::new();
46/// sub_app.update_schedule = Some(Main.intern());
47/// sub_app.insert_resource(Val(100));
48///
49/// // Setup an extract function to copy the resource's value in the main world.
50/// sub_app.set_extract(|main_world, sub_world| {
51///     sub_world.resource_mut::<Val>().0 = main_world.resource::<Val>().0;
52/// });
53///
54/// // Schedule a system that will verify extraction is working.
55/// sub_app.add_systems(Main, |counter: Res<Val>| {
56///     // The value will be copied during extraction, so we should see 10 instead of 100.
57///     assert_eq!(counter.0, 10);
58/// });
59///
60/// // Add the sub-app to the main app.
61/// app.insert_sub_app(ExampleApp, sub_app);
62///
63/// // Update the application once (using the default runner).
64/// app.run();
65/// ```
66pub struct SubApp {
67    /// The data of this application.
68    world: World,
69    /// List of plugins that have been added.
70    pub(crate) plugin_registry: Vec<Box<dyn Plugin>>,
71    /// The names of plugins that have been added to this app. (used to track duplicates and
72    /// already-registered plugins)
73    pub(crate) plugin_names: HashSet<String>,
74    /// Panics if an update is attempted while plugins are building.
75    pub(crate) plugin_build_depth: usize,
76    pub(crate) plugins_state: PluginsState,
77    /// The schedule that will be run by [`update`](Self::update).
78    pub update_schedule: Option<InternedScheduleLabel>,
79    /// A function that gives mutable access to two app worlds. This is primarily
80    /// intended for copying data from the main world to secondary worlds.
81    extract: Option<ExtractFn>,
82}
83
84impl Debug for SubApp {
85    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
86        write!(f, "SubApp")
87    }
88}
89
90impl Default for SubApp {
91    /// As part of default initialization, we schedule a [`message_update_system`] in [`First`].
92    /// We expect all [`SubApp`] implementations to schedule [`First`] regularly.
93    fn default() -> Self {
94        let mut world = World::new();
95        world.init_resource::<Schedules>();
96        world.resource_mut::<Schedules>().add_systems(
97            First,
98            message_update_system
99                .in_set(bevy_ecs::message::MessageUpdateSystems)
100                .run_if(bevy_ecs::message::message_update_condition),
101        );
102        Self {
103            world,
104            plugin_registry: Vec::default(),
105            plugin_names: HashSet::default(),
106            plugin_build_depth: 0,
107            plugins_state: PluginsState::Adding,
108            update_schedule: None,
109            extract: None,
110        }
111    }
112}
113
114impl SubApp {
115    /// Returns a default, empty [`SubApp`].
116    pub fn new() -> Self {
117        Self::default()
118    }
119
120    /// This method is a workaround. Each [`SubApp`] can have its own plugins, but [`Plugin`]
121    /// works on an [`App`] as a whole.
122    fn run_as_app<F>(&mut self, f: F)
123    where
124        F: FnOnce(&mut App),
125    {
126        let mut app = App::empty();
127        core::mem::swap(self, &mut app.sub_apps.main);
128        f(&mut app);
129        core::mem::swap(self, &mut app.sub_apps.main);
130    }
131
132    /// Returns a reference to the [`World`].
133    pub fn world(&self) -> &World {
134        &self.world
135    }
136
137    /// Returns a mutable reference to the [`World`].
138    pub fn world_mut(&mut self) -> &mut World {
139        &mut self.world
140    }
141
142    /// Runs the default schedule.
143    ///
144    /// Does not clear internal trackers used for change detection.
145    pub fn run_default_schedule(&mut self) {
146        if self.is_building_plugins() {
147            panic!("SubApp::update() was called while a plugin was building.");
148        }
149
150        if let Some(label) = self.update_schedule {
151            self.world.run_schedule(label);
152        }
153    }
154
155    /// Runs the default schedule and updates internal component trackers.
156    pub fn update(&mut self) {
157        self.run_default_schedule();
158        self.world.clear_trackers();
159    }
160
161    /// Extracts data from `world` into the app's world using the registered extract method.
162    ///
163    /// **Note:** There is no default extract method. Calling `extract` does nothing if
164    /// [`set_extract`](Self::set_extract) has not been called.
165    pub fn extract(&mut self, world: &mut World) {
166        if let Some(f) = self.extract.as_mut() {
167            f(world, &mut self.world);
168        }
169    }
170
171    /// Sets the method that will be called by [`extract`](Self::extract).
172    ///
173    /// The first argument is the `World` to extract data from, the second argument is the app `World`.
174    pub fn set_extract<F>(&mut self, extract: F) -> &mut Self
175    where
176        F: FnMut(&mut World, &mut World) + Send + 'static,
177    {
178        self.extract = Some(Box::new(extract));
179        self
180    }
181
182    /// Take the function that will be called by [`extract`](Self::extract) out of the app, if any was set,
183    /// and replace it with `None`.
184    ///
185    /// If you use Bevy, `bevy_render` will set a default extract function used to extract data from
186    /// the main world into the render world as part of the Extract phase. In that case, you cannot replace
187    /// it with your own function. Instead, take the Bevy default function with this, and install your own
188    /// instead which calls the Bevy default.
189    ///
190    /// ```
191    /// # use bevy_app::SubApp;
192    /// # let mut app = SubApp::new();
193    /// let mut default_fn = app.take_extract();
194    /// app.set_extract(move |main, render| {
195    ///     // Do pre-extract custom logic
196    ///     // [...]
197    ///
198    ///     // Call Bevy's default, which executes the Extract phase
199    ///     if let Some(f) = default_fn.as_mut() {
200    ///         f(main, render);
201    ///     }
202    ///
203    ///     // Do post-extract custom logic
204    ///     // [...]
205    /// });
206    /// ```
207    pub fn take_extract(&mut self) -> Option<ExtractFn> {
208        self.extract.take()
209    }
210
211    /// See [`App::insert_resource`].
212    pub fn insert_resource<R: Resource>(&mut self, resource: R) -> &mut Self {
213        self.world.insert_resource(resource);
214        self
215    }
216
217    /// See [`App::init_resource`].
218    pub fn init_resource<R: Resource + FromWorld>(&mut self) -> &mut Self {
219        self.world.init_resource::<R>();
220        self
221    }
222
223    /// See [`App::add_systems`].
224    pub fn add_systems<M>(
225        &mut self,
226        schedule: impl ScheduleLabel,
227        systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
228    ) -> &mut Self {
229        let mut schedules = self.world.resource_mut::<Schedules>();
230        schedules.add_systems(schedule, systems);
231
232        self
233    }
234
235    /// See [`App::remove_systems_in_set`]
236    pub fn remove_systems_in_set<M>(
237        &mut self,
238        schedule: impl ScheduleLabel,
239        set: impl IntoSystemSet<M>,
240        policy: ScheduleCleanupPolicy,
241    ) -> Result<usize, ScheduleError> {
242        self.world.schedule_scope(schedule, |world, schedule| {
243            schedule.remove_systems_in_set(set, world, policy)
244        })
245    }
246
247    /// See [`App::register_system`].
248    pub fn register_system<I, O, M>(
249        &mut self,
250        system: impl IntoSystem<I, O, M> + 'static,
251    ) -> SystemId<I, O>
252    where
253        I: SystemInput + 'static,
254        O: 'static,
255    {
256        self.world.register_system(system)
257    }
258
259    /// See [`App::register_tracked_system`].
260    pub fn register_tracked_system<I, O, M>(
261        &mut self,
262        system: impl IntoSystem<I, O, M> + 'static,
263    ) -> bevy_ecs::system::SystemHandle<I, O>
264    where
265        I: SystemInput + 'static,
266        O: 'static,
267    {
268        self.world.register_tracked_system(system)
269    }
270
271    /// See [`App::configure_sets`].
272    #[track_caller]
273    pub fn configure_sets<M>(
274        &mut self,
275        schedule: impl ScheduleLabel,
276        sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
277    ) -> &mut Self {
278        let mut schedules = self.world.resource_mut::<Schedules>();
279        schedules.configure_sets(schedule, sets);
280        self
281    }
282
283    /// See [`App::add_schedule`].
284    pub fn add_schedule(&mut self, schedule: Schedule) -> &mut Self {
285        let mut schedules = self.world.resource_mut::<Schedules>();
286        let _old_schedule = schedules.insert(schedule);
287
288        #[cfg(feature = "trace")]
289        if let Some(schedule) = _old_schedule {
290            warn!(
291                "Schedule {:?} was re-inserted, all previous configuration has been removed",
292                schedule.label()
293            );
294        }
295
296        self
297    }
298
299    /// See [`App::init_schedule`].
300    pub fn init_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self {
301        let label = label.intern();
302        let mut schedules = self.world.resource_mut::<Schedules>();
303        if !schedules.contains(label) {
304            schedules.insert(Schedule::new(label));
305        }
306        self
307    }
308
309    /// See [`App::get_schedule`].
310    pub fn get_schedule(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
311        let schedules = self.world.get_resource::<Schedules>()?;
312        schedules.get(label)
313    }
314
315    /// See [`App::get_schedule_mut`].
316    pub fn get_schedule_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
317        let schedules = self.world.get_resource_mut::<Schedules>()?;
318        // We must call `.into_inner` here because the borrow checker only understands reborrows
319        // using ordinary references, not our `Mut` smart pointers.
320        schedules.into_inner().get_mut(label)
321    }
322
323    /// See [`App::edit_schedule`].
324    pub fn edit_schedule(
325        &mut self,
326        label: impl ScheduleLabel,
327        mut f: impl FnMut(&mut Schedule),
328    ) -> &mut Self {
329        let label = label.intern();
330        let mut schedules = self.world.resource_mut::<Schedules>();
331        if !schedules.contains(label) {
332            schedules.insert(Schedule::new(label));
333        }
334
335        let schedule = schedules.get_mut(label).unwrap();
336        f(schedule);
337
338        self
339    }
340
341    /// See [`App::configure_schedules`].
342    pub fn configure_schedules(
343        &mut self,
344        schedule_build_settings: ScheduleBuildSettings,
345    ) -> &mut Self {
346        self.world_mut()
347            .resource_mut::<Schedules>()
348            .configure_schedules(schedule_build_settings);
349        self
350    }
351
352    /// See [`App::allow_ambiguous_component`].
353    pub fn allow_ambiguous_component<T: Component>(&mut self) -> &mut Self {
354        self.world_mut().allow_ambiguous_component::<T>();
355        self
356    }
357
358    /// See [`App::allow_ambiguous_resource`].
359    pub fn allow_ambiguous_resource<T: Resource>(&mut self) -> &mut Self {
360        self.world_mut().allow_ambiguous_resource::<T>();
361        self
362    }
363
364    /// See [`App::ignore_ambiguity`].
365    #[track_caller]
366    pub fn ignore_ambiguity<M1, M2, S1, S2>(
367        &mut self,
368        schedule: impl ScheduleLabel,
369        a: S1,
370        b: S2,
371    ) -> &mut Self
372    where
373        S1: IntoSystemSet<M1>,
374        S2: IntoSystemSet<M2>,
375    {
376        let schedule = schedule.intern();
377        let mut schedules = self.world.resource_mut::<Schedules>();
378
379        schedules.ignore_ambiguity(schedule, a, b);
380
381        self
382    }
383
384    /// See [`App::add_observer`].
385    pub fn add_observer<M>(&mut self, observer: impl IntoObserver<M>) -> &mut Self {
386        self.world_mut().add_observer(observer);
387        self
388    }
389
390    /// See [`App::add_message`].
391    pub fn add_message<T>(&mut self) -> &mut Self
392    where
393        T: Message,
394    {
395        if !self.world.contains_resource::<Messages<T>>() {
396            MessageRegistry::register_message::<T>(self.world_mut());
397        }
398
399        self
400    }
401
402    /// See [`App::add_plugins`].
403    pub fn add_plugins<M>(&mut self, plugins: impl Plugins<M>) -> &mut Self {
404        self.run_as_app(|app| plugins.add_to_app(app));
405        self
406    }
407
408    /// See [`App::is_plugin_added`].
409    pub fn is_plugin_added<T>(&self) -> bool
410    where
411        T: Plugin,
412    {
413        self.plugin_names.contains(core::any::type_name::<T>())
414    }
415
416    /// See [`App::get_added_plugins`].
417    pub fn get_added_plugins<T>(&self) -> Vec<&T>
418    where
419        T: Plugin,
420    {
421        self.plugin_registry
422            .iter()
423            .filter_map(|p| p.downcast_ref())
424            .collect()
425    }
426
427    /// Returns `true` if there is no plugin in the middle of being built.
428    pub(crate) fn is_building_plugins(&self) -> bool {
429        self.plugin_build_depth > 0
430    }
431
432    /// Return the state of plugins.
433    #[inline]
434    pub fn plugins_state(&mut self) -> PluginsState {
435        match self.plugins_state {
436            PluginsState::Adding => {
437                let mut state = PluginsState::Ready;
438                let plugins = core::mem::take(&mut self.plugin_registry);
439                self.run_as_app(|app| {
440                    for plugin in &plugins {
441                        if !plugin.ready(app) {
442                            state = PluginsState::Adding;
443                            return;
444                        }
445                    }
446                });
447                self.plugin_registry = plugins;
448                state
449            }
450            state => state,
451        }
452    }
453
454    /// Runs [`Plugin::finish`] for each plugin.
455    pub fn finish(&mut self) {
456        // do hokey pokey with a boxed zst plugin (doesn't allocate)
457        let mut hokeypokey: Box<dyn Plugin> = Box::new(crate::HokeyPokey);
458        for i in 0..self.plugin_registry.len() {
459            core::mem::swap(&mut self.plugin_registry[i], &mut hokeypokey);
460            #[cfg(feature = "trace")]
461            let _plugin_finish_span =
462                info_span!("plugin finish", plugin = hokeypokey.name()).entered();
463            self.run_as_app(|app| {
464                hokeypokey.finish(app);
465            });
466            core::mem::swap(&mut self.plugin_registry[i], &mut hokeypokey);
467        }
468        self.plugins_state = PluginsState::Finished;
469    }
470
471    /// Runs [`Plugin::cleanup`] for each plugin.
472    pub fn cleanup(&mut self) {
473        // do hokey pokey with a boxed zst plugin (doesn't allocate)
474        let mut hokeypokey: Box<dyn Plugin> = Box::new(crate::HokeyPokey);
475        for i in 0..self.plugin_registry.len() {
476            core::mem::swap(&mut self.plugin_registry[i], &mut hokeypokey);
477            #[cfg(feature = "trace")]
478            let _plugin_cleanup_span =
479                info_span!("plugin cleanup", plugin = hokeypokey.name()).entered();
480            self.run_as_app(|app| {
481                hokeypokey.cleanup(app);
482            });
483            core::mem::swap(&mut self.plugin_registry[i], &mut hokeypokey);
484        }
485        self.plugins_state = PluginsState::Cleaned;
486    }
487
488    /// See [`App::register_type`].
489    #[cfg(feature = "bevy_reflect")]
490    pub fn register_type<T: bevy_reflect::GetTypeRegistration>(&mut self) -> &mut Self {
491        let registry = self.world.resource_mut::<AppTypeRegistry>();
492        registry.write().register::<T>();
493        self
494    }
495
496    /// See [`App::register_type_data`].
497    #[cfg(feature = "bevy_reflect")]
498    pub fn register_type_data<
499        T: bevy_reflect::Reflect + bevy_reflect::TypePath,
500        D: bevy_reflect::CreateTypeData<T>,
501    >(
502        &mut self,
503    ) -> &mut Self {
504        let registry = self.world.resource_mut::<AppTypeRegistry>();
505        registry.write().register_type_data::<T, D>();
506        self
507    }
508
509    /// See [`App::register_type_conversion`].
510    #[cfg(feature = "bevy_reflect")]
511    pub fn register_type_conversion<T, U, F>(&mut self, function: F) -> &mut Self
512    where
513        T: bevy_reflect::Reflect + bevy_reflect::TypePath,
514        U: bevy_reflect::Reflect + bevy_reflect::TypePath,
515        F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
516    {
517        let registry = self.world.resource_mut::<AppTypeRegistry>();
518        registry
519            .write()
520            .register_type_conversion::<T, U, _>(function);
521        self
522    }
523
524    /// See [`App::register_into_type_conversion`].
525    #[cfg(feature = "bevy_reflect")]
526    pub fn register_into_type_conversion<T, U>(&mut self) -> &mut Self
527    where
528        T: bevy_reflect::Reflect + bevy_reflect::TypePath,
529        U: bevy_reflect::Reflect + bevy_reflect::TypePath + From<T>,
530    {
531        let registry = self.world.resource_mut::<AppTypeRegistry>();
532        registry.write().register_into_type_conversion::<T, U>();
533        self
534    }
535
536    /// See [`App::register_function`].
537    #[cfg(feature = "reflect_functions")]
538    pub fn register_function<F, Marker>(&mut self, function: F) -> &mut Self
539    where
540        F: bevy_reflect::func::IntoFunction<'static, Marker> + 'static,
541    {
542        let registry = self.world.resource_mut::<AppFunctionRegistry>();
543        registry.write().register(function).unwrap();
544        self
545    }
546
547    /// See [`App::register_function_with_name`].
548    #[cfg(feature = "reflect_functions")]
549    pub fn register_function_with_name<F, Marker>(
550        &mut self,
551        name: impl Into<alloc::borrow::Cow<'static, str>>,
552        function: F,
553    ) -> &mut Self
554    where
555        F: bevy_reflect::func::IntoFunction<'static, Marker> + 'static,
556    {
557        let registry = self.world.resource_mut::<AppFunctionRegistry>();
558        registry.write().register_with_name(name, function).unwrap();
559        self
560    }
561}
562
563/// The collection of sub-apps that belong to an [`App`].
564#[derive(Default)]
565pub struct SubApps {
566    /// The primary sub-app that contains the "main" world.
567    pub main: SubApp,
568    /// Other, labeled sub-apps.
569    pub sub_apps: HashMap<InternedAppLabel, SubApp>,
570}
571
572impl SubApps {
573    /// Calls [`update`](SubApp::update) for the main sub-app, and then calls
574    /// [`extract`](SubApp::extract) and [`update`](SubApp::update) for the rest.
575    pub fn update(&mut self) {
576        #[cfg(feature = "trace")]
577        let _bevy_update_span = info_span!("update").entered();
578        {
579            #[cfg(feature = "trace")]
580            let _bevy_frame_update_span = info_span!("main app").entered();
581            self.main.run_default_schedule();
582        }
583        for (_label, sub_app) in self.sub_apps.iter_mut() {
584            #[cfg(feature = "trace")]
585            let _sub_app_span = info_span!("sub app", name = ?_label).entered();
586            sub_app.extract(&mut self.main.world);
587            sub_app.update();
588        }
589
590        self.main.world.clear_trackers();
591    }
592
593    /// Returns an iterator over the sub-apps (starting with the main one).
594    pub fn iter(&self) -> impl Iterator<Item = &SubApp> + '_ {
595        core::iter::once(&self.main).chain(self.sub_apps.values())
596    }
597
598    /// Returns a mutable iterator over the sub-apps (starting with the main one).
599    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut SubApp> + '_ {
600        core::iter::once(&mut self.main).chain(self.sub_apps.values_mut())
601    }
602
603    /// Extract data from the main world into the [`SubApp`] with the given label and perform an update if it exists.
604    pub fn update_subapp_by_label(&mut self, label: impl AppLabel) {
605        if let Some(sub_app) = self.sub_apps.get_mut(&label.intern()) {
606            sub_app.extract(&mut self.main.world);
607            sub_app.update();
608        }
609    }
610}
611
612#[cfg(test)]
613mod tests {
614    #[test]
615    fn sub_app_add_message_schedules_update_system() {
616        use crate::{First, SubApp};
617        use bevy_ecs::message::Messages;
618        use bevy_ecs::prelude::Message;
619        use bevy_ecs::schedule::ScheduleLabel;
620
621        #[derive(Message, Clone, Copy)]
622        struct TestMsg;
623
624        // Wire the sub-app to actually run `First` each update so the test
625        // does not silently pass simply because nothing in the schedule runs.
626        let mut sub_app = SubApp {
627            update_schedule: Some(First.intern()),
628            ..Default::default()
629        };
630
631        sub_app.add_message::<TestMsg>();
632
633        {
634            let mut msgs = sub_app.world_mut().resource_mut::<Messages<TestMsg>>();
635            msgs.write(TestMsg);
636            msgs.write(TestMsg);
637        }
638
639        assert_eq!(sub_app.world().resource::<Messages<TestMsg>>().len(), 2);
640
641        // Two updates will let the double buffer rotate twice and drop
642        // the events. As long as a `message_update_system` is set up,
643        // the following assertion should pass.
644        sub_app.update();
645        sub_app.update();
646
647        assert_eq!(sub_app.world().resource::<Messages<TestMsg>>().len(), 0);
648    }
649}