Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow UIPlugin to be used without rendering #4000

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8c42799
Make raw_window_handle optional for unit testing purposes.
Wcubed Jan 29, 2022
b0475ca
Add example of testing ui positioning without a real window.
Wcubed Jan 31, 2022
482f134
Minor typo
Wcubed Jan 31, 2022
10c9376
panic when the same plugin is added twice
mockersf Oct 17, 2021
2b301ee
add startup resources
mockersf Oct 18, 2021
ca4327e
move WindowDescriptor to a startup resource
mockersf Oct 18, 2021
1076123
move LogSettings to a startup resource
mockersf Oct 18, 2021
478dcdc
can override plugin uniqueness
mockersf Oct 18, 2021
8018dc9
add tests on plugin controls
mockersf Oct 18, 2021
d32e867
fix log when adding a plugin
mockersf Oct 18, 2021
e2c0525
rename startup to initialization
mockersf Oct 18, 2021
5bfcbb4
more doc on uniqueness of plugins
mockersf Oct 18, 2021
9ddd894
typo
mockersf Oct 18, 2021
3908ada
missed a few rename
mockersf Oct 18, 2021
26e51e8
and another
mockersf Oct 18, 2021
58b70d1
wording
mockersf Oct 18, 2021
2e42e0b
move AssetServerSettings as an initialization resource
mockersf Nov 22, 2021
3c41a96
add method `is_plugin_added`
mockersf Dec 15, 2021
243ca8c
move WgpuOptions as an initialization resource
mockersf Dec 31, 2021
aefc996
update new examples
mockersf Jan 24, 2022
6d29800
WinitConfig as an initialization resource
mockersf Jan 24, 2022
04a2ea3
DefaultTaskPoolOptions as an initialization resource
mockersf Jan 24, 2022
a4da44a
initialization ro setup
mockersf Jan 24, 2022
9863daf
remove duplicate import
mockersf Feb 4, 2022
6cec29d
update doc
mockersf Feb 4, 2022
03f63a6
Make UIPlugin work without rendering
kirusfg Feb 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 167 additions & 5 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ use bevy_ecs::{
system::Resource,
world::World,
};
use bevy_utils::{tracing::debug, HashMap};
use std::fmt::Debug;
use bevy_utils::{
tracing::{debug, error},
HashMap,
};
use std::{any::TypeId, fmt::Debug};

#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
bevy_utils::define_label!(AppLabel);

/// Wrapper struct for setting setup resources aside
struct Setup<T>(T);

#[allow(clippy::needless_doctest_main)]
/// Containers of app logic and data
///
Expand Down Expand Up @@ -55,6 +61,8 @@ pub struct App {
/// A container of [`Stage`]s set to be run in a linear order.
pub schedule: Schedule,
sub_apps: HashMap<Box<dyn AppLabel>, SubApp>,
plugins: HashMap<std::any::TypeId, Option<&'static str>>,
setup_resources: HashMap<std::any::TypeId, &'static str>,
}

/// Each [`SubApp`] has its own [`Schedule`] and [`World`], enabling a separation of concerns.
Expand Down Expand Up @@ -98,6 +106,8 @@ impl App {
schedule: Default::default(),
runner: Box::new(run_once),
sub_apps: HashMap::default(),
plugins: Default::default(),
setup_resources: Default::default(),
}
}

