Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion crates/bevy_app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ bevy_reflect = ["dep:bevy_reflect", "bevy_ecs/bevy_reflect"]
bevy_derive = { path = "../bevy_derive", version = "0.11.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.11.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.11.0-dev", optional = true }
bevy_tasks = { path = "../bevy_tasks", version = "0.11.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.11.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]
wasm-bindgen = { version = "0.2" }
web-sys = { version = "0.3", features = [ "Window" ] }
gloo-timers = "0.2"

133 changes: 120 additions & 13 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,17 @@ pub struct App {
/// This is initially set to [`Main`].
pub main_schedule_label: BoxedScheduleLabel,
sub_apps: HashMap<AppLabelId, SubApp>,
plugin_registry: Vec<Box<dyn Plugin>>,
plugin_registry: Vec<(Box<dyn Plugin>, PluginState)>,
plugin_name_added: HashSet<String>,
/// A private counter to prevent incorrect calls to `App::run()` from `Plugin::build()`
building_plugin_depth: usize,
}

enum PluginState {
Built,
Finished,
}

impl Debug for App {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "App {{ sub_apps: ")?;
Expand Down Expand Up @@ -302,7 +307,7 @@ impl App {
/// event loop, but can be useful for situations where you want to use [`App::update`]
pub fn ready(&self) -> bool {
for plugin in &self.plugin_registry {
if !plugin.ready(self) {
if matches!(plugin.1, PluginState::Built) && !plugin.0.ready(self) {
return false;
}
}
Expand All @@ -314,9 +319,12 @@ impl App {
/// [`App::update`].
pub fn finish(&mut self) {
// temporarily remove the plugin registry to run each plugin's setup function on app.
let plugin_registry = std::mem::take(&mut self.plugin_registry);
for plugin in &plugin_registry {
plugin.finish(self);
let mut plugin_registry = std::mem::take(&mut self.plugin_registry);
for plugin in &mut plugin_registry {
if matches!(plugin.1, PluginState::Built) {
plugin.0.finish(self);
plugin.1 = PluginState::Finished;
}
}
self.plugin_registry = plugin_registry;
}
Expand All @@ -327,7 +335,9 @@ impl App {
// temporarily remove the plugin registry to run each plugin's setup function on app.
let plugin_registry = std::mem::take(&mut self.plugin_registry);
for plugin in &plugin_registry {
plugin.cleanup(self);
if matches!(plugin.1, PluginState::Finished) {
plugin.0.cleanup(self);
}
}
self.plugin_registry = plugin_registry;
}
Expand Down Expand Up @@ -695,12 +705,91 @@ impl App {
{
match self.add_boxed_plugin(Box::new(plugin)) {
Ok(app) => app,
Err(AppError::DuplicatePlugin { plugin_name }) => panic!(
"Error adding plugin {plugin_name}: : plugin was already added in application"
),
Err(AppError::DuplicatePlugin { plugin_name }) => {
panic!("Error adding plugin {plugin_name}: plugin was already added in application")
}
}
}

/// Adds a single [`Plugin`].
///
/// Unlike the [`add_plugin`](Self::add_plugin) method, this method will return a future that
/// will be complete once the [lifecycle of the plugin](Plugin) is finished, leaving only the
/// `cleanup` stage to do.
///
/// # Panics
///
/// Panics if the plugin was already added to the application.
pub async fn add_plugin_async<T: Plugin>(&mut self, plugin: T) -> &mut Self {
match self.add_boxed_plugin_async(Box::new(plugin)).await {
Ok(app) => app,
Err(AppError::DuplicatePlugin { plugin_name }) => {
panic!("Error adding plugin {plugin_name}: plugin was already added in application")
}
}
}

pub(crate) async fn add_boxed_plugin_async(
&mut self,
plugin: Box<dyn Plugin>,
) -> Result<&mut Self, AppError> {
struct FuturePlugin<'a>(&'a mut App, usize);
impl<'a> std::future::Future for FuturePlugin<'a> {
type Output = ();

fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
// wait for the given plugin and all subplugins to be initialized completely
for plugin in self.0.plugin_registry.iter().skip(self.1) {
if !plugin.0.ready(self.0) {
#[cfg(not(target_arch = "wasm32"))]
{
// tick task pools, then wake this future
bevy_tasks::tick_global_task_pools_on_main_thread();
cx.waker().wake_by_ref();
}
#[cfg(target_arch = "wasm32")]
{
// wake this future in 10ms, to let other futures a chance to run
let waker = cx.waker().clone();
use gloo_timers::callback::Timeout;
let timeout = Timeout::new(10, move || {
waker.wake();
});
timeout.forget();
}
return std::task::Poll::Pending;
}
}
// temporarily remove the plugin registry to run each plugin's setup function on app.
let mut plugin_registry = std::mem::take(&mut self.0.plugin_registry);
for plugin in plugin_registry.iter_mut().skip(self.1) {
if matches!(plugin.1, PluginState::Built) {
plugin.0.finish(self.0);
plugin.1 = PluginState::Finished;
}
}
self.0.plugin_registry = plugin_registry;

std::task::Poll::Ready(())
}
}
impl<'b> FuturePlugin<'b> {
fn build(app: &'b mut App, plugin: Box<dyn Plugin>) -> Result<Self, AppError> {
let current_registry_len = app.plugin_registry.len();
app.add_boxed_plugin(plugin)?;
Ok(Self(app, current_registry_len))
}
}

let future = FuturePlugin::build(self, plugin)?;

future.await;
Ok(self)
}

/// Boxed variant of [`add_plugin`](App::add_plugin) that can be used from a [`PluginGroup`]
pub(crate) fn add_boxed_plugin(
&mut self,
Expand All @@ -715,15 +804,16 @@ impl App {

// Reserve that position in the plugin registry. if a plugin adds plugins, they will be correctly ordered
let plugin_position_in_registry = self.plugin_registry.len();
self.plugin_registry.push(Box::new(PlaceholderPlugin));
self.plugin_registry
.push((Box::new(PlaceholderPlugin), PluginState::Built));

self.building_plugin_depth += 1;
let result = catch_unwind(AssertUnwindSafe(|| plugin.build(self)));
self.building_plugin_depth -= 1;
if let Err(payload) = result {
resume_unwind(payload);
}
self.plugin_registry[plugin_position_in_registry] = plugin;
self.plugin_registry[plugin_position_in_registry] = (plugin, PluginState::Built);
Ok(self)
}

Expand All @@ -737,7 +827,7 @@ impl App {
{
self.plugin_registry
.iter()
.any(|p| p.downcast_ref::<T>().is_some())
.any(|p| p.0.downcast_ref::<T>().is_some())
}

/// Returns a vector of references to any plugins of type `T` that have been added.
Expand Down Expand Up @@ -765,7 +855,7 @@ impl App {
{
self.plugin_registry
.iter()
.filter_map(|p| p.downcast_ref())
.filter_map(|p| p.0.downcast_ref())
.collect()
}

Expand Down Expand Up @@ -797,6 +887,23 @@ impl App {
self
}

/// Adds a group of [`Plugin`]s.
///
/// [`Plugin`]s can be grouped into a set by using a [`PluginGroup`].
///
/// Unlike the [`add_plugins`](Self::add_plugins) method, this method will return a future that
/// will be complete once the [lifecycle of the plugins](Plugin) is finished, leaving only the
/// `cleanup` stage to do.
///
/// # Panics
///
/// Panics if one of the plugin in the group was already added to the application.
pub async fn add_plugins_async<T: PluginGroup>(&mut self, group: T) -> &mut Self {
let builder = group.build();
builder.finish_async(self).await;
self
}

/// Registers the type `T` in the [`TypeRegistry`](bevy_reflect::TypeRegistry) resource,
/// adding reflect data as specified in the [`Reflect`](bevy_reflect::Reflect) derive:
/// ```rust,ignore
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_app/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,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`
/// * once the app starts, it will wait for all registered [`Plugin::ready`] to return `true`
/// * it will then call all registered [`Plugin::finish`]
/// * and call all registered [`Plugin::cleanup`]
pub trait Plugin: Downcast + Any + Send + Sync {
Expand Down
30 changes: 30 additions & 0 deletions crates/bevy_app/src/plugin_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,36 @@ impl PluginGroupBuilder {
}
}
}

/// Consumes the [`PluginGroupBuilder`] and [builds](Plugin::build) the contained [`Plugin`]s
/// in the order specified, then wait for them to be [ready](Plugin::ready) and
/// [finishes](Plugin::finish) their setup.
///
/// Unlike the [`finish`](Self::finish) method, this method will return a future that
/// will be complete once the [lifecycle of the plugins](Plugin) is finished, leaving only the
/// `cleanup` stage to do.
///
/// # Panics
///
/// Panics if one of the plugin in the group was already added to the application.
pub async fn finish_async(mut self, app: &mut App) {
for ty in &self.order {
if let Some(entry) = self.plugins.remove(ty) {
if entry.enabled {
debug!("added plugin: {}", entry.plugin.name());
if let Err(AppError::DuplicatePlugin { plugin_name }) =
app.add_boxed_plugin_async(entry.plugin).await
{
panic!(
"Error adding plugin {} in group {}: plugin was already added in application",
plugin_name,
self.group_name
);
}
}
}
}
}
}

/// A plugin group which doesn't do anything. Useful for examples:
Expand Down
66 changes: 49 additions & 17 deletions crates/bevy_derive/src/bevy_main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,53 @@ pub fn bevy_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
"`bevy_main` can only be used on a function called 'main'.",
);

TokenStream::from(quote! {
#[no_mangle]
#[cfg(target_os = "android")]
fn android_main(android_app: bevy::winit::AndroidApp) {
let _ = bevy::winit::ANDROID_APP.set(android_app);
main();
}

#[no_mangle]
#[cfg(target_os = "ios")]
extern "C" fn main_rs() {
main();
}

#[allow(unused)]
#input
})
if input.sig.asyncness.is_some() {
TokenStream::from(quote! {
#[no_mangle]
#[cfg(target_os = "android")]
fn android_main(android_app: bevy::winit::AndroidApp) {
let _ = bevy::winit::ANDROID_APP.set(android_app);
futures_lite::future::block_on(main());
}

#[no_mangle]
#[cfg(target_os = "ios")]
extern "C" fn main_rs() {
futures_lite::future::block_on(main());
}

#[cfg(any(target_os = "ios", target_os = "android"))]
#[allow(unused)]
#input

#[cfg(all(not(target_os = "ios"), not(target_os = "android")))]
fn main() {
#input

#[cfg(target_arch = "wasm32")]
wasm_bindgen_futures::spawn_local(async move {main().await;});
#[cfg(not(target_arch = "wasm32"))]
futures_lite::future::block_on(main());

}
})
} else {
TokenStream::from(quote! {
#[no_mangle]
#[cfg(target_os = "android")]
fn android_main(android_app: bevy::winit::AndroidApp) {
let _ = bevy::winit::ANDROID_APP.set(android_app);
main();
}

#[no_mangle]
#[cfg(target_os = "ios")]
extern "C" fn main_rs() {
main();
}

#[allow(unused)]
#input
})
}
}