Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fallible System Parameters #6923

Closed
131 changes: 116 additions & 15 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream {
// Conflicting params in ParamSet are not accessible at the same time
// ParamSets are guaranteed to not conflict with other SystemParams
unsafe {
<#param::State as SystemParamState>::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick)
<#param::State as SystemParamState<Infallible>>::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick)
}
}
});
Expand All @@ -240,7 +240,7 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream {
let meta = &metas[0..param_count];
let param_fn_mut = &param_fn_muts[0..param_count];
tokens.extend(TokenStream::from(quote! {
impl<'w, 's, #(#param: SystemParam,)*> SystemParam for ParamSet<'w, 's, (#(#param,)*)>
impl<'w, 's, #(#param: SystemParam<Infallible>,)*> SystemParam<Infallible> for ParamSet<'w, 's, (#(#param,)*)>
{
type State = ParamSetState<(#(#param::State,)*)>;
}
Expand All @@ -254,9 +254,9 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream {
// SAFETY: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts
// with any prior access, a panic will occur.

unsafe impl<#(#param_state: SystemParamState,)*> SystemParamState for ParamSetState<(#(#param_state,)*)>
unsafe impl<#(#param_state: SystemParamState<Infallible>,)*> SystemParamState<Infallible> for ParamSetState<(#(#param_state,)*)>
{
type Item<'w, 's> = ParamSet<'w, 's, (#(<#param_state as SystemParamState>::Item::<'w, 's>,)*)>;
type Item<'w, 's> = ParamSet<'w, 's, (#(<#param_state as SystemParamState<Infallible>>::Item::<'w, 's>,)*)>;

fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
#(
Expand Down Expand Up @@ -305,7 +305,7 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream {
}
}

impl<'w, 's, #(#param: SystemParam,)*> ParamSet<'w, 's, (#(#param,)*)>
impl<'w, 's, #(#param: SystemParam<Infallible>,)*> ParamSet<'w, 's, (#(#param,)*)>
{

#(#param_fn_mut)*
Expand All @@ -322,9 +322,17 @@ struct SystemParamFieldAttributes {
}

static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param";
static FALLIBILITY_ATTRIBUTE_NAME: &str = "fallibility";

#[derive(PartialEq)]
enum Fallibility {
Infallible,
Optional,
Resultful,
}

/// Implement `SystemParam` to use a struct as a parameter in a system
#[proc_macro_derive(SystemParam, attributes(system_param))]
#[proc_macro_derive(SystemParam, attributes(fallibility, system_param))]
pub fn derive_system_param(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let fields = match get_named_struct_fields(&ast.data) {
Expand All @@ -333,6 +341,66 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
};
let path = bevy_ecs_path();

let mut fallibility = None;

// Read struct-level attributes
for attr in &ast.attrs {
let Some(attr_ident) = attr.path.get_ident() else { continue };
if attr_ident == FALLIBILITY_ATTRIBUTE_NAME {
if fallibility.is_none() {
if let Ok(fallible_ident) =
attr.parse_args_with(|input: ParseStream| input.parse::<Ident>())
{
match fallible_ident.to_string().as_str() {
"Infallible" => {
fallibility = Some(Fallibility::Infallible);
}
"Optional" => {
fallibility = Some(Fallibility::Optional);
}
"Resultful" => {
fallibility = Some(Fallibility::Resultful);
},
_ => return syn::Error::new_spanned(
attr,
"The content of `fallibility` should be either `Infallible`, `Optional`, or `Resultful`."
)
.into_compile_error()
.into()
}
} else {
return syn::Error::new_spanned(
attr,
"The content of `fallibility` should be a single identifier.",
)
.into_compile_error()
.into();
}
} else {
return syn::Error::new_spanned(
attr,
"There should only be one `fallibility` attribute.",
)
.into_compile_error()
.into();
}
}
}

let fallibility = fallibility.unwrap_or(Fallibility::Infallible);

let fallible = {
let mut f = path.clone();
f.segments.push(format_ident!("system").into());
match fallibility {
Fallibility::Infallible => f.segments.push(format_ident!("Infallible").into()),
Fallibility::Optional => f.segments.push(format_ident!("Optional").into()),
Fallibility::Resultful => f.segments.push(format_ident!("Resultful").into()),
}
f
};

// Read field-level attributes
let field_attributes = fields
.iter()
.map(|field| {
Expand Down Expand Up @@ -429,18 +497,54 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
let struct_name = &ast.ident;
let state_struct_visibility = &ast.vis;

let returned_struct = quote! {
#struct_name {
#(#fields,)*
#(#ignored_fields: <#ignored_field_types>::default(),)*
}
};

let (get_param_body, get_param_return) = match fallibility {
Fallibility::Infallible => (
quote! {
#(let #fields = <<#field_types as #path::system::SystemParam<#fallible>>::State as #path::system::SystemParamState<#fallible>>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick);)*

#returned_struct
},
quote! { Self::Item<'w, 's> },
),
Fallibility::Optional => (
quote! {
#(let Some(#fields) = <<#field_types as #path::system::SystemParam<#fallible>>::State as #path::system::SystemParamState<#fallible>>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick) else {
return None;
};)*

Some(#returned_struct)
},
quote! { Option<Self::Item<'w, 's>> },
),
Fallibility::Resultful => (
quote! {
#(let #fields = <<#field_types as #path::system::SystemParam<#fallible>>::State as #path::system::SystemParamState<#fallible>>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick)?;)*

Ok(#returned_struct)
},
quote! { Result<Self::Item<'w, 's>, &'s dyn std::error::Error> },
),
};

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
// <EventReader<'static, 'static, T> as SystemParam>::State
const _: () = {
impl<'w, 's, #punctuated_generics> #path::system::SystemParam for #struct_name #ty_generics #where_clause {
impl<'w, 's, #punctuated_generics> #path::system::SystemParam<#fallible> for #struct_name #ty_generics #where_clause {
type State = State<'w, 's, #punctuated_generic_idents>;
}

#[doc(hidden)]
type State<'w, 's, #punctuated_generic_idents> = FetchState<
(#(<#field_types as #path::system::SystemParam>::State,)*),
(#(<#field_types as #path::system::SystemParam<#fallible>>::State,)*),
#punctuated_generic_idents
>;

Expand All @@ -450,7 +554,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
marker: std::marker::PhantomData<fn()->(#punctuated_generic_idents)>
}

unsafe impl<'__w, '__s, #punctuated_generics> #path::system::SystemParamState for
unsafe impl<'__w, '__s, #punctuated_generics> #path::system::SystemParamState<#fallible> for
State<'__w, '__s, #punctuated_generic_idents>
#where_clause {
type Item<'w, 's> = #struct_name #ty_generics;
Expand All @@ -467,19 +571,16 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
}

fn apply(&mut self, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) {
self.state.apply(system_meta, world)
#path::system::SystemParamState::apply(&mut self.state, system_meta, world)
}

unsafe fn get_param<'w, 's>(
state: &'s mut Self,
system_meta: &#path::system::SystemMeta,
world: &'w #path::world::World,
change_tick: u32,
) -> Self::Item<'w, 's> {
#struct_name {
#(#fields: <<#field_types as #path::system::SystemParam>::State as #path::system::SystemParamState>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick),)*
#(#ignored_fields: <#ignored_field_types>::default(),)*
}
) -> #get_param_return {
#get_param_body
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ecs/src/system/commands/parallel_scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use thread_local::ThreadLocal;
use crate::{
entity::Entities,
prelude::World,
system::{SystemMeta, SystemParam, SystemParamState},
system::{Infallible, SystemMeta, SystemParam, SystemParamState},
};

use super::{CommandQueue, Commands};
Expand Down Expand Up @@ -48,12 +48,12 @@ pub struct ParallelCommands<'w, 's> {
entities: &'w Entities,
}

impl SystemParam for ParallelCommands<'_, '_> {
impl SystemParam<Infallible> for ParallelCommands<'_, '_> {
type State = ParallelCommandsState;
}

// SAFETY: no component or resource access to report
unsafe impl SystemParamState for ParallelCommandsState {
unsafe impl SystemParamState<Infallible> for ParallelCommandsState {
type Item<'w, 's> = ParallelCommands<'w, 's>;

fn init(_: &mut World, _: &mut crate::system::SystemMeta) -> Self {
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ecs/src/system/exclusive_system_param.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
prelude::{FromWorld, QueryState},
query::{ReadOnlyWorldQuery, WorldQuery},
system::{Local, LocalState, SystemMeta, SystemParam, SystemState},
system::{Infallible, Local, LocalState, SystemMeta, SystemParam, SystemState},
world::World,
};
use bevy_ecs_macros::all_tuples;
Expand Down Expand Up @@ -45,11 +45,11 @@ impl<Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> ExclusiveSystemPa
}
}

impl<'a, P: SystemParam + 'static> ExclusiveSystemParam for &'a mut SystemState<P> {
impl<'a, P: SystemParam<Infallible> + 'static> ExclusiveSystemParam for &'a mut SystemState<P> {
type State = SystemState<P>;
}

impl<P: SystemParam> ExclusiveSystemParamState for SystemState<P> {
impl<P: SystemParam<Infallible>> ExclusiveSystemParamState for SystemState<P> {
type Item<'s> = &'s mut SystemState<P>;

fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self {
Expand Down
Loading