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

fix: added plugin registry #13400

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7694270
fix: added plugin registry
pietrosophya May 16, 2024
d4b2fad
chore: updating plugins not working fully yet
pietrosophya May 17, 2024
4994fd6
chore: updating plugins not working fully yet
pietrosophya May 17, 2024
06a7c9f
fix: window is running again
pietrosophya May 17, 2024
d0d2fc2
fix: fixed running basic example
pietrosophya May 17, 2024
2f928b5
chore: added docs
pietrosophya May 17, 2024
fa6c448
fix: fixing plugins
pietrosophya May 17, 2024
5ed6fb6
fix: fixing plugins
pietrosophya May 17, 2024
97ff405
fix: fixing plugins
pietrosophya May 17, 2024
a2f36e6
fix: fixing renderer setup
pietrosophya May 17, 2024
1c1dd7e
fix: fixed renderer setup
pietrosophya May 17, 2024
e581bee
Merge branch 'main' into fix/rewrite-plugin-registry
pietrosophya May 17, 2024
94f3b18
Merge branch 'main' into fix/rewrite-plugin-registry
pietrosophya May 18, 2024
0ad4cfb
fix: removed unneeded PlaceholderPlugin and added docs
pietrosophya May 18, 2024
acd6503
fix: extract sub app that must exist
pietrosophya May 18, 2024
569a658
fix: check sub app in ready_to_finalize
pietrosophya May 18, 2024
6bc60c8
chore: fixed some logs
pietrosophya May 18, 2024
a0fd7d2
chore: fixed CI
pietrosophya May 18, 2024
ad4234f
chore: fixed CI
pietrosophya May 18, 2024
ea35d86
Merge branch 'main' into fix/rewrite-plugin-registry
pietrosophya May 18, 2024
686c9ad
chore: fixed CI
pietrosophya May 18, 2024
c86af5e
chore: fixing CI
pietrosophya May 18, 2024
02d7d91
fix: fixed CI tests
pietrosophya May 18, 2024
f0e78f0
fix: fixing CI tests
pietrosophya May 18, 2024
f04d557
fix: fixing CI tests
pietrosophya May 18, 2024
8ae903a
fix: fixing CI tests
pietrosophya May 18, 2024
290e218
Apply suggestions from code review
pietrosophya May 18, 2024
12c9981
chore: fixing comments
pietrosophya May 18, 2024
0db7783
fix: fixing comments
pietrosophya May 19, 2024
a5f03a3
fix: fixing comments
pietrosophya May 19, 2024
997ea33
fix: fixing comments
pietrosophya May 19, 2024
bd95b32
fix: fixing comments
pietrosophya May 19, 2024
0133487
fix: fixing comments
pietrosophya May 19, 2024
cb37cd7
fix: fixing apps
pietrosophya May 19, 2024
1c2631c
Merge branch 'main' into fix/rewrite-plugin-registry
pietrosophya May 19, 2024
4aa6ee5
chore: fix CI
pietrosophya May 19, 2024
4ea8965
chore: merged with main
pietrosophya May 31, 2024
7ab7dab
fix: handling non-unique plugins
pietrosophya May 31, 2024
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
261 changes: 167 additions & 94 deletions crates/bevy_app/src/app.rs

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions crates/bevy_app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod main_schedule;
mod panic_handler;
mod plugin;
mod plugin_group;
mod plugin_registry;
mod schedule_runner;
mod sub_app;

Expand All @@ -21,19 +22,20 @@ pub use main_schedule::*;
pub use panic_handler::*;
pub use plugin::*;
pub use plugin_group::*;
pub use plugin_registry::*;
pub use schedule_runner::*;
pub use sub_app::*;

