diff --git a/Cargo.toml b/Cargo.toml index 36b643ee30d03..c41a302506705 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1079,6 +1079,16 @@ description = "Shows how to iterate over combinations of query results" category = "ECS (Entity Component System)" wasm = true +[[example]] +name = "optional_system_param" +path = "examples/ecs/optional_system_param.rs" + +[package.metadata.example.optional_system_param] +name = "Optional System Parameter" +description = "Provides an advanced pattern for working with trait object resources via `OptionalSystemParam`" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "parallel_query" path = "examples/ecs/parallel_query.rs" @@ -1099,6 +1109,16 @@ description = "Query for entities that had a specific component removed earlier category = "ECS (Entity Component System)" wasm = false +[[example]] +name = "resultful_system_param" +path = "examples/ecs/resultful_system_param.rs" + +[package.metadata.example.resultful_system_param] +name = "Resultful System Parameter" +description = "Provides an advanced pattern for encapsulating simple behavior with `ResultfulSystemParam`" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "run_conditions" path = "examples/ecs/run_conditions.rs" diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index a59c54db8d1ef..eb40f9e6a3f62 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -5,14 +5,25 @@ mod fetch; mod set; mod states; +mod kw { + syn::custom_keyword!(ignore); + syn::custom_keyword!(infallible); + syn::custom_keyword!(optional); + syn::custom_keyword!(resultful); +} + use crate::{fetch::derive_world_query_impl, set::derive_set}; use bevy_macro_utils::{derive_boxed_label, get_named_struct_fields, BevyManifest}; use proc_macro::TokenStream; use proc_macro2::Span; use quote::{format_ident, quote}; use syn::{ - parse::ParseStream, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, - ConstParam, DeriveInput, Field, GenericParam, Ident, Index, Meta, MetaList, NestedMeta, Token, + parenthesized, + parse::{Parse, ParseStream}, + parse_macro_input, parse_quote, + punctuated::Punctuated, + spanned::Spanned, + ConstParam, DeriveInput, GenericParam, Ident, Index, Meta, MetaList, NestedMeta, Result, Token, TypeParam, }; @@ -27,7 +38,7 @@ const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore"; #[proc_macro_derive(Bundle, attributes(bundle))] pub fn derive_bundle(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - let ecs_path = bevy_ecs_path(); + let bevy_ecs = bevy_ecs_path(); let named_fields = match get_named_struct_fields(&ast.data) { Ok(fields) => &fields.named, @@ -82,13 +93,13 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { match field_kind { BundleFieldKind::Component => { field_component_ids.push(quote! { - <#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids); + <#field_type as #bevy_ecs::bundle::Bundle>::component_ids(components, storages, &mut *ids); }); field_get_components.push(quote! { self.#field.get_components(&mut *func); }); field_from_components.push(quote! { - #field: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func), + #field: <#field_type as #bevy_ecs::bundle::Bundle>::from_components(ctx, &mut *func), }); } @@ -108,11 +119,11 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { // - ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order // - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass // the correct `StorageType` into the callback. - unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause { + unsafe impl #impl_generics #bevy_ecs::bundle::Bundle for #struct_name #ty_generics #where_clause { fn component_ids( - components: &mut #ecs_path::component::Components, - storages: &mut #ecs_path::storage::Storages, - ids: &mut impl FnMut(#ecs_path::component::ComponentId) + components: &mut #bevy_ecs::component::Components, + storages: &mut #bevy_ecs::storage::Storages, + ids: &mut impl FnMut(#bevy_ecs::component::ComponentId) ){ #(#field_component_ids)* } @@ -120,7 +131,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { #[allow(unused_variables, non_snake_case)] unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self where - __F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_> + __F: FnMut(&mut __T) -> #bevy_ecs::ptr::OwningPtr<'_> { Self { #(#field_from_components)* @@ -128,12 +139,12 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { } } - impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause { + impl #impl_generics #bevy_ecs::bundle::DynamicBundle for #struct_name #ty_generics #where_clause { #[allow(unused_variables)] #[inline] fn get_components( self, - func: &mut impl FnMut(#ecs_path::component::StorageType, #ecs_path::ptr::OwningPtr<'_>) + func: &mut impl FnMut(#bevy_ecs::component::StorageType, #bevy_ecs::ptr::OwningPtr<'_>) ) { #(#field_get_components)* } @@ -250,9 +261,84 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { tokens } +#[derive(PartialEq, Clone)] +enum SystemParamFieldUsage { + Ignore, + Infallible, + Optional, + Resultful, +} + +impl Parse for SystemParamFieldUsage { + fn parse(input: ParseStream) -> Result { + if input.parse::>()?.is_some() { + return Ok(Self::Ignore); + } + + if input.parse::>()?.is_some() { + return Ok(Self::Infallible); + } + + if input.parse::>()?.is_some() { + return Ok(Self::Optional); + } + + if input.parse::>()?.is_some() { + return Ok(Self::Resultful); + } + + Err(input.error("Expected one of 'ignore', 'infallible', 'optional', or 'resultful'")) + } +} + +impl From for SystemParamFieldUsage { + fn from(u: SystemParamStructUsage) -> Self { + match u { + SystemParamStructUsage::Infallible => SystemParamFieldUsage::Infallible, + SystemParamStructUsage::Optional => SystemParamFieldUsage::Optional, + SystemParamStructUsage::Resultful(_) => SystemParamFieldUsage::Resultful, + } + } +} + +#[derive(PartialEq, Clone)] +enum SystemParamStructUsage { + Infallible, + Optional, + Resultful(Ident), +} + +impl Parse for SystemParamStructUsage { + fn parse(input: ParseStream) -> Result { + if input.parse::>()?.is_some() { + return Ok(Self::Infallible); + } + + if input.parse::>()?.is_some() { + return Ok(Self::Optional); + } + + if input.parse::>()?.is_some() { + let content; + parenthesized!(content in input); + let err_ty = match content.parse::() { + Ok(o) => o, + Err(_) => { + return Err( + input.error("Expected an identifier for `ResultfulSystemParam::Error`.") + ) + } + }; + return Ok(Self::Resultful(err_ty)); + } + + Err(input.error("Expected one of 'infallible', 'optional', or 'resultful(ErrorType)'")) + } +} + #[derive(Default)] -struct SystemParamFieldAttributes { - pub ignore: bool, +struct SystemParamAttributes { + pub usage: Option, } static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param"; @@ -260,59 +346,124 @@ static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param"; /// Implement `SystemParam` to use a struct as a parameter in a system #[proc_macro_derive(SystemParam, attributes(system_param))] pub fn derive_system_param(input: TokenStream) -> TokenStream { + let bevy_ecs = bevy_ecs_path(); let ast = parse_macro_input!(input as DeriveInput); - let syn::Data::Struct(syn::DataStruct { fields: field_definitions, ..}) = ast.data else { + let syn::Data::Struct(syn::DataStruct { fields, ..}) = ast.data else { return syn::Error::new(ast.span(), "Invalid `SystemParam` type: expected a `struct`") .into_compile_error() .into(); }; - let path = bevy_ecs_path(); - let field_attributes = field_definitions - .iter() - .map(|field| { - ( - field, - field - .attrs - .iter() - .find(|a| *a.path.get_ident().as_ref().unwrap() == SYSTEM_PARAM_ATTRIBUTE_NAME) - .map_or_else(SystemParamFieldAttributes::default, |a| { - syn::custom_keyword!(ignore); - let mut attributes = SystemParamFieldAttributes::default(); - a.parse_args_with(|input: ParseStream| { - if input.parse::>()?.is_some() { - attributes.ignore = true; - } - Ok(()) - }) - .expect("Invalid 'system_param' attribute format."); - - attributes - }), - ) - }) - .collect::>(); - let mut field_locals = Vec::new(); - let mut fields = Vec::new(); + let mut fallibility = None; + for attr in &ast.attrs { + let Some(attr_ident) = attr.path.get_ident() else { continue; }; + if attr_ident == SYSTEM_PARAM_ATTRIBUTE_NAME { + if fallibility.is_none() { + let usage = match attr.parse_args_with(SystemParamStructUsage::parse) { + Ok(u) => u, + Err(e) => return e.into_compile_error().into(), + }; + fallibility = Some(usage); + } else { + return syn::Error::new( + attr.span(), + "Multiple `system_param` struct attributes found.", + ) + .into_compile_error() + .into(); + } + } + } + + let fallibility = fallibility.unwrap_or(SystemParamStructUsage::Infallible); + + let associated_error = match fallibility { + SystemParamStructUsage::Resultful(ref err_ty) => quote! { type Error = #err_ty; }, + _ => quote! {}, + }; + + let mut field_idents = Vec::new(); + let mut field_getters = Vec::new(); + let mut field_patterns = Vec::new(); let mut field_types = Vec::new(); let mut ignored_fields = Vec::new(); let mut ignored_field_types = Vec::new(); - for (i, (field, attrs)) in field_attributes.iter().enumerate() { - if attrs.ignore { - ignored_fields.push(field.ident.as_ref().unwrap()); - ignored_field_types.push(&field.ty); - } else { - field_locals.push(format_ident!("f{i}")); - let i = Index::from(i); - fields.push( - field - .ident - .as_ref() - .map(|f| quote! { #f }) - .unwrap_or_else(|| quote! { #i }), - ); - field_types.push(&field.ty); + for (i, field) in fields.iter().enumerate() { + let mut field_attrs = SystemParamAttributes::default(); + for attr in &field.attrs { + let Some(attr_ident) = attr.path.get_ident() else { continue; }; + if attr_ident == SYSTEM_PARAM_ATTRIBUTE_NAME { + if field_attrs.usage.is_none() { + field_attrs.usage = + Some(match attr.parse_args_with(SystemParamFieldUsage::parse) { + Ok(o) => o, + Err(e) => return e.into_compile_error().into(), + }); + } else { + return syn::Error::new( + attr.span(), + "Multiple `system_param` field attributes found.", + ) + .into_compile_error() + .into(); + } + } + } + + match field_attrs + .usage + .unwrap_or_else(|| fallibility.clone().into()) + { + SystemParamFieldUsage::Ignore => { + ignored_fields.push(match field.ident.as_ref() { + Some(s) => s, + None => { + return syn::Error::new(field.span(), "Field lacks an identifier.") + .into_compile_error() + .into() + } + }); + ignored_field_types.push(&field.ty); + } + field_fallibility => { + let ident = format_ident!("f{i}"); + let i = Index::from(i); + let ty = &field.ty; + field_idents.push( + field + .ident + .as_ref() + .map(|f| quote! { #f }) + .unwrap_or_else(|| quote! { #i }), + ); + field_getters.push(match fallibility { + SystemParamStructUsage::Infallible => match field_fallibility { + SystemParamFieldUsage::Infallible => quote! { #ident }, + SystemParamFieldUsage::Optional => quote! { #ident.expect("Optional system param was not infallible!") }, + SystemParamFieldUsage::Resultful => quote! { #ident.expect("Resultful system param was not infallible!") }, + _ => unreachable!(), + }, + SystemParamStructUsage::Optional => match field_fallibility { + SystemParamFieldUsage::Infallible => quote! { #ident }, + SystemParamFieldUsage::Optional => quote! { match #ident { Some(s) => s, None => return None, } }, + SystemParamFieldUsage::Resultful => quote! { match #ident { Ok(o) => o, Err(_) => return None, } }, + _ => unreachable!(), + }, + SystemParamStructUsage::Resultful(_) => match field_fallibility { + SystemParamFieldUsage::Infallible => quote! { #ident }, + SystemParamFieldUsage::Optional => quote! { match #ident { Some(s) => s, None => return Err(#bevy_ecs::system::SystemParamError::<#ty>::default().into()), } }, + SystemParamFieldUsage::Resultful => quote! { match #ident { Ok(o) => o, Err(e) => return Err(e.into()), } }, + _ => unreachable!(), + }, + }); + field_types.push(match field_fallibility { + SystemParamFieldUsage::Infallible => quote! { #ty }, + SystemParamFieldUsage::Optional => quote! { Option<#ty> }, + SystemParamFieldUsage::Resultful => quote! { Result<#ty, <#ty as #bevy_ecs::system::ResultfulSystemParam>::Error> }, + _ => unreachable!(), + }); + field_patterns.push(quote! { #ident }); + } } } @@ -369,32 +520,66 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { _ => unreachable!(), })); - let mut tuple_types: Vec<_> = field_types.iter().map(|x| quote! { #x }).collect(); - let mut tuple_patterns: Vec<_> = field_locals.iter().map(|x| quote! { #x }).collect(); - // If the number of fields exceeds the 16-parameter limit, // fold the fields into tuples of tuples until we are below the limit. const LIMIT: usize = 16; - while tuple_types.len() > LIMIT { - let end = Vec::from_iter(tuple_types.drain(..LIMIT)); - tuple_types.push(parse_quote!( (#(#end,)*) )); + while field_types.len() > LIMIT { + let end = Vec::from_iter(field_types.drain(..LIMIT)); + field_types.push(parse_quote! { (#(#end,)*) }); - let end = Vec::from_iter(tuple_patterns.drain(..LIMIT)); - tuple_patterns.push(parse_quote!( (#(#end,)*) )); + let end = Vec::from_iter(field_patterns.drain(..LIMIT)); + field_patterns.push(parse_quote! { (#(#end,)*) }); } + // Create a where clause for the `ReadOnlySystemParam` impl. // Ensure that each field implements `ReadOnlySystemParam`. let mut read_only_generics = generics.clone(); let read_only_where_clause = read_only_generics.make_where_clause(); for field_type in &field_types { - read_only_where_clause - .predicates - .push(syn::parse_quote!(#field_type: #path::system::ReadOnlySystemParam)); + read_only_where_clause.predicates.push( + match syn::parse(quote! { #field_type: #bevy_ecs::system::ReadOnlySystemParam }.into()) + { + Ok(o) => o, + Err(e) => { + return syn::Error::new( + Span::call_site(), + format!("Failed to create read-only predicate: {e}"), + ) + .into_compile_error() + .into() + } + }, + ); } let struct_name = &ast.ident; let state_struct_visibility = &ast.vis; + let get_param_output = quote! { + #struct_name { + #(#field_idents: #field_getters,)* + #(#ignored_fields: ::std::default::Default::default(),)* + } + }; + + let (system_param, get_param_return, get_param_output) = match fallibility { + SystemParamStructUsage::Infallible => ( + quote! { #bevy_ecs::system::SystemParam }, + quote! { Self::Item<'w2, 's2> }, + quote! { #get_param_output }, + ), + SystemParamStructUsage::Optional => ( + quote! { #bevy_ecs::system::OptionalSystemParam }, + quote! { Option> }, + quote! { Some(#get_param_output) }, + ), + SystemParamStructUsage::Resultful(_) => ( + quote! { #bevy_ecs::system::ResultfulSystemParam }, + quote! { Result, as #bevy_ecs::system::ResultfulSystemParam>::Error> }, + quote! { Ok(#get_param_output) }, + ), + }; + TokenStream::from(quote! { // We define the FetchState struct in an anonymous scope to avoid polluting the user namespace. // The struct can still be accessed via SystemParam::State, e.g. EventReaderState can be accessed via @@ -403,50 +588,48 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { #[doc(hidden)] #state_struct_visibility struct FetchState <'w, 's, #(#lifetimeless_generics,)*> #where_clause { - state: (#(<#tuple_types as #path::system::SystemParam>::State,)*), + state: (#(<#field_types as #bevy_ecs::system::SystemParam>::State,)*), marker: std::marker::PhantomData<( - <#path::prelude::Query<'w, 's, ()> as #path::system::SystemParam>::State, + <#bevy_ecs::prelude::Query<'w, 's, ()> as #bevy_ecs::system::SystemParam>::State, #(fn() -> #ignored_field_types,)* )>, } - unsafe impl<'w, 's, #punctuated_generics> #path::system::SystemParam for #struct_name #ty_generics #where_clause { + unsafe impl<'w, 's, #punctuated_generics> #system_param for #struct_name #ty_generics #where_clause { type State = FetchState<'static, 'static, #punctuated_generic_idents>; type Item<'_w, '_s> = #struct_name <#(#shadowed_lifetimes,)* #punctuated_generic_idents>; + #associated_error - fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State { + fn init_state(world: &mut #bevy_ecs::world::World, system_meta: &mut #bevy_ecs::system::SystemMeta) -> Self::State { FetchState { - state: <(#(#tuple_types,)*) as #path::system::SystemParam>::init_state(world, system_meta), + state: <(#(#field_types,)*) as #bevy_ecs::system::SystemParam>::init_state(world, system_meta), marker: std::marker::PhantomData, } } - fn new_archetype(state: &mut Self::State, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) { - <(#(#tuple_types,)*) as #path::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta) + fn new_archetype(state: &mut Self::State, archetype: &#bevy_ecs::archetype::Archetype, system_meta: &mut #bevy_ecs::system::SystemMeta) { + <(#(#field_types,)*) as #bevy_ecs::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta) } - fn apply(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) { - <(#(#tuple_types,)*) as #path::system::SystemParam>::apply(&mut state.state, system_meta, world); + fn apply(state: &mut Self::State, system_meta: &#bevy_ecs::system::SystemMeta, world: &mut #bevy_ecs::world::World) { + <(#(#field_types,)*) as #bevy_ecs::system::SystemParam>::apply(&mut state.state, system_meta, world); } unsafe fn get_param<'w2, 's2>( state: &'s2 mut Self::State, - system_meta: &#path::system::SystemMeta, - world: &'w2 #path::world::World, - change_tick: #path::component::Tick, - ) -> Self::Item<'w2, 's2> { - let (#(#tuple_patterns,)*) = < - (#(#tuple_types,)*) as #path::system::SystemParam + system_meta: &#bevy_ecs::system::SystemMeta, + world: &'w2 #bevy_ecs::world::World, + change_tick: #bevy_ecs::component::Tick, + ) -> #get_param_return { + let (#(#field_patterns,)*) = < + (#(#field_types,)*) as #bevy_ecs::system::SystemParam >::get_param(&mut state.state, system_meta, world, change_tick); - #struct_name { - #(#fields: #field_locals,)* - #(#ignored_fields: std::default::Default::default(),)* - } + #get_param_output } } // Safety: Each field is `ReadOnlySystemParam`, so this can only read from the `World` - unsafe impl<'w, 's, #punctuated_generics> #path::system::ReadOnlySystemParam for #struct_name #ty_generics #read_only_where_clause {} + unsafe impl<'w, 's, #punctuated_generics> #bevy_ecs::system::ReadOnlySystemParam for #struct_name #ty_generics #read_only_where_clause {} }; }) } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 47f668d8ae340..0f91fa68deb76 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -18,16 +18,22 @@ use bevy_ptr::UnsafeCellDeref; use bevy_utils::{all_tuples, synccell::SyncCell}; use std::{ borrow::Cow, - fmt::Debug, + error::Error, + fmt::{Debug, Display}, + marker::PhantomData, ops::{Deref, DerefMut}, }; -/// A parameter that can be used in a [`System`](super::System). +/// A parameter that can be used in a [`System`](super::System) as `T`. +/// +/// Parameters that implement [`SystemParam`] are infallible, which means they must return or panic. +/// If a parameter is not guaranteed to be infallible, it's better to implement +/// [`OptionalSystemParam`] or [`ResultfulSystemParam`] and access it as `Option` or `Result`. /// /// # Derive /// -/// This trait can be derived with the [`derive@super::SystemParam`] macro. -/// This macro only works if each field on the derived struct implements [`SystemParam`]. +/// This trait, as well as [`OptionalSystemParam`] and [`ResultfulSystemParam`], +/// can be derived with the [`derive@super::SystemParam`] macro. /// Note: There are additional requirements on the field types. /// See the *Generic `SystemParam`s* section for details and workarounds of the probable /// cause if this derive causes an error to be emitted. @@ -37,9 +43,79 @@ use std::{ /// /// ## Attributes /// -/// `#[system_param(ignore)]`: -/// Can be added to any field in the struct. Fields decorated with this attribute -/// will be created with the default value upon realisation. +/// `#[system_param(VALUE)]` is used to control the derive macro, +/// and can be used on both the struct itself or its fields. +/// +/// The default value for field-level attributes is the value of struct-level attribute, +/// which is `infallible` by default. +/// +/// ### Struct-level +/// +/// Struct-level attributes tell the macro which trait to implement. +/// +/// #### `infallible` +/// +/// The default value. Treats the struct as a [`SystemParam`]. +/// +/// Fields marked `optional` or `resultful` will be unwrapped. +/// This value exists as a convenience for writing macros. +/// +/// #### `optional` +/// +/// Treats the struct as an [`OptionalSystemParam`]. +/// +/// Fields marked `infallible` are expected to succeed. +/// Fields marked `resultful` will unwrap or return `None`. +/// +/// #### `resultful(ERROR_TYPE)` +/// +/// Treats the struct as a [`ResultfulSystemParam`] where `Error` is `ERROR_TYPE`. +/// +/// Fields marked `infallible` are expected to succeed. +/// Fields marked `optional` will unwrap or return [`SystemParamError`]. +/// +/// ### Field-level +/// +/// Field-level attributes tell the macro how to access the field. +/// +/// For example, suppose `#[system_param(resultful)]` was specified at the struct-level and a field, `T`, only implements [`OptionalSystemParam`]. +/// The macro will try to access the field as [`ResultfulSystemParam`], which will fail. +/// Instead, a more restrictive level of fallibility must be used. +/// +/// There are a few options: infallible `Option`, optional `T` and infallible `T`. +/// +/// - Infallible `Option` will never fail; if it's missing it will return `None`. +/// - Optional `T` might fail and will result in an error. +/// - Infallible `T` might fail and will result in a panic. +/// +/// Each of these possibilities can be achieved by using the correct type and field-level attribute. +/// +/// #### `infallible` +/// +/// Treats the field as a [`SystemParam`]. +/// +/// This is useful for using an infallible param inside an encapsulating fallible param. +/// For example, an infallible `Option` will not block the system param from returning. +/// Compare this to an optional `T`, which will make the encapsulating system param return `None` if it returns `None`. +/// +/// #### `optional` +/// +/// Treats the field as an [`OptionalSystemParam`]. +/// +/// This is useful for using an optional param inside a resultful param. +/// For example, [`Res`] implements [`OptionalSystemParam`], so it can't be used directly in a resultful param. +/// When an optional param fails inside a resultful param, it can be handled by implementing [`From`](SystemParamError). +/// +/// #### `resultful` +/// +/// Treats the field as a [`ResultfulSystemParam`]. +/// +/// This value exists as a convenience for writing macros. +/// +/// #### `ignore` +/// +/// Ignores treating the field as a system parameter, instead returning the default value for its type. +/// /// This is most useful for `PhantomData` fields, such as markers for generic types. /// /// # Example @@ -79,10 +155,10 @@ use std::{ /// /// ## Details /// -/// The derive macro requires that the [`SystemParam`] implementation of +/// The derive macro requires that the `*SystemParam` implementation of /// each field `F`'s [`Item`](`SystemParam::Item`)'s is itself `F` /// (ignoring lifetimes for simplicity). -/// This assumption is due to type inference reasons, so that the derived [`SystemParam`] can be +/// This assumption is due to type inference reasons, so that the derived `*SystemParam` can be /// used as an argument to a function system. /// If the compiler cannot validate this property for `[ParamType]`, it will error in the form shown above. /// @@ -141,15 +217,360 @@ pub unsafe trait SystemParam: Sized { ) -> Self::Item<'world, 'state>; } +/// A parameter that can be used in a [`System`](super::System) as `T` or `Option`. +/// +/// # Derive +/// +/// This trait, as well as [`SystemParam`] and [`ResultfulSystemParam`], +/// can be derived with the [`derive@super::SystemParam`] macro. +/// +/// [Please refer to `SystemParam` for details on its usage.](./trait.SystemParam.html#Derive) +/// +/// # Example +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # #[derive(Resource)] +/// # struct SomeResource; +/// use std::marker::PhantomData; +/// use bevy_ecs::system::SystemParam; +/// +/// #[derive(SystemParam)] +/// #[system_param(optional)] +/// struct MyParam<'w> { +/// foo: Res<'w, SomeResource>, +/// } +/// +/// fn my_system(param: Option) { +/// // Access the resource through field `foo` after unwrapping `param` +/// } +/// +/// # bevy_ecs::system::assert_is_system(my_system); +/// ``` +/// +/// # Safety +/// +/// The implementor must ensure the following is true. +/// - [`OptionalSystemParam::init_state`] correctly registers all [`World`] accesses used +/// by [`OptionalSystemParam::get_param`] with the provided [`system_meta`](SystemMeta). +/// - None of the world accesses may conflict with any prior accesses registered +/// on `system_meta`. +pub unsafe trait OptionalSystemParam: Sized { + /// Used to store data which persists across invocations of a system. + type State: Send + Sync + 'static; + + /// The item type returned when constructing this system param succeeds. + /// The value of this associated type should be `Self`, instantiated with new lifetimes. + /// + /// You could think of `SystemParam::Item<'w, 's>` as being an *operation* that changes the lifetimes bound to `Self`. + type Item<'world, 'state>: OptionalSystemParam; + + /// Registers any [`World`] access used by this [`SystemParam`] + /// and creates a new instance of this param's [`State`](Self::State). + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State; + + /// For the specified [`Archetype`], registers the components accessed by this [`SystemParam`] (if applicable). + #[inline] + fn new_archetype( + _state: &mut Self::State, + _archetype: &Archetype, + _system_meta: &mut SystemMeta, + ) { + } + + /// Applies any deferred mutations stored in this [`SystemParam`]'s state. + /// This is used to apply [`Commands`] at the end of a stage. + /// + /// [`Commands`]: crate::prelude::Commands + #[inline] + #[allow(unused_variables)] + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {} + + /// # Safety + /// + /// This call might use any of the [`World`] accesses that were registered in [`Self::init_state`]. + /// - None of those accesses may conflict with any other [`SystemParam`]s + /// that exist at the same time, including those on other threads. + /// - `world` must be the same `World` that was used to initialize [`state`](SystemParam::init_state). + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &SystemMeta, + world: &'world World, + change_tick: Tick, + ) -> Option>; +} + +/// SAFETY: requires the implementation of `OptionalSystemParam` to be safe. +unsafe impl SystemParam for T { + type State = T::State; + + type Item<'world, 'state> = T::Item<'world, 'state>; + + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + T::init_state(world, system_meta) + } + + fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) { + T::new_archetype(state, archetype, system_meta); + } + + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + T::apply(state, system_meta, world); + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &SystemMeta, + world: &'world World, + change_tick: Tick, + ) -> Self::Item<'world, 'state> { + T::get_param(state, system_meta, world, change_tick).unwrap() + } +} + +/// SAFETY: requires the implementation of `OptionalSystemParam` to be safe. +unsafe impl SystemParam for Option { + type State = T::State; + + type Item<'world, 'state> = Option>; + + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + T::init_state(world, system_meta) + } + + fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) { + T::new_archetype(state, archetype, system_meta); + } + + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + T::apply(state, system_meta, world); + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &SystemMeta, + world: &'world World, + change_tick: Tick, + ) -> Self::Item<'world, 'state> { + T::get_param(state, system_meta, world, change_tick) + } +} + +/// A parameter that can be used in a [`System`](super::System) as `T`, `Option`, or `Result`. +/// +/// # Derive +/// +/// This trait, as well as [`SystemParam`] and [`OptionalSystemParam`], +/// can be derived with the [`derive@super::SystemParam`] macro. +/// +/// [Please refer to `SystemParam` for details on its usage.](./trait.SystemParam.html#Derive) +/// +/// # Example +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # #[derive(Resource)] +/// # struct SomeResource; +/// use std::marker::PhantomData; +/// use bevy_ecs::system::{SystemParam, SystemParamError}; +/// +/// #[derive(SystemParam)] +/// #[system_param(resultful(MyParamError))] +/// struct MyParam<'w> { +/// #[system_param(optional)] +/// foo: Res<'w, SomeResource>, +/// } +/// +/// #[derive(Debug)] +/// enum MyParamError { +/// Foo +/// } +/// +/// impl std::fmt::Display for MyParamError { +/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +/// match self { +/// MyParamError::Foo => write!(f, "Failed to get field `foo`!") +/// } +/// } +/// } +/// +/// impl std::error::Error for MyParamError {} +/// +/// impl From>> for MyParamError { +/// fn from(_: SystemParamError>) -> Self { +/// MyParamError::Foo +/// } +/// } +/// +/// fn my_system(param: Result) { +/// // Access the resource through field `foo` after unwrapping `param` +/// } +/// +/// # bevy_ecs::system::assert_is_system(my_system); +/// ``` +/// +/// # Safety +/// +/// The implementor must ensure the following is true. +/// - [`ResultfulSystemParam::init_state`] correctly registers all [`World`] accesses used +/// by [`ResultfulSystemParam::get_param`] with the provided [`system_meta`](SystemMeta). +/// - None of the world accesses may conflict with any prior accesses registered +/// on `system_meta`. +pub unsafe trait ResultfulSystemParam: Sized { + /// Used to store data which persists across invocations of a system. + type State: Send + Sync + 'static; + + /// The item type returned when constructing this system param succeeds. + /// The value of this associated type should be `Self`, instantiated with new lifetimes. + /// + /// You could think of `SystemParam::Item<'w, 's>` as being an *operation* that changes the lifetimes bound to `Self`. + type Item<'world, 'state>: ResultfulSystemParam; + + /// The error type returned when constructing this system param fails. + type Error: Error; + + /// Registers any [`World`] access used by this [`SystemParam`] + /// and creates a new instance of this param's [`State`](Self::State). + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State; + + /// For the specified [`Archetype`], registers the components accessed by this [`SystemParam`] (if applicable). + #[inline] + fn new_archetype( + _state: &mut Self::State, + _archetype: &Archetype, + _system_meta: &mut SystemMeta, + ) { + } + + /// Applies any deferred mutations stored in this [`SystemParam`]'s state. + /// This is used to apply [`Commands`] at the end of a stage. + /// + /// [`Commands`]: crate::prelude::Commands + #[inline] + #[allow(unused_variables)] + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {} + + /// # Safety + /// + /// This call might use any of the [`World`] accesses that were registered in [`Self::init_state`]. + /// - None of those accesses may conflict with any other [`SystemParam`]s + /// that exist at the same time, including those on other threads. + /// - `world` must be the same `World` that was used to initialize [`state`](SystemParam::init_state). + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &SystemMeta, + world: &'world World, + change_tick: Tick, + ) -> Result< + Self::Item<'world, 'state>, + as ResultfulSystemParam>::Error, + >; +} + +/// SAFETY: requires the implementation of `ResultfulSystemParam` to be safe. +unsafe impl OptionalSystemParam for T { + type State = T::State; + + type Item<'world, 'state> = T::Item<'world, 'state>; + + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + T::init_state(world, system_meta) + } + + fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) { + T::new_archetype(state, archetype, system_meta); + } + + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + T::apply(state, system_meta, world); + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &SystemMeta, + world: &'world World, + change_tick: Tick, + ) -> Option> { + T::get_param(state, system_meta, world, change_tick).ok() + } +} + +/// SAFETY: requires the implementation of `ResultfulSystemParam` to be safe. +unsafe impl SystemParam for Result { + type State = T::State; + + type Item<'world, 'state> = + Result, as ResultfulSystemParam>::Error>; + + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + T::init_state(world, system_meta) + } + + fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) { + T::new_archetype(state, archetype, system_meta); + } + + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + T::apply(state, system_meta, world); + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &SystemMeta, + world: &'world World, + change_tick: Tick, + ) -> Self::Item<'world, 'state> { + T::get_param(state, system_meta, world, change_tick) + } +} + /// A [`SystemParam`] that only reads a given [`World`]. /// /// # Safety /// This must only be implemented for [`SystemParam`] impls that exclusively read the World passed in to [`SystemParam::get_param`] pub unsafe trait ReadOnlySystemParam: SystemParam {} +// SAFETY: the system param is read-only by the trait bound +// and so maybe getting it will never cause write-access to be granted. +unsafe impl ReadOnlySystemParam for Option {} + +// SAFETY: the system param is read-only by the trait bound +// and so maybe getting it will never cause write-access to be granted. +unsafe impl ReadOnlySystemParam + for Result +{ +} + /// Shorthand way of accessing the associated type [`SystemParam::Item`] for a given [`SystemParam`]. pub type SystemParamItem<'w, 's, P> =

