diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index d8ce9e91d0..af2d533437 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -24,6 +24,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.9.0-dev" } # other serde = { version = "1.0", features = ["derive"], optional = true } ron = { version = "0.8.0", optional = true } +downcast-rs = "1.2.0" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 25c2271365..63ce7d052d 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1,4 +1,4 @@ -use crate::{CoreStage, Plugin, PluginGroup, PluginGroupBuilder, StartupSchedule, StartupStage}; +use crate::{CoreStage, Plugin, PluginGroup, StartupSchedule, StartupStage}; pub use bevy_derive::AppLabel; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ @@ -838,7 +838,8 @@ impl App { /// The [`PluginGroup`]s available by default are `DefaultPlugins` and `MinimalPlugins`. /// /// To customize the plugins in the group (reorder, disable a plugin, add a new plugin - /// before / after another plugin), see [`add_plugins_with`](Self::add_plugins_with). + /// before / after another plugin), call [`build()`](PluginGroup::build) on the group, + /// which will convert it to a [`PluginGroupBuilder`](crate::PluginGroupBuilder). /// /// ## Examples /// ``` @@ -847,61 +848,9 @@ impl App { /// App::new() /// .add_plugins(MinimalPlugins); /// ``` - pub fn add_plugins(&mut self, mut group: T) -> &mut Self { - let mut plugin_group_builder = PluginGroupBuilder::default(); - group.build(&mut plugin_group_builder); - plugin_group_builder.finish(self); - self - } - - /// Adds a group of [`Plugin`]s with an initializer method. - /// - /// Can be used to add a group of [`Plugin`]s, where the group is modified - /// before insertion into a Bevy application. For example, you can add - /// additional [`Plugin`]s at a specific place in the [`PluginGroup`], or deactivate - /// specific [`Plugin`]s while keeping the rest using a [`PluginGroupBuilder`]. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::{prelude::*, PluginGroupBuilder}; - /// # - /// # // Dummies created to avoid using `bevy_internal` and `bevy_log`, - /// # // which pulls in too many dependencies and breaks rust-analyzer - /// # pub mod bevy_log { - /// # use bevy_app::prelude::*; - /// # #[derive(Default)] - /// # pub struct LogPlugin; - /// # impl Plugin for LogPlugin{ - /// # fn build(&self, app: &mut App) {} - /// # } - /// # } - /// # struct DefaultPlugins; - /// # impl PluginGroup for DefaultPlugins { - /// # fn build(&mut self, group: &mut PluginGroupBuilder){ - /// # group.add(bevy_log::LogPlugin::default()); - /// # } - /// # } - /// # - /// # struct MyOwnPlugin; - /// # impl Plugin for MyOwnPlugin { - /// # fn build(&self, app: &mut App){;} - /// # } - /// # - /// App::new() - /// .add_plugins_with(DefaultPlugins, |group| { - /// group.add_before::(MyOwnPlugin) - /// }); - /// ``` - pub fn add_plugins_with(&mut self, mut group: T, func: F) -> &mut Self - where - T: PluginGroup, - F: FnOnce(&mut PluginGroupBuilder) -> &mut PluginGroupBuilder, - { - let mut plugin_group_builder = PluginGroupBuilder::default(); - group.build(&mut plugin_group_builder); - func(&mut plugin_group_builder); - plugin_group_builder.finish(self); + pub fn add_plugins(&mut self, group: T) -> &mut Self { + let builder = group.build(); + builder.finish(self); self } diff --git a/crates/bevy_app/src/plugin.rs b/crates/bevy_app/src/plugin.rs index e6e70cf856..b768acd0f3 100644 --- a/crates/bevy_app/src/plugin.rs +++ b/crates/bevy_app/src/plugin.rs @@ -1,3 +1,5 @@ +use downcast_rs::{impl_downcast, Downcast}; + use crate::App; use std::any::Any; @@ -5,7 +7,7 @@ use std::any::Any; /// /// Plugins configure an [`App`]. When an [`App`] registers a plugin, /// the plugin's [`Plugin::build`] function is run. -pub trait Plugin: Any + Send + Sync { +pub trait Plugin: Downcast + Any + Send + Sync { /// Configures the [`App`] to which this plugin is added. fn build(&self, app: &mut App); /// Configures a name for the [`Plugin`] which is primarily used for debugging. @@ -14,6 +16,8 @@ pub trait Plugin: Any + Send + Sync { } } +impl_downcast!(Plugin); + /// A type representing an unsafe function that returns a mutable pointer to a [`Plugin`]. /// It is used for dynamically loading plugins. /// diff --git a/crates/bevy_app/src/plugin_group.rs b/crates/bevy_app/src/plugin_group.rs index bc7f75d7d8..91e6773b48 100644 --- a/crates/bevy_app/src/plugin_group.rs +++ b/crates/bevy_app/src/plugin_group.rs @@ -3,9 +3,13 @@ use bevy_utils::{tracing::debug, tracing::warn, HashMap}; use std::any::TypeId; /// Combines multiple [`Plugin`]s into a single unit. -pub trait PluginGroup { +pub trait PluginGroup: Sized { /// Configures the [`Plugin`]s that are to be added. - fn build(&mut self, group: &mut PluginGroupBuilder); + fn build(self) -> PluginGroupBuilder; + /// Sets the value of the given [`Plugin`], if it exists + fn set(self, plugin: T) -> PluginGroupBuilder { + self.build().set(plugin) + } } struct PluginEntry { @@ -13,6 +17,12 @@ struct PluginEntry { enabled: bool, } +impl PluginGroup for PluginGroupBuilder { + fn build(self) -> PluginGroupBuilder { + self + } +} + /// Facilitates the creation and configuration of a [`PluginGroup`]. /// Provides a build ordering to ensure that [`Plugin`]s which produce/require a [`Resource`](bevy_ecs::system::Resource) /// are built before/after dependent/depending [`Plugin`]s. [`Plugin`]s inside the group @@ -25,7 +35,7 @@ pub struct PluginGroupBuilder { impl PluginGroupBuilder { /// Finds the index of a target [`Plugin`]. Panics if the target's [`TypeId`] is not found. - fn index_of(&mut self) -> usize { + fn index_of(&self) -> usize { let index = self .order .iter() @@ -68,9 +78,27 @@ impl PluginGroupBuilder { } } + /// Sets the value of the given [`Plugin`], if it exists. + /// + /// # Panics + /// + /// Panics if the [`Plugin`] does not exist. + pub fn set(mut self, plugin: T) -> Self { + let entry = self.plugins.get_mut(&TypeId::of::()).unwrap_or_else(|| { + panic!( + "{} does not exist in this PluginGroup", + std::any::type_name::(), + ) + }); + entry.plugin = Box::new(plugin); + self + } + /// Adds the plugin [`Plugin`] at the end of this [`PluginGroupBuilder`]. If the plugin was /// already in the group, it is removed from its previous place. - pub fn add(&mut self, plugin: T) -> &mut Self { + // This is not confusing, clippy! + #[allow(clippy::should_implement_trait)] + pub fn add(mut self, plugin: T) -> Self { let target_index = self.order.len(); self.order.push(TypeId::of::()); self.upsert_plugin_state(plugin, target_index); @@ -80,7 +108,7 @@ impl PluginGroupBuilder { /// Adds a [`Plugin`] in this [`PluginGroupBuilder`] before the plugin of type `Target`. /// If the plugin was already the group, it is removed from its previous place. There must /// be a plugin of type `Target` in the group or it will panic. - pub fn add_before(&mut self, plugin: T) -> &mut Self { + pub fn add_before(mut self, plugin: T) -> Self { let target_index = self.index_of::(); self.order.insert(target_index, TypeId::of::()); self.upsert_plugin_state(plugin, target_index); @@ -90,7 +118,7 @@ impl PluginGroupBuilder { /// Adds a [`Plugin`] in this [`PluginGroupBuilder`] after the plugin of type `Target`. /// If the plugin was already the group, it is removed from its previous place. There must /// be a plugin of type `Target` in the group or it will panic. - pub fn add_after(&mut self, plugin: T) -> &mut Self { + pub fn add_after(mut self, plugin: T) -> Self { let target_index = self.index_of::() + 1; self.order.insert(target_index, TypeId::of::()); self.upsert_plugin_state(plugin, target_index); @@ -102,7 +130,7 @@ impl PluginGroupBuilder { /// [`Plugin`]s within a [`PluginGroup`] are enabled by default. This function is used to /// opt back in to a [`Plugin`] after [disabling](Self::disable) it. If there are no plugins /// of type `T` in this group, it will panic. - pub fn enable(&mut self) -> &mut Self { + pub fn enable(mut self) -> Self { let mut plugin_entry = self .plugins .get_mut(&TypeId::of::()) @@ -116,7 +144,7 @@ impl PluginGroupBuilder { /// still be used for ordering with [`add_before`](Self::add_before) or /// [`add_after`](Self::add_after), or it can be [re-enabled](Self::enable). If there are no /// plugins of type `T` in this group, it will panic. - pub fn disable(&mut self) -> &mut Self { + pub fn disable(mut self) -> Self { let mut plugin_entry = self .plugins .get_mut(&TypeId::of::()) @@ -152,7 +180,9 @@ impl PluginGroupBuilder { pub struct NoopPluginGroup; impl PluginGroup for NoopPluginGroup { - fn build(&mut self, _: &mut PluginGroupBuilder) {} + fn build(self) -> PluginGroupBuilder { + PluginGroupBuilder::default() + } } #[cfg(test)] @@ -177,10 +207,10 @@ mod tests { #[test] fn basic_ordering() { - let mut group = PluginGroupBuilder::default(); - group.add(PluginA); - group.add(PluginB); - group.add(PluginC); + let group = PluginGroupBuilder::default() + .add(PluginA) + .add(PluginB) + .add(PluginC); assert_eq!( group.order, @@ -194,10 +224,10 @@ mod tests { #[test] fn add_after() { - let mut group = PluginGroupBuilder::default(); - group.add(PluginA); - group.add(PluginB); - group.add_after::(PluginC); + let group = PluginGroupBuilder::default() + .add(PluginA) + .add(PluginB) + .add_after::(PluginC); assert_eq!( group.order, @@ -211,10 +241,10 @@ mod tests { #[test] fn add_before() { - let mut group = PluginGroupBuilder::default(); - group.add(PluginA); - group.add(PluginB); - group.add_before::(PluginC); + let group = PluginGroupBuilder::default() + .add(PluginA) + .add(PluginB) + .add_before::(PluginC); assert_eq!( group.order, @@ -228,11 +258,11 @@ mod tests { #[test] fn readd() { - let mut group = PluginGroupBuilder::default(); - group.add(PluginA); - group.add(PluginB); - group.add(PluginC); - group.add(PluginB); + let group = PluginGroupBuilder::default() + .add(PluginA) + .add(PluginB) + .add(PluginC) + .add(PluginB); assert_eq!( group.order, @@ -246,11 +276,11 @@ mod tests { #[test] fn readd_after() { - let mut group = PluginGroupBuilder::default(); - group.add(PluginA); - group.add(PluginB); - group.add(PluginC); - group.add_after::(PluginC); + let group = PluginGroupBuilder::default() + .add(PluginA) + .add(PluginB) + .add(PluginC) + .add_after::(PluginC); assert_eq!( group.order, @@ -264,11 +294,11 @@ mod tests { #[test] fn readd_before() { - let mut group = PluginGroupBuilder::default(); - group.add(PluginA); - group.add(PluginB); - group.add(PluginC); - group.add_before::(PluginC); + let group = PluginGroupBuilder::default() + .add(PluginA) + .add(PluginB) + .add(PluginC) + .add_before::(PluginC); assert_eq!( group.order, diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 46b2a0f550..6e26ad1b23 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -83,8 +83,8 @@ pub struct AssetServerInternal { /// # use bevy_asset::*; /// # use bevy_app::*; /// # let mut app = App::new(); -/// // AssetServerSettings must be inserted before adding the AssetPlugin or DefaultPlugins. -/// app.insert_resource(AssetServerSettings { +/// // The asset plugin can be configured to watch for asset changes. +/// app.add_plugin(AssetPlugin { /// watch_for_changes: true, /// ..Default::default() /// }); @@ -293,7 +293,7 @@ impl AssetServer { /// `"CARGO_MANIFEST_DIR"` is automatically set to the root folder of your crate (workspace). /// /// The name of the asset folder is set inside the - /// [`AssetServerSettings`](crate::AssetServerSettings) resource. The default name is + /// [`AssetPlugin`](crate::AssetPlugin). The default name is /// `"assets"`. /// /// The asset is loaded asynchronously, and will generally not be available by the time diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 97b30dd53d..eb04ceb2cb 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -430,7 +430,7 @@ mod tests { struct MyAsset; let mut app = App::new(); app.add_plugin(bevy_core::CorePlugin) - .add_plugin(crate::AssetPlugin); + .add_plugin(crate::AssetPlugin::default()); app.add_asset::(); let mut assets_before = app.world.resource_mut::>(); let handle = assets_before.add(MyAsset); diff --git a/crates/bevy_asset/src/debug_asset_server.rs b/crates/bevy_asset/src/debug_asset_server.rs index fafc3be759..690dd6769d 100644 --- a/crates/bevy_asset/src/debug_asset_server.rs +++ b/crates/bevy_asset/src/debug_asset_server.rs @@ -16,8 +16,7 @@ use std::{ }; use crate::{ - Asset, AssetEvent, AssetPlugin, AssetServer, AssetServerSettings, Assets, FileAssetIo, Handle, - HandleUntyped, + Asset, AssetEvent, AssetPlugin, AssetServer, Assets, FileAssetIo, Handle, HandleUntyped, }; /// A helper [`App`] used for hot reloading internal assets, which are compiled-in to Bevy plugins. @@ -75,12 +74,10 @@ impl Plugin for DebugAssetServerPlugin { .build() }); let mut debug_asset_app = App::new(); - debug_asset_app - .insert_resource(AssetServerSettings { - asset_folder: "crates".to_string(), - watch_for_changes: true, - }) - .add_plugin(AssetPlugin); + debug_asset_app.add_plugin(AssetPlugin { + asset_folder: "crates".to_string(), + watch_for_changes: true, + }); app.insert_non_send_resource(DebugAssetApp(debug_asset_app)); app.add_system(run_debug_asset_app); } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 24e9d975b1..ff125d3abd 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -30,7 +30,7 @@ mod path; pub mod prelude { #[doc(hidden)] pub use crate::{ - AddAsset, AssetEvent, AssetServer, AssetServerSettings, Assets, Handle, HandleUntyped, + AddAsset, AssetEvent, AssetPlugin, AssetServer, Assets, Handle, HandleUntyped, }; } @@ -45,10 +45,7 @@ pub use loader::*; pub use path::*; use bevy_app::{prelude::Plugin, App}; -use bevy_ecs::{ - schedule::{StageLabel, SystemStage}, - system::Resource, -}; +use bevy_ecs::schedule::{StageLabel, SystemStage}; /// The names of asset stages in an [`App`] schedule. #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] @@ -63,14 +60,7 @@ pub enum AssetStage { /// /// Assets are typed collections with change tracking, which are added as App Resources. Examples of /// assets: textures, sounds, 3d models, maps, scenes -#[derive(Default)] -pub struct AssetPlugin; - -/// Settings for the [`AssetServer`]. -/// -/// This resource must be added before the [`AssetPlugin`] or `DefaultPlugins` to take effect. -#[derive(Resource)] -pub struct AssetServerSettings { +pub struct AssetPlugin { /// The base folder where assets are loaded from, relative to the executable. pub asset_folder: String, /// Whether to watch for changes in asset files. Requires the `filesystem_watcher` feature, @@ -78,7 +68,7 @@ pub struct AssetServerSettings { pub watch_for_changes: bool, } -impl Default for AssetServerSettings { +impl Default for AssetPlugin { fn default() -> Self { Self { asset_folder: "assets".to_string(), @@ -87,29 +77,27 @@ impl Default for AssetServerSettings { } } -/// Creates an instance of the platform's default `AssetIo`. -/// -/// This is useful when providing a custom `AssetIo` instance that needs to -/// delegate to the default `AssetIo` for the platform. -pub fn create_platform_default_asset_io(app: &mut App) -> Box { - let settings = app - .world - .get_resource_or_insert_with(AssetServerSettings::default); +impl AssetPlugin { + /// Creates an instance of the platform's default `AssetIo`. + /// + /// This is useful when providing a custom `AssetIo` instance that needs to + /// delegate to the default `AssetIo` for the platform. + pub fn create_platform_default_asset_io(&self) -> Box { + #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))] + let source = FileAssetIo::new(&self.asset_folder, self.watch_for_changes); + #[cfg(target_arch = "wasm32")] + let source = WasmAssetIo::new(&self.asset_folder); + #[cfg(target_os = "android")] + let source = AndroidAssetIo::new(&self.asset_folder); - #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))] - let source = FileAssetIo::new(&settings.asset_folder, settings.watch_for_changes); - #[cfg(target_arch = "wasm32")] - let source = WasmAssetIo::new(&settings.asset_folder); - #[cfg(target_os = "android")] - let source = AndroidAssetIo::new(&settings.asset_folder); - - Box::new(source) + Box::new(source) + } } impl Plugin for AssetPlugin { fn build(&self, app: &mut App) { if !app.world.contains_resource::() { - let source = create_platform_default_asset_io(app); + let source = self.create_platform_default_asset_io(); let asset_server = AssetServer::with_boxed_io(source); app.insert_resource(asset_server); } diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index c4f2390ebf..8ad4f95217 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -8,7 +8,7 @@ //! fn main() { //! App::new() //! .add_plugins(MinimalPlugins) -//! .add_plugin(AssetPlugin) +//! .add_plugin(AssetPlugin::default()) //! .add_plugin(AudioPlugin) //! .add_startup_system(play_background_audio) //! .run(); diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index 07383f5023..bfbe7c5b83 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -25,59 +25,91 @@ use bevy_app::{PluginGroup, PluginGroupBuilder}; pub struct DefaultPlugins; impl PluginGroup for DefaultPlugins { - fn build(&mut self, group: &mut PluginGroupBuilder) { - group.add(bevy_log::LogPlugin::default()); - group.add(bevy_core::CorePlugin::default()); - group.add(bevy_time::TimePlugin::default()); - group.add(bevy_transform::TransformPlugin::default()); - group.add(bevy_hierarchy::HierarchyPlugin::default()); - group.add(bevy_diagnostic::DiagnosticsPlugin::default()); - group.add(bevy_input::InputPlugin::default()); - group.add(bevy_window::WindowPlugin::default()); + fn build(self) -> PluginGroupBuilder { + let mut group = PluginGroupBuilder::default(); + group = group + .add(bevy_log::LogPlugin::default()) + .add(bevy_core::CorePlugin::default()) + .add(bevy_time::TimePlugin::default()) + .add(bevy_transform::TransformPlugin::default()) + .add(bevy_hierarchy::HierarchyPlugin::default()) + .add(bevy_diagnostic::DiagnosticsPlugin::default()) + .add(bevy_input::InputPlugin::default()) + .add(bevy_window::WindowPlugin::default()); #[cfg(feature = "bevy_asset")] - group.add(bevy_asset::AssetPlugin::default()); + { + group = group.add(bevy_asset::AssetPlugin::default()); + } #[cfg(feature = "debug_asset_server")] - group.add(bevy_asset::debug_asset_server::DebugAssetServerPlugin::default()); + { + group = group.add(bevy_asset::debug_asset_server::DebugAssetServerPlugin::default()); + } #[cfg(feature = "bevy_scene")] - group.add(bevy_scene::ScenePlugin::default()); + { + group = group.add(bevy_scene::ScenePlugin::default()); + } #[cfg(feature = "bevy_winit")] - group.add(bevy_winit::WinitPlugin::default()); + { + group = group.add(bevy_winit::WinitPlugin::default()); + } #[cfg(feature = "bevy_render")] - group.add(bevy_render::RenderPlugin::default()); + { + group = group.add(bevy_render::RenderPlugin::default()); + } #[cfg(feature = "bevy_core_pipeline")] - group.add(bevy_core_pipeline::CorePipelinePlugin::default()); + { + group = group.add(bevy_core_pipeline::CorePipelinePlugin::default()); + } #[cfg(feature = "bevy_sprite")] - group.add(bevy_sprite::SpritePlugin::default()); + { + group = group.add(bevy_sprite::SpritePlugin::default()); + } #[cfg(feature = "bevy_text")] - group.add(bevy_text::TextPlugin::default()); + { + group = group.add(bevy_text::TextPlugin::default()); + } #[cfg(feature = "bevy_ui")] - group.add(bevy_ui::UiPlugin::default()); + { + group = group.add(bevy_ui::UiPlugin::default()); + } #[cfg(feature = "bevy_pbr")] - group.add(bevy_pbr::PbrPlugin::default()); + { + group = group.add(bevy_pbr::PbrPlugin::default()); + } // NOTE: Load this after renderer initialization so that it knows about the supported // compressed texture formats #[cfg(feature = "bevy_gltf")] - group.add(bevy_gltf::GltfPlugin::default()); + { + group = group.add(bevy_gltf::GltfPlugin::default()); + } #[cfg(feature = "bevy_audio")] - group.add(bevy_audio::AudioPlugin::default()); + { + group = group.add(bevy_audio::AudioPlugin::default()); + } #[cfg(feature = "bevy_gilrs")] - group.add(bevy_gilrs::GilrsPlugin::default()); + { + group = group.add(bevy_gilrs::GilrsPlugin::default()); + } #[cfg(feature = "bevy_animation")] - group.add(bevy_animation::AnimationPlugin::default()); + { + group = group.add(bevy_animation::AnimationPlugin::default()); + } + + group } } @@ -90,9 +122,10 @@ impl PluginGroup for DefaultPlugins { pub struct MinimalPlugins; impl PluginGroup for MinimalPlugins { - fn build(&mut self, group: &mut PluginGroupBuilder) { - group.add(bevy_core::CorePlugin::default()); - group.add(bevy_time::TimePlugin::default()); - group.add(bevy_app::ScheduleRunnerPlugin::default()); + fn build(self) -> PluginGroupBuilder { + PluginGroupBuilder::default() + .add(bevy_core::CorePlugin::default()) + .add(bevy_time::TimePlugin::default()) + .add(bevy_app::ScheduleRunnerPlugin::default()) } } diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index a7518327a5..5718cee240 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -71,13 +71,13 @@ use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter}; /// will be ignored. /// /// If you want to setup your own tracing collector, you should disable this -/// plugin from `DefaultPlugins` with [`App::add_plugins_with`]: +/// plugin from `DefaultPlugins`: /// ```no_run -/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins}; +/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup}; /// # use bevy_log::LogPlugin; /// fn main() { /// App::new() -/// .add_plugins_with(DefaultPlugins, |group| group.disable::()) +/// .add_plugins(DefaultPlugins.build().disable::()) /// .run(); /// } /// ``` diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 6b17c363d7..a00f851d35 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -17,8 +17,8 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ CursorEntered, CursorIcon, CursorLeft, CursorMoved, FileDragAndDrop, MonitorSelection, - ReceivedCharacter, Window, WindowDescriptor, WindowMode, WindowMoved, WindowPosition, - Windows, + ReceivedCharacter, Window, WindowDescriptor, WindowMode, WindowMoved, WindowPlugin, + WindowPosition, Windows, }; } @@ -26,15 +26,22 @@ use bevy_app::prelude::*; use bevy_ecs::{ event::Events, schedule::{IntoSystemDescriptor, SystemLabel}, - system::Resource, }; -/// The configuration information for the [`WindowPlugin`]. -/// -/// It can be added as a [`Resource`](bevy_ecs::system::Resource) before the [`WindowPlugin`] -/// runs, to configure how it behaves. -#[derive(Resource, Clone)] -pub struct WindowSettings { +impl Default for WindowPlugin { + fn default() -> Self { + WindowPlugin { + window: Default::default(), + add_primary_window: true, + exit_on_all_closed: true, + close_when_requested: true, + } + } +} + +/// A [`Plugin`] that defines an interface for windowing support in Bevy. +pub struct WindowPlugin { + pub window: WindowDescriptor, /// Whether to create a window when added. /// /// Note that if there are no windows, by default the App will exit, @@ -58,20 +65,6 @@ pub struct WindowSettings { pub close_when_requested: bool, } -impl Default for WindowSettings { - fn default() -> Self { - WindowSettings { - add_primary_window: true, - exit_on_all_closed: true, - close_when_requested: true, - } - } -} - -/// A [`Plugin`] that defines an interface for windowing support in Bevy. -#[derive(Default)] -pub struct WindowPlugin; - impl Plugin for WindowPlugin { fn build(&self, app: &mut App) { app.add_event::() @@ -91,32 +84,21 @@ impl Plugin for WindowPlugin { .add_event::() .init_resource::(); - let settings = app - .world - .get_resource::() - .cloned() - .unwrap_or_default(); - - if settings.add_primary_window { - let window_descriptor = app - .world - .get_resource::() - .cloned() - .unwrap_or_default(); + if self.add_primary_window { let mut create_window_event = app.world.resource_mut::>(); create_window_event.send(CreateWindow { id: WindowId::primary(), - descriptor: window_descriptor, + descriptor: self.window.clone(), }); } - if settings.exit_on_all_closed { + if self.exit_on_all_closed { app.add_system_to_stage( CoreStage::PostUpdate, exit_on_all_closed.after(ModifiesWindows), ); } - if settings.close_when_requested { + if self.close_when_requested { app.add_system(close_when_requested); } } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 2ee019d04b..1c7416b2c9 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -1,4 +1,3 @@ -use bevy_ecs::system::Resource; use bevy_math::{DVec2, IVec2, UVec2, Vec2}; use bevy_reflect::{FromReflect, Reflect}; use bevy_utils::{tracing::warn, Uuid}; @@ -895,7 +894,7 @@ pub enum MonitorSelection { /// See [`examples/window/window_settings.rs`] for usage. /// /// [`examples/window/window_settings.rs`]: https://github.com/bevyengine/bevy/blob/latest/examples/window/window_settings.rs -#[derive(Resource, Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct WindowDescriptor { /// The requested logical width of the window's client area. diff --git a/examples/app/plugin_group.rs b/examples/app/plugin_group.rs index 5eb4a27a7c..474bd5b6f5 100644 --- a/examples/app/plugin_group.rs +++ b/examples/app/plugin_group.rs @@ -10,11 +10,14 @@ fn main() { // Adding a plugin group adds all plugins in the group by default .add_plugins(HelloWorldPlugins) // You can also modify a PluginGroup (such as disabling plugins) like this: - // .add_plugins_with(HelloWorldPlugins, |group| { - // group + // .add_plugins( + // HelloWorldPlugins + // .build() // .disable::() - // .add_before::(bevy::diagnostic::LogDiagnosticsPlugin::default()) }) + // .add_before::( + // bevy::diagnostic::LogDiagnosticsPlugin::default(), + // ), + // ) .run(); } @@ -22,8 +25,10 @@ fn main() { pub struct HelloWorldPlugins; impl PluginGroup for HelloWorldPlugins { - fn build(&mut self, group: &mut PluginGroupBuilder) { - group.add(PrintHelloPlugin).add(PrintWorldPlugin); + fn build(self) -> PluginGroupBuilder { + PluginGroupBuilder::default() + .add(PrintHelloPlugin) + .add(PrintWorldPlugin) } } diff --git a/examples/app/return_after_run.rs b/examples/app/return_after_run.rs index d1ef945390..bc7e2b1f0e 100644 --- a/examples/app/return_after_run.rs +++ b/examples/app/return_after_run.rs @@ -20,9 +20,7 @@ fn main() { ..default() }) .insert_resource(ClearColor(Color::rgb(0.2, 0.8, 0.2))) - .add_plugins_with(DefaultPlugins, |group| { - group.disable::() - }) + .add_plugins(DefaultPlugins.build().disable::()) .add_system(system2) .run(); println!("Done."); diff --git a/examples/app/without_winit.rs b/examples/app/without_winit.rs index b06a012967..1171225661 100644 --- a/examples/app/without_winit.rs +++ b/examples/app/without_winit.rs @@ -5,7 +5,7 @@ use bevy::winit::WinitPlugin; fn main() { App::new() - .add_plugins_with(DefaultPlugins, |group| group.disable::()) + .add_plugins(DefaultPlugins.build().disable::()) .add_system(setup_system) .run(); } diff --git a/examples/asset/custom_asset_io.rs b/examples/asset/custom_asset_io.rs index 0255978ee6..e8f369effa 100644 --- a/examples/asset/custom_asset_io.rs +++ b/examples/asset/custom_asset_io.rs @@ -51,35 +51,30 @@ struct CustomAssetIoPlugin; impl Plugin for CustomAssetIoPlugin { fn build(&self, app: &mut App) { - let asset_io = { - // the platform default asset io requires a reference to the app - // builder to find its configuration + let default_io = AssetPlugin::default().create_platform_default_asset_io(); - let default_io = bevy::asset::create_platform_default_asset_io(app); - - // create the custom asset io instance - - CustomAssetIo(default_io) - }; + // create the custom asset io instance + let asset_io = CustomAssetIo(default_io); // the asset server is constructed and added the resource manager - app.insert_resource(AssetServer::new(asset_io)); } } fn main() { App::new() - .add_plugins_with(DefaultPlugins, |group| { - // the custom asset io plugin must be inserted in-between the - // `CorePlugin' and `AssetPlugin`. It needs to be after the - // CorePlugin, so that the IO task pool has already been constructed. - // And it must be before the `AssetPlugin` so that the asset plugin - // doesn't create another instance of an asset server. In general, - // the AssetPlugin should still run so that other aspects of the - // asset system are initialized correctly. - group.add_before::(CustomAssetIoPlugin) - }) + .add_plugins( + DefaultPlugins + .build() + // the custom asset io plugin must be inserted in-between the + // `CorePlugin' and `AssetPlugin`. It needs to be after the + // CorePlugin, so that the IO task pool has already been constructed. + // And it must be before the `AssetPlugin` so that the asset plugin + // doesn't create another instance of an asset server. In general, + // the AssetPlugin should still run so that other aspects of the + // asset system are initialized correctly. + .add_before::(CustomAssetIoPlugin), + ) .add_startup_system(setup) .run(); } diff --git a/examples/asset/hot_asset_reloading.rs b/examples/asset/hot_asset_reloading.rs index a6e2832892..2d716bc86f 100644 --- a/examples/asset/hot_asset_reloading.rs +++ b/examples/asset/hot_asset_reloading.rs @@ -2,16 +2,15 @@ //! running. This lets you immediately see the results of your changes without restarting the game. //! This example illustrates hot reloading mesh changes. -use bevy::{asset::AssetServerSettings, prelude::*}; +use bevy::prelude::*; fn main() { App::new() - // Tell the asset server to watch for asset changes on disk: - .insert_resource(AssetServerSettings { + .add_plugins(DefaultPlugins.set(AssetPlugin { + // Tell the asset server to watch for asset changes on disk: watch_for_changes: true, ..default() - }) - .add_plugins(DefaultPlugins) + })) .add_startup_system(setup) .run(); } diff --git a/examples/ios/src/lib.rs b/examples/ios/src/lib.rs index aca39c99a5..9924aa62a4 100644 --- a/examples/ios/src/lib.rs +++ b/examples/ios/src/lib.rs @@ -4,12 +4,14 @@ use bevy::{input::touch::TouchPhase, prelude::*, window::WindowMode}; #[bevy_main] fn main() { App::new() - .insert_resource(WindowDescriptor { - resizable: false, - mode: WindowMode::BorderlessFullscreen, + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + resizable: false, + mode: WindowMode::BorderlessFullscreen, + ..default() + }, ..default() - }) - .add_plugins(DefaultPlugins) + })) .add_startup_system(setup_scene) .add_startup_system(setup_music) .add_system(touch_camera) diff --git a/examples/scene/scene.rs b/examples/scene/scene.rs index e8e0271af2..20efbc936f 100644 --- a/examples/scene/scene.rs +++ b/examples/scene/scene.rs @@ -6,14 +6,12 @@ use bevy::{prelude::*, tasks::IoTaskPool, utils::Duration}; fn main() { App::new() - // This tells the AssetServer to watch for changes to assets. - // It enables our scenes to automatically reload in game when we modify their files. - // AssetServerSettings must be inserted before the DefaultPlugins are added. - .insert_resource(AssetServerSettings { + .add_plugins(DefaultPlugins.set(AssetPlugin { + // This tells the AssetServer to watch for changes to assets. + // It enables our scenes to automatically reload in game when we modify their files. watch_for_changes: true, ..default() - }) - .add_plugins(DefaultPlugins) + })) .register_type::() .register_type::() .add_startup_system(save_scene_system) diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index aff074fd28..af665f362a 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -22,12 +22,14 @@ const WORKGROUP_SIZE: u32 = 8; fn main() { App::new() .insert_resource(ClearColor(Color::BLACK)) - .insert_resource(WindowDescriptor { - // uncomment for unthrottled FPS - // present_mode: bevy::window::PresentMode::AutoNoVsync, + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + // uncomment for unthrottled FPS + // present_mode: bevy::window::PresentMode::AutoNoVsync, + ..default() + }, ..default() - }) - .add_plugins(DefaultPlugins) + })) .add_plugin(GameOfLifeComputePlugin) .add_startup_system(setup) .run(); diff --git a/examples/stress_tests/bevymark.rs b/examples/stress_tests/bevymark.rs index 6d40ec2d23..f60df88edc 100644 --- a/examples/stress_tests/bevymark.rs +++ b/examples/stress_tests/bevymark.rs @@ -29,15 +29,17 @@ struct Bird { fn main() { App::new() - .insert_resource(WindowDescriptor { - title: "BevyMark".to_string(), - width: 800., - height: 600., - present_mode: PresentMode::AutoNoVsync, - resizable: true, + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + title: "BevyMark".to_string(), + width: 800., + height: 600., + present_mode: PresentMode::AutoNoVsync, + resizable: true, + ..default() + }, ..default() - }) - .add_plugins(DefaultPlugins) + })) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) .insert_resource(BevyCounter { diff --git a/examples/stress_tests/many_buttons.rs b/examples/stress_tests/many_buttons.rs index 74a3d6b37d..8ea438753e 100644 --- a/examples/stress_tests/many_buttons.rs +++ b/examples/stress_tests/many_buttons.rs @@ -11,11 +11,13 @@ const FONT_SIZE: f32 = 7.0; /// This example shows what happens when there is a lot of buttons on screen. fn main() { App::new() - .insert_resource(WindowDescriptor { - present_mode: PresentMode::Immediate, + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + present_mode: PresentMode::Immediate, + ..default() + }, ..default() - }) - .add_plugins(DefaultPlugins) + })) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) .init_resource::() diff --git a/examples/stress_tests/many_cubes.rs b/examples/stress_tests/many_cubes.rs index b1afdadd64..b693205435 100644 --- a/examples/stress_tests/many_cubes.rs +++ b/examples/stress_tests/many_cubes.rs @@ -21,11 +21,13 @@ use bevy::{ fn main() { App::new() - .insert_resource(WindowDescriptor { - present_mode: PresentMode::AutoNoVsync, + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + present_mode: PresentMode::AutoNoVsync, + ..default() + }, ..default() - }) - .add_plugins(DefaultPlugins) + })) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) .add_startup_system(setup) diff --git a/examples/stress_tests/many_foxes.rs b/examples/stress_tests/many_foxes.rs index d67d05e5b5..80a940937a 100644 --- a/examples/stress_tests/many_foxes.rs +++ b/examples/stress_tests/many_foxes.rs @@ -18,12 +18,14 @@ struct Foxes { fn main() { App::new() - .insert_resource(WindowDescriptor { - title: "🦊🦊🦊 Many Foxes! 🦊🦊🦊".to_string(), - present_mode: PresentMode::AutoNoVsync, + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + title: "🦊🦊🦊 Many Foxes! 🦊🦊🦊".to_string(), + present_mode: PresentMode::AutoNoVsync, + ..default() + }, ..default() - }) - .add_plugins(DefaultPlugins) + })) .add_plugin(FrameTimeDiagnosticsPlugin) .add_plugin(LogDiagnosticsPlugin::default()) .insert_resource(Foxes { diff --git a/examples/stress_tests/many_lights.rs b/examples/stress_tests/many_lights.rs index fa63701fee..1e76e15162 100644 --- a/examples/stress_tests/many_lights.rs +++ b/examples/stress_tests/many_lights.rs @@ -15,14 +15,16 @@ use rand::{thread_rng, Rng}; fn main() { App::new() - .insert_resource(WindowDescriptor { - width: 1024.0, - height: 768.0, - title: "many_lights".to_string(), - present_mode: PresentMode::AutoNoVsync, + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + width: 1024.0, + height: 768.0, + title: "many_lights".to_string(), + present_mode: PresentMode::AutoNoVsync, + ..default() + }, ..default() - }) - .add_plugins(DefaultPlugins) + })) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) .add_startup_system(setup) diff --git a/examples/stress_tests/many_sprites.rs b/examples/stress_tests/many_sprites.rs index 1a232c37ae..ef39c64b17 100644 --- a/examples/stress_tests/many_sprites.rs +++ b/examples/stress_tests/many_sprites.rs @@ -24,17 +24,19 @@ struct ColorTint(bool); fn main() { App::new() - .insert_resource(WindowDescriptor { - present_mode: PresentMode::AutoNoVsync, - ..default() - }) .insert_resource(ColorTint( std::env::args().nth(1).unwrap_or_default() == "--colored", )) // Since this is also used as a benchmark, we want it to display performance data. .add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin::default()) - .add_plugins(DefaultPlugins) + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + present_mode: PresentMode::AutoNoVsync, + ..default() + }, + ..default() + })) .add_startup_system(setup) .add_system(print_sprite_count) .add_system(move_camera.after(print_sprite_count)) diff --git a/examples/tools/scene_viewer.rs b/examples/tools/scene_viewer.rs index fac2833773..8f9c9af767 100644 --- a/examples/tools/scene_viewer.rs +++ b/examples/tools/scene_viewer.rs @@ -45,16 +45,22 @@ Controls: color: Color::WHITE, brightness: 1.0 / 5.0f32, }) - .insert_resource(AssetServerSettings { - asset_folder: std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string()), - watch_for_changes: true, - }) - .insert_resource(WindowDescriptor { - title: "bevy scene viewer".to_string(), - ..default() - }) .init_resource::() - .add_plugins(DefaultPlugins) + .add_plugins( + DefaultPlugins + .set(WindowPlugin { + window: WindowDescriptor { + title: "bevy scene viewer".to_string(), + ..default() + }, + ..default() + }) + .set(AssetPlugin { + asset_folder: std::env::var("CARGO_MANIFEST_DIR") + .unwrap_or_else(|_| ".".to_string()), + watch_for_changes: true, + }), + ) .add_startup_system(setup) .add_system_to_stage(CoreStage::PreUpdate, scene_load_check) .add_system_to_stage(CoreStage::PreUpdate, setup_scene_after_load) diff --git a/examples/ui/text_debug.rs b/examples/ui/text_debug.rs index d67e61ace3..19cc0a0fb7 100644 --- a/examples/ui/text_debug.rs +++ b/examples/ui/text_debug.rs @@ -8,11 +8,13 @@ use bevy::{ fn main() { App::new() - .insert_resource(WindowDescriptor { - present_mode: PresentMode::AutoNoVsync, + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + present_mode: PresentMode::AutoNoVsync, + ..default() + }, ..default() - }) - .add_plugins(DefaultPlugins) + })) .add_plugin(FrameTimeDiagnosticsPlugin) .add_startup_system(infotext_system) .add_system(change_text_system) diff --git a/examples/window/low_power.rs b/examples/window/low_power.rs index e97f20fc5e..bf747f76dd 100644 --- a/examples/window/low_power.rs +++ b/examples/window/low_power.rs @@ -24,13 +24,15 @@ fn main() { }, ..default() }) - // Turn off vsync to maximize CPU/GPU usage - .insert_resource(WindowDescriptor { - present_mode: PresentMode::AutoNoVsync, - ..default() - }) .insert_resource(ExampleMode::Game) - .add_plugins(DefaultPlugins) + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + // Turn off vsync to maximize CPU/GPU usage + present_mode: PresentMode::AutoNoVsync, + ..default() + }, + ..default() + })) .add_startup_system(test_setup::setup) .add_system(test_setup::cycle_modes) .add_system(test_setup::rotate_cube) diff --git a/examples/window/scale_factor_override.rs b/examples/window/scale_factor_override.rs index 648a67b5dc..2d95ceae9e 100644 --- a/examples/window/scale_factor_override.rs +++ b/examples/window/scale_factor_override.rs @@ -5,12 +5,14 @@ use bevy::prelude::*; fn main() { App::new() - .insert_resource(WindowDescriptor { - width: 500., - height: 300., + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + width: 500., + height: 300., + ..default() + }, ..default() - }) - .add_plugins(DefaultPlugins) + })) .add_startup_system(setup) .add_system(toggle_override) .add_system(change_scale_factor) diff --git a/examples/window/transparent_window.rs b/examples/window/transparent_window.rs index b229b05092..b40c65c6d9 100644 --- a/examples/window/transparent_window.rs +++ b/examples/window/transparent_window.rs @@ -10,15 +10,17 @@ fn main() { App::new() // ClearColor must have 0 alpha, otherwise some color will bleed through .insert_resource(ClearColor(Color::NONE)) - .insert_resource(WindowDescriptor { - // Setting `transparent` allows the `ClearColor`'s alpha value to take effect - transparent: true, - // Disabling window decorations to make it feel more like a widget than a window - decorations: false, - ..default() - }) .add_startup_system(setup) - .add_plugins(DefaultPlugins) + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + // Setting `transparent` allows the `ClearColor`'s alpha value to take effect + transparent: true, + // Disabling window decorations to make it feel more like a widget than a window + decorations: false, + ..default() + }, + ..default() + })) .run(); } diff --git a/examples/window/window_settings.rs b/examples/window/window_settings.rs index 9fa9b6c9f8..06e1b5686f 100644 --- a/examples/window/window_settings.rs +++ b/examples/window/window_settings.rs @@ -9,14 +9,16 @@ use bevy::{ fn main() { App::new() - .insert_resource(WindowDescriptor { - title: "I am a window!".to_string(), - width: 500., - height: 300., - present_mode: PresentMode::AutoVsync, + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + title: "I am a window!".to_string(), + width: 500., + height: 300., + present_mode: PresentMode::AutoVsync, + ..default() + }, ..default() - }) - .add_plugins(DefaultPlugins) + })) .add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin) .add_system(change_title) diff --git a/tests/window/minimising.rs b/tests/window/minimising.rs index f7198fd187..55a28d6fde 100644 --- a/tests/window/minimising.rs +++ b/tests/window/minimising.rs @@ -6,11 +6,13 @@ fn main() { // TODO: Combine this with `resizing` once multiple_windows is simpler than // it is currently. App::new() - .insert_resource(WindowDescriptor { - title: "Minimising".into(), - ..Default::default() - }) - .add_plugins(DefaultPlugins) + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + title: "Minimising".into(), + ..Default::default() + }, + ..default() + })) .add_system(minimise_automatically) .add_startup_system(setup_3d) .add_startup_system(setup_2d) diff --git a/tests/window/resizing.rs b/tests/window/resizing.rs index 24824e0ed0..446c45d067 100644 --- a/tests/window/resizing.rs +++ b/tests/window/resizing.rs @@ -19,18 +19,20 @@ struct Dimensions { fn main() { App::new() - .insert_resource(WindowDescriptor { - width: MAX_WIDTH.try_into().unwrap(), - height: MAX_HEIGHT.try_into().unwrap(), - scale_factor_override: Some(1.), - title: "Resizing".into(), - ..Default::default() - }) .insert_resource(Dimensions { width: MAX_WIDTH, height: MAX_HEIGHT, }) - .add_plugins(DefaultPlugins) + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: WindowDescriptor { + width: MAX_WIDTH.try_into().unwrap(), + height: MAX_HEIGHT.try_into().unwrap(), + scale_factor_override: Some(1.), + title: "Resizing".into(), + ..Default::default() + }, + ..default() + })) .insert_resource(Phase::ContractingY) .add_system(change_window_size) .add_system(sync_dimensions)