
# Objective NOTE: This depends on #7267 and should not be merged until #7267 is merged. If you are reviewing this before that is merged, I highly recommend viewing the Base Sets commit instead of trying to find my changes amongst those from #7267. "Default sets" as described by the [Stageless RFC](https://github.com/bevyengine/rfcs/pull/45) have some [unfortunate consequences](https://github.com/bevyengine/bevy/discussions/7365). ## Solution This adds "base sets" as a variant of `SystemSet`: A set is a "base set" if `SystemSet::is_base` returns `true`. Typically this will be opted-in to using the `SystemSet` derive: ```rust #[derive(SystemSet, Clone, Hash, Debug, PartialEq, Eq)] #[system_set(base)] enum MyBaseSet { A, B, } ``` **Base sets are exclusive**: a system can belong to at most one "base set". Adding a system to more than one will result in an error. When possible we fail immediately during system-config-time with a nice file + line number. For the more nested graph-ey cases, this will fail at the final schedule build. **Base sets cannot belong to other sets**: this is where the word "base" comes from Systems and Sets can only be added to base sets using `in_base_set`. Calling `in_set` with a base set will fail. As will calling `in_base_set` with a normal set. ```rust app.add_system(foo.in_base_set(MyBaseSet::A)) // X must be a normal set ... base sets cannot be added to base sets .configure_set(X.in_base_set(MyBaseSet::A)) ``` Base sets can still be configured like normal sets: ```rust app.add_system(MyBaseSet::B.after(MyBaseSet::Ap)) ``` The primary use case for base sets is enabling a "default base set": ```rust schedule.set_default_base_set(CoreSet::Update) // this will belong to CoreSet::Update by default .add_system(foo) // this will override the default base set with PostUpdate .add_system(bar.in_base_set(CoreSet::PostUpdate)) ``` This allows us to build apis that work by default in the standard Bevy style. This is a rough analog to the "default stage" model, but it use the new "stageless sets" model instead, with all of the ordering flexibility (including exclusive systems) that it provides. --- ## Changelog - Added "base sets" and ported CoreSet to use them. ## Migration Guide TODO
85 lines
2.7 KiB
Rust
85 lines
2.7 KiB
Rust
use proc_macro::TokenStream;
|
|
use quote::{quote, ToTokens};
|
|
use syn::parse::{Parse, ParseStream};
|
|
|
|
pub static SYSTEM_SET_ATTRIBUTE_NAME: &str = "system_set";
|
|
pub static BASE_ATTRIBUTE_NAME: &str = "base";
|
|
|
|
/// Derive a label trait
|
|
///
|
|
/// # Args
|
|
///
|
|
/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait
|
|
/// - `trait_path`: The path [`syn::Path`] to the label trait
|
|
pub fn derive_set(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream {
|
|
let ident = input.ident;
|
|
|
|
let mut is_base = false;
|
|
for attr in &input.attrs {
|
|
if !attr
|
|
.path
|
|
.get_ident()
|
|
.map_or(false, |ident| ident == SYSTEM_SET_ATTRIBUTE_NAME)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
attr.parse_args_with(|input: ParseStream| {
|
|
let meta = input.parse_terminated::<syn::Meta, syn::token::Comma>(syn::Meta::parse)?;
|
|
for meta in meta {
|
|
let ident = meta.path().get_ident().unwrap_or_else(|| {
|
|
panic!(
|
|
"Unrecognized attribute: `{}`",
|
|
meta.path().to_token_stream()
|
|
)
|
|
});
|
|
if ident == BASE_ATTRIBUTE_NAME {
|
|
if let syn::Meta::Path(_) = meta {
|
|
is_base = true;
|
|
} else {
|
|
panic!(
|
|
"The `{BASE_ATTRIBUTE_NAME}` attribute is expected to have no value or arguments",
|
|
);
|
|
}
|
|
} else {
|
|
panic!(
|
|
"Unrecognized attribute: `{}`",
|
|
meta.path().to_token_stream()
|
|
);
|
|
}
|
|
}
|
|
Ok(())
|
|
})
|
|
.unwrap_or_else(|_| panic!("Invalid `{SYSTEM_SET_ATTRIBUTE_NAME}` attribute format"));
|
|
}
|
|
|
|
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
|
let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
|
|
where_token: Default::default(),
|
|
predicates: Default::default(),
|
|
});
|
|
where_clause.predicates.push(
|
|
syn::parse2(quote! {
|
|
Self: 'static + Send + Sync + Clone + Eq + ::std::fmt::Debug + ::std::hash::Hash
|
|
})
|
|
.unwrap(),
|
|
);
|
|
|
|
(quote! {
|
|
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
|
|
fn is_system_type(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn is_base(&self) -> bool {
|
|
#is_base
|
|
}
|
|
|
|
fn dyn_clone(&self) -> std::boxed::Box<dyn #trait_path> {
|
|
std::boxed::Box::new(std::clone::Clone::clone(self))
|
|
}
|
|
}
|
|
})
|
|
.into()
|
|
}
|