::Item<'w, 's>; +/// An error that allows a [`ResultfulSystemParam`] to handle [`OptionalSystemParam`] fields that return `None`. +pub struct SystemParamError { + _marker: PhantomData, +} + +impl Debug for SystemParamError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "SystemParamError<{}>", std::any::type_name::()) + } +} + +impl Default for SystemParamError { + fn default() -> Self { + Self { + _marker: PhantomData, + } + } +} + +impl Display for SystemParamError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Failed to get {}", std::any::type_name::()) + } +} + +impl Error for SystemParamError {} + // SAFETY: QueryState is constrained to read-only fetches, so it only reads World. unsafe impl<'w, 's, Q: ReadOnlyWorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> ReadOnlySystemParam for Query<'w, 's, Q, F> @@ -405,8 +826,8 @@ pub trait Resource: Send + Sync + 'static {} unsafe impl<'a, T: Resource> ReadOnlySystemParam for Res<'a, T> {} // SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res -// conflicts with any prior access, a panic will occur. -unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { +// conflicts with any prior access, an error will occur. +unsafe impl<'a, T: Resource> OptionalSystemParam for Res<'a, T> { type State = ComponentId; type Item<'w, 's> = Res<'w, T>; @@ -439,48 +860,7 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { system_meta: &SystemMeta, world: &'w World, change_tick: Tick, - ) -> Self::Item<'w, 's> { - let (ptr, ticks) = world - .as_unsafe_world_cell_migration_internal() - .get_resource_with_ticks(component_id) - .unwrap_or_else(|| { - panic!( - "Resource requested by {} does not exist: {}", - system_meta.name, - std::any::type_name::() - ) - }); - Res { - value: ptr.deref(), - ticks: Ticks { - added: ticks.added.deref(), - changed: ticks.changed.deref(), - last_run: system_meta.last_run, - this_run: change_tick, - }, - } - } -} - -// SAFETY: Only reads a single World resource -unsafe impl<'a, T: Resource> ReadOnlySystemParam for Option> {} - -// SAFETY: this impl defers to `Res`, which initializes and validates the correct world access. -unsafe impl<'a, T: Resource> SystemParam for Option> { - type State = ComponentId; - type Item<'w, 's> = Option>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - Res::::init_state(world, system_meta) - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: &'w World, - change_tick: Tick, - ) -> Self::Item<'w, 's> { + ) -> Option> { world .as_unsafe_world_cell_migration_internal() .get_resource_with_ticks(component_id) @@ -498,7 +878,7 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { // SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res // conflicts with any prior access, a panic will occur. -unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { +unsafe impl<'a, T: Resource> OptionalSystemParam for ResMut<'a, T> { type State = ComponentId; type Item<'w, 's> = ResMut<'w, T>; @@ -534,45 +914,7 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { system_meta: &SystemMeta, world: &'w World, change_tick: Tick, - ) -> Self::Item<'w, 's> { - let value = world - .as_unsafe_world_cell_migration_internal() - .get_resource_mut_by_id(component_id) - .unwrap_or_else(|| { - panic!( - "Resource requested by {} does not exist: {}", - system_meta.name, - std::any::type_name::() - ) - }); - ResMut { - value: value.value.deref_mut::(), - ticks: TicksMut { - added: value.ticks.added, - changed: value.ticks.changed, - last_run: system_meta.last_run, - this_run: change_tick, - }, - } - } -} - -// SAFETY: this impl defers to `ResMut`, which initializes and validates the correct world access. -unsafe impl<'a, T: Resource> SystemParam for Option> { - type State = ComponentId; - type Item<'w, 's> = Option>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - ResMut::::init_state(world, system_meta) - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: &'w World, - change_tick: Tick, - ) -> Self::Item<'w, 's> { + ) -> Option> { world .as_unsafe_world_cell_migration_internal() .get_resource_mut_by_id(component_id) @@ -990,7 +1332,7 @@ impl<'a, T> From> for NonSend<'a, T> { // SAFETY: NonSendComponentId and ArchetypeComponentId access is applied to SystemMeta. If this // NonSend conflicts with any prior access, a panic will occur. -unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { +unsafe impl<'a, T: 'static> OptionalSystemParam for NonSend<'a, T> { type State = ComponentId; type Item<'w, 's> = NonSend<'w, T>; @@ -1025,46 +1367,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { system_meta: &SystemMeta, world: &'w World, change_tick: Tick, - ) -> Self::Item<'w, 's> { - let (ptr, ticks) = world - .as_unsafe_world_cell_migration_internal() - .get_non_send_with_ticks(component_id) - .unwrap_or_else(|| { - panic!( - "Non-send resource requested by {} does not exist: {}", - system_meta.name, - std::any::type_name::() - ) - }); - - NonSend { - value: ptr.deref(), - ticks: ticks.read(), - last_run: system_meta.last_run, - this_run: change_tick, - } - } -} - -// SAFETY: Only reads a single World non-send resource -unsafe impl ReadOnlySystemParam for Option> {} - -// SAFETY: this impl defers to `NonSend`, which initializes and validates the correct world access. -unsafe impl SystemParam for Option> { - type State = ComponentId; - type Item<'w, 's> = Option>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - NonSend::::init_state(world, system_meta) - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: &'w World, - change_tick: Tick, - ) -> Self::Item<'w, 's> { + ) -> Option> { world .as_unsafe_world_cell_migration_internal() .get_non_send_with_ticks(component_id) @@ -1079,7 +1382,7 @@ unsafe impl SystemParam for Option> { // SAFETY: NonSendMut ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this // NonSendMut conflicts with any prior access, a panic will occur. -unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { +unsafe impl<'a, T: 'static> OptionalSystemParam for NonSendMut<'a, T> { type State = ComponentId; type Item<'w, 's> = NonSendMut<'w, T>; @@ -1117,40 +1420,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { system_meta: &SystemMeta, world: &'w World, change_tick: Tick, - ) -> Self::Item<'w, 's> { - let (ptr, ticks) = world - .as_unsafe_world_cell_migration_internal() - .get_non_send_with_ticks(component_id) - .unwrap_or_else(|| { - panic!( - "Non-send resource requested by {} does not exist: {}", - system_meta.name, - std::any::type_name::() - ) - }); - NonSendMut { - value: ptr.assert_unique().deref_mut(), - ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick), - } - } -} - -// SAFETY: this impl defers to `NonSendMut`, which initializes and validates the correct world access. -unsafe impl<'a, T: 'static> SystemParam for Option> { - type State = ComponentId; - type Item<'w, 's> = Option>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - NonSendMut::::init_state(world, system_meta) - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: &'w World, - change_tick: Tick, - ) -> Self::Item<'w, 's> { + ) -> Option> { world .as_unsafe_world_cell_migration_internal() .get_non_send_with_ticks(component_id) @@ -1625,6 +1895,60 @@ mod tests { #[derive(SystemParam)] pub struct EncapsulatedParam<'w>(Res<'w, PrivateResource>); + #[derive(SystemParam)] + #[system_param(infallible)] + pub struct ExplicitInfallibleParam<'w> { + #[system_param(optional)] + _r0: Res<'w, R<0>>, + #[system_param(infallible)] + _r1: Option>>, + } + + #[derive(SystemParam)] + #[system_param(optional)] + pub struct OptionalParam<'w> { + _r0: Res<'w, R<0>>, + #[system_param(optional)] + _r1: Res<'w, R<1>>, + #[system_param(infallible)] + _r2: Res<'w, R<2>>, + } + + #[derive(SystemParam)] + #[system_param(resultful(ResultfulParamErr))] + pub struct ResultfulParam<'w> { + #[system_param(optional)] + _r0: Res<'w, R<0>>, + #[system_param(optional)] + _r1: Res<'w, R<1>>, + } + + #[derive(Debug)] + pub enum ResultfulParamErr { + R0, + R1, + } + + impl std::fmt::Display for ResultfulParamErr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ResultfulParamErr is here!") + } + } + + impl std::error::Error for ResultfulParamErr {} + + impl From>>> for ResultfulParamErr { + fn from(_: SystemParamError>>) -> Self { + Self::R0 + } + } + + impl From>>> for ResultfulParamErr { + fn from(_: SystemParamError>>) -> Self { + Self::R1 + } + } + // regression test for https://github.com/bevyengine/bevy/issues/7103. #[derive(SystemParam)] pub struct WhereParam<'w, 's, Q> diff --git a/examples/README.md b/examples/README.md index 6e1723c44739a..e6cb046e5c7e7 100644 --- a/examples/README.md +++ b/examples/README.md @@ -210,8 +210,10 @@ Example | Description [Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities [Iter Combinations](../examples/ecs/iter_combinations.rs) | Shows how to iterate over combinations of query results [Nondeterministic System Order](../examples/ecs/nondeterministic_system_order.rs) | Systems run in paralell, but their order isn't always deteriministic. Here's how to detect and fix this. +[Optional System Parameter](../examples/ecs/optional_system_param.rs) | Provides an advanced pattern for working with trait object resources via `OptionalSystemParam` [Parallel Query](../examples/ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator` [Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed earlier in the current frame +[Resultful System Parameter](../examples/ecs/resultful_system_param.rs) | Provides an advanced pattern for encapsulating simple behavior with `ResultfulSystemParam` [Run Conditions](../examples/ecs/run_conditions.rs) | Run systems only when one or multiple conditions are met [Startup System](../examples/ecs/startup_system.rs) | Demonstrates a startup system (one that runs once when the app starts up) [State](../examples/ecs/state.rs) | Illustrates how to use States to control transitioning from a Menu state to an InGame state diff --git a/examples/ecs/optional_system_param.rs b/examples/ecs/optional_system_param.rs new file mode 100644 index 0000000000000..531da268836fc --- /dev/null +++ b/examples/ecs/optional_system_param.rs @@ -0,0 +1,196 @@ +//! This example shows how an [`OptionalSystemParam`] can be used to create a flexible system for interacting with a trait object resource. +//! +//! This is fairly advanced and the [`SystemParam`](bevy::ecs::system::SystemParam) derive macro can be used in most cases. +//! +//! This pattern is useful for working with resources where the exact type isn't known. +//! The system param allows for expressing the desired type as a type parameter, +//! which is far more convenient than getting the resource directly and handling it in every system. + +use std::ops::{Deref, DerefMut}; + +use bevy::{ + ecs::{ + component::{ComponentId, Tick}, + system::{OptionalSystemParam, ReadOnlySystemParam, SystemMeta}, + }, + prelude::*, +}; + +// Resources simulating game statistics. +#[derive(Resource)] +pub struct GameTime(f32); + +#[derive(Resource)] +pub struct GameKills(u32); + +fn main() { + App::new() + .insert_resource(GameTime(532.1)) + .insert_resource(GameKills(31)) + .insert_resource(CurrentGameMode::new(Deathmatch { + max_time: 600.0, + max_kills: 30, + })) + .add_systems(Update, update_deathmatch) + .run(); +} + +// This struct encapsulates the trait object so it can used as a resource. +#[derive(Resource)] +pub struct CurrentGameMode(Box); + +impl CurrentGameMode { + pub fn new(mode: impl GameMode) -> Self { + Self(Box::new(mode)) + } + + pub fn to_ref(&self) -> Option<&T> { + GameMode::as_reflect(&*self.0).downcast_ref() + } + + pub fn to_mut(&mut self) -> Option<&mut T> { + GameMode::as_reflect_mut(&mut *self.0).downcast_mut() + } +} + +// This is the optional system param that abstracts away converting the resource to the actual type. +pub struct Game<'w, T: GameMode> { + mode: &'w T, +} + +// Deref makes it convenient to interact with the actual data. +impl<'w, T: GameMode> Deref for Game<'w, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.mode + } +} + +#[doc(hidden)] +pub struct GameState { + // The `OptionalSystemParam::State` of `Res`. + mode_id: ComponentId, +} + +unsafe impl<'w, T: GameMode> OptionalSystemParam for Game<'w, T> { + type State = GameState; + + type Item<'world, 'state> = Game<'world, T>; + + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + GameState { + mode_id: as OptionalSystemParam>::init_state(world, system_meta), + } + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &SystemMeta, + world: &'world World, + change_tick: Tick, + ) -> Option> { + let current_mode = as OptionalSystemParam>::get_param( + &mut state.mode_id, + system_meta, + world, + change_tick, + )? + .into_inner(); + current_mode.to_ref().map(|mode| Game { mode }) + } +} + +// SAFETY: since this system param only reads the resource, it can be marked read-only to enable shared access. +unsafe impl<'w, T: GameMode> ReadOnlySystemParam for Game<'w, T> {} + +// A mutable version of the system param. +// Note: it does not implement `ReadOnlySystemParam`. +pub struct GameMut<'w, T: GameMode> { + mode: &'w mut T, +} + +impl<'w, T: GameMode> Deref for GameMut<'w, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.mode + } +} + +impl<'w, T: GameMode> DerefMut for GameMut<'w, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.mode + } +} + +unsafe impl<'w, T: GameMode> OptionalSystemParam for GameMut<'w, T> { + type State = GameState; + + type Item<'world, 'state> = GameMut<'world, T>; + + fn init_state( + world: &mut World, + system_meta: &mut bevy::ecs::system::SystemMeta, + ) -> Self::State { + GameState { + mode_id: as OptionalSystemParam>::init_state( + world, + system_meta, + ), + } + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &bevy::ecs::system::SystemMeta, + world: &'world World, + change_tick: Tick, + ) -> Option> { + let current_mode = as OptionalSystemParam>::get_param( + &mut state.mode_id, + system_meta, + world, + change_tick, + )? + .into_inner(); + current_mode.to_mut().map(|mode| GameMut { mode }) + } +} + +// This trait can be used to implement common behavior. +pub trait GameMode: Reflect + Send + Sync + 'static { + fn as_reflect(&self) -> &dyn Reflect; + + fn as_reflect_mut(&mut self) -> &mut dyn Reflect; +} + +// This struct implements the trait. +// There could be many structs like this, not necessarily defined in the same library. +#[derive(Reflect)] +pub struct Deathmatch { + pub max_time: f32, + pub max_kills: u32, +} + +impl GameMode for Deathmatch { + fn as_reflect(&self) -> &dyn Reflect { + self + } + + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self + } +} + +fn update_deathmatch(time: Res, kills: Res, game: Option>) { + let Some(game) = game else { return }; + + if time.0 >= game.max_time { + println!("Time ran out!"); + } + + if kills.0 >= game.max_kills { + println!("Max kills reached!"); + } +} diff --git a/examples/ecs/resultful_system_param.rs b/examples/ecs/resultful_system_param.rs new file mode 100644 index 0000000000000..3100894716f2d --- /dev/null +++ b/examples/ecs/resultful_system_param.rs @@ -0,0 +1,103 @@ +//! This example shows how [`ResultfulSystemParam`] can be used to encapsulate simple behavior for use across systems. +//! +//! This is fairly advanced and the [`SystemParam`] derive macro can be used in many cases. + +use bevy::{ + ecs::{ + component::Tick, + system::{ReadOnlySystemParam, ResultfulSystemParam, SystemMeta, SystemParam}, + }, + prelude::*, +}; + +fn main() { + App::new() + .add_systems(Startup, setup) + .add_systems(Update, display_statistics) + .run(); +} + +// A component associated with each player representing game progress. +#[derive(Component)] +pub struct Score(pub u32); + +// The system param that represents the average score of all players. +// It fails when there aren't any `Score` components in the world. +pub struct AverageScore(f32); + +#[doc(hidden)] +pub struct AverageScoreState { + query_state: QueryState<&'static Score>, +} + +unsafe impl ResultfulSystemParam for AverageScore { + type State = AverageScoreState; + + type Item<'world, 'state> = AverageScore; + + type Error = AverageScoreError; + + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + AverageScoreState { + query_state: as SystemParam>::init_state(world, system_meta), + } + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &SystemMeta, + world: &'world World, + change_tick: Tick, + ) -> Result< + Self::Item<'world, 'state>, + as ResultfulSystemParam>::Error, + > { + let query = as SystemParam>::get_param( + &mut state.query_state, + system_meta, + world, + change_tick, + ); + + if query.is_empty() { + return Err(AverageScoreError::Empty); + } + + let total = query.iter().map(|p| p.0).sum::() as f32; + + let size = query.iter().len() as f32; + + Ok(AverageScore(total / size)) + } +} + +// SAFETY: since the system param only reads the query, it can be marked read-only to enable shared access. +unsafe impl ReadOnlySystemParam for AverageScore {} + +#[derive(Debug)] +pub enum AverageScoreError { + Empty, +} + +impl std::fmt::Display for AverageScoreError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AverageScoreError::Empty => write!(f, "No players found!"), + } + } +} + +impl std::error::Error for AverageScoreError {} + +fn setup(mut commands: Commands) { + commands.spawn(Score(30)); + commands.spawn(Score(5)); + commands.spawn(Score(25)); +} + +fn display_statistics(avg_score: Result) { + match avg_score { + Ok(AverageScore(avg)) => println!("Average Score: {avg}"), + Err(AverageScoreError::Empty) => println!("Average Score: Not Available"), + } +}