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 tuples in add_plugins. #7687

Closed
wants to merge 12 commits into from
78 changes: 67 additions & 11 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{CoreSchedule, CoreSet, Plugin, PluginGroup, StartupSet};
use crate::{CoreSchedule, CoreSet, IntoPlugin, IntoPluginGroup, Plugin, StartupSet};
pub use bevy_derive::AppLabel;
use bevy_ecs::{
prelude::*,
Expand All @@ -13,6 +13,7 @@ use std::fmt::Debug;

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

bevy_utils::define_label!(
/// A strongly-typed class of labels used to identify an [`App`].
AppLabel,
Expand Down Expand Up @@ -742,19 +743,17 @@ impl App {
/// # Panics
///
/// Panics if the plugin was already added to the application.
pub fn add_plugin<T>(&mut self, plugin: T) -> &mut Self
where
T: Plugin,
{
match self.add_boxed_plugin(Box::new(plugin)) {
pub fn add_plugin<P>(&mut self, plugin: impl IntoPlugin<P>) -> &mut Self {
let plugin = Box::new(plugin.into_plugin(self));
match self.add_boxed_plugin(plugin) {
Ok(app) => app,
Err(AppError::DuplicatePlugin { plugin_name }) => panic!(
"Error adding plugin {plugin_name}: : plugin was already added in application"
),
}
}

/// Boxed variant of `add_plugin`, can be used from a [`PluginGroup`]
/// Boxed variant of `add_plugin`, can be used from a [`PluginGroup`](super::PluginGroup)
pub(crate) fn add_boxed_plugin(
&mut self,
plugin: Box<dyn Plugin>,
Expand Down Expand Up @@ -822,22 +821,35 @@ 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), call [`build()`](PluginGroup::build) on the group,
/// before / after another plugin), call [`build()`](super::PluginGroup::build) on the group,
/// which will convert it to a [`PluginGroupBuilder`](crate::PluginGroupBuilder).
///
/// You can also specify a group of [`Plugin`]s by using a tuple over [`Plugin`]s and
/// [`PluginGroup`]s.
///
/// ## Examples
/// ```
/// # use bevy_app::{prelude::*, PluginGroupBuilder, NoopPluginGroup as MinimalPlugins};
/// #
/// # // Dummies created to avoid using `bevy_log`,
/// # // which pulls in too many dependencies and breaks rust-analyzer
/// # pub struct LogPlugin;
/// # impl Plugin for LogPlugin {
/// # fn build(&self, app: &mut App) {}
/// # }
/// App::new()
/// .add_plugins(MinimalPlugins);
/// App::new()
/// .add_plugins((MinimalPlugins, LogPlugin));
/// ```
///
/// # Panics
///
/// Panics if one of the plugin in the group was already added to the application.
pub fn add_plugins<T: PluginGroup>(&mut self, group: T) -> &mut Self {
let builder = group.build();
///
/// [`PluginGroup`]:super::PluginGroup
pub fn add_plugins<P>(&mut self, group: impl IntoPluginGroup<P>) -> &mut Self {
let builder = group.into_plugin_group_builder(self);
builder.finish(self);
self
}
Expand Down Expand Up @@ -1023,7 +1035,7 @@ pub struct AppExit;

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

struct PluginA;
impl Plugin for PluginA {
Expand All @@ -1045,6 +1057,15 @@ mod tests {
}
}

struct PluginGroupAB;
impl PluginGroup for PluginGroupAB {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
.add(PluginA)
.add(PluginB)
}
}

#[test]
fn can_add_two_plugins() {
App::new().add_plugin(PluginA).add_plugin(PluginB);
Expand Down Expand Up @@ -1077,4 +1098,39 @@ mod tests {
}
App::new().add_plugin(PluginRun);
}

#[test]
fn add_plugin_via_factory_fn() {
use bevy_ecs::system::Resource;
#[derive(Resource)]
struct Loggers;

struct LoggerPlugin;

impl LoggerPlugin {
fn with_loggers(loggers: Loggers) -> impl FnOnce(&mut App) -> LoggerPlugin {
|app| {
app.insert_resource(loggers);
LoggerPlugin
}
}
}

impl Plugin for LoggerPlugin {
fn build(&self, app: &mut App) {
let _loggers = app.world.remove_resource::<Loggers>();
}
}

App::new()
.add_plugin(LoggerPlugin::with_loggers(Loggers))
.run();
}

#[test]
fn add_multiple_plugins_at_once() {
App::new()
.add_plugins((PluginC(()), PluginD, PluginGroupAB, PluginD))
.run();
}
}
36 changes: 36 additions & 0 deletions crates/bevy_app/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,39 @@ impl_downcast!(Plugin);
///
/// See `bevy_dynamic_plugin/src/loader.rs#dynamically_load_plugin`.
pub type CreatePlugin = unsafe fn() -> *mut dyn Plugin;

/// Types that can be converted into a [`Plugin`].
///
/// This is implemented for all types which implement [`Plugin`] or
/// [`FnOnce(&mut App) -> impl Plugin`](FnOnce).
pub trait IntoPlugin<Params>: sealed::IntoPlugin<Params> {}

impl<Params, T> IntoPlugin<Params> for T where T: sealed::IntoPlugin<Params> {}

mod sealed {

use crate::{App, Plugin};

pub trait IntoPlugin<Params> {
type Plugin: Plugin;
fn into_plugin(self, app: &mut App) -> Self::Plugin;
}

pub struct IsPlugin;
pub struct IsFunction;

impl<P: Plugin> IntoPlugin<IsPlugin> for P {
type Plugin = Self;
fn into_plugin(self, _: &mut App) -> Self {
self
}
}

impl<F: FnOnce(&mut App) -> P, P: Plugin> IntoPlugin<IsFunction> for F {
type Plugin = P;

fn into_plugin(self, app: &mut App) -> Self::Plugin {
self(app)
}
}
}
102 changes: 102 additions & 0 deletions crates/bevy_app/src/plugin_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,39 @@ impl PluginGroupBuilder {
}
}

pub(crate) fn from_plugin<P: Plugin>(plugin: P) -> Self {
Self {
group_name: plugin.name().to_string(),
plugins: Default::default(),
order: Default::default(),
}
.add(plugin)
}

pub(crate) fn merge(builders: Vec<PluginGroupBuilder>) -> Self {
let mut group_name = "(".to_string();
let mut plugins = HashMap::new();
let mut order = Vec::new();
let mut first = true;
for mut builder in builders {
if first {
first = false;
} else {
group_name += ", ";
}
for (type_id, entry) in builder.plugins {
plugins.insert(type_id, entry);
}
order.append(&mut builder.order);
}
group_name += ")";
Self {
group_name,
plugins,
order,
}
}

/// Finds the index of a target [`Plugin`]. Panics if the target's [`TypeId`] is not found.
fn index_of<Target: Plugin>(&self) -> usize {
let index = self
Expand Down Expand Up @@ -192,6 +225,75 @@ impl PluginGroupBuilder {
}
}

/// Types that can be converted into a [`PluginGroup`].
///
/// This is implemented for all types which implement [`PluginGroup`] or
/// [`FnOnce(&mut App) -> impl PluginGroup`](FnOnce) and for tuples over types that implement
/// [`IntoPlugin`](super::IntoPlugin) or [`IntoPluginGroup`].
pub trait IntoPluginGroup<Params>: sealed::IntoPluginGroup<Params> {}

impl<Params, T> IntoPluginGroup<Params> for T where T: sealed::IntoPluginGroup<Params> {}

mod sealed {
use bevy_ecs::all_tuples;

use crate::{App, Plugin, PluginGroup, PluginGroupBuilder};

pub trait IntoPluginGroup<Params>: IntoPluginGroupBuilder<Params> {}

pub trait IntoPluginGroupBuilder<Params> {
fn into_plugin_group_builder(self, app: &mut App) -> PluginGroupBuilder;
}

pub struct IsPlugin;
pub struct IsPluginGroup;
pub struct IsFunction;

impl<P: Plugin> IntoPluginGroupBuilder<IsPlugin> for P {
fn into_plugin_group_builder(self, _: &mut App) -> PluginGroupBuilder {
PluginGroupBuilder::from_plugin(self)
}
}

impl<P: PluginGroup> IntoPluginGroupBuilder<IsPluginGroup> for P {
fn into_plugin_group_builder(self, _: &mut App) -> PluginGroupBuilder {
self.build()
}
}

impl<P: PluginGroup> IntoPluginGroup<IsPluginGroup> for P {}

impl<F: FnOnce(&mut App) -> PG, PG: PluginGroup> IntoPluginGroupBuilder<IsFunction> for F {
fn into_plugin_group_builder(self, app: &mut App) -> PluginGroupBuilder {
self(app).build()
}
}

impl<F: FnOnce(&mut App) -> PG, PG: PluginGroup> IntoPluginGroup<IsFunction> for F {}

macro_rules! impl_plugin_collection {
($(($param: ident, $plugins: ident)),*) => {
impl<$($param, $plugins),*> IntoPluginGroupBuilder<($($param,)*)> for ($($plugins,)*)
where
$($plugins: IntoPluginGroupBuilder<$param>),*
{
#[allow(non_snake_case, unused_variables)]
fn into_plugin_group_builder(self, app: &mut App) -> PluginGroupBuilder {
let ($($plugins,)*) = self;
PluginGroupBuilder::merge(vec![$($plugins.into_plugin_group_builder(app),)*])
}
}

impl<$($param, $plugins),*> IntoPluginGroup<($($param,)*)> for ($($plugins,)*)
where
$($plugins: IntoPluginGroupBuilder<$param>),*
{}
}
}

all_tuples!(impl_plugin_collection, 0, 15, P, S);
}

/// A plugin group which doesn't do anything. Useful for examples:
/// ```rust
/// # use bevy_app::prelude::*;
Expand Down