Skip to content

Commit

Permalink
Make Resource trait opt-in, requiring #[derive(Resource)] V2 (bev…
Browse files Browse the repository at this point in the history
…yengine#5577)

*This PR description is an edited copy of bevyengine#5007, written by @alice-i-cecile.*
# Objective
Follow-up to bevyengine#2254. The `Resource` trait currently has a blanket implementation for all types that meet its bounds.

While ergonomic, this results in several drawbacks:

* it is possible to make confusing, silent mistakes such as inserting a function pointer (Foo) rather than a value (Foo::Bar) as a resource
* it is challenging to discover if a type is intended to be used as a resource
* we cannot later add customization options (see the [RFC](https://github.com/bevyengine/rfcs/blob/main/rfcs/27-derive-component.md) for the equivalent choice for Component).
* dependencies can use the same Rust type as a resource in invisibly conflicting ways
* raw Rust types used as resources cannot preserve privacy appropriately, as anyone able to access that type can read and write to internal values
* we cannot capture a definitive list of possible resources to display to users in an editor
## Notes to reviewers
 * Review this commit-by-commit; there's effectively no back-tracking and there's a lot of churn in some of these commits.
   *ira: My commits are not as well organized :')*
 * I've relaxed the bound on Local to Send + Sync + 'static: I don't think these concerns apply there, so this can keep things simple. Storing e.g. a u32 in a Local is fine, because there's a variable name attached explaining what it does.
 * I think this is a bad place for the Resource trait to live, but I've left it in place to make reviewing easier. IMO that's best tackled with bevyengine#4981.

## Changelog
`Resource` is no longer automatically implemented for all matching types. Instead, use the new `#[derive(Resource)]` macro.

## Migration Guide
Add `#[derive(Resource)]` to all types you are using as a resource.

If you are using a third party type as a resource, wrap it in a tuple struct to bypass orphan rules. Consider deriving `Deref` and `DerefMut` to improve ergonomics.

`ClearColor` no longer implements `Component`. Using `ClearColor` as a component in 0.8 did nothing.
Use the `ClearColorConfig` in the `Camera3d` and `Camera2d` components instead.


Co-authored-by: Alice <alice.i.cecile@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: devil-ira <justthecooldude@gmail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
  • Loading branch information
4 people authored and maccesch committed Sep 28, 2022
1 parent b2b57d7 commit fee9d53
Show file tree
Hide file tree
Showing 133 changed files with 804 additions and 524 deletions.
4 changes: 2 additions & 2 deletions benches/benches/bevy_ecs/scheduling/run_criteria.rs
@@ -1,6 +1,6 @@
use bevy_ecs::{
component::Component,
prelude::{ParallelSystemDescriptorCoercion, Res, RunCriteriaDescriptorCoercion},
prelude::{ParallelSystemDescriptorCoercion, Res, Resource, RunCriteriaDescriptorCoercion},
schedule::{ShouldRun, Stage, SystemStage},
system::Query,
world::World,
Expand Down Expand Up @@ -136,7 +136,7 @@ pub fn run_criteria_no_with_labels(criterion: &mut Criterion) {
group.finish();
}

#[derive(Component)]
#[derive(Component, Resource)]
struct TestBool(pub bool);

pub fn run_criteria_yes_with_query(criterion: &mut Criterion) {
Expand Down
21 changes: 16 additions & 5 deletions crates/bevy_app/src/app.rs
@@ -1,5 +1,6 @@
use crate::{CoreStage, Plugin, PluginGroup, PluginGroupBuilder, StartupSchedule, StartupStage};
pub use bevy_derive::AppLabel;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
event::{Event, Events},
prelude::{FromWorld, IntoExclusiveSystem},
Expand All @@ -22,6 +23,11 @@ bevy_utils::define_label!(
AppLabelId,
);

/// The [`Resource`] that stores the [`App`]'s [`TypeRegistry`](bevy_reflect::TypeRegistry).
#[cfg(feature = "bevy_reflect")]
#[derive(Resource, Clone, Deref, DerefMut, Default)]
pub struct AppTypeRegistry(pub bevy_reflect::TypeRegistryArc);

#[allow(clippy::needless_doctest_main)]
/// A container of app logic and data.
///
Expand Down Expand Up @@ -74,7 +80,7 @@ impl Default for App {
fn default() -> Self {
let mut app = App::empty();
#[cfg(feature = "bevy_reflect")]
app.init_resource::<bevy_reflect::TypeRegistryArc>();
app.init_resource::<AppTypeRegistry>();

app.add_default_stages()
.add_event::<AppExit>()
Expand Down Expand Up @@ -647,7 +653,9 @@ impl App {
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(Resource)]
/// struct MyCounter {
/// counter: usize,
/// }
Expand All @@ -660,15 +668,16 @@ impl App {
self
}

/// Inserts a non-send [`Resource`] to the app.
/// Inserts a non-send resource to the app.
///
/// You usually want to use [`insert_resource`](Self::insert_resource),
/// but there are some special cases when a [`Resource`] cannot be sent across threads.
/// but there are some special cases when a resource cannot be sent across threads.
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #
/// struct MyCounter {
/// counter: usize,
Expand All @@ -694,7 +703,9 @@ impl App {
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(Resource)]
/// struct MyCounter {
/// counter: usize,
/// }
Expand Down Expand Up @@ -873,7 +884,7 @@ impl App {
#[cfg(feature = "bevy_reflect")]
pub fn register_type<T: bevy_reflect::GetTypeRegistration>(&mut self) -> &mut Self {
{
let registry = self.world.resource_mut::<bevy_reflect::TypeRegistryArc>();
let registry = self.world.resource_mut::<AppTypeRegistry>();
registry.write().register::<T>();
}
self
Expand Down Expand Up @@ -906,7 +917,7 @@ impl App {
&mut self,
) -> &mut Self {
{
let registry = self.world.resource_mut::<bevy_reflect::TypeRegistryArc>();
let registry = self.world.resource_mut::<AppTypeRegistry>();
registry.write().register_type_data::<T, D>();
}
self
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_app/src/ci_testing.rs
@@ -1,14 +1,15 @@
use crate::{app::AppExit, App};
use serde::Deserialize;

use bevy_ecs::prelude::Resource;
use bevy_utils::tracing::info;

/// A configuration struct for automated CI testing.
///
/// It gets used when the `bevy_ci_testing` feature is enabled to automatically
/// exit a Bevy app when run through the CI. This is needed because otherwise
/// Bevy apps would be stuck in the game loop and wouldn't allow the CI to progress.
#[derive(Deserialize)]
#[derive(Deserialize, Resource)]
pub struct CiTestingConfig {
/// The number of frames after which Bevy should exit.
pub exit_after: Option<u32>,
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_app/src/lib.rs
Expand Up @@ -18,6 +18,9 @@ pub use schedule_runner::*;

#[allow(missing_docs)]
pub mod prelude {
#[cfg(feature = "bevy_reflect")]
#[doc(hidden)]
pub use crate::AppTypeRegistry;
#[doc(hidden)]
pub use crate::{
app::App, CoreStage, DynamicPlugin, Plugin, PluginGroup, StartupSchedule, StartupStage,
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_app/src/schedule_runner.rs
Expand Up @@ -3,6 +3,7 @@ use crate::{
plugin::Plugin,
};
use bevy_ecs::event::{Events, ManualEventReader};
use bevy_ecs::prelude::Resource;
use bevy_utils::{Duration, Instant};

#[cfg(target_arch = "wasm32")]
Expand Down Expand Up @@ -34,7 +35,7 @@ impl Default for RunMode {
/// The configuration information for the [`ScheduleRunnerPlugin`].
///
/// It gets added as a [`Resource`](bevy_ecs::system::Resource) inside of the [`ScheduleRunnerPlugin`].
#[derive(Copy, Clone, Default)]
#[derive(Copy, Clone, Default, Resource)]
pub struct ScheduleRunnerSettings {
/// Determines whether the [`Schedule`](bevy_ecs::schedule::Schedule) is run once or repeatedly.
pub run_mode: RunMode,
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_asset/src/asset_server.rs
Expand Up @@ -5,7 +5,7 @@ use crate::{
RefChange, RefChangeChannel, SourceInfo, SourceMeta,
};
use anyhow::Result;
use bevy_ecs::system::{Res, ResMut};
use bevy_ecs::system::{Res, ResMut, Resource};
use bevy_log::warn;
use bevy_tasks::IoTaskPool;
use bevy_utils::{Entry, HashMap, Uuid};
Expand Down Expand Up @@ -102,7 +102,7 @@ pub struct AssetServerInternal {
/// See the [`asset_loading`] example for more information.
///
/// [`asset_loading`]: https://github.com/bevyengine/bevy/tree/latest/examples/asset/asset_loading.rs
#[derive(Clone)]
#[derive(Clone, Resource)]
pub struct AssetServer {
pub(crate) server: Arc<AssetServerInternal>,
}
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_asset/src/assets.rs
Expand Up @@ -5,7 +5,7 @@ use crate::{
use bevy_app::App;
use bevy_ecs::{
event::{EventWriter, Events},
system::ResMut,
system::{ResMut, Resource},
world::FromWorld,
};
use bevy_utils::HashMap;
Expand Down Expand Up @@ -66,7 +66,7 @@ impl<T: Asset> Debug for AssetEvent<T> {
/// Remember, if there are no Strong handles for an asset (i.e. they have all been dropped), the
/// asset will unload. Make sure you always have a Strong handle when you want to keep an asset
/// loaded!
#[derive(Debug)]
#[derive(Debug, Resource)]
pub struct Assets<T: Asset> {
assets: HashMap<HandleId, T>,
events: Events<AssetEvent<T>>,
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_asset/src/debug_asset_server.rs
Expand Up @@ -6,7 +6,7 @@ use bevy_app::{App, Plugin};
use bevy_ecs::{
event::Events,
schedule::SystemLabel,
system::{NonSendMut, Res, ResMut, SystemState},
system::{NonSendMut, Res, ResMut, Resource, SystemState},
};
use bevy_tasks::{IoTaskPool, TaskPoolBuilder};
use bevy_utils::HashMap;
Expand Down Expand Up @@ -52,6 +52,7 @@ pub struct DebugAssetServerPlugin;

/// A collection that maps internal assets in a [`DebugAssetApp`]'s asset server to their mirrors in
/// the main [`App`].
#[derive(Resource)]
pub struct HandleMap<T: Asset> {
/// The collection of asset handles.
pub handles: HashMap<Handle<T>, Handle<T>>,
Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_asset/src/lib.rs
Expand Up @@ -43,7 +43,10 @@ pub use loader::*;
pub use path::*;

use bevy_app::{prelude::Plugin, App};
use bevy_ecs::schedule::{StageLabel, SystemStage};
use bevy_ecs::{
schedule::{StageLabel, SystemStage},
system::Resource,
};

/// The names of asset stages in an [`App`] schedule.
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
Expand All @@ -62,6 +65,7 @@ pub enum AssetStage {
pub struct AssetPlugin;

/// [`AssetServer`] settings.
#[derive(Resource)]
pub struct AssetServerSettings {
/// The base folder where assets are loaded from, relative to the executable.
pub asset_folder: String,
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_audio/src/audio.rs
@@ -1,9 +1,10 @@
use crate::{AudioSink, AudioSource, Decodable};
use bevy_asset::{Asset, Handle, HandleId};
use bevy_ecs::system::Resource;
use parking_lot::RwLock;
use std::{collections::VecDeque, fmt};

/// Use this resource to play audio
/// Use this [`Resource`] to play audio.
///
/// ```
/// # use bevy_ecs::system::Res;
Expand All @@ -13,6 +14,7 @@ use std::{collections::VecDeque, fmt};
/// audio.play(asset_server.load("my_sound.ogg"));
/// }
/// ```
#[derive(Resource)]
pub struct Audio<Source = AudioSource>
where
Source: Asset + Decodable,
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_core/src/task_pool_options.rs
@@ -1,3 +1,4 @@
use bevy_ecs::prelude::Resource;
use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuilder};
use bevy_utils::tracing::trace;

Expand Down Expand Up @@ -33,7 +34,7 @@ impl TaskPoolThreadAssignmentPolicy {
/// Helper for configuring and creating the default task pools. For end-users who want full control,
/// insert the default task pools into the resource map manually. If the pools are already inserted,
/// this helper will do nothing.
#[derive(Clone)]
#[derive(Clone, Resource)]
pub struct DefaultTaskPoolOptions {
/// If the number of physical cores is less than min_total_threads, force using
/// min_total_threads
Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_core_pipeline/src/clear_color.rs
Expand Up @@ -13,11 +13,11 @@ pub enum ClearColorConfig {
None,
}

/// When used as a resource, sets the color that is used to clear the screen between frames.
/// A [`Resource`] that stores the color that is used to clear the screen between frames.
///
/// This color appears as the "background" color for simple apps, when
/// there are portions of the screen with nothing rendered.
#[derive(Component, Clone, Debug, Deref, DerefMut, ExtractResource, Reflect)]
/// This color appears as the "background" color for simple apps,
/// when there are portions of the screen with nothing rendered.
#[derive(Resource, Clone, Debug, Deref, DerefMut, ExtractResource, Reflect)]
#[reflect(Resource)]
pub struct ClearColor(pub Color);

Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_diagnostic/src/diagnostic.rs
@@ -1,3 +1,4 @@
use bevy_ecs::system::Resource;
use bevy_log::warn;
use bevy_utils::{Duration, Instant, StableHashMap, Uuid};
use std::{borrow::Cow, collections::VecDeque};
Expand Down Expand Up @@ -154,7 +155,7 @@ impl Diagnostic {
}

/// A collection of [Diagnostic]s
#[derive(Debug, Default)]
#[derive(Debug, Default, Resource)]
pub struct Diagnostics {
// This uses a [`StableHashMap`] to ensure that the iteration order is deterministic between
// runs when all diagnostics are inserted in the same order.
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs
@@ -1,12 +1,13 @@
use crate::{Diagnostic, DiagnosticId, Diagnostics};
use bevy_app::prelude::*;
use bevy_ecs::system::{Res, ResMut};
use bevy_ecs::system::{Res, ResMut, Resource};
use bevy_time::Time;

/// Adds "frame time" diagnostic to an App, specifically "frame time", "fps" and "frame count"
#[derive(Default)]
pub struct FrameTimeDiagnosticsPlugin;

#[derive(Resource)]
pub struct FrameTimeDiagnosticsState {
frame_count: u64,
}
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_diagnostic/src/log_diagnostics_plugin.rs
@@ -1,6 +1,6 @@
use super::{Diagnostic, DiagnosticId, Diagnostics};
use bevy_app::prelude::*;
use bevy_ecs::system::{Res, ResMut};
use bevy_ecs::system::{Res, ResMut, Resource};
use bevy_log::{debug, info};
use bevy_time::{Time, Timer};
use bevy_utils::Duration;
Expand All @@ -13,6 +13,7 @@ pub struct LogDiagnosticsPlugin {
}

/// State used by the [`LogDiagnosticsPlugin`]
#[derive(Resource)]
struct LogDiagnosticsState {
timer: Timer,
filter: Option<Vec<DiagnosticId>>,
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_ecs/README.md
Expand Up @@ -91,7 +91,7 @@ Apps often require unique resources, such as asset collections, renderers, audio
```rust
use bevy_ecs::prelude::*;

#[derive(Default)]
#[derive(Resource, Default)]
struct Time {
seconds: f32,
}
Expand Down Expand Up @@ -213,6 +213,7 @@ Resources also expose change state:
```rust
use bevy_ecs::prelude::*;

#[derive(Resource)]
struct Time(f32);

// Prints "time changed!" if the Time resource has changed since the last run of the System
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/examples/change_detection.rs
Expand Up @@ -40,7 +40,7 @@ fn main() {
}

// This struct will be used as a Resource keeping track of the total amount of spawned entities
#[derive(Debug)]
#[derive(Debug, Resource)]
struct EntityCounter {
pub value: i32,
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/examples/resources.rs
Expand Up @@ -27,7 +27,7 @@ fn main() {
}

// Counter resource to be increased and read by systems
#[derive(Debug)]
#[derive(Debug, Resource)]
struct Counter {
pub value: i32,
}
Expand Down
18 changes: 18 additions & 0 deletions crates/bevy_ecs/macros/src/component.rs
Expand Up @@ -4,6 +4,24 @@ 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: 5 additions & 0 deletions crates/bevy_ecs/macros/src/lib.rs
Expand Up @@ -537,6 +537,11 @@ pub(crate) fn bevy_ecs_path() -> syn::Path {
BevyManifest::default().get_path("bevy_ecs")
}

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

#[proc_macro_derive(Component, attributes(component))]
pub fn derive_component(input: TokenStream) -> TokenStream {
component::derive_component(input)
Expand Down

0 comments on commit fee9d53

Please sign in to comment.