Update App:is_plugin_added to work inside Plugin::finish and Plugin::clean (#12761)

# Objective

I have been trying to check for the existing of some plugins via
`App::is_plugin_added` to conditionally run some behaviour in the
`Plugin::finish` part of my plugin, before realizing that the plugin
registry is actually not available during this step.
This is because the `App::is_plugin_added` using the plugin registry to
check for previous registration.

## Solution

- Switch the `App::is_plugin_added` to use the list of plugin names to
check for previous registrations
- Add a unit test showcasing that `App::is_plugin_added` works during
`Plugin::finish`
This commit is contained in:
Charles Bournhonesque 2024-04-28 17:32:16 -04:00 committed by GitHub
parent 16531fb3e3
commit f73950767b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 53 additions and 40 deletions

View file

@ -209,15 +209,15 @@ impl App {
let mut overall_plugins_state = match self.main_mut().plugins_state { let mut overall_plugins_state = match self.main_mut().plugins_state {
PluginsState::Adding => { PluginsState::Adding => {
let mut state = PluginsState::Ready; let mut state = PluginsState::Ready;
let plugins = std::mem::take(&mut self.main_mut().plugins); let plugins = std::mem::take(&mut self.main_mut().plugin_registry);
for plugin in &plugins.registry { for plugin in &plugins {
// plugins installed to main need to see all sub-apps // plugins installed to main need to see all sub-apps
if !plugin.ready(self) { if !plugin.ready(self) {
state = PluginsState::Adding; state = PluginsState::Adding;
break; break;
} }
} }
self.main_mut().plugins = plugins; self.main_mut().plugin_registry = plugins;
state state
} }
state => state, state => state,
@ -235,12 +235,12 @@ impl App {
/// plugins are ready, but can be useful for situations where you want to use [`App::update`]. /// plugins are ready, but can be useful for situations where you want to use [`App::update`].
pub fn finish(&mut self) { pub fn finish(&mut self) {
// plugins installed to main should see all sub-apps // plugins installed to main should see all sub-apps
let plugins = std::mem::take(&mut self.main_mut().plugins); let plugins = std::mem::take(&mut self.main_mut().plugin_registry);
for plugin in &plugins.registry { for plugin in &plugins {
plugin.finish(self); plugin.finish(self);
} }
let main = self.main_mut(); let main = self.main_mut();
main.plugins = plugins; main.plugin_registry = plugins;
main.plugins_state = PluginsState::Finished; main.plugins_state = PluginsState::Finished;
self.sub_apps.iter_mut().skip(1).for_each(|s| s.finish()); self.sub_apps.iter_mut().skip(1).for_each(|s| s.finish());
} }
@ -249,12 +249,12 @@ impl App {
/// [`App::finish`], but can be useful for situations where you want to use [`App::update`]. /// [`App::finish`], but can be useful for situations where you want to use [`App::update`].
pub fn cleanup(&mut self) { pub fn cleanup(&mut self) {
// plugins installed to main should see all sub-apps // plugins installed to main should see all sub-apps
let plugins = std::mem::take(&mut self.main_mut().plugins); let plugins = std::mem::take(&mut self.main_mut().plugin_registry);
for plugin in &plugins.registry { for plugin in &plugins {
plugin.cleanup(self); plugin.cleanup(self);
} }
let main = self.main_mut(); let main = self.main_mut();
main.plugins = plugins; main.plugin_registry = plugins;
main.plugins_state = PluginsState::Cleaned; main.plugins_state = PluginsState::Cleaned;
self.sub_apps.iter_mut().skip(1).for_each(|s| s.cleanup()); self.sub_apps.iter_mut().skip(1).for_each(|s| s.cleanup());
} }
@ -490,8 +490,7 @@ impl App {
if plugin.is_unique() if plugin.is_unique()
&& !self && !self
.main_mut() .main_mut()
.plugins .plugin_names
.names
.insert(plugin.name().to_string()) .insert(plugin.name().to_string())
{ {
Err(AppError::DuplicatePlugin { Err(AppError::DuplicatePlugin {
@ -501,10 +500,9 @@ impl App {
// Reserve position in the plugin registry. If the plugin adds more plugins, // Reserve position in the plugin registry. If the plugin adds more plugins,
// they'll all end up in insertion order. // they'll all end up in insertion order.
let index = self.main().plugins.registry.len(); let index = self.main().plugin_registry.len();
self.main_mut() self.main_mut()
.plugins .plugin_registry
.registry
.push(Box::new(PlaceholderPlugin)); .push(Box::new(PlaceholderPlugin));
self.main_mut().plugin_build_depth += 1; self.main_mut().plugin_build_depth += 1;
@ -515,7 +513,7 @@ impl App {
resume_unwind(payload); resume_unwind(payload);
} }
self.main_mut().plugins.registry[index] = plugin; self.main_mut().plugin_registry[index] = plugin;
Ok(self) Ok(self)
} }
@ -1008,6 +1006,18 @@ mod tests {
} }
} }
struct PluginE;
impl Plugin for PluginE {
fn build(&self, _app: &mut App) {}
fn finish(&self, app: &mut App) {
if app.is_plugin_added::<PluginA>() {
panic!("cannot run if PluginA is already registered");
}
}
}
#[test] #[test]
fn can_add_two_plugins() { fn can_add_two_plugins() {
App::new().add_plugins((PluginA, PluginB)); App::new().add_plugins((PluginA, PluginB));
@ -1078,6 +1088,15 @@ mod tests {
assert_eq!(app.world().entities().len(), 2); assert_eq!(app.world().entities().len(), 2);
} }
#[test]
#[should_panic]
fn test_is_plugin_added_works_during_finish() {
let mut app = App::new();
app.add_plugins(PluginA);
app.add_plugins(PluginE);
app.finish();
}
#[test] #[test]
fn test_derive_app_label() { fn test_derive_app_label() {
use super::AppLabel; use super::AppLabel;

View file

@ -10,17 +10,11 @@ use bevy_ecs::{
}; };
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
use bevy_utils::tracing::info_span; use bevy_utils::tracing::info_span;
use bevy_utils::{default, HashMap, HashSet}; use bevy_utils::{HashMap, HashSet};
use std::fmt::Debug; use std::fmt::Debug;
type ExtractFn = Box<dyn Fn(&mut World, &mut World) + Send>; type ExtractFn = Box<dyn Fn(&mut World, &mut World) + Send>;
#[derive(Default)]
pub(crate) struct PluginStore {
pub(crate) registry: Vec<Box<dyn Plugin>>,
pub(crate) names: HashSet<String>,
}
/// A secondary application with its own [`World`]. These can run independently of each other. /// A secondary application with its own [`World`]. These can run independently of each other.
/// ///
/// These are useful for situations where certain processes (e.g. a render thread) need to be kept /// These are useful for situations where certain processes (e.g. a render thread) need to be kept
@ -67,8 +61,11 @@ pub(crate) struct PluginStore {
pub struct SubApp { pub struct SubApp {
/// The data of this application. /// The data of this application.
world: World, world: World,
/// Metadata for installed plugins. /// List of plugins that have been added.
pub(crate) plugins: PluginStore, pub(crate) plugin_registry: Vec<Box<dyn Plugin>>,
/// The names of plugins that have been added to this app. (used to track duplicates and
/// already-registered plugins)
pub(crate) plugin_names: HashSet<String>,
/// Panics if an update is attempted while plugins are building. /// Panics if an update is attempted while plugins are building.
pub(crate) plugin_build_depth: usize, pub(crate) plugin_build_depth: usize,
pub(crate) plugins_state: PluginsState, pub(crate) plugins_state: PluginsState,
@ -91,7 +88,8 @@ impl Default for SubApp {
world.init_resource::<Schedules>(); world.init_resource::<Schedules>();
Self { Self {
world, world,
plugins: default(), plugin_registry: Vec::default(),
plugin_names: HashSet::default(),
plugin_build_depth: 0, plugin_build_depth: 0,
plugins_state: PluginsState::Adding, plugins_state: PluginsState::Adding,
update_schedule: None, update_schedule: None,
@ -380,10 +378,7 @@ impl SubApp {
where where
T: Plugin, T: Plugin,
{ {
self.plugins self.plugin_names.contains(std::any::type_name::<T>())
.registry
.iter()
.any(|p| p.downcast_ref::<T>().is_some())
} }
/// See [`App::get_added_plugins`]. /// See [`App::get_added_plugins`].
@ -391,8 +386,7 @@ impl SubApp {
where where
T: Plugin, T: Plugin,
{ {
self.plugins self.plugin_registry
.registry
.iter() .iter()
.filter_map(|p| p.downcast_ref()) .filter_map(|p| p.downcast_ref())
.collect() .collect()
@ -409,16 +403,16 @@ impl SubApp {
match self.plugins_state { match self.plugins_state {
PluginsState::Adding => { PluginsState::Adding => {
let mut state = PluginsState::Ready; let mut state = PluginsState::Ready;
let plugins = std::mem::take(&mut self.plugins); let plugins = std::mem::take(&mut self.plugin_registry);
self.run_as_app(|app| { self.run_as_app(|app| {
for plugin in &plugins.registry { for plugin in &plugins {
if !plugin.ready(app) { if !plugin.ready(app) {
state = PluginsState::Adding; state = PluginsState::Adding;
return; return;
} }
} }
}); });
self.plugins = plugins; self.plugin_registry = plugins;
state state
} }
state => state, state => state,
@ -427,25 +421,25 @@ impl SubApp {
/// Runs [`Plugin::finish`] for each plugin. /// Runs [`Plugin::finish`] for each plugin.
pub fn finish(&mut self) { pub fn finish(&mut self) {
let plugins = std::mem::take(&mut self.plugins); let plugins = std::mem::take(&mut self.plugin_registry);
self.run_as_app(|app| { self.run_as_app(|app| {
for plugin in &plugins.registry { for plugin in &plugins {
plugin.finish(app); plugin.finish(app);
} }
}); });
self.plugins = plugins; self.plugin_registry = plugins;
self.plugins_state = PluginsState::Finished; self.plugins_state = PluginsState::Finished;
} }
/// Runs [`Plugin::cleanup`] for each plugin. /// Runs [`Plugin::cleanup`] for each plugin.
pub fn cleanup(&mut self) { pub fn cleanup(&mut self) {
let plugins = std::mem::take(&mut self.plugins); let plugins = std::mem::take(&mut self.plugin_registry);
self.run_as_app(|app| { self.run_as_app(|app| {
for plugin in &plugins.registry { for plugin in &plugins {
plugin.cleanup(app); plugin.cleanup(app);
} }
}); });
self.plugins = plugins; self.plugin_registry = plugins;
self.plugins_state = PluginsState::Cleaned; self.plugins_state = PluginsState::Cleaned;
} }