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

Plugin - setup resource #2988

Closed
wants to merge 1 commit into from
Closed
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
59 changes: 58 additions & 1 deletion crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use bevy_ecs::{
system::Resource,
world::World,
};
use bevy_utils::{tracing::debug, HashMap};
use bevy_utils::{
tracing::{debug, error},
HashMap,
};
use std::fmt::Debug;

#[cfg(feature = "trace")]
Expand All @@ -28,6 +31,10 @@ bevy_utils::define_label!(
#[derive(Resource, Clone, Deref, DerefMut, Default)]
pub struct AppTypeRegistry(pub bevy_reflect::TypeRegistryArc);

/// Wrapper struct for setting setup resources aside
#[derive(Resource)]
struct Setup<T>(T);

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

impl Debug for App {
Expand Down Expand Up @@ -131,6 +139,7 @@ impl App {
schedule: Default::default(),
runner: Box::new(run_once),
sub_apps: HashMap::default(),
setup_resources: Default::default(),
}
}

Expand All @@ -156,6 +165,8 @@ impl App {
#[cfg(feature = "trace")]
let _bevy_app_run_span = info_span!("bevy_app").entered();

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 @@ -674,6 +685,52 @@ impl App {
self
}

/// 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<R>(&mut self, resource: R) -> &mut Self
where
R: Resource,
{
if R::IS_SETUP_RESOURCE {
self.setup_resources
.insert(std::any::TypeId::of::<R>(), std::any::type_name::<R>());
self.insert_resource(Setup(resource));
} else {
// Using `eprintln` here as this is supposed to be called before logs are set up
eprintln!(
"Resource {} is not a setup resource",
std::any::type_name::<R>()
);
}
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<R>(&mut self) -> Option<R>
where
R: Resource,
{
self.setup_resources.remove(&std::any::TypeId::of::<R>());
self.world
.remove_resource::<Setup<R>>()
.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. [`Resource`]s must be added to Bevy apps
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_asset/src/debug_asset_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl Plugin for DebugAssetServerPlugin {
});
let mut debug_asset_app = App::new();
debug_asset_app
.insert_resource(AssetServerSettings {
.insert_setup_resource(AssetServerSettings {
asset_folder: "crates".to_string(),
watch_for_changes: true,
})
Expand Down
5 changes: 3 additions & 2 deletions crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub struct AssetPlugin;
///
/// This resource must be added before the [`AssetPlugin`] or `DefaultPlugins` to take effect.
#[derive(Resource)]
#[resource(setup)]
pub struct AssetServerSettings {
/// The base folder where assets are loaded from, relative to the executable.
pub asset_folder: String,
Expand All @@ -93,8 +94,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, settings.watch_for_changes);
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 @@ -29,9 +29,7 @@ pub struct CorePlugin;
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();

Expand Down
1 change: 1 addition & 0 deletions crates/bevy_core/src/task_pool_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ impl TaskPoolThreadAssignmentPolicy {
/// insert the default task pools into the resource map manually. If the pools are already inserted,
/// this helper will do nothing.
#[derive(Clone, Resource)]
#[resource(setup)]
pub struct DefaultTaskPoolOptions {
/// If the number of physical cores is less than min_total_threads, force using
/// min_total_threads
Expand Down
18 changes: 0 additions & 18 deletions crates/bevy_ecs/macros/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,6 @@ use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens};
use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident, Path, Result};

pub fn derive_resource(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();

ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });

let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();

TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::system::Resource for #struct_name #type_generics #where_clause {
}
})
}

pub fn derive_component(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();
Expand Down
5 changes: 3 additions & 2 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ extern crate proc_macro;

mod component;
mod fetch;
mod resource;

use crate::fetch::derive_world_query_impl;
use bevy_macro_utils::{derive_label, get_named_struct_fields, BevyManifest};
Expand Down Expand Up @@ -454,9 +455,9 @@ pub(crate) fn bevy_ecs_path() -> syn::Path {
BevyManifest::default().get_path("bevy_ecs")
}

#[proc_macro_derive(Resource)]
#[proc_macro_derive(Resource, attributes(resource))]
pub fn derive_resource(input: TokenStream) -> TokenStream {
component::derive_resource(input)
resource::derive_resource(input)
}

#[proc_macro_derive(Component, attributes(component))]
Expand Down
73 changes: 73 additions & 0 deletions crates/bevy_ecs/macros/src/resource.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use bevy_macro_utils::Symbol;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident, Path, Result};

pub fn derive_resource(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();

let attrs = match parse_resource_attr(&ast) {
Ok(attrs) => attrs,
Err(e) => return e.into_compile_error().into(),
};

let is_setup_resource = Ident::new(&format!("{}", attrs.is_setup_resource), Span::call_site());

ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });

let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();

TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::system::Resource for #struct_name #type_generics #where_clause {
const IS_SETUP_RESOURCE: bool = #is_setup_resource;
}
})
}

