Skip to content

Commit 8af2460

Browse files
committed
add setup resources
1 parent 1914696 commit 8af2460

38 files changed

+210
-77
lines changed

crates/bevy_app/src/app.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ use bevy_ecs::{
1111
system::Resource,
1212
world::World,
1313
};
14-
use bevy_utils::{tracing::debug, HashMap};
14+
use bevy_utils::{
15+
tracing::{debug, error},
16+
HashMap,
17+
};
1518
use std::fmt::Debug;
1619

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

34+
/// Wrapper struct for setting setup resources aside
35+
#[derive(Resource)]
36+
struct Setup<T>(T);
37+
3138
#[allow(clippy::needless_doctest_main)]
3239
/// A container of app logic and data.
3340
///
@@ -68,6 +75,7 @@ pub struct App {
6875
/// A container of [`Stage`]s set to be run in a linear order.
6976
pub schedule: Schedule,
7077
sub_apps: HashMap<AppLabelId, SubApp>,
78+
setup_resources: HashMap<std::any::TypeId, &'static str>,
7179
}
7280

7381
/// Each `SubApp` has its own [`Schedule`] and [`World`], enabling a separation of concerns.
@@ -111,6 +119,7 @@ impl App {
111119
schedule: Default::default(),
112120
runner: Box::new(run_once),
113121
sub_apps: HashMap::default(),
122+
setup_resources: Default::default(),
114123
}
115124
}
116125