Expand Down Expand Up @@ -127,6 +137,8 @@ impl App {
#[cfg(feature = "trace")]
let _bevy_app_run_guard = bevy_app_run_span.enter();

self.check_all_setup_resources_consumed();

let mut app = std::mem::replace(self, App::empty());
let runner = std::mem::replace(&mut app.runner, Box::new(run_once));
(runner)(app);
Expand Down Expand Up @@ -370,6 +382,9 @@ impl App {
stage_label: impl StageLabel,
system: impl IntoSystemDescriptor<Params>,
) -> &mut Self {
if stage_label.type_id() == TypeId::of::<StartupStage>() {
panic!("add systems to a startup stage using App::add_startup_system_to_stage");
}
self.schedule.add_system_to_stage(stage_label, system);
self
}
Expand Down Expand Up @@ -400,6 +415,9 @@ impl App {
stage_label: impl StageLabel,
system_set: SystemSet,
) -> &mut Self {
if stage_label.type_id() == TypeId::of::<StartupStage>() {
panic!("add system sets to a startup stage using App::add_startup_system_set_to_stage");
}
self.schedule
.add_system_set_to_stage(stage_label, system_set);
self
Expand Down Expand Up @@ -619,6 +637,44 @@ impl App {
.add_system_to_stage(CoreStage::First, Events::<T>::update_system)
}

/// Inserts a setup resource to the current [App] and overwrites any resource
/// previously added of the same type.
///
/// A setup resource is used at startup for plugin initialisation and configuration.
/// All setup resources inserted must be consumed by a plugin and removed before the
/// application is ran.
pub fn insert_setup_resource<T>(&mut self, resource: T) -> &mut Self
where
T: Resource,
{
self.setup_resources
.insert(std::any::TypeId::of::<T>(), std::any::type_name::<T>());
self.insert_resource(Setup(resource));
self
}

/// Consumes a setup resource, and removes it from the current [App] so that a plugin
/// can use it for its setup.
pub fn consume_setup_resource<T>(&mut self) -> Option<T>
where
T: Resource,
{
self.setup_resources.remove(&std::any::TypeId::of::<T>());
self.world
.remove_resource::<Setup<T>>()
.map(|setup| setup.0)
}

/// Check that all setup resources have been consumed, panicking otherwise.
fn check_all_setup_resources_consumed(&self) {
self.setup_resources
.values()
.for_each(|v| error!("Setup resource \"{}\" has not been consumed", v));
if !self.setup_resources.is_empty() {
panic!("Not all setup resources have been consumed. This can happen if you inserted a setup resource after the plugin consuming it.")
}
}

/// Inserts a resource to the current [App] and overwrites any resource previously added of the same type.
///
/// A resource in Bevy represents globally unique data. Resources must be added to Bevy Apps
Expand Down Expand Up @@ -751,6 +807,14 @@ impl App {
self
}

/// Check that a plugin has already been added to the app.
pub fn is_plugin_added<T>(&self) -> bool
where
T: Plugin,
{
self.plugins.contains_key(&std::any::TypeId::of::<T>())
}

/// Adds a single plugin
///
/// One of Bevy's core principles is modularity. All Bevy engine features are implemented
Expand All @@ -768,11 +832,41 @@ impl App {
where
T: Plugin,
{
debug!("added plugin: {}", plugin.name());
debug!("added plugin {}", plugin.name());
if plugin.is_unique() {
self.register_plugin(&std::any::TypeId::of::<T>(), plugin.name(), None);
}
plugin.build(self);
self
}

/// Checks that a plugin has not already been added to an application. It will panic with an
/// helpful message the second time a plugin is being added.
pub(crate) fn register_plugin(
&mut self,
plugin_type: &TypeId,
plugin_name: &str,
from_group: Option<&'static str>,
) {
if let Some(existing_from_group) = self.plugins.insert(*plugin_type, from_group) {
match (from_group, existing_from_group) {
(None, None) => panic!("Plugin \"{}\" was already added", plugin_name),
(None, Some(existing_from_group)) => panic!(
"Plugin \"{}\" was already added with group \"{}\"",
plugin_name, existing_from_group
),
(Some(from_group), None) => panic!(
"Plugin \"{}\" from group \"{}\" was already added",
plugin_name, from_group
),
(Some(from_group), Some(existing_from_group)) => panic!(
"Plugin \"{}\" from group \"{}\" was already added with group \"{}\"",
plugin_name, from_group, existing_from_group
),
};
}
}

/// Adds a group of plugins
///
/// Bevy plugins can be grouped into a set of plugins. Bevy provides
Expand All @@ -794,9 +888,20 @@ impl App {
/// .add_plugins(MinimalPlugins);
/// ```
pub fn add_plugins<T: PluginGroup>(&mut self, mut group: T) -> &mut Self {
if self
.plugins
.insert(std::any::TypeId::of::<T>(), None)
.is_some()
{
panic!(
"Plugin Group \"{}\" was already added",
std::any::type_name::<T>()
);
}

let mut plugin_group_builder = PluginGroupBuilder::default();
group.build(&mut plugin_group_builder);
plugin_group_builder.finish(self);
plugin_group_builder.finish::<T>(self);
self
}

Expand Down Expand Up @@ -834,10 +939,21 @@ impl App {
T: PluginGroup,
F: FnOnce(&mut PluginGroupBuilder) -> &mut PluginGroupBuilder,
{
if self
.plugins
.insert(std::any::TypeId::of::<T>(), None)
.is_some()
{
panic!(
"Plugin Group \"{}\" was already added",
std::any::type_name::<T>()
);
}

let mut plugin_group_builder = PluginGroupBuilder::default();
group.build(&mut plugin_group_builder);
func(&mut plugin_group_builder);
plugin_group_builder.finish(self);
plugin_group_builder.finish::<T>(self);
self
}

Expand Down Expand Up @@ -917,3 +1033,49 @@ fn run_once(mut app: App) {
/// An event that indicates the app should exit. This will fully exit the app process.
#[derive(Debug, Clone)]
pub struct AppExit;

#[cfg(test)]
mod tests {
use crate::{App, Plugin};

struct PluginA;
impl Plugin for PluginA {
fn build(&self, _app: &mut crate::App) {}
}
struct PluginB;
impl Plugin for PluginB {
fn build(&self, _app: &mut crate::App) {}
}
struct PluginC<T>(T);
impl<T: Send + Sync + 'static> Plugin for PluginC<T> {
fn build(&self, _app: &mut crate::App) {}
}
struct PluginD;
impl Plugin for PluginD {
fn build(&self, _app: &mut crate::App) {}
fn is_unique(&self) -> bool {
false
}
}

#[test]
fn can_add_two_plugins() {
App::new().add_plugin(PluginA).add_plugin(PluginB);
}

#[test]
#[should_panic]
fn cant_add_twice_the_same_plugin() {
App::new().add_plugin(PluginA).add_plugin(PluginA);
}

#[test]
fn can_add_twice_the_same_plugin_with_different_type_param() {
App::new().add_plugin(PluginC(0)).add_plugin(PluginC(true));
}

#[test]
fn can_add_twice_the_same_plugin_not_unique() {
App::new().add_plugin(PluginD).add_plugin(PluginD);
}
}
10 changes: 9 additions & 1 deletion crates/bevy_app/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ use std::any::Any;
/// A collection of Bevy App logic and configuration
///
/// Plugins configure an [`App`](crate::App). When an [`App`](crate::App) registers
/// a plugin, the plugin's [`Plugin::build`] function is run.
/// a plugin, the plugin's [`Plugin::build`] function is run. By default, a plugin
/// can only be added once to an [`App`](crate::App). If the plugin may need to be
/// added twice or more, the function [`is_unique`](Plugin::is_unique) should be
/// overriden to return `false`.
pub trait Plugin: Any + Send + Sync {
/// Configures the [`App`] to which this plugin is added.
fn build(&self, app: &mut App);
/// Configures a name for the [`Plugin`]. Primarily for debugging.
fn name(&self) -> &str {
std::any::type_name::<Self>()
}
/// If the plugin can be instantiated several times in an [`App`](crate::App), override this
/// method to return `false`.
fn is_unique(&self) -> bool {
true
}
}

/// Type representing an unsafe function that returns a mutable pointer to a [`Plugin`].
Expand Down
17 changes: 14 additions & 3 deletions crates/bevy_app/src/plugin_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use bevy_utils::{tracing::debug, HashMap};
use std::any::TypeId;

/// Combines multiple [`Plugin`]s into a single unit.
pub trait PluginGroup {
pub trait PluginGroup: 'static {
/// Configures the [`Plugin`]s that are to be added.
fn build(&mut self, group: &mut PluginGroupBuilder);
}
Expand Down Expand Up @@ -110,11 +110,22 @@ impl PluginGroupBuilder {
}

/// Consumes the [`PluginGroupBuilder`] and [builds](Plugin::build) the contained [`Plugin`]s.
pub fn finish(self, app: &mut App) {
pub fn finish<T: PluginGroup>(self, app: &mut App) {
for ty in self.order.iter() {
if let Some(entry) = self.plugins.get(ty) {
if entry.enabled {
debug!("added plugin: {}", entry.plugin.name());
debug!(
"added plugin {} from group {}",
entry.plugin.name(),
std::any::type_name::<T>()
);
if entry.plugin.is_unique() {
app.register_plugin(
ty,
entry.plugin.name(),
Some(std::any::type_name::<T>()),
);
}
entry.plugin.build(app);
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ impl Default for AssetServerSettings {
/// 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);
.consume_setup_resource::<AssetServerSettings>()
.unwrap_or_default();

#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
let source = FileAssetIo::new(&settings.asset_folder);
Expand Down
4 changes: 1 addition & 3 deletions crates/bevy_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ pub enum CoreSystem {
impl Plugin for CorePlugin {
fn build(&self, app: &mut App) {
// Setup the default bevy task pools
app.world
.get_resource::<DefaultTaskPoolOptions>()
.cloned()
app.consume_setup_resource::<DefaultTaskPoolOptions>()
.unwrap_or_default()
.create_default_pools(&mut app.world);

Expand Down
8 changes: 5 additions & 3 deletions crates/bevy_log/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter};
/// * Using [`tracing-wasm`](https://crates.io/crates/tracing-wasm) in WASM, logging
/// to the browser console.
///
/// You can configure this plugin using the resource [`LogSettings`].
/// You can configure this plugin using the setup resource [`LogSettings`].
/// ```no_run
/// # use bevy_internal::DefaultPlugins;
/// # use bevy_app::App;
/// # use bevy_log::LogSettings;
/// # use bevy_utils::tracing::Level;
/// fn main() {
/// App::new()
/// .insert_resource(LogSettings {
/// .insert_setup_resource(LogSettings {
/// level: Level::DEBUG,
/// filter: "wgpu=error,bevy_render=info".to_string(),
/// })
Expand Down Expand Up @@ -106,7 +106,9 @@ impl Default for LogSettings {
impl Plugin for LogPlugin {
fn build(&self, app: &mut App) {
let default_filter = {
let settings = app.world.get_resource_or_insert_with(LogSettings::default);
let settings = app
.consume_setup_resource::<LogSettings>()
.unwrap_or_default();
format!("{},{}", settings.level, settings.filter)
};
LogTracer::init().unwrap();
Expand Down
Loading