Plugins own their settings. Rework PluginGroup trait. (#6336)

# Objective

Fixes #5884 #2879
Alternative to #2988 #5885 #2886

"Immutable" Plugin settings are currently represented as normal ECS resources, which are read as part of plugin init. This presents a number of problems:

1. If a user inserts the plugin settings resource after the plugin is initialized, it will be silently ignored (and use the defaults instead)
2. Users can modify the plugin settings resource after the plugin has been initialized. This creates a false sense of control over settings that can no longer be changed.

(1) and (2) are especially problematic and confusing for the `WindowDescriptor` resource, but this is a general problem.

## Solution

Immutable Plugin settings now live on each Plugin struct (ex: `WindowPlugin`). PluginGroups have been reworked to support overriding plugin values. This also removes the need for the `add_plugins_with` api, as the `add_plugins` api can use the builder pattern directly. Settings that can be used at runtime continue to be represented as ECS resources.

Plugins are now configured like this:

```rust
app.add_plugin(AssetPlugin {
  watch_for_changes: true,
  ..default()
})
```

PluginGroups are now configured like this:

```rust
app.add_plugins(DefaultPlugins
  .set(AssetPlugin {
    watch_for_changes: true,
    ..default()
  })
)
```

This is an alternative to #2988, which is similar. But I personally prefer this solution for a couple of reasons:
* ~~#2988 doesn't solve (1)~~ #2988 does solve (1) and will panic in that case. I was wrong!
* This PR directly ties plugin settings to Plugin types in a 1:1 relationship, rather than a loose "setup resource" <-> plugin coupling (where the setup resource is consumed by the first plugin that uses it).
* I'm not a huge fan of overloading the ECS resource concept and implementation for something that has very different use cases and constraints.

## Changelog

- PluginGroups can now be configured directly using the builder pattern. Individual plugin values can be overridden by using `plugin_group.set(SomePlugin {})`, which enables overriding default plugin values.  
- `WindowDescriptor` plugin settings have been moved to `WindowPlugin` and `AssetServerSettings` have been moved to `AssetPlugin`
- `app.add_plugins_with` has been replaced by using `add_plugins` with the builder pattern.

## Migration Guide

The `WindowDescriptor` settings have been moved from a resource to `WindowPlugin::window`:

```rust
// Old (Bevy 0.8)
app
  .insert_resource(WindowDescriptor {
    width: 400.0,
    ..default()
  })
  .add_plugins(DefaultPlugins)

// New (Bevy 0.9)
app.add_plugins(DefaultPlugins.set(WindowPlugin {
  window: WindowDescriptor {
    width: 400.0,
    ..default()
  },
  ..default()
}))
```


The `AssetServerSettings` resource has been removed in favor of direct `AssetPlugin` configuration:

```rust
// Old (Bevy 0.8)
app
  .insert_resource(AssetServerSettings {
    watch_for_changes: true,
    ..default()
  })
  .add_plugins(DefaultPlugins)

// New (Bevy 0.9)
app.add_plugins(DefaultPlugins.set(AssetPlugin {
  watch_for_changes: true,
  ..default()
}))
```

`add_plugins_with` has been replaced by `add_plugins` in combination with the builder pattern:

```rust
// Old (Bevy 0.8)
app.add_plugins_with(DefaultPlugins, |group| group.disable::<AssetPlugin>());

// New (Bevy 0.9)
app.add_plugins(DefaultPlugins.build().disable::<AssetPlugin>());
```
This commit is contained in:
Carter Anderson 2022-10-24 21:20:33 +00:00
parent beab0bdc63
commit 1bb751cb8d
35 changed files with 358 additions and 344 deletions

View file

@ -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]

View file

