Skip to content

Commit f16768d

Browse files
committed
bevy_derive: Add derives for Deref and DerefMut (bevyengine#4328)
# Objective A common pattern in Rust is the [newtype](https://doc.rust-lang.org/rust-by-example/generics/new_types.html). This is an especially useful pattern in Bevy as it allows us to give common/foreign types different semantics (such as allowing it to implement `Component` or `FromWorld`) or to simply treat them as a "new type" (clever). For example, it allows us to wrap a common `Vec<String>` and do things like: ```rust #[derive(Component)] struct Items(Vec<String>); fn give_sword(query: Query<&mut Items>) { query.single_mut().0.push(String::from("Flaming Poisoning Raging Sword of Doom")); } ``` > We could then define another struct that wraps `Vec<String>` without anything clashing in the query. However, one of the worst parts of this pattern is the ugly `.0` we have to write in order to access the type we actually care about. This is why people often implement `Deref` and `DerefMut` in order to get around this. Since it's such a common pattern, especially for Bevy, it makes sense to add a derive macro to automatically add those implementations. ## Solution Added a derive macro for `Deref` and another for `DerefMut` (both exported into the prelude). This works on all structs (including tuple structs) as long as they only contain a single field: ```rust #[derive(Deref)] struct Foo(String); #[derive(Deref, DerefMut)] struct Bar { name: String, } ``` This allows us to then remove that pesky `.0`: ```rust #[derive(Component, Deref, DerefMut)] struct Items(Vec<String>); fn give_sword(query: Query<&mut Items>) { query.single_mut().push(String::from("Flaming Poisoning Raging Sword of Doom")); } ``` ### Alternatives There are other alternatives to this such as by using the [`derive_more`](https://crates.io/crates/derive_more) crate. However, it doesn't seem like we need an entire crate just yet since we only need `Deref` and `DerefMut` (for now). ### Considerations One thing to consider is that the Rust std library recommends _not_ using `Deref` and `DerefMut` for things like this: "`Deref` should only be implemented for smart pointers to avoid confusion" ([reference](https://doc.rust-lang.org/std/ops/trait.Deref.html)). Personally, I believe it makes sense to use it in the way described above, but others may disagree. ### Additional Context Discord: https://discord.com/channels/691052431525675048/692572690833473578/956648422163746827 (controversiality discussed [here](https://discord.com/channels/691052431525675048/692572690833473578/956711911481835630)) --- ## Changelog - Add `Deref` derive macro (exported to prelude) - Add `DerefMut` derive macro (exported to prelude) - Updated most newtypes in examples to use one or both derives Co-authored-by: MrGVSV <49806985+MrGVSV@users.noreply.github.com>
1 parent 28ba87e commit f16768d

18 files changed

+174
-37
lines changed

crates/bevy_derive/src/derefs.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use proc_macro::{Span, TokenStream};
2+
use quote::quote;
3+
use syn::{parse_macro_input, Data, DeriveInput, Index, Member, Type};
4+
5+
pub fn derive_deref(input: TokenStream) -> TokenStream {
6+
let ast = parse_macro_input!(input as DeriveInput);
7+
8+
let ident = &ast.ident;
9+
let (field_member, field_type) = match get_inner_field(&ast, false) {
10+
Ok(items) => items,
11+
Err(err) => {
12+
return err.into_compile_error().into();
13+
}
14+
};
15+
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
16+
17+
TokenStream::from(quote! {
18+
impl #impl_generics ::std::ops::Deref for #ident #ty_generics #where_clause {
19+
type Target = #field_type;
20+
21+
fn deref(&self) -> &Self::Target {
22+
&self.#field_member
23+
}
24+
}
25+
})
26+
}
27+
28+
pub fn derive_deref_mut(input: TokenStream) -> TokenStream {
29+
let ast = parse_macro_input!(input as DeriveInput);
30+
31+
let ident = &ast.ident;
32+
let (field_member, _) = match get_inner_field(&ast, true) {
33+
Ok(items) => items,
34+
Err(err) => {
35+
return err.into_compile_error().into();
36+
}
37+
};
38+
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
39+
40+
TokenStream::from(quote! {
41+
impl #impl_generics ::std::ops::DerefMut for #ident #ty_generics #where_clause {
42+
fn deref_mut(&mut self) -> &mut Self::Target {
43+
&mut self.#field_member
44+
}
45+
}
46+
})
47+
}
48+
49+
fn get_inner_field(ast: &DeriveInput, is_mut: bool) -> syn::Result<(Member, &Type)> {
50+
match &ast.data {
51+
Data::Struct(data_struct) if data_struct.fields.len() == 1 => {
52+
let field = data_struct.fields.iter().next().unwrap();
53+
let member = field
54+
.ident
55+
.as_ref()
56+
.map(|name| Member::Named(name.clone()))
57+
.unwrap_or_else(|| Member::Unnamed(Index::from(0)));
58+
Ok((member, &field.ty))
59+
}
60+
_ => {
61+
let msg = if is_mut {
62+
"DerefMut can only be derived for structs with a single field"
63+
} else {
64+
"Deref can only be derived for structs with a single field"
65+
};
66+
Err(syn::Error::new(Span::call_site().into(), msg))
67+
}
68+
}
69+
}

