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 {
67 world: World,
69 pub(crate) plugin_registry: Vec<Box<dyn Plugin>>,
71 pub(crate) plugin_names: HashSet<String>,
74 pub(crate) plugin_build_depth: usize,
76 pub(crate) plugins_state: PluginsState,
77 pub update_schedule: Option<InternedScheduleLabel>,
79 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 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 pub fn new() -> Self {
117 Self::default()
118 }
119
120 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 pub fn world(&self) -> &World {
134 &self.world
135 }
136
137 pub fn world_mut(&mut self) -> &mut World {
139 &mut self.world
140 }
141
142 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 pub fn update(&mut self) {
157 self.run_default_schedule();
158 self.world.clear_trackers();
159 }
160
161 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 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 pub fn take_extract(&mut self) -> Option<ExtractFn> {
208 self.extract.take()
209 }
210
211 pub fn insert_resource<R: Resource>(&mut self, resource: R) -> &mut Self {
213 self.world.insert_resource(resource);
214 self
215 }
216
217 pub fn init_resource<R: Resource + FromWorld>(&mut self) -> &mut Self {
219 self.world.init_resource::<R>();
220 self
221 }
222
223 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 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 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 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 #[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 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 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 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 pub fn get_schedule_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
317 let schedules = self.world.get_resource_mut::<Schedules>()?;
318 schedules.into_inner().get_mut(label)
321 }
322
323 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 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 pub fn allow_ambiguous_component<T: Component>(&mut self) -> &mut Self {
354 self.world_mut().allow_ambiguous_component::<T>();
355 self
356 }
357
358 pub fn allow_ambiguous_resource<T: Resource>(&mut self) -> &mut Self {
360 self.world_mut().allow_ambiguous_resource::<T>();
361 self
362 }
363
364 #[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 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 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 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 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 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 pub(crate) fn is_building_plugins(&self) -> bool {
429 self.plugin_build_depth > 0
430 }
431
432 #[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 pub fn finish(&mut self) {
456 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 pub fn cleanup(&mut self) {
473 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 #[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 #[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 #[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 #[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 #[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 #[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#[derive(Default)]
565pub struct SubApps {
566 pub main: SubApp,
568 pub sub_apps: HashMap<InternedAppLabel, SubApp>,
570}
571
572impl SubApps {
573 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 pub fn iter(&self) -> impl Iterator<Item = &SubApp> + '_ {
595 core::iter::once(&self.main).chain(self.sub_apps.values())
596 }
597
598 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 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 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 sub_app.update();
645 sub_app.update();
646
647 assert_eq!(sub_app.world().resource::<Messages<TestMsg>>().len(), 0);
648 }
649}