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
21pub struct SubApp {
66 world: World,
68 pub(crate) plugin_registry: Vec<Box<dyn Plugin>>,
70 pub(crate) plugin_names: HashSet<String>,
73 pub(crate) plugin_build_depth: usize,
75 pub(crate) plugins_state: PluginsState,
76 pub update_schedule: Option<InternedScheduleLabel>,
78 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 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 pub fn new() -> Self {
116 Self::default()
117 }
118
119 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 pub fn world(&self) -> &World {
133 &self.world
134 }
135
136 pub fn world_mut(&mut self) -> &mut World {
138 &mut self.world
139 }
140
141 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 pub fn update(&mut self) {
156 self.run_default_schedule();
157 self.world.clear_trackers();
158 }
159
160 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 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 pub fn take_extract(&mut self) -> Option<ExtractFn> {
207 self.extract.take()
208 }
209
210 pub fn insert_resource<R: Resource>(&mut self, resource: R) -> &mut Self {
212 self.world.insert_resource(resource);
213 self
214 }
215
216 pub fn init_resource<R: Resource + FromWorld>(&mut self) -> &mut Self {
218 self.world.init_resource::<R>();
219 self
220 }
221
222 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 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 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 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 #[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 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 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 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 pub fn get_schedule_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
316 let schedules = self.world.get_resource_mut::<Schedules>()?;
317 schedules.into_inner().get_mut(label)
320 }
321
322 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 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 pub fn allow_ambiguous_component<T: Component>(&mut self) -> &mut Self {
353 self.world_mut().allow_ambiguous_component::<T>();
354 self
355 }
356
357 pub fn allow_ambiguous_resource<T: Resource>(&mut self) -> &mut Self {
359 self.world_mut().allow_ambiguous_resource::<T>();
360 self
361 }
362
363 #[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 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 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 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 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 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 pub(crate) fn is_building_plugins(&self) -> bool {
428 self.plugin_build_depth > 0
429 }
430
431 #[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 pub fn finish(&mut self) {
455 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 pub fn cleanup(&mut self) {
472 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 #[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 #[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 #[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 #[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 #[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 #[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#[derive(Default)]
564pub struct SubApps {
565 pub main: SubApp,
567 pub sub_apps: HashMap<InternedAppLabel, SubApp>,
569}
570
571impl SubApps {
572 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 pub fn iter(&self) -> impl Iterator<Item = &SubApp> + '_ {
594 core::iter::once(&self.main).chain(self.sub_apps.values())
595 }
596
597 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 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 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 sub_app.update();
644 sub_app.update();
645
646 assert_eq!(sub_app.world().resource::<Messages<TestMsg>>().len(), 0);
647 }
648}