crates/bevy_derive/src/lib.rs

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

33
mod app_plugin;
44
mod bevy_main;
5+
mod derefs;
56
mod enum_variant_meta;
67
mod modules;
78

@@ -15,6 +16,61 @@ pub fn derive_dynamic_plugin(input: TokenStream) -> TokenStream {
1516
app_plugin::derive_dynamic_plugin(input)
1617
}
1718

19+
/// Implements [`Deref`] for _single-item_ structs. This is especially useful when
20+
/// utilizing the [newtype] pattern.
21+
///
22+
/// If you need [`DerefMut`] as well, consider using the other [derive] macro alongside
23+
/// this one.
24+
///
25+
/// # Example
26+
///
27+
/// ```
28+
/// use bevy_derive::Deref;
29+
///
30+
/// #[derive(Deref)]
31+
/// struct MyNewtype(String);
32+
///
33+
/// let foo = MyNewtype(String::from("Hello"));
34+
/// assert_eq!(5, foo.len());
35+
/// ```
36+
///
37+
/// [`Deref`]: std::ops::Deref
38+
/// [newtype]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html
39+
/// [`DerefMut`]: std::ops::DerefMut
40+
/// [derive]: crate::derive_deref_mut
41+
#[proc_macro_derive(Deref)]
42+
pub fn derive_deref(input: TokenStream) -> TokenStream {
43+
derefs::derive_deref(input)
44+
}
45+
46+
/// Implements [`DerefMut`] for _single-item_ structs. This is especially useful when
47+
/// utilizing the [newtype] pattern.
48+
///
49+
/// [`DerefMut`] requires a [`Deref`] implementation. You can implement it manually or use
50+
/// Bevy's [derive] macro for convenience.
51+
///
52+
/// # Example
53+
///
54+
/// ```
55+
/// use bevy_derive::{Deref, DerefMut};
56+
///
57+
/// #[derive(Deref, DerefMut)]
58+
/// struct MyNewtype(String);
59+
///
60+
/// let mut foo = MyNewtype(String::from("Hello"));
61+
/// foo.push_str(" World!");
62+
/// assert_eq!("Hello World!", *foo);
63+
/// ```
64+
///
65+
/// [`DerefMut`]: std::ops::DerefMut
66+
/// [newtype]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html
67+
/// [`Deref`]: std::ops::Deref
68+
/// [derive]: crate::derive_deref
69+
#[proc_macro_derive(DerefMut)]
70+
pub fn derive_deref_mut(input: TokenStream) -> TokenStream {
71+
derefs::derive_deref_mut(input)
72+
}
73+
1874
#[proc_macro_attribute]
1975
pub fn bevy_main(attr: TokenStream, item: TokenStream) -> TokenStream {
2076
bevy_main::bevy_main(attr, item)

crates/bevy_internal/src/prelude.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub use crate::{
55
transform::prelude::*, utils::prelude::*, window::prelude::*, DefaultPlugins, MinimalPlugins,
66
};
77

8-
pub use bevy_derive::bevy_main;
8+
pub use bevy_derive::{bevy_main, Deref, DerefMut};
99

1010
#[doc(hidden)]
1111
#[cfg(feature = "bevy_audio")]

examples/2d/contributors.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ struct ContributorSelection {
2727
idx: usize,
2828
}
2929

30+
#[derive(Deref, DerefMut)]
3031
struct SelectTimer(Timer);
3132

3233
#[derive(Component)]
@@ -161,7 +162,7 @@ fn select_system(
161162
mut query: Query<(&Contributor, &mut Sprite, &mut Transform)>,
162163
time: Res<Time>,
163164
) {
164-
if !timer.0.tick(time.delta()).just_finished() {
165+
if !timer.tick(time.delta()).just_finished() {
165166
return;
166167
}
167168

examples/2d/many_sprites.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Cam
7474
* Transform::from_translation(Vec3::X * CAMERA_SPEED * time.delta_seconds());
7575
}
7676

77+
#[derive(Deref, DerefMut)]
7778
struct PrintingTimer(Timer);
7879

7980
impl Default for PrintingTimer {
@@ -84,9 +85,9 @@ impl Default for PrintingTimer {
8485

8586
// System for printing the number of sprites on every tick of the timer
8687
fn print_sprite_count(time: Res<Time>, mut timer: Local<PrintingTimer>, sprites: Query<&Sprite>) {
87-
timer.0.tick(time.delta());
88+
timer.tick(time.delta());
8889

89-
if timer.0.just_finished() {
90+
if timer.just_finished() {
9091
info!("Sprites: {}", sprites.iter().count(),);
9192
}
9293
}

examples/2d/sprite_sheet.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ fn main() {
88
.run();
99
}
1010

11-
#[derive(Component)]
11+
#[derive(Component, Deref, DerefMut)]
1212
struct AnimationTimer(Timer);
1313

1414
fn animate_sprite(
@@ -21,8 +21,8 @@ fn animate_sprite(
2121
)>,
2222
) {
2323
for (mut timer, mut sprite, texture_atlas_handle) in query.iter_mut() {
24-
timer.0.tick(time.delta());
25-
if timer.0.just_finished() {
24+
timer.tick(time.delta());
25+
if timer.just_finished() {
2626
let texture_atlas = texture_atlases.get(texture_atlas_handle).unwrap();
2727
sprite.index = (sprite.index + 1) % texture_atlas.textures.len();
2828
}

examples/3d/many_cubes.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,9 @@ fn print_mesh_count(
145145
mut timer: Local<PrintingTimer>,
146146
sprites: Query<(&Handle<Mesh>, &ComputedVisibility)>,
147147
) {
148-
timer.0.tick(time.delta());
148+
timer.tick(time.delta());
149149

150-
if timer.0.just_finished() {
150+
if timer.just_finished() {
151151
info!(
152152
"Meshes: {} - Visible Meshes {}",
153153
sprites.iter().len(),
@@ -156,6 +156,7 @@ fn print_mesh_count(
156156
}
157157
}
158158

159+
#[derive(Deref, DerefMut)]
159160
struct PrintingTimer(Timer);
160161

161162
impl Default for PrintingTimer {

examples/async_tasks/async_compute.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ fn main() {
2222
// Number of cubes to spawn across the x, y, and z axis
2323
const NUM_CUBES: u32 = 6;
2424

25+
#[derive(Deref)]
2526
struct BoxMeshHandle(Handle<Mesh>);
27+
#[derive(Deref)]
2628
struct BoxMaterialHandle(Handle<StandardMaterial>);
2729

2830
/// Startup system which runs only once and generates our Box Mesh
@@ -84,8 +86,8 @@ fn handle_tasks(
8486
if let Some(transform) = future::block_on(future::poll_once(&mut *task)) {
8587
// Add our new PbrBundle of components to our tagged entity
8688
commands.entity(entity).insert_bundle(PbrBundle {
87-
mesh: box_mesh_handle.0.clone(),
88-
material: box_material_handle.0.clone(),
89+
mesh: box_mesh_handle.clone(),
90+
material: box_material_handle.clone(),
8991
transform,
9092
..default()
9193
});

examples/async_tasks/external_source_external_thread.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ fn main() {
1515
.run();
1616
}
1717

18+
#[derive(Deref)]
1819
struct StreamReceiver(Receiver<u32>);
1920
struct StreamEvent(u32);
2021

22+
#[derive(Deref)]
2123
struct LoadedFont(Handle<Font>);
2224

2325
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
@@ -43,7 +45,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
4345

4446
// This system reads from the receiver and sends events to Bevy
4547
fn read_stream(receiver: ResMut<StreamReceiver>, mut events: EventWriter<StreamEvent>) {
46-
for from_stream in receiver.0.try_iter() {
48+
for from_stream in receiver.try_iter() {
4749
events.send(StreamEvent(from_stream));
4850
}
4951
}
@@ -54,7 +56,7 @@ fn spawn_text(
5456
loaded_font: Res<LoadedFont>,
5557
) {
5658
let text_style = TextStyle {
57-
font: loaded_font.0.clone(),
59+
font: loaded_font.clone(),
5860
font_size: 20.0,
5961
color: Color::WHITE,
6062
};

examples/ecs/generic_system.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ enum AppState {
2020
#[derive(Component)]
2121
struct TextToPrint(String);
2222

23-
#[derive(Component)]
23+
#[derive(Component, Deref, DerefMut)]
2424
struct PrinterTick(bevy::prelude::Timer);
2525

2626
#[derive(Component)]
@@ -67,7 +67,7 @@ fn setup_system(mut commands: Commands) {
6767

6868
fn print_text_system(time: Res<Time>, mut query: Query<(&mut PrinterTick, &TextToPrint)>) {
6969
for (mut timer, text) in query.iter_mut() {
70-
if timer.0.tick(time.delta()).just_finished() {
70+
if timer.tick(time.delta()).just_finished() {
7171
info!("{}", text.0);
7272
}
7373
}

0 commit comments

Comments
 (0)