Improved Require Syntax (#18555)
# Objective Requires are currently more verbose than they need to be. People would like to define inline component values. Additionally, the current `#[require(Foo(custom_constructor))]` and `#[require(Foo(|| Foo(10))]` syntax doesn't really make sense within the context of the Rust type system. #18309 was an attempt to improve ergonomics for some cases, but it came at the cost of even more weirdness / unintuitive behavior. Our approach as a whole needs a rethink. ## Solution Rework the `#[require()]` syntax to make more sense. This is a breaking change, but I think it will make the system easier to learn, while also improving ergonomics substantially: ```rust #[derive(Component)] #[require( A, // this will use A::default() B(1), // inline tuple-struct value C { value: 1 }, // inline named-struct value D::Variant, // inline enum variant E::SOME_CONST, // inline associated const F::new(1), // inline constructor G = returns_g(), // an expression that returns G H = SomethingElse::new(), // expression returns SomethingElse, where SomethingElse: Into<H> )] struct Foo; ``` ## Migration Guide Custom-constructor requires should use the new expression-style syntax: ```rust // before #[derive(Component)] #[require(A(returns_a))] struct Foo; // after #[derive(Component)] #[require(A = returns_a())] struct Foo; ``` Inline-closure-constructor requires should use the inline value syntax where possible: ```rust // before #[derive(Component)] #[require(A(|| A(10))] struct Foo; // after #[derive(Component)] #[require(A(10)] struct Foo; ``` In cases where that is not possible, use the expression-style syntax: ```rust // before #[derive(Component)] #[require(A(|| A(10))] struct Foo; // after #[derive(Component)] #[require(A = A(10)] struct Foo; ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: François Mockers <mockersf@gmail.com>
This commit is contained in:
parent
10f1fbf589
commit
538afe2330
@ -18,9 +18,9 @@ use bevy_transform::prelude::{GlobalTransform, Transform};
|
|||||||
#[require(
|
#[require(
|
||||||
Camera,
|
Camera,
|
||||||
DebandDither,
|
DebandDither,
|
||||||
CameraRenderGraph(|| CameraRenderGraph::new(Core2d)),
|
CameraRenderGraph::new(Core2d),
|
||||||
Projection(|| Projection::Orthographic(OrthographicProjection::default_2d())),
|
Projection::Orthographic(OrthographicProjection::default_2d()),
|
||||||
Frustum(|| OrthographicProjection::default_2d().compute_frustum(&GlobalTransform::from(Transform::default()))),
|
Frustum = OrthographicProjection::default_2d().compute_frustum(&GlobalTransform::from(Transform::default())),
|
||||||
Tonemapping(|| Tonemapping::None),
|
Tonemapping::None,
|
||||||
)]
|
)]
|
||||||
pub struct Camera2d;
|
pub struct Camera2d;
|
||||||
|
@ -21,8 +21,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[reflect(Component, Default, Clone)]
|
#[reflect(Component, Default, Clone)]
|
||||||
#[require(
|
#[require(
|
||||||
Camera,
|
Camera,
|
||||||
DebandDither(|| DebandDither::Enabled),
|
DebandDither::Enabled,
|
||||||
CameraRenderGraph(|| CameraRenderGraph::new(Core3d)),
|
CameraRenderGraph::new(Core3d),
|
||||||
Projection,
|
Projection,
|
||||||
Tonemapping,
|
Tonemapping,
|
||||||
ColorGrading,
|
ColorGrading,
|
||||||
|
@ -3,13 +3,13 @@ use proc_macro2::{Span, TokenStream as TokenStream2};
|
|||||||
use quote::{format_ident, quote, ToTokens};
|
use quote::{format_ident, quote, ToTokens};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use syn::{
|
use syn::{
|
||||||
parenthesized,
|
braced, parenthesized,
|
||||||
parse::Parse,
|
parse::Parse,
|
||||||
parse_macro_input, parse_quote,
|
parse_macro_input, parse_quote,
|
||||||
punctuated::Punctuated,
|
punctuated::Punctuated,
|
||||||
spanned::Spanned,
|
spanned::Spanned,
|
||||||
token::{Comma, Paren},
|
token::{Brace, Comma, Paren},
|
||||||
Data, DataEnum, DataStruct, DeriveInput, Expr, ExprCall, ExprClosure, ExprPath, Field, Fields,
|
Data, DataEnum, DataStruct, DeriveInput, Expr, ExprCall, ExprPath, Field, FieldValue, Fields,
|
||||||
Ident, LitStr, Member, Path, Result, Token, Type, Visibility,
|
Ident, LitStr, Member, Path, Result, Token, Type, Visibility,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -207,17 +207,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
match &require.func {
|
match &require.func {
|
||||||
Some(RequireFunc::Path(func)) => {
|
Some(func) => {
|
||||||
register_required.push(quote! {
|
|
||||||
components.register_required_components_manual::<Self, #ident>(
|
|
||||||
required_components,
|
|
||||||
|| { let x: #ident = #func().into(); x },
|
|
||||||
inheritance_depth,
|
|
||||||
recursion_check_stack
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Some(RequireFunc::Closure(func)) => {
|
|
||||||
register_required.push(quote! {
|
register_required.push(quote! {
|
||||||
components.register_required_components_manual::<Self, #ident>(
|
components.register_required_components_manual::<Self, #ident>(
|
||||||
required_components,
|
required_components,
|
||||||
@ -478,12 +468,7 @@ enum StorageTy {
|
|||||||
|
|
||||||
struct Require {
|
struct Require {
|
||||||
path: Path,
|
path: Path,
|
||||||
func: Option<RequireFunc>,
|
func: Option<TokenStream2>,
|
||||||
}
|
|
||||||
|
|
||||||
enum RequireFunc {
|
|
||||||
Path(Path),
|
|
||||||
Closure(ExprClosure),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Relationship {
|
struct Relationship {
|
||||||
@ -580,25 +565,71 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
|
|||||||
|
|
||||||
impl Parse for Require {
|
impl Parse for Require {
|
||||||
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
|
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
|
||||||
let path = input.parse::<Path>()?;
|
let mut path = input.parse::<Path>()?;
|
||||||
let func = if input.peek(Paren) {
|
let mut last_segment_is_lower = false;
|
||||||
|
let mut is_constructor_call = false;
|
||||||
|
// Use the case of the type name to check if it's an enum
|
||||||
|
// This doesn't match everything that can be an enum according to the rust spec
|
||||||
|
// but it matches what clippy is OK with
|
||||||
|
let is_enum = {
|
||||||
|
let mut first_chars = path
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.filter_map(|s| s.ident.to_string().chars().next());
|
||||||
|
if let Some(last) = first_chars.next() {
|
||||||
|
if last.is_uppercase() {
|
||||||
|
if let Some(last) = first_chars.next() {
|
||||||
|
last.is_uppercase()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
last_segment_is_lower = true;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let func = if input.peek(Token![=]) {
|
||||||
|
// If there is an '=', then this is a "function style" require
|
||||||
|
let _t: syn::Token![=] = input.parse()?;
|
||||||
|
let expr: Expr = input.parse()?;
|
||||||
|
let tokens: TokenStream = quote::quote! (|| #expr).into();
|
||||||
|
Some(TokenStream2::from(tokens))
|
||||||
|
} else if input.peek(Brace) {
|
||||||
|
// This is a "value style" named-struct-like require
|
||||||
|
let content;
|
||||||
|
braced!(content in input);
|
||||||
|
let fields = Punctuated::<FieldValue, Token![,]>::parse_terminated(&content)?;
|
||||||
|
let tokens: TokenStream = quote::quote! (|| #path { #fields }).into();
|
||||||
|
Some(TokenStream2::from(tokens))
|
||||||
|
} else if input.peek(Paren) {
|
||||||
|
// This is a "value style" tuple-struct-like require
|
||||||
let content;
|
let content;
|
||||||
parenthesized!(content in input);
|
parenthesized!(content in input);
|
||||||
if let Ok(func) = content.parse::<ExprClosure>() {
|
is_constructor_call = last_segment_is_lower;
|
||||||
Some(RequireFunc::Closure(func))
|
let fields = Punctuated::<Expr, Token![,]>::parse_terminated(&content)?;
|
||||||
} else {
|
let tokens: TokenStream = quote::quote! (|| #path (#fields)).into();
|
||||||
let func = content.parse::<Path>()?;
|
Some(TokenStream2::from(tokens))
|
||||||
Some(RequireFunc::Path(func))
|
} else if is_enum {
|
||||||
}
|
// if this is an enum, then it is an inline enum component declaration
|
||||||
} else if input.peek(Token![=]) {
|
let tokens: TokenStream = quote::quote! (|| #path).into();
|
||||||
let _t: syn::Token![=] = input.parse()?;
|
Some(TokenStream2::from(tokens))
|
||||||
let label: Ident = input.parse()?;
|
|
||||||
let tokens: TokenStream = quote::quote! (|| #path::#label).into();
|
|
||||||
let func = syn::parse(tokens).unwrap();
|
|
||||||
Some(RequireFunc::Closure(func))
|
|
||||||
} else {
|
} else {
|
||||||
|
// if this isn't any of the above, then it is a component ident, which will use Default
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if is_enum || is_constructor_call {
|
||||||
|
let path_len = path.segments.len();
|
||||||
|
path = Path {
|
||||||
|
leading_colon: path.leading_colon,
|
||||||
|
segments: Punctuated::from_iter(path.segments.into_iter().take(path_len - 1)),
|
||||||
|
};
|
||||||
|
}
|
||||||
Ok(Require { path, func })
|
Ok(Require { path, func })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,16 +160,69 @@ use thiserror::Error;
|
|||||||
/// assert_eq!(&C(0), world.entity(id).get::<C>().unwrap());
|
/// assert_eq!(&C(0), world.entity(id).get::<C>().unwrap());
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// You can also define a custom constructor function or closure:
|
/// You can define inline component values that take the following forms:
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_ecs::prelude::*;
|
||||||
|
/// #[derive(Component)]
|
||||||
|
/// #[require(
|
||||||
|
/// B(1), // tuple structs
|
||||||
|
/// C { value: 1 }, // named-field structs
|
||||||
|
/// D::One, // enum variants
|
||||||
|
/// E::ONE, // associated consts
|
||||||
|
/// F::new(1) // constructors
|
||||||
|
/// )]
|
||||||
|
/// struct A;
|
||||||
|
///
|
||||||
|
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||||
|
/// struct B(u8);
|
||||||
|
///
|
||||||
|
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||||
|
/// struct C {
|
||||||
|
/// value: u8
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||||
|
/// enum D {
|
||||||
|
/// Zero,
|
||||||
|
/// One,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||||
|
/// struct E(u8);
|
||||||
|
///
|
||||||
|
/// impl E {
|
||||||
|
/// pub const ONE: Self = Self(1);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||||
|
/// struct F(u8);
|
||||||
|
///
|
||||||
|
/// impl F {
|
||||||
|
/// fn new(value: u8) -> Self {
|
||||||
|
/// Self(value)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # let mut world = World::default();
|
||||||
|
/// let id = world.spawn(A).id();
|
||||||
|
/// assert_eq!(&B(1), world.entity(id).get::<B>().unwrap());
|
||||||
|
/// assert_eq!(&C { value: 1 }, world.entity(id).get::<C>().unwrap());
|
||||||
|
/// assert_eq!(&D::One, world.entity(id).get::<D>().unwrap());
|
||||||
|
/// assert_eq!(&E(1), world.entity(id).get::<E>().unwrap());
|
||||||
|
/// assert_eq!(&F(1), world.entity(id).get::<F>().unwrap());
|
||||||
|
/// ````
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// You can also define arbitrary expressions by using `=`
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bevy_ecs::prelude::*;
|
/// # use bevy_ecs::prelude::*;
|
||||||
/// #[derive(Component)]
|
/// #[derive(Component)]
|
||||||
/// #[require(C(init_c))]
|
/// #[require(C = init_c())]
|
||||||
/// struct A;
|
/// struct A;
|
||||||
///
|
///
|
||||||
/// #[derive(Component, PartialEq, Eq, Debug)]
|
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||||
/// #[require(C(|| C(20)))]
|
/// #[require(C = C(20))]
|
||||||
/// struct B;
|
/// struct B;
|
||||||
///
|
///
|
||||||
/// #[derive(Component, PartialEq, Eq, Debug)]
|
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||||
@ -189,34 +242,6 @@ use thiserror::Error;
|
|||||||
/// assert_eq!(&C(20), world.entity(id).get::<C>().unwrap());
|
/// assert_eq!(&C(20), world.entity(id).get::<C>().unwrap());
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// For convenience sake, you can abbreviate enum labels or constant values, with the type inferred to match that of the component you are requiring:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use bevy_ecs::prelude::*;
|
|
||||||
/// #[derive(Component)]
|
|
||||||
/// #[require(B = One, C = ONE)]
|
|
||||||
/// struct A;
|
|
||||||
///
|
|
||||||
/// #[derive(Component, PartialEq, Eq, Debug)]
|
|
||||||
/// enum B {
|
|
||||||
/// Zero,
|
|
||||||
/// One,
|
|
||||||
/// Two
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// #[derive(Component, PartialEq, Eq, Debug)]
|
|
||||||
/// struct C(u8);
|
|
||||||
///
|
|
||||||
/// impl C {
|
|
||||||
/// pub const ONE: Self = Self(1);
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// # let mut world = World::default();
|
|
||||||
/// let id = world.spawn(A).id();
|
|
||||||
/// assert_eq!(&B::One, world.entity(id).get::<B>().unwrap());
|
|
||||||
/// assert_eq!(&C(1), world.entity(id).get::<C>().unwrap());
|
|
||||||
/// ````
|
|
||||||
///
|
|
||||||
/// Required components are _recursive_. This means, if a Required Component has required components,
|
/// Required components are _recursive_. This means, if a Required Component has required components,
|
||||||
/// those components will _also_ be inserted if they are missing:
|
/// those components will _also_ be inserted if they are missing:
|
||||||
///
|
///
|
||||||
@ -252,13 +277,13 @@ use thiserror::Error;
|
|||||||
/// struct X(usize);
|
/// struct X(usize);
|
||||||
///
|
///
|
||||||
/// #[derive(Component, Default)]
|
/// #[derive(Component, Default)]
|
||||||
/// #[require(X(|| X(1)))]
|
/// #[require(X(1))]
|
||||||
/// struct Y;
|
/// struct Y;
|
||||||
///
|
///
|
||||||
/// #[derive(Component)]
|
/// #[derive(Component)]
|
||||||
/// #[require(
|
/// #[require(
|
||||||
/// Y,
|
/// Y,
|
||||||
/// X(|| X(2)),
|
/// X(2),
|
||||||
/// )]
|
/// )]
|
||||||
/// struct Z;
|
/// struct Z;
|
||||||
///
|
///
|
||||||
|
@ -1229,7 +1229,7 @@ mod tests {
|
|||||||
struct A;
|
struct A;
|
||||||
|
|
||||||
#[derive(Component, Clone, PartialEq, Debug, Default)]
|
#[derive(Component, Clone, PartialEq, Debug, Default)]
|
||||||
#[require(C(|| C(5)))]
|
#[require(C(5))]
|
||||||
struct B;
|
struct B;
|
||||||
|
|
||||||
#[derive(Component, Clone, PartialEq, Debug)]
|
#[derive(Component, Clone, PartialEq, Debug)]
|
||||||
@ -1257,7 +1257,7 @@ mod tests {
|
|||||||
struct A;
|
struct A;
|
||||||
|
|
||||||
#[derive(Component, Clone, PartialEq, Debug, Default)]
|
#[derive(Component, Clone, PartialEq, Debug, Default)]
|
||||||
#[require(C(|| C(5)))]
|
#[require(C(5))]
|
||||||
struct B;
|
struct B;
|
||||||
|
|
||||||
#[derive(Component, Clone, PartialEq, Debug)]
|
#[derive(Component, Clone, PartialEq, Debug)]
|
||||||
|
@ -1926,7 +1926,7 @@ mod tests {
|
|||||||
struct X;
|
struct X;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
#[require(Z(new_z))]
|
#[require(Z = new_z())]
|
||||||
struct Y {
|
struct Y {
|
||||||
value: String,
|
value: String,
|
||||||
}
|
}
|
||||||
@ -2651,7 +2651,7 @@ mod tests {
|
|||||||
struct MyRequired(bool);
|
struct MyRequired(bool);
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
#[require(MyRequired(|| MyRequired(false)))]
|
#[require(MyRequired(false))]
|
||||||
struct MiddleMan;
|
struct MiddleMan;
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
@ -2659,7 +2659,7 @@ mod tests {
|
|||||||
struct ConflictingRequire;
|
struct ConflictingRequire;
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
#[require(MyRequired(|| MyRequired(true)))]
|
#[require(MyRequired(true))]
|
||||||
struct MyComponent;
|
struct MyComponent;
|
||||||
|
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
|
@ -6157,7 +6157,7 @@ mod tests {
|
|||||||
struct A;
|
struct A;
|
||||||
|
|
||||||
#[derive(Component, Clone, PartialEq, Debug, Default)]
|
#[derive(Component, Clone, PartialEq, Debug, Default)]
|
||||||
#[require(C(|| C(3)))]
|
#[require(C(3))]
|
||||||
struct B;
|
struct B;
|
||||||
|
|
||||||
#[derive(Component, Clone, PartialEq, Debug, Default)]
|
#[derive(Component, Clone, PartialEq, Debug, Default)]
|
||||||
|
@ -67,7 +67,7 @@ impl Plugin for ForwardDecalPlugin {
|
|||||||
/// * Looking at forward decals at a steep angle can cause distortion. This can be mitigated by padding your decal's
|
/// * Looking at forward decals at a steep angle can cause distortion. This can be mitigated by padding your decal's
|
||||||
/// texture with extra transparent pixels on the edges.
|
/// texture with extra transparent pixels on the edges.
|
||||||
#[derive(Component, Reflect)]
|
#[derive(Component, Reflect)]
|
||||||
#[require(Mesh3d(|| Mesh3d(FORWARD_DECAL_MESH_HANDLE)))]
|
#[require(Mesh3d(FORWARD_DECAL_MESH_HANDLE))]
|
||||||
pub struct ForwardDecal;
|
pub struct ForwardDecal;
|
||||||
|
|
||||||
/// Type alias for an extended material with a [`ForwardDecalMaterialExt`] extension.
|
/// Type alias for an extended material with a [`ForwardDecalMaterialExt`] extension.
|
||||||
|
@ -5,5 +5,5 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
|||||||
/// Marker struct for buttons
|
/// Marker struct for buttons
|
||||||
#[derive(Component, Debug, Default, Clone, Copy, PartialEq, Eq, Reflect)]
|
#[derive(Component, Debug, Default, Clone, Copy, PartialEq, Eq, Reflect)]
|
||||||
#[reflect(Component, Default, Debug, PartialEq, Clone)]
|
#[reflect(Component, Default, Debug, PartialEq, Clone)]
|
||||||
#[require(Node, FocusPolicy(|| FocusPolicy::Block), Interaction)]
|
#[require(Node, FocusPolicy::Block, Interaction)]
|
||||||
pub struct Button;
|
pub struct Button;
|
||||||
|
Loading…
Reference in New Issue
Block a user