Skip to content

Commit a830530

Browse files
geieredgarjoseph-giocart
authored
Replace all labels with interned labels (#7762)
# Objective First of all, this PR took heavy inspiration from #7760 and #5715. It intends to also fix #5569, but with a slightly different approach. This also fixes #9335 by reexporting `DynEq`. ## Solution The advantage of this API is that we can intern a value without allocating for zero-sized-types and for enum variants that have no fields. This PR does this automatically in the `SystemSet` and `ScheduleLabel` derive macros for unit structs and fieldless enum variants. So this should cover many internal and external use cases of `SystemSet` and `ScheduleLabel`. In these optimal use cases, no memory will be allocated. - The interning returns a `Interned<dyn SystemSet>`, which is just a wrapper around a `&'static dyn SystemSet`. - `Hash` and `Eq` are implemented in terms of the pointer value of the reference, similar to my first approach of anonymous system sets in #7676. - Therefore, `Interned<T>` does not implement `Borrow<T>`, only `Deref`. - The debug output of `Interned<T>` is the same as the interned value. Edit: - `AppLabel` is now also interned and the old `derive_label`/`define_label` macros were replaced with the new interning implementation. - Anonymous set ids are reused for different `Schedule`s, reducing the amount of leaked memory. ### Pros - `InternedSystemSet` and `InternedScheduleLabel` behave very similar to the current `BoxedSystemSet` and `BoxedScheduleLabel`, but can be copied without an allocation. - Many use cases don't allocate at all. - Very fast lookups and comparisons when using `InternedSystemSet` and `InternedScheduleLabel`. - The `intern` module might be usable in other areas. - `Interned{ScheduleLabel, SystemSet, AppLabel}` does implement `{ScheduleLabel, SystemSet, AppLabel}`, increasing ergonomics. ### Cons - Implementors of `SystemSet` and `ScheduleLabel` still need to implement `Hash` and `Eq` (and `Clone`) for it to work. ## Changelog ### Added - Added `intern` module to `bevy_utils`. - Added reexports of `DynEq` to `bevy_ecs` and `bevy_app`. ### Changed - Replaced `BoxedSystemSet` and `BoxedScheduleLabel` with `InternedSystemSet` and `InternedScheduleLabel`. - Replaced `impl AsRef<dyn ScheduleLabel>` with `impl ScheduleLabel`. - Replaced `AppLabelId` with `InternedAppLabel`. - Changed `AppLabel` to use `Debug` for error messages. - Changed `AppLabel` to use interning. - Changed `define_label`/`derive_label` to use interning. - Replaced `define_boxed_label`/`derive_boxed_label` with `define_label`/`derive_label`. - Changed anonymous set ids to be only unique inside a schedule, not globally. - Made interned label types implement their label trait. ### Removed - Removed `define_boxed_label` and `derive_boxed_label`. ## Migration guide - Replace `BoxedScheduleLabel` and `Box<dyn ScheduleLabel>` with `InternedScheduleLabel` or `Interned<dyn ScheduleLabel>`. - Replace `BoxedSystemSet` and `Box<dyn SystemSet>` with `InternedSystemSet` or `Interned<dyn SystemSet>`. - Replace `AppLabelId` with `InternedAppLabel` or `Interned<dyn AppLabel>`. - Types manually implementing `ScheduleLabel`, `AppLabel` or `SystemSet` need to implement: - `dyn_hash` directly instead of implementing `DynHash` - `as_dyn_eq` - Pass labels to `World::try_schedule_scope`, `World::schedule_scope`, `World::try_run_schedule`. `World::run_schedule`, `Schedules::remove`, `Schedules::remove_entry`, `Schedules::contains`, `Schedules::get` and `Schedules::get_mut` by value instead of by reference. --------- Co-authored-by: Joseph <21144246+JoJoJet@users.noreply.github.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
1 parent 317903f commit a830530

File tree

22 files changed

+919
-459
lines changed

22 files changed

+919
-459
lines changed

crates/bevy_app/src/app.rs

+137-25
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use bevy_ecs::{
44
prelude::*,
55
schedule::{
66
apply_state_transition, common_conditions::run_once as run_once_condition,
7-
run_enter_schedule, BoxedScheduleLabel, IntoSystemConfigs, IntoSystemSetConfigs,
7+
run_enter_schedule, InternedScheduleLabel, IntoSystemConfigs, IntoSystemSetConfigs,
88
ScheduleBuildSettings, ScheduleLabel,
99
},
1010
};
11-
use bevy_utils::{tracing::debug, HashMap, HashSet};
11+
use bevy_utils::{intern::Interned, tracing::debug, HashMap, HashSet};
1212
use std::{
1313
fmt::Debug,
1414
panic::{catch_unwind, resume_unwind, AssertUnwindSafe},
@@ -20,10 +20,14 @@ use bevy_utils::tracing::info_span;
2020
bevy_utils::define_label!(
2121
/// A strongly-typed class of labels used to identify an [`App`].
2222
AppLabel,
23-
/// A strongly-typed identifier for an [`AppLabel`].
24-
AppLabelId,
23+
APP_LABEL_INTERNER
2524
);
2625

26+
pub use bevy_utils::label::DynEq;
27+
28+
/// A shorthand for `Interned<dyn AppLabel>`.
29+
pub type InternedAppLabel = Interned<dyn AppLabel>;
30+
2731
pub(crate) enum AppError {
2832
DuplicatePlugin { plugin_name: String },
2933
}
@@ -70,8 +74,8 @@ pub struct App {
7074
/// The schedule that runs the main loop of schedule execution.
7175
///
7276
/// This is initially set to [`Main`].
73-
pub main_schedule_label: BoxedScheduleLabel,
74-
sub_apps: HashMap<AppLabelId, SubApp>,
77+
pub main_schedule_label: InternedScheduleLabel,
78+
sub_apps: HashMap<InternedAppLabel, SubApp>,
7579
plugin_registry: Vec<Box<dyn Plugin>>,
7680
plugin_name_added: HashSet<String>,
7781
/// A private counter to prevent incorrect calls to `App::run()` from `Plugin::build()`
@@ -157,7 +161,7 @@ impl SubApp {
157161

158162
/// Runs the [`SubApp`]'s default schedule.
159163
pub fn run(&mut self) {
160-
self.app.world.run_schedule(&*self.app.main_schedule_label);
164+
self.app.world.run_schedule(self.app.main_schedule_label);
161165
self.app.world.clear_trackers();
162166
}
163167

@@ -233,7 +237,7 @@ impl App {
233237
sub_apps: HashMap::default(),
234238
plugin_registry: Vec::default(),
235239
plugin_name_added: Default::default(),
236-
main_schedule_label: Box::new(Main),
240+
main_schedule_label: Main.intern(),
237241
building_plugin_depth: 0,
238242
plugins_state: PluginsState::Adding,
239243
}
@@ -256,7 +260,7 @@ impl App {
256260
{
257261
#[cfg(feature = "trace")]
258262
let _bevy_main_update_span = info_span!("main app").entered();
259-
self.world.run_schedule(&*self.main_schedule_label);
263+
self.world.run_schedule(self.main_schedule_label);
260264
}
261265
for (_label, sub_app) in &mut self.sub_apps {
262266
#[cfg(feature = "trace")]
@@ -409,9 +413,10 @@ impl App {
409413
schedule: impl ScheduleLabel,
410414
systems: impl IntoSystemConfigs<M>,
411415
) -> &mut Self {
416+
let schedule = schedule.intern();
412417
let mut schedules = self.world.resource_mut::<Schedules>();
413418

414-
if let Some(schedule) = schedules.get_mut(&schedule) {
419+
if let Some(schedule) = schedules.get_mut(schedule) {
415420
schedule.add_systems(systems);
416421
} else {
417422
let mut new_schedule = Schedule::new(schedule);
@@ -440,8 +445,9 @@ impl App {
440445
schedule: impl ScheduleLabel,
441446
sets: impl IntoSystemSetConfigs,
442447
) -> &mut Self {
448+
let schedule = schedule.intern();
443449
let mut schedules = self.world.resource_mut::<Schedules>();
444-
if let Some(schedule) = schedules.get_mut(&schedule) {
450+
if let Some(schedule) = schedules.get_mut(schedule) {
445451
schedule.configure_sets(sets);
446452
} else {
447453
let mut new_schedule = Schedule::new(schedule);
@@ -784,16 +790,15 @@ impl App {
784790
pub fn sub_app_mut(&mut self, label: impl AppLabel) -> &mut App {
785791
match self.get_sub_app_mut(label) {
786792
Ok(app) => app,
787-
Err(label) => panic!("Sub-App with label '{:?}' does not exist", label.as_str()),
793+
Err(label) => panic!("Sub-App with label '{:?}' does not exist", label),
788794
}
789795
}
790796

791797
/// Retrieves a `SubApp` inside this [`App`] with the given label, if it exists. Otherwise returns
792798
/// an [`Err`] containing the given label.
793-
pub fn get_sub_app_mut(&mut self, label: impl AppLabel) -> Result<&mut App, AppLabelId> {
794-
let label = label.as_label();
799+
pub fn get_sub_app_mut(&mut self, label: impl AppLabel) -> Result<&mut App, impl AppLabel> {
795800
self.sub_apps
796-
.get_mut(&label)
801+
.get_mut(&label.intern())
797802
.map(|sub_app| &mut sub_app.app)
798803
.ok_or(label)
799804
}
@@ -806,25 +811,25 @@ impl App {
806811
pub fn sub_app(&self, label: impl AppLabel) -> &App {
807812
match self.get_sub_app(label) {
808813
Ok(app) => app,
809-
Err(label) => panic!("Sub-App with label '{:?}' does not exist", label.as_str()),
814+
Err(label) => panic!("Sub-App with label '{:?}' does not exist", label),
810815
}
811816
}
812817

813818
/// Inserts an existing sub app into the app
814819
pub fn insert_sub_app(&mut self, label: impl AppLabel, sub_app: SubApp) {
815-
self.sub_apps.insert(label.as_label(), sub_app);
820+
self.sub_apps.insert(label.intern(), sub_app);
816821
}
817822

818823
/// Removes a sub app from the app. Returns [`None`] if the label doesn't exist.
819824
pub fn remove_sub_app(&mut self, label: impl AppLabel) -> Option<SubApp> {
820-
self.sub_apps.remove(&label.as_label())
825+
self.sub_apps.remove(&label.intern())
821826
}
822827

823828
/// Retrieves a `SubApp` inside this [`App`] with the given label, if it exists. Otherwise returns
824829
/// an [`Err`] containing the given label.
825830
pub fn get_sub_app(&self, label: impl AppLabel) -> Result<&App, impl AppLabel> {
826831
self.sub_apps
827-
.get(&label.as_label())
832+
.get(&label.intern())
828833
.map(|sub_app| &sub_app.app)
829834
.ok_or(label)
830835
}
@@ -845,8 +850,9 @@ impl App {
845850
///
846851
/// See [`App::add_schedule`] to pass in a pre-constructed schedule.
847852
pub fn init_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self {
853+
let label = label.intern();
848854
let mut schedules = self.world.resource_mut::<Schedules>();
849-
if !schedules.contains(&label) {
855+
if !schedules.contains(label) {
850856
schedules.insert(Schedule::new(label));
851857
}
852858
self
@@ -855,15 +861,15 @@ impl App {
855861
/// Gets read-only access to the [`Schedule`] with the provided `label` if it exists.
856862
pub fn get_schedule(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
857863
let schedules = self.world.get_resource::<Schedules>()?;
858-
schedules.get(&label)
864+
schedules.get(label)
859865
}
860866

861867
/// Gets read-write access to a [`Schedule`] with the provided `label` if it exists.
862868
pub fn get_schedule_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
863869
let schedules = self.world.get_resource_mut::<Schedules>()?;
864870
// We need to call .into_inner here to satisfy the borrow checker:
865871
// it can reason about reborrows using ordinary references but not the `Mut` smart pointer.
866-
schedules.into_inner().get_mut(&label)
872+
schedules.into_inner().get_mut(label)
867873
}
868874

869875
/// Applies the function to the [`Schedule`] associated with `label`.
@@ -874,13 +880,14 @@ impl App {
874880
label: impl ScheduleLabel,
875881
f: impl FnOnce(&mut Schedule),
876882
) -> &mut Self {
883+
let label = label.intern();
877884
let mut schedules = self.world.resource_mut::<Schedules>();
878885

879-
if schedules.get(&label).is_none() {
880-
schedules.insert(Schedule::new(label.dyn_clone()));
886+
if schedules.get(label).is_none() {
887+
schedules.insert(Schedule::new(label));
881888
}
882889

883-
let schedule = schedules.get_mut(&label).unwrap();
890+
let schedule = schedules.get_mut(label).unwrap();
884891
// Call the function f, passing in the schedule retrieved
885892
f(schedule);
886893

@@ -1008,6 +1015,8 @@ pub struct AppExit;
10081015

10091016
#[cfg(test)]
10101017
mod tests {
1018+
use std::marker::PhantomData;
1019+
10111020
use bevy_ecs::{
10121021
schedule::{OnEnter, States},
10131022
system::Commands,
@@ -1104,4 +1113,107 @@ mod tests {
11041113
app.world.run_schedule(OnEnter(AppState::MainMenu));
11051114
assert_eq!(app.world.entities().len(), 2);
11061115
}
1116+
1117+
#[test]
1118+
fn test_derive_app_label() {
1119+
use super::AppLabel;
1120+
use crate::{self as bevy_app};
1121+
1122+
#[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
1123+
struct UnitLabel;
1124+
1125+
#[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
1126+
struct TupleLabel(u32, u32);
1127+
1128+
#[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
1129+
struct StructLabel {
1130+
a: u32,
1131+
b: u32,
1132+
}
1133+
1134+
#[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
1135+
struct EmptyTupleLabel();
1136+
1137+
#[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
1138+
struct EmptyStructLabel {}
1139+
1140+
#[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
1141+
enum EnumLabel {
1142+
#[default]
1143+
Unit,
1144+
Tuple(u32, u32),
1145+
Struct {
1146+
a: u32,
1147+
b: u32,
1148+
},
1149+
}
1150+
1151+
#[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
1152+
struct GenericLabel<T>(PhantomData<T>);
1153+
1154+
assert_eq!(UnitLabel.intern(), UnitLabel.intern());
1155+
assert_eq!(EnumLabel::Unit.intern(), EnumLabel::Unit.intern());
1156+
assert_ne!(UnitLabel.intern(), EnumLabel::Unit.intern());
1157+
assert_ne!(UnitLabel.intern(), TupleLabel(0, 0).intern());
1158+
assert_ne!(EnumLabel::Unit.intern(), EnumLabel::Tuple(0, 0).intern());
1159+
1160+
assert_eq!(TupleLabel(0, 0).intern(), TupleLabel(0, 0).intern());
1161+
assert_eq!(
1162+
EnumLabel::Tuple(0, 0).intern(),
1163+
EnumLabel::Tuple(0, 0).intern()
1164+
);
1165+
assert_ne!(TupleLabel(0, 0).intern(), TupleLabel(0, 1).intern());
1166+
assert_ne!(
1167+
EnumLabel::Tuple(0, 0).intern(),
1168+
EnumLabel::Tuple(0, 1).intern()
1169+
);
1170+
assert_ne!(TupleLabel(0, 0).intern(), EnumLabel::Tuple(0, 0).intern());
1171+
assert_ne!(
1172+
TupleLabel(0, 0).intern(),
1173+
StructLabel { a: 0, b: 0 }.intern()
1174+
);
1175+
assert_ne!(
1176+
EnumLabel::Tuple(0, 0).intern(),
1177+
EnumLabel::Struct { a: 0, b: 0 }.intern()
1178+
);
1179+
1180+
assert_eq!(
1181+
StructLabel { a: 0, b: 0 }.intern(),
1182+
StructLabel { a: 0, b: 0 }.intern()
1183+
);
1184+
assert_eq!(
1185+
EnumLabel::Struct { a: 0, b: 0 }.intern(),
1186+
EnumLabel::Struct { a: 0, b: 0 }.intern()
1187+
);
1188+
assert_ne!(
1189+
StructLabel { a: 0, b: 0 }.intern(),
1190+
StructLabel { a: 0, b: 1 }.intern()
1191+
);
1192+
assert_ne!(
1193+
EnumLabel::Struct { a: 0, b: 0 }.intern(),
1194+
EnumLabel::Struct { a: 0, b: 1 }.intern()
1195+
);
1196+
assert_ne!(
1197+
StructLabel { a: 0, b: 0 }.intern(),
1198+
EnumLabel::Struct { a: 0, b: 0 }.intern()
1199+
);
1200+
assert_ne!(
1201+
StructLabel { a: 0, b: 0 }.intern(),
1202+
EnumLabel::Struct { a: 0, b: 0 }.intern()
1203+
);
1204+
assert_ne!(StructLabel { a: 0, b: 0 }.intern(), UnitLabel.intern(),);
1205+
assert_ne!(
1206+
EnumLabel::Struct { a: 0, b: 0 }.intern(),
1207+
EnumLabel::Unit.intern()
1208+
);
1209+
1210+
assert_eq!(
1211+
GenericLabel::<u32>(PhantomData).intern(),
1212+
GenericLabel::<u32>(PhantomData).intern()
1213+
);
1214+
assert_ne!(
1215+
GenericLabel::<u32>(PhantomData).intern(),
1216+
GenericLabel::<u64>(PhantomData).intern()
1217+
);
1218+
}
11071219
}

crates/bevy_app/src/main_schedule.rs

+13-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{App, Plugin};
22
use bevy_ecs::{
3-
schedule::{ExecutorKind, Schedule, ScheduleLabel},
3+
schedule::{ExecutorKind, InternedScheduleLabel, Schedule, ScheduleLabel},
44
system::{Local, Resource},
55
world::{Mut, World},
66
};
@@ -105,21 +105,21 @@ pub struct Last;
105105
#[derive(Resource, Debug)]
106106
pub struct MainScheduleOrder {
107107
/// The labels to run for the [`Main`] schedule (in the order they will be run).
108-
pub labels: Vec<Box<dyn ScheduleLabel>>,
108+
pub labels: Vec<InternedScheduleLabel>,
109109
}
110110

111111
impl Default for MainScheduleOrder {
112112
fn default() -> Self {
113113
Self {
114114
labels: vec![
115-
Box::new(First),
116-
Box::new(PreUpdate),
117-
Box::new(StateTransition),
118-
Box::new(RunFixedUpdateLoop),
119-
Box::new(Update),
120-
Box::new(SpawnScene),
121-
Box::new(PostUpdate),
122-
Box::new(Last),
115+
First.intern(),
116+
PreUpdate.intern(),
117+
StateTransition.intern(),
118+
RunFixedUpdateLoop.intern(),
119+
Update.intern(),
120+
SpawnScene.intern(),
121+
PostUpdate.intern(),
122+
Last.intern(),
123123
],
124124
}
125125
}
@@ -133,7 +133,7 @@ impl MainScheduleOrder {
133133
.iter()
134134
.position(|current| (**current).eq(&after))
135135
.unwrap_or_else(|| panic!("Expected {after:?} to exist"));
136-
self.labels.insert(index + 1, Box::new(schedule));
136+
self.labels.insert(index + 1, schedule.intern());
137137
}
138138
}
139139

@@ -148,8 +148,8 @@ impl Main {
148148
}
149149

150150
world.resource_scope(|world, order: Mut<MainScheduleOrder>| {
151-
for label in &order.labels {
152-
let _ = world.try_run_schedule(&**label);
151+
for &label in &order.labels {
152+
let _ = world.try_run_schedule(label);
153153
}
154154
});
155155
}

crates/bevy_derive/src/lib.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,13 @@ pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream {
201201

202202
/// Generates an impl of the `AppLabel` trait.
203203
///
204-
/// This works only for unit structs, or enums with only unit variants.
205-
/// You may force a struct or variant to behave as if it were fieldless with `#[app_label(ignore_fields)]`.
206-
#[proc_macro_derive(AppLabel, attributes(app_label))]
204+
/// This does not work for unions.
205+
#[proc_macro_derive(AppLabel)]
207206
pub fn derive_app_label(input: TokenStream) -> TokenStream {
208207
let input = syn::parse_macro_input!(input as syn::DeriveInput);
209208
let mut trait_path = BevyManifest::default().get_path("bevy_app");
209+
let mut dyn_eq_path = trait_path.clone();
210210
trait_path.segments.push(format_ident!("AppLabel").into());
211-
derive_label(input, &trait_path, "app_label")
211+
dyn_eq_path.segments.push(format_ident!("DynEq").into());
212+
derive_label(input, "AppLabel", &trait_path, &dyn_eq_path)
212213
}

0 commit comments

Comments
 (0)