@@ -136,6 +145,8 @@ impl App {
136145
#[cfg(feature = "trace")]
137146
let _bevy_app_run_span = info_span!("bevy_app").entered();
138147

148+
self.check_all_setup_resources_consumed();
149+
139150
let mut app = std::mem::replace(self, App::empty());
140151
let runner = std::mem::replace(&mut app.runner, Box::new(run_once));
141152
(runner)(app);
@@ -654,6 +665,52 @@ impl App {
654665
self
655666
}
656667

668+
/// Inserts a setup resource to the current [App] and overwrites any resource
669+
/// previously added of the same type.
670+
///
671+
/// A setup resource is used at startup for plugin initialisation and configuration.
672+
/// All setup resources inserted must be consumed by a plugin and removed before the
673+
/// application is ran.
674+
pub fn insert_setup_resource<R>(&mut self, resource: R) -> &mut Self
675+
where
676+
R: Resource,
677+
{
678+
if R::IS_SETUP_RESOURCE {
679+
self.setup_resources
680+
.insert(std::any::TypeId::of::<R>(), std::any::type_name::<R>());
681+
self.insert_resource(Setup(resource));
682+
} else {
683+
// Using `eprintln` here as this is supposed to be called before logs are set up
684+
eprintln!(
685+
"Resource {} is not a setup resource",
686+
std::any::type_name::<R>()
687+
);
688+
}
689+
self
690+
}
691+
692+
/// Consumes a setup resource, and removes it from the current [App] so that a plugin
693+
/// can use it for its setup.
694+
pub fn consume_setup_resource<R>(&mut self) -> Option<R>
695+
where
696+
R: Resource,
697+
{
698+
self.setup_resources.remove(&std::any::TypeId::of::<R>());
699+
self.world
700+
.remove_resource::<Setup<R>>()
701+
.map(|setup| setup.0)
702+
}
703+
704+
/// Check that all setup resources have been consumed, panicking otherwise.
705+
fn check_all_setup_resources_consumed(&self) {
706+
self.setup_resources
707+
.values()
708+
.for_each(|v| error!("Setup resource \"{}\" has not been consumed", v));
709+
if !self.setup_resources.is_empty() {
710+
panic!("Not all setup resources have been consumed. This can happen if you inserted a setup resource after the plugin consuming it.")
711+
}
712+
}
713+
657714
/// Inserts a [`Resource`] to the current [`App`] and overwrites any [`Resource`] previously added of the same type.
658715
///
659716
/// A [`Resource`] in Bevy represents globally unique data. [`Resource`]s must be added to Bevy apps

crates/bevy_asset/src/debug_asset_server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl Plugin for DebugAssetServerPlugin {
7676
});
7777
let mut debug_asset_app = App::new();
7878
debug_asset_app
79-
.insert_resource(AssetServerSettings {
79+
.insert_setup_resource(AssetServerSettings {
8080
asset_folder: "crates".to_string(),
8181
watch_for_changes: true,
8282
})

crates/bevy_asset/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ pub struct AssetPlugin;
6868
///
6969
/// This resource must be added before the [`AssetPlugin`] or `DefaultPlugins` to take effect.
7070
#[derive(Resource)]
71+
#[resource(setup)]
7172
pub struct AssetServerSettings {
7273
/// The base folder where assets are loaded from, relative to the executable.
7374
pub asset_folder: String,
@@ -91,8 +92,8 @@ impl Default for AssetServerSettings {
9192
/// delegate to the default `AssetIo` for the platform.
9293
pub fn create_platform_default_asset_io(app: &mut App) -> Box<dyn AssetIo> {
9394
let settings = app
94-
.world
95-
.get_resource_or_insert_with(AssetServerSettings::default);
95+
.consume_setup_resource::<AssetServerSettings>()
96+
.unwrap_or_default();
9697

9798
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
9899
let source = FileAssetIo::new(&settings.asset_folder, settings.watch_for_changes);

crates/bevy_core/src/lib.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ pub struct CorePlugin;
2727
impl Plugin for CorePlugin {
2828
fn build(&self, app: &mut App) {
2929
// Setup the default bevy task pools
30-
app.world
31-
.get_resource::<DefaultTaskPoolOptions>()
32-
.cloned()
30+
app.consume_setup_resource::<DefaultTaskPoolOptions>()
3331
.unwrap_or_default()
3432
.create_default_pools();
3533

crates/bevy_core/src/task_pool_options.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ impl TaskPoolThreadAssignmentPolicy {
3535
/// insert the default task pools into the resource map manually. If the pools are already inserted,
3636
/// this helper will do nothing.
3737
#[derive(Clone, Resource)]
38+
#[resource(setup)]
3839
pub struct DefaultTaskPoolOptions {
3940
/// If the number of physical cores is less than min_total_threads, force using
4041
/// min_total_threads

crates/bevy_ecs/macros/src/component.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,6 @@ use proc_macro2::{Span, TokenStream as TokenStream2};
44
use quote::{quote, ToTokens};
55
use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident, Path, Result};
66

7-
pub fn derive_resource(input: TokenStream) -> TokenStream {
8-
let mut ast = parse_macro_input!(input as DeriveInput);
9-
let bevy_ecs_path: Path = crate::bevy_ecs_path();
10-
11-
ast.generics
12-
.make_where_clause()
13-
.predicates
14-
.push(parse_quote! { Self: Send + Sync + 'static });
15-
16-
let struct_name = &ast.ident;
17-
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
18-
19-
TokenStream::from(quote! {
20-
impl #impl_generics #bevy_ecs_path::system::Resource for #struct_name #type_generics #where_clause {
21-
}
22-
})
23-
}
24-
257
pub fn derive_component(input: TokenStream) -> TokenStream {
268
let mut ast = parse_macro_input!(input as DeriveInput);
279
let bevy_ecs_path: Path = crate::bevy_ecs_path();

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ extern crate proc_macro;
22

33
mod component;
44
mod fetch;
5+
mod resource;
56

67
use crate::fetch::derive_world_query_impl;
78
use bevy_macro_utils::{derive_label, get_named_struct_fields, BevyManifest};
@@ -496,9 +497,9 @@ pub(crate) fn bevy_ecs_path() -> syn::Path {
496497
BevyManifest::default().get_path("bevy_ecs")
497498
}
498499

499-
#[proc_macro_derive(Resource)]
500+
#[proc_macro_derive(Resource, attributes(resource))]
500501
pub fn derive_resource(input: TokenStream) -> TokenStream {
501-
component::derive_resource(input)
502+
resource::derive_resource(input)
502503
}
503504

504505
#[proc_macro_derive(Component, attributes(component))]
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use bevy_macro_utils::Symbol;
2+
use proc_macro::TokenStream;
3+
use proc_macro2::Span;
4+
use quote::{quote, ToTokens};
5+
use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident, Path, Result};
6+
7+
pub fn derive_resource(input: TokenStream) -> TokenStream {
8+
let mut ast = parse_macro_input!(input as DeriveInput);
9+
let bevy_ecs_path: Path = crate::bevy_ecs_path();
10+
11+
let attrs = match parse_resource_attr(&ast) {
12+
Ok(attrs) => attrs,
13+
Err(e) => return e.into_compile_error().into(),
14+
};
15+
16+
let is_setup_resource = Ident::new(&format!("{}", attrs.is_setup_resource), Span::call_site());
17+
18+
ast.generics
19+
.make_where_clause()
20+
.predicates
21+
.push(parse_quote! { Self: Send + Sync + 'static });
22+
23+
let struct_name = &ast.ident;
24+
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
25+
26+
TokenStream::from(quote! {
27+
impl #impl_generics #bevy_ecs_path::system::Resource for #struct_name #type_generics #where_clause {
28+
const IS_SETUP_RESOURCE: bool = #is_setup_resource;
29+
}
30+
})
31+
}
32+
33+
pub const RESOURCE: Symbol = Symbol("resource");
34+
pub const SETUP_RESOURCE: Symbol = Symbol("setup");
35+
36+
struct Attrs {
37+
is_setup_resource: bool,
38+
}
39+
40+
fn parse_resource_attr(ast: &DeriveInput) -> Result<Attrs> {
41+
let meta_items = bevy_macro_utils::parse_attrs(ast, RESOURCE)?;
42+
43+
let mut attrs = Attrs {
44+
is_setup_resource: false,
45+
};
46+
47+
for meta in meta_items {
48+
use syn::{
49+
Meta::Path,
50+
NestedMeta::{Lit, Meta},
51+
};
52+
match meta {
53+
Meta(Path(m)) if m == SETUP_RESOURCE => attrs.is_setup_resource = true,
54+
Meta(meta_item) => {
55+
return Err(Error::new_spanned(
56+
meta_item.path(),
57+
format!(
58+
"unknown component attribute `{}`",
59+
meta_item.path().into_token_stream()
60+
),
61+
));
62+
}
63+
Lit(lit) => {
64+
return Err(Error::new_spanned(
65+
lit,
66+
"unexpected literal in component attribute",
67+
))
68+
}
69+
}
70+
}
71+
72+
Ok(attrs)
73+
}

crates/bevy_ecs/src/system/commands/mod.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -373,9 +373,13 @@ impl<'w, 's> Commands<'w, 's> {
373373
/// # bevy_ecs::system::assert_is_system(initialise_scoreboard);
374374
/// ```
375375
pub fn init_resource<R: Resource + FromWorld>(&mut self) {
376-
self.queue.push(InitResource::<R> {
377-
_phantom: PhantomData::<R>::default(),
378-
});
376+
if !R::IS_SETUP_RESOURCE {
377+
self.queue.push(InitResource::<R> {
378+
_phantom: PhantomData::<R>::default(),
379+
});
380+
} else {
381+
error!("Initializing resource {} during execution has no effect. It should be added during setup using `insert_setup_resource`.", std::any::type_name::<R>());
382+
}
379383
}
380384

381385
/// Inserts a resource to the [`World`], overwriting any previous value of the same type.
@@ -404,7 +408,11 @@ impl<'w, 's> Commands<'w, 's> {
404408
/// # bevy_ecs::system::assert_is_system(system);
405409
/// ```
406410
pub fn insert_resource<R: Resource>(&mut self, resource: R) {
407-
self.queue.push(InsertResource { resource });
411+
if !R::IS_SETUP_RESOURCE {
412+
self.queue.push(InsertResource { resource });
413+
} else {
414+
error!("Inserting resource {} during execution has no effect. It should be added during setup using `insert_setup_resource`.", std::any::type_name::<R>());
415+
}
408416
}
409417

410418
/// Removes a resource from the [`World`].

crates/bevy_ecs/src/system/system_param.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,9 @@ impl_param_set!();
252252
/// # schedule.add_system_to_stage("update", write_resource_system.after("first"));
253253
/// # schedule.run_once(&mut world);
254254
/// ```
255-
pub trait Resource: Send + Sync + 'static {}
255+
pub trait Resource: Send + Sync + 'static {
256+
const IS_SETUP_RESOURCE: bool = false;
257+
}
256258

257259
/// Shared borrow of a [`Resource`].
258260
///

crates/bevy_ecs/src/world/mod.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::{
2121
system::Resource,
2222
};
2323
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
24-
use bevy_utils::tracing::debug;
24+
use bevy_utils::tracing::{debug, error};
2525
use std::{
2626
any::TypeId,
2727
fmt,
@@ -659,13 +659,20 @@ impl World {
659659
/// you will overwrite any existing data.
660660
#[inline]
661661
pub fn insert_resource<R: Resource>(&mut self, value: R) {
662-
let component_id = self.components.init_resource::<R>();
663-
OwningPtr::make(value, |ptr| {
664-
// SAFETY: component_id was just initialized and corresponds to resource of type R
665-
unsafe {
666-
self.insert_resource_by_id(component_id, ptr);
667-
}
668-
});
662+
if !R::IS_SETUP_RESOURCE {
663+
let component_id = self.components.init_resource::<R>();
664+
OwningPtr::make(value, |ptr| {
665+
// SAFETY: component_id was just initialized and corresponds to resource of type R
666+
unsafe {
667+
self.insert_resource_by_id(component_id, ptr);
668+
}
669+
});
670+
} else {
671+
error!(
672+
"Inserting setup resource {} as a normal resource, ignoring",
673+
std::any::type_name::<R>()
674+
);
675+
}
669676
}
670677

671678
/// Inserts a new non-send resource with standard starting values.

crates/bevy_log/src/lib.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@ use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter};
4747
/// * Using [`tracing-wasm`](https://crates.io/crates/tracing-wasm) in WASM, logging
4848
/// to the browser console.
4949
///
50-
/// You can configure this plugin using the resource [`LogSettings`].
50+
/// You can configure this plugin using the setup resource [`LogSettings`].
5151
/// ```no_run
5252
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins};
5353
/// # use bevy_log::LogSettings;
5454
/// # use bevy_utils::tracing::Level;
5555
/// fn main() {
5656
/// App::new()
57-
/// .insert_resource(LogSettings {
57+
/// .insert_setup_resource(LogSettings {
5858
/// level: Level::DEBUG,
5959
/// filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(),
6060
/// })
@@ -92,6 +92,7 @@ pub struct LogPlugin;
9292

9393
/// `LogPlugin` settings
9494
#[derive(Resource)]
95+
#[resource(setup)]
9596
pub struct LogSettings {
9697
/// Filters logs using the [`EnvFilter`] format
9798
pub filter: String,
@@ -122,7 +123,9 @@ impl Plugin for LogPlugin {
122123
}
123124

124125
let default_filter = {
125-
let settings = app.world.get_resource_or_insert_with(LogSettings::default);
126+
let settings = app
127+
.consume_setup_resource::<LogSettings>()
128+
.unwrap_or_default();
126129
format!("{},{}", settings.level, settings.filter)
127130
};
128131
LogTracer::init().unwrap();

crates/bevy_render/src/texture/image.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,9 @@ impl ImageSampler {
160160

161161
/// Global resource for [`Image`] settings.
162162
///
163-
/// Can be set via `insert_resource` during app initialization to change the default settings.
163+
/// Can be set via `insert_setup_resource` during app initialization to change the default settings.
164164
#[derive(Resource)]
165+
#[resource(setup)]
165166
pub struct ImageSettings {
166167
/// The default image sampler to use when [`ImageSampler`] is set to `Default`.
167168
pub default_sampler: wgpu::SamplerDescriptor<'static>,

0 commit comments

Comments
 (0)