#[allow(missing_docs)]
pub mod prelude {
#[doc(hidden)]
pub use crate::{
app::{App, AppExit},
app::{App, AppExit, AppLabel, InternedAppLabel},
main_schedule::{
First, FixedFirst, FixedLast, FixedPostUpdate, FixedPreUpdate, FixedUpdate, Last, Main,
PostStartup, PostUpdate, PreStartup, PreUpdate, SpawnScene, Startup, Update,
},
sub_app::SubApp,
DynamicPlugin, Plugin, PluginGroup,
DynamicPlugin, Plugin, PluginGroup, PluginState,
};
}
24 changes: 24 additions & 0 deletions crates/bevy_app/src/main_schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,30 @@ impl Main {
pub struct MainSchedulePlugin;

impl Plugin for MainSchedulePlugin {
fn init(&self, app: &mut App) {
// simple "facilitator" schedules benefit from simpler single threaded scheduling
pietrosophya marked this conversation as resolved.
Show resolved Hide resolved
let mut main_schedule = Schedule::new(Main);
main_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
let mut fixed_main_schedule = Schedule::new(FixedMain);
fixed_main_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
let mut fixed_main_loop_schedule = Schedule::new(RunFixedMainLoop);
fixed_main_loop_schedule.set_executor_kind(ExecutorKind::SingleThreaded);

app.add_schedule(main_schedule)
.add_schedule(fixed_main_schedule)
.add_schedule(fixed_main_loop_schedule)
.init_resource::<MainScheduleOrder>()
.init_resource::<FixedMainScheduleOrder>()
.add_systems(Main, Main::run_main)
.add_systems(FixedMain, FixedMain::run_fixed_main);

#[cfg(feature = "bevy_debug_stepping")]
{
use bevy_ecs::schedule::{IntoSystemConfigs, Stepping};
app.add_systems(Main, Stepping::begin_frame.before(Main::run_main));
}
}

fn build(&self, app: &mut App) {
// simple "facilitator" schedules benefit from simpler single threaded scheduling
let mut main_schedule = Schedule::new(Main);
Expand Down
129 changes: 99 additions & 30 deletions crates/bevy_app/src/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,41 @@
use downcast_rs::{impl_downcast, Downcast};

use crate::App;
use crate::{App, InternedAppLabel};
use std::any::Any;

/// Plugin state in the application
Copy link
Member

@alice-i-cecile alice-i-cecile May 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More docs please! Information about how to access this data in the form of a doc test would be particularly valuable, but we should also have a note about how this is the expected lifecycle that plugins flow through.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to add a full example, that maybe explains also what each state purpose is. I left this part out until there's a general agreement for the PR, if that makes sense.

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum PluginState {
/// Plugin is not initialized.
#[default]
Idle,
/// Plugin is initialized.
Init,
/// Plugin is being built.
Building,
/// Plugin is being configured.
Configuring,
/// Plugin configuration is finishing.
Finalizing,
/// Plugin configuration is completed.
Done,
/// Plugin resources are cleaned up.
Cleaned,
}

impl PluginState {
pub(crate) fn next(self) -> Self {
match self {
Self::Idle => Self::Init,
Self::Init => Self::Building,
Self::Building => Self::Configuring,
Self::Configuring => Self::Finalizing,
Self::Finalizing => Self::Done,
s => unreachable!("Cannot handle {:?} state", s),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we return an Option here and avoid ever panicking?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of those cases that should really not happen, or it would be a bug in the plugin registry management. Would still an option be a better choice?

}
}
}

/// A collection of Bevy app logic and configuration.
///
/// Plugins configure an [`App`]. When an [`App`] registers a plugin,
Expand All @@ -19,7 +52,7 @@ use std::any::Any;
/// When adding a plugin to an [`App`]:
/// * the app calls [`Plugin::build`] immediately, and register the plugin
/// * once the app started, it will wait for all registered [`Plugin::ready`] to return `true`
/// * it will then call all registered [`Plugin::finish`]
/// * it will then call all registered [`Plugin::finalize`]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to call out the other life cycle steps added in this PR in these docs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I'll fix all pending docs comments after the general code review, if that's ok (but open to do otherwise if you feel this is better)

/// * and call all registered [`Plugin::cleanup`]
///
/// ## Defining a plugin.
Expand Down Expand Up @@ -56,19 +89,46 @@ use std::any::Any;
/// # fn damp_flickering() {}
/// ````
pub trait Plugin: Downcast + Any + Send + Sync {
/// Configures the [`App`] to which this plugin is added.
fn build(&self, app: &mut App);
/// Returns required sub apps before finalizing this plugin.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More docs here about why this is useful to users please.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep this, a doc test demonstrating how to require the rendering subapp would be really valuable. This will be by far the most common pattern.

fn require_sub_apps(&self) -> Vec<InternedAppLabel> {
pietrosophya marked this conversation as resolved.
Show resolved Hide resolved
Vec::new()
}

/// Has the plugin finished its setup? This can be useful for plugins that need something
/// asynchronous to happen before they can finish their setup, like the initialization of a renderer.
/// Once the plugin is ready, [`finish`](Plugin::finish) should be called.
fn ready(&self, _app: &App) -> bool {
/// Pre-configures the [`App`] to which this plugin is added. This is usually executed before
pietrosophya marked this conversation as resolved.
Show resolved Hide resolved
/// the event loop is started.
fn init(&self, _app: &mut App) {
// do nothing
}

/// Is the plugin ready to be built?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More information about how this might be used would be good.

fn ready_to_build(&self, _app: &mut App) -> bool {
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
true
}

/// Finish adding this plugin to the [`App`], once all plugins registered are ready. This can
/// Builds the [`Plugin`] resources. This is usually executed inside an event loop.
pietrosophya marked this conversation as resolved.
Show resolved Hide resolved
fn build(&self, _app: &mut App) {
pietrosophya marked this conversation as resolved.
Show resolved Hide resolved
// do nothing
}

/// Is the plugin ready to be configured?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More docs.

fn ready_to_configure(&self, _app: &mut App) -> bool {
true
}

/// Configures the [`App`] to which this plugin is added. This can
/// be useful for plugins that needs completing asynchronous configuration.
fn configure(&self, _app: &mut App) {
// do nothing
}

/// Is the plugin ready to be finalized?
fn ready_to_finalize(&self, _app: &mut App) -> bool {
true
}

/// Finalizes this plugin to the [`App`]. This can
/// be useful for plugins that depends on another plugin asynchronous setup, like the renderer.
fn finish(&self, _app: &mut App) {
fn finalize(&self, _app: &mut App) {
pietrosophya marked this conversation as resolved.
Show resolved Hide resolved
// do nothing
}

Expand All @@ -90,6 +150,35 @@ pub trait Plugin: Downcast + Any + Send + Sync {
fn is_unique(&self) -> bool {
true
}

/// Updates the plugin to a desired [`PluginState`].
pietrosophya marked this conversation as resolved.
Show resolved Hide resolved
fn update(&mut self, app: &mut App, state: PluginState) {
match state {
PluginState::Init => self.init(app),
PluginState::Building => self.build(app),
PluginState::Configuring => self.configure(app),
PluginState::Finalizing => self.finalize(app),
PluginState::Done => {}
s => panic!("Cannot handle {s:?} state"),
Copy link
Member

@alice-i-cecile alice-i-cecile May 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just remove this arm, correct? Exhaustive matching is good!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is the extra cleanup, that should not be executed as part of the update. But it could be just added to the match and the error described better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idle and cleanup are the two states that cannot be processed. Is it better to add both to the match?

}
}

/// Updates if the plugin is ready to progress to the desired next [`PluginState`].
fn ready(&self, app: &mut App, next_state: PluginState) -> bool {
match next_state {
PluginState::Building => self.ready_to_build(app),
PluginState::Configuring => self.ready_to_configure(app),
PluginState::Finalizing => self.ready_to_finalize(app),
_ => true,
}
}

/// Checks all required [`SubApp`]s.
fn check_required_sub_apps(&mut self, app: &App) -> bool {
self.require_sub_apps()
.iter()
.all(|s| app.contains_sub_app(*s))
}
}

impl_downcast!(Plugin);
Expand All @@ -100,26 +189,6 @@ impl<T: Fn(&mut App) + Send + Sync + 'static> Plugin for T {
}
}

/// Plugins state in the application
#[derive(PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]
pub enum PluginsState {
/// Plugins are being added.
Adding,
/// All plugins already added are ready.
Ready,
/// Finish has been executed for all plugins added.
Finished,
/// Cleanup has been executed for all plugins added.
Cleaned,
}

/// A dummy plugin that's to temporarily occupy an entry in an app's plugin registry.
pub(crate) struct PlaceholderPlugin;

impl Plugin for PlaceholderPlugin {
fn build(&self, _app: &mut App) {}
}

/// A type representing an unsafe function that returns a mutable pointer to a [`Plugin`].
/// It is used for dynamically loading plugins.
///
Expand Down
Loading
Loading