pub const RESOURCE: Symbol = Symbol("resource");
pub const SETUP_RESOURCE: Symbol = Symbol("setup");

struct Attrs {
is_setup_resource: bool,
}

fn parse_resource_attr(ast: &DeriveInput) -> Result<Attrs> {
let meta_items = bevy_macro_utils::parse_attrs(ast, RESOURCE)?;

let mut attrs = Attrs {
is_setup_resource: false,
};

for meta in meta_items {
use syn::{
Meta::Path,
NestedMeta::{Lit, Meta},
};
match meta {
Meta(Path(m)) if m == SETUP_RESOURCE => attrs.is_setup_resource = true,
Meta(meta_item) => {
return Err(Error::new_spanned(
meta_item.path(),
format!(
"unknown component attribute `{}`",
meta_item.path().into_token_stream()
),
));
}
Lit(lit) => {
return Err(Error::new_spanned(
lit,
"unexpected literal in component attribute",
))
}
}
}

Ok(attrs)
}
16 changes: 12 additions & 4 deletions crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,9 +439,13 @@ impl<'w, 's> Commands<'w, 's> {
/// # bevy_ecs::system::assert_is_system(initialise_scoreboard);
/// ```
pub fn init_resource<R: Resource + FromWorld>(&mut self) {
self.queue.push(InitResource::<R> {
_phantom: PhantomData::<R>::default(),
});
if !R::IS_SETUP_RESOURCE {
self.queue.push(InitResource::<R> {
_phantom: PhantomData::<R>::default(),
});
} else {
error!("Initializing resource {} during execution has no effect. It should be added during setup using `insert_setup_resource`.", std::any::type_name::<R>());
}
}

/// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with a specific value.
Expand Down Expand Up @@ -470,7 +474,11 @@ impl<'w, 's> Commands<'w, 's> {
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn insert_resource<R: Resource>(&mut self, resource: R) {
self.queue.push(InsertResource { resource });
if !R::IS_SETUP_RESOURCE {
self.queue.push(InsertResource { resource });
} else {
error!("Inserting resource {} during execution has no effect. It should be added during setup using `insert_setup_resource`.", std::any::type_name::<R>());
}
}

/// Pushes a [`Command`] to the queue for removing a [`Resource`] from the [`World`].
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,9 @@ impl_param_set!();
/// # schedule.add_system_to_stage("update", write_resource_system.after("first"));
/// # schedule.run_once(&mut world);
/// ```
pub trait Resource: Send + Sync + 'static {}
pub trait Resource: Send + Sync + 'static {
const IS_SETUP_RESOURCE: bool = false;
}

/// Shared borrow of a [`Resource`].
///
Expand Down
23 changes: 15 additions & 8 deletions crates/bevy_ecs/src/world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
system::Resource,
};
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
use bevy_utils::tracing::debug;
use bevy_utils::tracing::{debug, error};
use std::{
any::TypeId,
fmt,
Expand Down Expand Up @@ -734,13 +734,20 @@ impl World {
/// you will overwrite any existing data.
#[inline]
pub fn insert_resource<R: Resource>(&mut self, value: R) {
let component_id = self.components.init_resource::<R>();
OwningPtr::make(value, |ptr| {
// SAFETY: component_id was just initialized and corresponds to resource of type R
unsafe {
self.insert_resource_by_id(component_id, ptr);
}
});
if !R::IS_SETUP_RESOURCE {
let component_id = self.components.init_resource::<R>();
OwningPtr::make(value, |ptr| {
// SAFETY: component_id was just initialized and corresponds to resource of type R
unsafe {
self.insert_resource_by_id(component_id, ptr);
}
});
} else {
error!(
"Inserting setup resource {} as a normal resource, ignoring",
std::any::type_name::<R>()
);
}
}

/// Inserts a new non-send resource with standard starting values.
Expand Down
9 changes: 6 additions & 3 deletions crates/bevy_log/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ 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_app::{App, NoopPluginGroup as DefaultPlugins};
/// # 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,bevy_ecs=trace".to_string(),
/// })
Expand Down Expand Up @@ -92,6 +92,7 @@ pub struct LogPlugin;

/// `LogPlugin` settings
#[derive(Resource)]
#[resource(setup)]
pub struct LogSettings {
/// Filters logs using the [`EnvFilter`] format
pub filter: String,
Expand Down Expand Up @@ -122,7 +123,9 @@ impl Plugin for LogPlugin {
}

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
3 changes: 2 additions & 1 deletion crates/bevy_render/src/texture/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,9 @@ impl ImageSampler {

/// Global resource for [`Image`] settings.
///
/// Can be set via `insert_resource` during app initialization to change the default settings.
/// Can be set via `insert_setup_resource` during app initialization to change the default settings.
#[derive(Resource)]
#[resource(setup)]
pub struct ImageSettings {
/// The default image sampler to use when [`ImageSampler`] is set to `Default`.
pub default_sampler: wgpu::SamplerDescriptor<'static>,
Expand Down
Loading