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