Skip to content

Commit dcc0372

Browse files
committed
Base Sets (bevyengine#7466)
# Objective NOTE: This depends on bevyengine#7267 and should not be merged until bevyengine#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 bevyengine#7267. "Default sets" as described by the [Stageless RFC](bevyengine/rfcs#45) have some [unfortunate consequences](bevyengine#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
1 parent 206c7ce commit dcc0372

File tree

84 files changed

+916
-274
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+916
-274
lines changed

crates/bevy_animation/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ impl Plugin for AnimationPlugin {
552552
.register_type::<AnimationPlayer>()
553553
.add_system(
554554
animation_player
555-
.in_set(CoreSet::PostUpdate)
555+
.in_base_set(CoreSet::PostUpdate)
556556
.before(TransformSystem::TransformPropagate),
557557
);
558558
}

crates/bevy_app/src/app.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -328,14 +328,14 @@ impl App {
328328
apply_state_transition::<S>,
329329
)
330330
.chain()
331-
.in_set(CoreSet::StateTransitions),
331+
.in_base_set(CoreSet::StateTransitions),
332332
);
333333

334334
let main_schedule = self.get_schedule_mut(CoreSchedule::Main).unwrap();
335335
for variant in S::variants() {
336336
main_schedule.configure_set(
337337
OnUpdate(variant.clone())
338-
.in_set(CoreSet::StateTransitions)
338+
.in_base_set(CoreSet::StateTransitions)
339339
.run_if(state_equals(variant))
340340
.after(apply_state_transition::<S>),
341341
);
@@ -580,7 +580,7 @@ impl App {
580580
{
581581
if !self.world.contains_resource::<Events<T>>() {
582582
self.init_resource::<Events<T>>()
583-
.add_system(Events::<T>::update_system.in_set(CoreSet::First));
583+
.add_system(Events::<T>::update_system.in_base_set(CoreSet::First));
584584
}
585585
self
586586
}

crates/bevy_app/src/lib.rs

+27-16
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ pub mod prelude {
2929

3030
use bevy_ecs::{
3131
schedule_v3::{
32-
apply_system_buffers, IntoSystemConfig, IntoSystemSetConfig, Schedule, ScheduleLabel,
33-
SystemSet,
32+
apply_system_buffers, IntoSystemConfig, IntoSystemSetConfig, IntoSystemSetConfigs,
33+
Schedule, ScheduleLabel, SystemSet,
3434
},
3535
system::Local,
3636
world::World,
@@ -90,6 +90,7 @@ impl CoreSchedule {
9090
/// that runs immediately after the matching system set.
9191
/// These can be useful for ordering, but you almost never want to add your systems to these sets.
9292
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
93+
#[system_set(base)]
9394
pub enum CoreSet {
9495
/// Runs before all other members of this set.
9596
First,
@@ -129,20 +130,30 @@ impl CoreSet {
129130
let mut schedule = Schedule::new();
130131

131132
// Create "stage-like" structure using buffer flushes + ordering
132-
schedule.add_system(apply_system_buffers.in_set(FirstFlush));
133-
schedule.add_system(apply_system_buffers.in_set(PreUpdateFlush));
134-
schedule.add_system(apply_system_buffers.in_set(UpdateFlush));
135-
schedule.add_system(apply_system_buffers.in_set(PostUpdateFlush));
136-
schedule.add_system(apply_system_buffers.in_set(LastFlush));
137-
138-
schedule.configure_set(First.before(FirstFlush));
139-
schedule.configure_set(PreUpdate.after(FirstFlush).before(PreUpdateFlush));
140-
schedule.configure_set(StateTransitions.after(PreUpdateFlush).before(FixedUpdate));
141-
schedule.configure_set(FixedUpdate.after(StateTransitions).before(Update));
142-
schedule.configure_set(Update.after(FixedUpdate).before(UpdateFlush));
143-
schedule.configure_set(PostUpdate.after(UpdateFlush).before(PostUpdateFlush));
144-
schedule.configure_set(Last.after(PostUpdateFlush).before(LastFlush));
145-
133+
schedule
134+
.set_default_base_set(Update)
135+
.add_system(apply_system_buffers.in_base_set(FirstFlush))
136+
.add_system(apply_system_buffers.in_base_set(PreUpdateFlush))
137+
.add_system(apply_system_buffers.in_base_set(UpdateFlush))
138+
.add_system(apply_system_buffers.in_base_set(PostUpdateFlush))
139+
.add_system(apply_system_buffers.in_base_set(LastFlush))
140+
.configure_sets(
141+
(
142+
First,
143+
FirstFlush,
144+
PreUpdate,
145+
PreUpdateFlush,
146+
StateTransitions,
147+
FixedUpdate,
148+
Update,
149+
UpdateFlush,
150+
PostUpdate,
151+
PostUpdateFlush,
152+
Last,
153+
LastFlush,
154+
)
155+
.chain(),
156+
);
146157
schedule
147158
}
148159
}

crates/bevy_asset/src/asset_server.rs

+3-11
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ pub fn free_unused_assets_system(asset_server: Res<AssetServer>) {
644644
mod test {
645645
use super::*;
646646
use crate::{loader::LoadedAsset, update_asset_storage_system};
647-
use bevy_app::{App, CoreSet};
647+
use bevy_app::App;
648648
use bevy_ecs::prelude::*;
649649
use bevy_reflect::TypeUuid;
650650
use bevy_utils::BoxedFuture;
@@ -852,16 +852,8 @@ mod test {
852852
let mut app = App::new();
853853
app.insert_resource(assets);
854854
app.insert_resource(asset_server);
855-
app.add_system(
856-
free_unused_assets_system
857-
.in_set(FreeUnusedAssets)
858-
.in_set(CoreSet::Update),
859-
);
860-
app.add_system(
861-
update_asset_storage_system::<PngAsset>
862-
.after(FreeUnusedAssets)
863-
.in_set(CoreSet::Update),
864-
);
855+
app.add_system(free_unused_assets_system.in_set(FreeUnusedAssets));
856+
app.add_system(update_asset_storage_system::<PngAsset>.after(FreeUnusedAssets));
865857

866858
fn load_asset(path: AssetPath, world: &World) -> HandleUntyped {
867859
let asset_server = world.resource::<AssetServer>();

crates/bevy_asset/src/assets.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,8 @@ impl AddAsset for App {
331331
};
332332

333333
self.insert_resource(assets)
334-
.add_system(Assets::<T>::asset_event_system.in_set(AssetSet::AssetEvents))
335-
.add_system(update_asset_storage_system::<T>.in_set(AssetSet::LoadAssets))
334+
.add_system(Assets::<T>::asset_event_system.in_base_set(AssetSet::AssetEvents))
335+
.add_system(update_asset_storage_system::<T>.in_base_set(AssetSet::LoadAssets))
336336
.register_type::<Handle<T>>()
337337
.add_event::<AssetEvent<T>>()
338338
}

crates/bevy_asset/src/debug_asset_server.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! Internal assets (e.g. shaders) are bundled directly into an application and can't be hot
44
//! reloaded using the conventional API.
5-
use bevy_app::{App, CoreSet, Plugin};
5+
use bevy_app::{App, Plugin};
66
use bevy_ecs::{prelude::*, system::SystemState};
77
use bevy_tasks::{IoTaskPool, TaskPoolBuilder};
88
use bevy_utils::HashMap;
@@ -75,7 +75,7 @@ impl Plugin for DebugAssetServerPlugin {
7575
watch_for_changes: true,
7676
});
7777
app.insert_non_send_resource(DebugAssetApp(debug_asset_app));
78-
app.add_system(run_debug_asset_app.in_set(CoreSet::Update));
78+
app.add_system(run_debug_asset_app);
7979
}
8080
}
8181

crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ impl<T: Asset> Default for AssetCountDiagnosticsPlugin<T> {
1919
impl<T: Asset> Plugin for AssetCountDiagnosticsPlugin<T> {
2020
fn build(&self, app: &mut App) {
2121
app.add_startup_system(Self::setup_system.in_set(StartupSet::Startup))
22-
.add_system(Self::diagnostic_system.in_set(CoreSet::Update));
22+
.add_system(Self::diagnostic_system);
2323
}
2424
}
2525

crates/bevy_asset/src/lib.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ use bevy_ecs::prelude::*;
5151

5252
/// [`SystemSet`]s for asset loading in an [`App`] schedule.
5353
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
54+
#[system_set(base)]
5455
pub enum AssetSet {
5556
/// Asset storages are updated.
5657
LoadAssets,
@@ -109,22 +110,20 @@ impl Plugin for AssetPlugin {
109110

110111
app.configure_set(
111112
AssetSet::LoadAssets
112-
.no_default_set()
113113
.before(CoreSet::PreUpdate)
114114
.after(CoreSet::First),
115115
)
116116
.configure_set(
117117
AssetSet::AssetEvents
118-
.no_default_set()
119118
.after(CoreSet::PostUpdate)
120119
.before(CoreSet::Last),
121120
)
122-
.add_system(asset_server::free_unused_assets_system.in_set(CoreSet::PreUpdate));
121+
.add_system(asset_server::free_unused_assets_system.in_base_set(CoreSet::PreUpdate));
123122

124123
#[cfg(all(
125124
feature = "filesystem_watcher",
126125
all(not(target_arch = "wasm32"), not(target_os = "android"))
127126
))]
128-
app.add_system(io::filesystem_watcher_system.in_set(AssetSet::LoadAssets));
127+
app.add_system(io::filesystem_watcher_system.in_base_set(AssetSet::LoadAssets));
129128
}
130129
}

crates/bevy_audio/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl Plugin for AudioPlugin {
5656
.add_asset::<AudioSource>()
5757
.add_asset::<AudioSink>()
5858
.init_resource::<Audio<AudioSource>>()
59-
.add_system(play_queued_audio_system::<AudioSource>.in_set(CoreSet::PostUpdate));
59+
.add_system(play_queued_audio_system::<AudioSource>.in_base_set(CoreSet::PostUpdate));
6060

6161
#[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))]
6262
app.init_asset_loader::<AudioLoader>();
@@ -71,6 +71,6 @@ impl AddAudioSource for App {
7171
self.add_asset::<T>()
7272
.init_resource::<Audio<T>>()
7373
.init_resource::<AudioOutput<T>>()
74-
.add_system(play_queued_audio_system::<T>.in_set(CoreSet::PostUpdate))
74+
.add_system(play_queued_audio_system::<T>.in_base_set(CoreSet::PostUpdate))
7575
}
7676
}

crates/bevy_core/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ impl Plugin for TaskPoolPlugin {
107107
self.task_pool_options.create_default_pools();
108108

109109
#[cfg(not(target_arch = "wasm32"))]
110-
app.add_system(tick_global_task_pools.in_set(bevy_app::CoreSet::Last));
110+
app.add_system(tick_global_task_pools.in_base_set(bevy_app::CoreSet::Last));
111111
}
112112
}
113113
/// A dummy type that is [`!Send`](Send), to force systems to run on the main thread.
@@ -142,7 +142,7 @@ pub struct FrameCountPlugin;
142142
impl Plugin for FrameCountPlugin {
143143
fn build(&self, app: &mut App) {
144144
app.init_resource::<FrameCount>();
145-
app.add_system(update_frame_count.in_set(CoreSet::Last));
145+
app.add_system(update_frame_count.in_base_set(CoreSet::Last));
146146
}
147147
}
148148

crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub struct EntityCountDiagnosticsPlugin;
1010
impl Plugin for EntityCountDiagnosticsPlugin {
1111
fn build(&self, app: &mut App) {
1212
app.add_startup_system(Self::setup_system.in_set(StartupSet::Startup))
13-
.add_system(Self::diagnostic_system.in_set(CoreSet::Update));
13+
.add_system(Self::diagnostic_system);
1414
}
1515
}
1616

crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub struct FrameTimeDiagnosticsPlugin;
1111
impl Plugin for FrameTimeDiagnosticsPlugin {
1212
fn build(&self, app: &mut bevy_app::App) {
1313
app.add_startup_system(Self::setup_system.in_set(StartupSet::Startup))
14-
.add_system(Self::diagnostic_system.in_set(CoreSet::Update));
14+
.add_system(Self::diagnostic_system);
1515
}
1616
}
1717

crates/bevy_diagnostic/src/log_diagnostics_plugin.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ impl Plugin for LogDiagnosticsPlugin {
3737
});
3838

3939
if self.debug {
40-
app.add_system(Self::log_diagnostics_debug_system.in_set(CoreSet::PostUpdate));
40+
app.add_system(Self::log_diagnostics_debug_system.in_base_set(CoreSet::PostUpdate));
4141
} else {
42-
app.add_system(Self::log_diagnostics_system.in_set(CoreSet::PostUpdate));
42+
app.add_system(Self::log_diagnostics_system.in_base_set(CoreSet::PostUpdate));
4343
}
4444
}
4545
}

crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub struct SystemInformationDiagnosticsPlugin;
1616
impl Plugin for SystemInformationDiagnosticsPlugin {
1717
fn build(&self, app: &mut App) {
1818
app.add_startup_system(internal::setup_system.in_set(StartupSet::Startup))
19-
.add_system(internal::diagnostic_system.in_set(CoreSet::Update));
19+
.add_system(internal::diagnostic_system);
2020
}
2121
}
2222

crates/bevy_ecs/macros/src/lib.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ extern crate proc_macro;
22

33
mod component;
44
mod fetch;
5+
mod set;
56

6-
use crate::fetch::derive_world_query_impl;
7-
use bevy_macro_utils::{derive_boxed_label, derive_set, get_named_struct_fields, BevyManifest};
7+
use crate::{fetch::derive_world_query_impl, set::derive_set};
8+
use bevy_macro_utils::{derive_boxed_label, get_named_struct_fields, BevyManifest};
89
use proc_macro::TokenStream;
910
use proc_macro2::Span;
1011
use quote::{format_ident, quote};
@@ -537,7 +538,7 @@ pub fn derive_schedule_label(input: TokenStream) -> TokenStream {
537538
}
538539

539540
/// Derive macro generating an impl of the trait `SystemSet`.
540-
#[proc_macro_derive(SystemSet)]
541+
#[proc_macro_derive(SystemSet, attributes(system_set))]
541542
pub fn derive_system_set(input: TokenStream) -> TokenStream {
542543
let input = parse_macro_input!(input as DeriveInput);
543544
let mut trait_path = bevy_ecs_path();

crates/bevy_ecs/macros/src/set.rs

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use proc_macro::TokenStream;
2+
use quote::{quote, ToTokens};
3+
use syn::parse::{Parse, ParseStream};
4+
5+
pub static SYSTEM_SET_ATTRIBUTE_NAME: &str = "system_set";
6+
pub static BASE_ATTRIBUTE_NAME: &str = "base";
7+
8+
/// Derive a label trait
9+
///
10+
/// # Args
11+
///
12+
/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait
13+
/// - `trait_path`: The path [`syn::Path`] to the label trait
14+
pub fn derive_set(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream {
15+
let ident = input.ident;
16+
17+
let mut is_base = false;
18+
for attr in &input.attrs {
19+
if !attr
20+
.path
21+
.get_ident()
22+
.map_or(false, |ident| ident == SYSTEM_SET_ATTRIBUTE_NAME)
23+
{
24+
continue;
25+
}
26+
27+
attr.parse_args_with(|input: ParseStream| {
28+
let meta = input.parse_terminated::<syn::Meta, syn::token::Comma>(syn::Meta::parse)?;
29+
for meta in meta {
30+
let ident = meta.path().get_ident().unwrap_or_else(|| {
31+
panic!(
32+
"Unrecognized attribute: `{}`",
33+
meta.path().to_token_stream()
34+
)
35+
});
36+
if ident == BASE_ATTRIBUTE_NAME {
37+
if let syn::Meta::Path(_) = meta {
38+
is_base = true;
39+
} else {
40+
panic!(
41+
"The `{BASE_ATTRIBUTE_NAME}` attribute is expected to have no value or arguments",
42+
);
43+
}
44+
} else {
45+
panic!(
46+
"Unrecognized attribute: `{}`",
47+
meta.path().to_token_stream()
48+
);
49+
}
50+
}
51+
Ok(())
52+
})
53+
.unwrap_or_else(|_| panic!("Invalid `{SYSTEM_SET_ATTRIBUTE_NAME}` attribute format"));
54+
}
55+
56+
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
57+
let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
58+
where_token: Default::default(),
59+
predicates: Default::default(),
60+
});
61+
where_clause.predicates.push(
62+
syn::parse2(quote! {
63+
Self: 'static + Send + Sync + Clone + Eq + ::std::fmt::Debug + ::std::hash::Hash
64+
})
65+
.unwrap(),
66+
);
67+
68+
(quote! {
69+
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
70+
fn is_system_type(&self) -> bool {
71+
false
72+
}
73+
74+
fn is_base(&self) -> bool {
75+
#is_base
76+
}
77+
78+
fn dyn_clone(&self) -> std::boxed::Box<dyn #trait_path> {
79+
std::boxed::Box::new(std::clone::Clone::clone(self))
80+
}
81+
}
82+
})
83+
.into()
84+
}

0 commit comments

Comments
 (0)