@ -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<T: PluginGroup>(&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::<bevy_log::LogPlugin, _>(MyOwnPlugin)
/// });
/// ```
pub fn add_plugins_with<T, F>(&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<T: PluginGroup>(&mut self, group: T) -> &mut Self {
let builder = group.build();
builder.finish(self);
self
}

View file

@ -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.
///

View file

@ -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<T: Plugin>(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<Target: Plugin>(&mut self) -> usize {
fn index_of<Target: Plugin>(&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<T: Plugin>(mut self, plugin: T) -> Self {
let entry = self.plugins.get_mut(&TypeId::of::<T>()).unwrap_or_else(|| {
panic!(
"{} does not exist in this PluginGroup",
std::any::type_name::<T>(),
)
});
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<T: Plugin>(&mut self, plugin: T) -> &mut Self {
// This is not confusing, clippy!
#[allow(clippy::should_implement_trait)]
pub fn add<T: Plugin>(mut self, plugin: T) -> Self {
let target_index = self.order.len();
self.order.push(TypeId::of::<T>());
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<Target: Plugin, T: Plugin>(&mut self, plugin: T) -> &mut Self {
pub fn add_before<Target: Plugin, T: Plugin>(mut self, plugin: T) -> Self {
let target_index = self.index_of::<Target>();
self.order.insert(target_index, TypeId::of::<T>());
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<Target: Plugin, T: Plugin>(&mut self, plugin: T) -> &mut Self {
pub fn add_after<Target: Plugin, T: Plugin>(mut self, plugin: T) -> Self {
let target_index = self.index_of::<Target>() + 1;
self.order.insert(target_index, TypeId::of::<T>());
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<T: Plugin>(&mut self) -> &mut Self {
pub fn enable<T: Plugin>(mut self) -> Self {
let mut plugin_entry = self
.plugins
.get_mut(&TypeId::of::<T>())
@ -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<T: Plugin>(&mut self) -> &mut Self {
pub fn disable<T: Plugin>(mut self) -> Self {
let mut plugin_entry = self
.plugins
.get_mut(&TypeId::of::<T>())
@ -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::<PluginA, PluginC>(PluginC);
let group = PluginGroupBuilder::default()
.add(PluginA)
.add(PluginB)
.add_after::<PluginA, PluginC>(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::<PluginB, PluginC>(PluginC);
let group = PluginGroupBuilder::default()
.add(PluginA)
.add(PluginB)
.add_before::<PluginB, PluginC>(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::<PluginA, PluginC>(PluginC);
let group = PluginGroupBuilder::default()
.add(PluginA)
.add(PluginB)
.add(PluginC)
.add_after::<PluginA, PluginC>(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::<PluginB, PluginC>(PluginC);
let group = PluginGroupBuilder::default()
.add(PluginA)
.add(PluginB)
.add(PluginC)
.add_before::<PluginB, PluginC>(PluginC);
assert_eq!(
group.order,

View file

@ -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

View file

@ -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::<MyAsset>();
let mut assets_before = app.world.resource_mut::<Assets<MyAsset>>();
let handle = assets_before.add(MyAsset);

View file

@ -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);
}

View file

@ -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<dyn AssetIo> {
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<dyn AssetIo> {
#[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::<AssetServer>() {
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);
}

View file

@ -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();

View file

@ -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())
}
}

View file

@ -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::<LogPlugin>())
/// .add_plugins(DefaultPlugins.build().disable::<LogPlugin>())
/// .run();
/// }
/// ```

View file

@ -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::<WindowResized>()
@ -91,32 +84,21 @@ impl Plugin for WindowPlugin {
.add_event::<WindowMoved>()
.init_resource::<Windows>();
let settings = app
.world
.get_resource::<WindowSettings>()
.cloned()
.unwrap_or_default();
if settings.add_primary_window {
let window_descriptor = app
.world
.get_resource::<WindowDescriptor>()
.cloned()
.unwrap_or_default();
if self.add_primary_window {
let mut create_window_event = app.world.resource_mut::<Events<CreateWindow>>();
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);
}
}

View file

@ -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.

View file

@ -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::<PrintWorldPlugin>()
// .add_before::<PrintHelloPlugin,
// _>(bevy::diagnostic::LogDiagnosticsPlugin::default()) })
// .add_before::<PrintHelloPlugin, _>(
// 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)
}
}

View file

@ -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::<bevy::log::LogPlugin>()
})
.add_plugins(DefaultPlugins.build().disable::<bevy::log::LogPlugin>())
.add_system(system2)
.run();
println!("Done.");

View file

@ -5,7 +5,7 @@ use bevy::winit::WinitPlugin;
fn main() {
App::new()
.add_plugins_with(DefaultPlugins, |group| group.disable::<WinitPlugin>())
.add_plugins(DefaultPlugins.build().disable::<WinitPlugin>())
.add_system(setup_system)
.run();
}

View file

@ -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::<bevy::asset::AssetPlugin, _>(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::<bevy::asset::AssetPlugin, _>(CustomAssetIoPlugin),
)
.add_startup_system(setup)
.run();
}

View file

@ -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();
}

View file

@ -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)

View file

@ -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::<ComponentA>()
.register_type::<ComponentB>()
.add_startup_system(save_scene_system)

View file

@ -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();

View file

@ -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 {

View file

@ -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::<UiFont>()

View file

@ -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)

View file

@ -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 {

View file

@ -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)

View file

@ -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))

View file

@ -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::<CameraTracker>()
.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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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();
}

View file

@ -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)

View file

@ -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)

View file

@ -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)