diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 3b206ce2deaea..72f7e1a2bf7f0 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -4,11 +4,11 @@ use bevy_ecs::{ prelude::*, schedule::{ apply_state_transition, common_conditions::run_once as run_once_condition, - run_enter_schedule, BoxedScheduleLabel, IntoSystemConfigs, IntoSystemSetConfigs, + run_enter_schedule, InternedScheduleLabel, IntoSystemConfigs, IntoSystemSetConfigs, ScheduleBuildSettings, ScheduleLabel, }, }; -use bevy_utils::{tracing::debug, HashMap, HashSet}; +use bevy_utils::{intern::Interned, tracing::debug, HashMap, HashSet}; use std::{ fmt::Debug, panic::{catch_unwind, resume_unwind, AssertUnwindSafe}, @@ -20,10 +20,14 @@ use bevy_utils::tracing::info_span; bevy_utils::define_label!( /// A strongly-typed class of labels used to identify an [`App`]. AppLabel, - /// A strongly-typed identifier for an [`AppLabel`]. - AppLabelId, + APP_LABEL_INTERNER ); +pub use bevy_utils::label::DynEq; + +/// A shorthand for `Interned`. +pub type InternedAppLabel = Interned; + pub(crate) enum AppError { DuplicatePlugin { plugin_name: String }, } @@ -70,8 +74,8 @@ pub struct App { /// The schedule that runs the main loop of schedule execution. /// /// This is initially set to [`Main`]. - pub main_schedule_label: BoxedScheduleLabel, - sub_apps: HashMap, + pub main_schedule_label: InternedScheduleLabel, + sub_apps: HashMap, plugin_registry: Vec>, plugin_name_added: HashSet, /// A private counter to prevent incorrect calls to `App::run()` from `Plugin::build()` @@ -157,7 +161,7 @@ impl SubApp { /// Runs the [`SubApp`]'s default schedule. pub fn run(&mut self) { - self.app.world.run_schedule(&*self.app.main_schedule_label); + self.app.world.run_schedule(self.app.main_schedule_label); self.app.world.clear_trackers(); } @@ -233,7 +237,7 @@ impl App { sub_apps: HashMap::default(), plugin_registry: Vec::default(), plugin_name_added: Default::default(), - main_schedule_label: Box::new(Main), + main_schedule_label: Main.intern(), building_plugin_depth: 0, plugins_state: PluginsState::Adding, } @@ -256,7 +260,7 @@ impl App { { #[cfg(feature = "trace")] let _bevy_main_update_span = info_span!("main app").entered(); - self.world.run_schedule(&*self.main_schedule_label); + self.world.run_schedule(self.main_schedule_label); } for (_label, sub_app) in &mut self.sub_apps { #[cfg(feature = "trace")] @@ -409,9 +413,10 @@ impl App { schedule: impl ScheduleLabel, systems: impl IntoSystemConfigs, ) -> &mut Self { + let schedule = schedule.intern(); let mut schedules = self.world.resource_mut::(); - if let Some(schedule) = schedules.get_mut(&schedule) { + if let Some(schedule) = schedules.get_mut(schedule) { schedule.add_systems(systems); } else { let mut new_schedule = Schedule::new(schedule); @@ -440,8 +445,9 @@ impl App { schedule: impl ScheduleLabel, sets: impl IntoSystemSetConfigs, ) -> &mut Self { + let schedule = schedule.intern(); let mut schedules = self.world.resource_mut::(); - if let Some(schedule) = schedules.get_mut(&schedule) { + if let Some(schedule) = schedules.get_mut(schedule) { schedule.configure_sets(sets); } else { let mut new_schedule = Schedule::new(schedule); @@ -784,16 +790,15 @@ impl App { pub fn sub_app_mut(&mut self, label: impl AppLabel) -> &mut App { match self.get_sub_app_mut(label) { Ok(app) => app, - Err(label) => panic!("Sub-App with label '{:?}' does not exist", label.as_str()), + Err(label) => panic!("Sub-App with label '{:?}' does not exist", label), } } /// Retrieves a `SubApp` inside this [`App`] with the given label, if it exists. Otherwise returns /// an [`Err`] containing the given label. - pub fn get_sub_app_mut(&mut self, label: impl AppLabel) -> Result<&mut App, AppLabelId> { - let label = label.as_label(); + pub fn get_sub_app_mut(&mut self, label: impl AppLabel) -> Result<&mut App, impl AppLabel> { self.sub_apps - .get_mut(&label) + .get_mut(&label.intern()) .map(|sub_app| &mut sub_app.app) .ok_or(label) } @@ -806,25 +811,25 @@ impl App { pub fn sub_app(&self, label: impl AppLabel) -> &App { match self.get_sub_app(label) { Ok(app) => app, - Err(label) => panic!("Sub-App with label '{:?}' does not exist", label.as_str()), + Err(label) => panic!("Sub-App with label '{:?}' does not exist", label), } } /// Inserts an existing sub app into the app pub fn insert_sub_app(&mut self, label: impl AppLabel, sub_app: SubApp) { - self.sub_apps.insert(label.as_label(), sub_app); + self.sub_apps.insert(label.intern(), sub_app); } /// Removes a sub app from the app. Returns [`None`] if the label doesn't exist. pub fn remove_sub_app(&mut self, label: impl AppLabel) -> Option { - self.sub_apps.remove(&label.as_label()) + self.sub_apps.remove(&label.intern()) } /// Retrieves a `SubApp` inside this [`App`] with the given label, if it exists. Otherwise returns /// an [`Err`] containing the given label. pub fn get_sub_app(&self, label: impl AppLabel) -> Result<&App, impl AppLabel> { self.sub_apps - .get(&label.as_label()) + .get(&label.intern()) .map(|sub_app| &sub_app.app) .ok_or(label) } @@ -845,8 +850,9 @@ impl App { /// /// See [`App::add_schedule`] to pass in a pre-constructed schedule. pub fn init_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { + let label = label.intern(); let mut schedules = self.world.resource_mut::(); - if !schedules.contains(&label) { + if !schedules.contains(label) { schedules.insert(Schedule::new(label)); } self @@ -855,7 +861,7 @@ impl App { /// Gets read-only access to the [`Schedule`] with the provided `label` if it exists. pub fn get_schedule(&self, label: impl ScheduleLabel) -> Option<&Schedule> { let schedules = self.world.get_resource::()?; - schedules.get(&label) + schedules.get(label) } /// Gets read-write access to a [`Schedule`] with the provided `label` if it exists. @@ -863,7 +869,7 @@ impl App { let schedules = self.world.get_resource_mut::()?; // We need to call .into_inner here to satisfy the borrow checker: // it can reason about reborrows using ordinary references but not the `Mut` smart pointer. - schedules.into_inner().get_mut(&label) + schedules.into_inner().get_mut(label) } /// Applies the function to the [`Schedule`] associated with `label`. @@ -874,13 +880,14 @@ impl App { label: impl ScheduleLabel, f: impl FnOnce(&mut Schedule), ) -> &mut Self { + let label = label.intern(); let mut schedules = self.world.resource_mut::(); - if schedules.get(&label).is_none() { - schedules.insert(Schedule::new(label.dyn_clone())); + if schedules.get(label).is_none() { + schedules.insert(Schedule::new(label)); } - let schedule = schedules.get_mut(&label).unwrap(); + let schedule = schedules.get_mut(label).unwrap(); // Call the function f, passing in the schedule retrieved f(schedule); @@ -1008,6 +1015,8 @@ pub struct AppExit; #[cfg(test)] mod tests { + use std::marker::PhantomData; + use bevy_ecs::{ schedule::{OnEnter, States}, system::Commands, @@ -1104,4 +1113,107 @@ mod tests { app.world.run_schedule(OnEnter(AppState::MainMenu)); assert_eq!(app.world.entities().len(), 2); } + + #[test] + fn test_derive_app_label() { + use super::AppLabel; + use crate::{self as bevy_app}; + + #[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct UnitLabel; + + #[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct TupleLabel(u32, u32); + + #[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct StructLabel { + a: u32, + b: u32, + } + + #[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct EmptyTupleLabel(); + + #[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct EmptyStructLabel {} + + #[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + enum EnumLabel { + #[default] + Unit, + Tuple(u32, u32), + Struct { + a: u32, + b: u32, + }, + } + + #[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct GenericLabel(PhantomData); + + assert_eq!(UnitLabel.intern(), UnitLabel.intern()); + assert_eq!(EnumLabel::Unit.intern(), EnumLabel::Unit.intern()); + assert_ne!(UnitLabel.intern(), EnumLabel::Unit.intern()); + assert_ne!(UnitLabel.intern(), TupleLabel(0, 0).intern()); + assert_ne!(EnumLabel::Unit.intern(), EnumLabel::Tuple(0, 0).intern()); + + assert_eq!(TupleLabel(0, 0).intern(), TupleLabel(0, 0).intern()); + assert_eq!( + EnumLabel::Tuple(0, 0).intern(), + EnumLabel::Tuple(0, 0).intern() + ); + assert_ne!(TupleLabel(0, 0).intern(), TupleLabel(0, 1).intern()); + assert_ne!( + EnumLabel::Tuple(0, 0).intern(), + EnumLabel::Tuple(0, 1).intern() + ); + assert_ne!(TupleLabel(0, 0).intern(), EnumLabel::Tuple(0, 0).intern()); + assert_ne!( + TupleLabel(0, 0).intern(), + StructLabel { a: 0, b: 0 }.intern() + ); + assert_ne!( + EnumLabel::Tuple(0, 0).intern(), + EnumLabel::Struct { a: 0, b: 0 }.intern() + ); + + assert_eq!( + StructLabel { a: 0, b: 0 }.intern(), + StructLabel { a: 0, b: 0 }.intern() + ); + assert_eq!( + EnumLabel::Struct { a: 0, b: 0 }.intern(), + EnumLabel::Struct { a: 0, b: 0 }.intern() + ); + assert_ne!( + StructLabel { a: 0, b: 0 }.intern(), + StructLabel { a: 0, b: 1 }.intern() + ); + assert_ne!( + EnumLabel::Struct { a: 0, b: 0 }.intern(), + EnumLabel::Struct { a: 0, b: 1 }.intern() + ); + assert_ne!( + StructLabel { a: 0, b: 0 }.intern(), + EnumLabel::Struct { a: 0, b: 0 }.intern() + ); + assert_ne!( + StructLabel { a: 0, b: 0 }.intern(), + EnumLabel::Struct { a: 0, b: 0 }.intern() + ); + assert_ne!(StructLabel { a: 0, b: 0 }.intern(), UnitLabel.intern(),); + assert_ne!( + EnumLabel::Struct { a: 0, b: 0 }.intern(), + EnumLabel::Unit.intern() + ); + + assert_eq!( + GenericLabel::(PhantomData).intern(), + GenericLabel::(PhantomData).intern() + ); + assert_ne!( + GenericLabel::(PhantomData).intern(), + GenericLabel::(PhantomData).intern() + ); + } } diff --git a/crates/bevy_app/src/main_schedule.rs b/crates/bevy_app/src/main_schedule.rs index 004aba0a6fc0a..0e4fa47d79eed 100644 --- a/crates/bevy_app/src/main_schedule.rs +++ b/crates/bevy_app/src/main_schedule.rs @@ -1,6 +1,6 @@ use crate::{App, Plugin}; use bevy_ecs::{ - schedule::{ExecutorKind, Schedule, ScheduleLabel}, + schedule::{ExecutorKind, InternedScheduleLabel, Schedule, ScheduleLabel}, system::{Local, Resource}, world::{Mut, World}, }; @@ -105,21 +105,21 @@ pub struct Last; #[derive(Resource, Debug)] pub struct MainScheduleOrder { /// The labels to run for the [`Main`] schedule (in the order they will be run). - pub labels: Vec>, + pub labels: Vec, } impl Default for MainScheduleOrder { fn default() -> Self { Self { labels: vec![ - Box::new(First), - Box::new(PreUpdate), - Box::new(StateTransition), - Box::new(RunFixedUpdateLoop), - Box::new(Update), - Box::new(SpawnScene), - Box::new(PostUpdate), - Box::new(Last), + First.intern(), + PreUpdate.intern(), + StateTransition.intern(), + RunFixedUpdateLoop.intern(), + Update.intern(), + SpawnScene.intern(), + PostUpdate.intern(), + Last.intern(), ], } } @@ -133,7 +133,7 @@ impl MainScheduleOrder { .iter() .position(|current| (**current).eq(&after)) .unwrap_or_else(|| panic!("Expected {after:?} to exist")); - self.labels.insert(index + 1, Box::new(schedule)); + self.labels.insert(index + 1, schedule.intern()); } } @@ -148,8 +148,8 @@ impl Main { } world.resource_scope(|world, order: Mut| { - for label in &order.labels { - let _ = world.try_run_schedule(&**label); + for &label in &order.labels { + let _ = world.try_run_schedule(label); } }); } diff --git a/crates/bevy_derive/src/lib.rs b/crates/bevy_derive/src/lib.rs index 9030d8791ba71..b6766c0dc8959 100644 --- a/crates/bevy_derive/src/lib.rs +++ b/crates/bevy_derive/src/lib.rs @@ -201,12 +201,13 @@ pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream { /// Generates an impl of the `AppLabel` trait. /// -/// This works only for unit structs, or enums with only unit variants. -/// You may force a struct or variant to behave as if it were fieldless with `#[app_label(ignore_fields)]`. -#[proc_macro_derive(AppLabel, attributes(app_label))] +/// This does not work for unions. +#[proc_macro_derive(AppLabel)] pub fn derive_app_label(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); let mut trait_path = BevyManifest::default().get_path("bevy_app"); + let mut dyn_eq_path = trait_path.clone(); trait_path.segments.push(format_ident!("AppLabel").into()); - derive_label(input, &trait_path, "app_label") + dyn_eq_path.segments.push(format_ident!("DynEq").into()); + derive_label(input, "AppLabel", &trait_path, &dyn_eq_path) } diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 067191595405c..7ee660f9987d7 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -2,13 +2,10 @@ extern crate proc_macro; mod component; mod fetch; -mod set; mod states; -use crate::{fetch::derive_world_query_impl, set::derive_set}; -use bevy_macro_utils::{ - derive_boxed_label, ensure_no_collision, get_named_struct_fields, BevyManifest, -}; +use crate::fetch::derive_world_query_impl; +use bevy_macro_utils::{derive_label, ensure_no_collision, get_named_struct_fields, BevyManifest}; use proc_macro::TokenStream; use proc_macro2::Span; use quote::{format_ident, quote}; @@ -438,25 +435,33 @@ pub fn derive_world_query(input: TokenStream) -> TokenStream { } /// Derive macro generating an impl of the trait `ScheduleLabel`. +/// +/// This does not work for unions. #[proc_macro_derive(ScheduleLabel)] pub fn derive_schedule_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut trait_path = bevy_ecs_path(); trait_path.segments.push(format_ident!("schedule").into()); + let mut dyn_eq_path = trait_path.clone(); trait_path .segments .push(format_ident!("ScheduleLabel").into()); - derive_boxed_label(input, &trait_path) + dyn_eq_path.segments.push(format_ident!("DynEq").into()); + derive_label(input, "ScheduleName", &trait_path, &dyn_eq_path) } /// Derive macro generating an impl of the trait `SystemSet`. +/// +/// This does not work for unions. #[proc_macro_derive(SystemSet)] pub fn derive_system_set(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut trait_path = bevy_ecs_path(); trait_path.segments.push(format_ident!("schedule").into()); + let mut dyn_eq_path = trait_path.clone(); trait_path.segments.push(format_ident!("SystemSet").into()); - derive_set(input, &trait_path) + dyn_eq_path.segments.push(format_ident!("DynEq").into()); + derive_label(input, "SystemSet", &trait_path, &dyn_eq_path) } pub(crate) fn bevy_ecs_path() -> syn::Path { diff --git a/crates/bevy_ecs/macros/src/set.rs b/crates/bevy_ecs/macros/src/set.rs deleted file mode 100644 index 66bfb601ecf35..0000000000000 --- a/crates/bevy_ecs/macros/src/set.rs +++ /dev/null @@ -1,33 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; - -/// Derive a set trait -/// -/// # Args -/// -/// - `input`: The [`syn::DeriveInput`] for the struct that we want to derive the set trait for -/// - `trait_path`: The [`syn::Path`] to the set trait -pub fn derive_set(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream { - let ident = input.ident; - - 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 dyn_clone(&self) -> std::boxed::Box { - std::boxed::Box::new(std::clone::Clone::clone(self)) - } - } - }) - .into() -} diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 526ab1ffe3ed7..1c60f5b3da700 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -4,7 +4,7 @@ use crate::{ schedule::{ condition::{BoxedCondition, Condition}, graph_utils::{Ambiguity, Dependency, DependencyKind, GraphInfo}, - set::{BoxedSystemSet, IntoSystemSet, SystemSet}, + set::{InternedSystemSet, IntoSystemSet, SystemSet}, }, system::{BoxedSystem, IntoSystem, System}, }; @@ -20,7 +20,7 @@ fn new_condition(condition: impl Condition) -> BoxedCondition { Box::new(condition_system) } -fn ambiguous_with(graph_info: &mut GraphInfo, set: BoxedSystemSet) { +fn ambiguous_with(graph_info: &mut GraphInfo, set: InternedSystemSet) { match &mut graph_info.ambiguous_with { detection @ Ambiguity::Check => { *detection = Ambiguity::IgnoreWithSet(vec![set]); @@ -92,20 +92,20 @@ impl SystemConfigs { impl NodeConfigs { /// Adds a new boxed system set to the systems. - pub fn in_set_dyn(&mut self, set: BoxedSystemSet) { + pub fn in_set_inner(&mut self, set: InternedSystemSet) { match self { Self::NodeConfig(config) => { config.graph_info.sets.push(set); } Self::Configs { configs, .. } => { for config in configs { - config.in_set_dyn(set.dyn_clone()); + config.in_set_inner(set); } } } } - fn before_inner(&mut self, set: BoxedSystemSet) { + fn before_inner(&mut self, set: InternedSystemSet) { match self { Self::NodeConfig(config) => { config @@ -115,13 +115,13 @@ impl NodeConfigs { } Self::Configs { configs, .. } => { for config in configs { - config.before_inner(set.dyn_clone()); + config.before_inner(set); } } } } - fn after_inner(&mut self, set: BoxedSystemSet) { + fn after_inner(&mut self, set: InternedSystemSet) { match self { Self::NodeConfig(config) => { config @@ -131,7 +131,7 @@ impl NodeConfigs { } Self::Configs { configs, .. } => { for config in configs { - config.after_inner(set.dyn_clone()); + config.after_inner(set); } } } @@ -150,14 +150,14 @@ impl NodeConfigs { } } - fn ambiguous_with_inner(&mut self, set: BoxedSystemSet) { + fn ambiguous_with_inner(&mut self, set: InternedSystemSet) { match self { Self::NodeConfig(config) => { ambiguous_with(&mut config.graph_info, set); } Self::Configs { configs, .. } => { for config in configs { - config.ambiguous_with_inner(set.dyn_clone()); + config.ambiguous_with_inner(set); } } } @@ -330,20 +330,20 @@ impl IntoSystemConfigs<()> for SystemConfigs { "adding arbitrary systems to a system type set is not allowed" ); - self.in_set_dyn(set.dyn_clone()); + self.in_set_inner(set.intern()); self } fn before(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - self.before_inner(set.dyn_clone()); + self.before_inner(set.intern()); self } fn after(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - self.after_inner(set.dyn_clone()); + self.after_inner(set.intern()); self } @@ -354,7 +354,7 @@ impl IntoSystemConfigs<()> for SystemConfigs { fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - self.ambiguous_with_inner(set.dyn_clone()); + self.ambiguous_with_inner(set.intern()); self } @@ -398,11 +398,11 @@ macro_rules! impl_system_collection { all_tuples!(impl_system_collection, 1, 20, P, S); /// A [`SystemSet`] with scheduling metadata. -pub type SystemSetConfig = NodeConfig; +pub type SystemSetConfig = NodeConfig; impl SystemSetConfig { #[track_caller] - pub(super) fn new(set: BoxedSystemSet) -> Self { + pub(super) fn new(set: InternedSystemSet) -> Self { // system type sets are automatically populated // to avoid unintentionally broad changes, they cannot be configured assert!( @@ -419,7 +419,7 @@ impl SystemSetConfig { } /// A collection of [`SystemSetConfig`]. -pub type SystemSetConfigs = NodeConfigs; +pub type SystemSetConfigs = NodeConfigs; /// Types that can convert into a [`SystemSetConfigs`]. pub trait IntoSystemSetConfigs @@ -485,21 +485,21 @@ impl IntoSystemSetConfigs for SystemSetConfigs { set.system_type().is_none(), "adding arbitrary systems to a system type set is not allowed" ); - self.in_set_dyn(set.dyn_clone()); + self.in_set_inner(set.intern()); self } fn before(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - self.before_inner(set.dyn_clone()); + self.before_inner(set.intern()); self } fn after(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - self.after_inner(set.dyn_clone()); + self.after_inner(set.intern()); self } @@ -512,7 +512,7 @@ impl IntoSystemSetConfigs for SystemSetConfigs { fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - self.ambiguous_with_inner(set.dyn_clone()); + self.ambiguous_with_inner(set.intern()); self } @@ -530,13 +530,7 @@ impl IntoSystemSetConfigs for SystemSetConfigs { impl IntoSystemSetConfigs for S { fn into_configs(self) -> SystemSetConfigs { - SystemSetConfigs::NodeConfig(SystemSetConfig::new(Box::new(self))) - } -} - -impl IntoSystemSetConfigs for BoxedSystemSet { - fn into_configs(self) -> SystemSetConfigs { - SystemSetConfigs::NodeConfig(SystemSetConfig::new(self)) + SystemSetConfigs::NodeConfig(SystemSetConfig::new(self.intern())) } } diff --git a/crates/bevy_ecs/src/schedule/graph_utils.rs b/crates/bevy_ecs/src/schedule/graph_utils.rs index 9209ff05b94b8..dd262877e54ab 100644 --- a/crates/bevy_ecs/src/schedule/graph_utils.rs +++ b/crates/bevy_ecs/src/schedule/graph_utils.rs @@ -51,11 +51,11 @@ pub(crate) enum DependencyKind { #[derive(Clone)] pub(crate) struct Dependency { pub(crate) kind: DependencyKind, - pub(crate) set: BoxedSystemSet, + pub(crate) set: InternedSystemSet, } impl Dependency { - pub fn new(kind: DependencyKind, set: BoxedSystemSet) -> Self { + pub fn new(kind: DependencyKind, set: InternedSystemSet) -> Self { Self { kind, set } } } @@ -66,14 +66,14 @@ pub(crate) enum Ambiguity { #[default] Check, /// Ignore warnings with systems in any of these system sets. May contain duplicates. - IgnoreWithSet(Vec), + IgnoreWithSet(Vec), /// Ignore all warnings. IgnoreAll, } #[derive(Clone, Default)] pub(crate) struct GraphInfo { - pub(crate) sets: Vec, + pub(crate) sets: Vec, pub(crate) dependencies: Vec, pub(crate) ambiguous_with: Ambiguity, } diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 9d48d03df40e9..7c7876186613c 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -1011,7 +1011,7 @@ mod tests { schedule.graph_mut().initialize(&mut world); let _ = schedule.graph_mut().build_schedule( world.components(), - &TestSchedule.dyn_clone(), + TestSchedule.intern(), &BTreeSet::new(), ); @@ -1060,7 +1060,7 @@ mod tests { schedule.graph_mut().initialize(&mut world); let _ = schedule.graph_mut().build_schedule( world.components(), - &TestSchedule.dyn_clone(), + TestSchedule.intern(), &BTreeSet::new(), ); diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 49da09b07facf..82732b1a7e052 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -28,7 +28,7 @@ use crate::{ /// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s. #[derive(Default, Resource)] pub struct Schedules { - inner: HashMap, + inner: HashMap, /// List of [`ComponentId`]s to ignore when reporting system order ambiguity conflicts pub ignored_scheduling_ambiguities: BTreeSet, } @@ -47,36 +47,35 @@ impl Schedules { /// If the map already had an entry for `label`, `schedule` is inserted, /// and the old schedule is returned. Otherwise, `None` is returned. pub fn insert(&mut self, schedule: Schedule) -> Option { - let label = schedule.name.dyn_clone(); - self.inner.insert(label, schedule) + self.inner.insert(schedule.name, schedule) } /// Removes the schedule corresponding to the `label` from the map, returning it if it existed. - pub fn remove(&mut self, label: &dyn ScheduleLabel) -> Option { - self.inner.remove(label) + pub fn remove(&mut self, label: impl ScheduleLabel) -> Option { + self.inner.remove(&label.intern()) } /// Removes the (schedule, label) pair corresponding to the `label` from the map, returning it if it existed. pub fn remove_entry( &mut self, - label: &dyn ScheduleLabel, - ) -> Option<(Box, Schedule)> { - self.inner.remove_entry(label) + label: impl ScheduleLabel, + ) -> Option<(InternedScheduleLabel, Schedule)> { + self.inner.remove_entry(&label.intern()) } /// Does a schedule with the provided label already exist? - pub fn contains(&self, label: &dyn ScheduleLabel) -> bool { - self.inner.contains_key(label) + pub fn contains(&self, label: impl ScheduleLabel) -> bool { + self.inner.contains_key(&label.intern()) } /// Returns a reference to the schedule associated with `label`, if it exists. - pub fn get(&self, label: &dyn ScheduleLabel) -> Option<&Schedule> { - self.inner.get(label) + pub fn get(&self, label: impl ScheduleLabel) -> Option<&Schedule> { + self.inner.get(&label.intern()) } /// Returns a mutable reference to the schedule associated with `label`, if it exists. - pub fn get_mut(&mut self, label: &dyn ScheduleLabel) -> Option<&mut Schedule> { - self.inner.get_mut(label) + pub fn get_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> { + self.inner.get_mut(&label.intern()) } /// Returns an iterator over all schedules. Iteration order is undefined. @@ -195,7 +194,7 @@ fn make_executor(kind: ExecutorKind) -> Box { /// } /// ``` pub struct Schedule { - name: BoxedScheduleLabel, + name: InternedScheduleLabel, graph: ScheduleGraph, executable: SystemSchedule, executor: Box, @@ -219,7 +218,7 @@ impl Schedule { /// Constructs an empty `Schedule`. pub fn new(label: impl ScheduleLabel) -> Self { Self { - name: label.dyn_clone(), + name: label.intern(), graph: ScheduleGraph::new(), executable: SystemSchedule::new(), executor: make_executor(ExecutorKind::default()), @@ -307,7 +306,7 @@ impl Schedule { &mut self.executable, world.components(), &ignored_ambiguities, - &self.name, + self.name, )?; self.graph.changed = false; self.executor_initialized = false; @@ -401,11 +400,11 @@ impl Dag { /// A [`SystemSet`] with metadata, stored in a [`ScheduleGraph`]. struct SystemSetNode { - inner: BoxedSystemSet, + inner: InternedSystemSet, } impl SystemSetNode { - pub fn new(set: BoxedSystemSet) -> Self { + pub fn new(set: InternedSystemSet) -> Self { Self { inner: set } } @@ -450,13 +449,14 @@ pub struct ScheduleGraph { system_conditions: Vec>, system_sets: Vec, system_set_conditions: Vec>, - system_set_ids: HashMap, + system_set_ids: HashMap, uninit: Vec<(NodeId, usize)>, hierarchy: Dag, dependency: Dag, ambiguous_with: UnGraphMap, ambiguous_with_all: HashSet, conflicting_systems: Vec<(NodeId, NodeId, Vec)>, + anonymous_sets: usize, changed: bool, settings: ScheduleBuildSettings, } @@ -476,6 +476,7 @@ impl ScheduleGraph { ambiguous_with: UnGraphMap::new(), ambiguous_with_all: HashSet::new(), conflicting_systems: Vec::new(), + anonymous_sets: 0, changed: false, settings: default(), } @@ -604,11 +605,11 @@ impl ScheduleGraph { let more_than_one_entry = configs.len() > 1; if !collective_conditions.is_empty() { if more_than_one_entry { - let set = AnonymousSet::new(); + let set = self.create_anonymous_set(); for config in &mut configs { - config.in_set_dyn(set.dyn_clone()); + config.in_set_inner(set.intern()); } - let mut set_config = SystemSetConfig::new(set.dyn_clone()); + let mut set_config = SystemSetConfig::new(set.intern()); set_config.conditions.extend(collective_conditions); self.configure_set_inner(set_config).unwrap(); } else { @@ -748,7 +749,7 @@ impl ScheduleGraph { let id = match self.system_set_ids.get(&set) { Some(&id) => id, - None => self.add_set(set.dyn_clone()), + None => self.add_set(set), }; // graph updates are immediate @@ -762,36 +763,42 @@ impl ScheduleGraph { Ok(id) } - fn add_set(&mut self, set: BoxedSystemSet) -> NodeId { + fn add_set(&mut self, set: InternedSystemSet) -> NodeId { let id = NodeId::Set(self.system_sets.len()); - self.system_sets.push(SystemSetNode::new(set.dyn_clone())); + self.system_sets.push(SystemSetNode::new(set)); self.system_set_conditions.push(Vec::new()); self.system_set_ids.insert(set, id); id } - fn check_set(&mut self, id: &NodeId, set: &dyn SystemSet) -> Result<(), ScheduleBuildError> { - match self.system_set_ids.get(set) { + fn check_set(&mut self, id: &NodeId, set: InternedSystemSet) -> Result<(), ScheduleBuildError> { + match self.system_set_ids.get(&set) { Some(set_id) => { if id == set_id { return Err(ScheduleBuildError::HierarchyLoop(self.get_node_name(id))); } } None => { - self.add_set(set.dyn_clone()); + self.add_set(set); } } Ok(()) } + fn create_anonymous_set(&mut self) -> AnonymousSet { + let id = self.anonymous_sets; + self.anonymous_sets += 1; + AnonymousSet::new(id) + } + fn check_sets( &mut self, id: &NodeId, graph_info: &GraphInfo, ) -> Result<(), ScheduleBuildError> { - for set in &graph_info.sets { - self.check_set(id, &**set)?; + for &set in &graph_info.sets { + self.check_set(id, set)?; } Ok(()) @@ -810,7 +817,7 @@ impl ScheduleGraph { } } None => { - self.add_set(set.dyn_clone()); + self.add_set(*set); } } } @@ -818,7 +825,7 @@ impl ScheduleGraph { if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with { for set in ambiguous_with { if !self.system_set_ids.contains_key(set) { - self.add_set(set.dyn_clone()); + self.add_set(*set); } } } @@ -915,7 +922,7 @@ impl ScheduleGraph { pub fn build_schedule( &mut self, components: &Components, - schedule_label: &BoxedScheduleLabel, + schedule_label: InternedScheduleLabel, ignored_ambiguities: &BTreeSet, ) -> Result { // check hierarchy for cycles @@ -1229,7 +1236,7 @@ impl ScheduleGraph { schedule: &mut SystemSchedule, components: &Components, ignored_ambiguities: &BTreeSet, - schedule_label: &BoxedScheduleLabel, + schedule_label: InternedScheduleLabel, ) -> Result<(), ScheduleBuildError> { if !self.uninit.is_empty() { return Err(ScheduleBuildError::Uninitialized); @@ -1296,7 +1303,7 @@ impl ProcessNodeConfig for BoxedSystem { } } -impl ProcessNodeConfig for BoxedSystemSet { +impl ProcessNodeConfig for InternedSystemSet { fn process_config(schedule_graph: &mut ScheduleGraph, config: NodeConfig) -> NodeId { schedule_graph.configure_set_inner(config).unwrap() } @@ -1372,7 +1379,7 @@ impl ScheduleGraph { fn optionally_check_hierarchy_conflicts( &self, transitive_edges: &[(NodeId, NodeId)], - schedule_label: &BoxedScheduleLabel, + schedule_label: InternedScheduleLabel, ) -> Result<(), ScheduleBuildError> { if self.settings.hierarchy_detection == LogLevel::Ignore || transitive_edges.is_empty() { return Ok(()); @@ -1584,7 +1591,7 @@ impl ScheduleGraph { &self, conflicts: &[(NodeId, NodeId, Vec)], components: &Components, - schedule_label: &BoxedScheduleLabel, + schedule_label: InternedScheduleLabel, ) -> Result<(), ScheduleBuildError> { if self.settings.ambiguity_detection == LogLevel::Ignore || conflicts.is_empty() { return Ok(()); diff --git a/crates/bevy_ecs/src/schedule/set.rs b/crates/bevy_ecs/src/schedule/set.rs index 769507ff48835..1001f20189dcf 100644 --- a/crates/bevy_ecs/src/schedule/set.rs +++ b/crates/bevy_ecs/src/schedule/set.rs @@ -2,57 +2,52 @@ use std::any::TypeId; use std::fmt::Debug; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; -use std::sync::atomic::{AtomicUsize, Ordering}; pub use bevy_ecs_macros::{ScheduleLabel, SystemSet}; -use bevy_utils::define_boxed_label; -use bevy_utils::label::DynHash; +use bevy_utils::define_label; +use bevy_utils::intern::Interned; +pub use bevy_utils::label::DynEq; use crate::system::{ ExclusiveSystemParamFunction, IsExclusiveFunctionSystem, IsFunctionSystem, SystemParamFunction, }; -define_boxed_label!(ScheduleLabel); - -/// A shorthand for `Box`. -pub type BoxedSystemSet = Box; -/// A shorthand for `Box`. -pub type BoxedScheduleLabel = Box; - -/// Types that identify logical groups of systems. -pub trait SystemSet: DynHash + Debug + Send + Sync + 'static { - /// Returns `Some` if this system set is a [`SystemTypeSet`]. - fn system_type(&self) -> Option { - None - } - - /// Returns `true` if this system set is an [`AnonymousSet`]. - fn is_anonymous(&self) -> bool { - false - } - /// Creates a boxed clone of the label corresponding to this system set. - fn dyn_clone(&self) -> Box; -} - -impl PartialEq for dyn SystemSet { - fn eq(&self, other: &Self) -> bool { - self.dyn_eq(other.as_dyn_eq()) - } -} - -impl Eq for dyn SystemSet {} - -impl Hash for dyn SystemSet { - fn hash(&self, state: &mut H) { - self.dyn_hash(state); +define_label!( + /// A strongly-typed class of labels used to identify an [`Schedule`]. + ScheduleLabel, + SCHEDULE_LABEL_INTERNER +); + +define_label!( + /// Types that identify logical groups of systems. + SystemSet, + SYSTEM_SET_INTERNER, + extra_methods: { + /// Returns `Some` if this system set is a [`SystemTypeSet`]. + fn system_type(&self) -> Option { + None + } + + /// Returns `true` if this system set is an [`AnonymousSet`]. + fn is_anonymous(&self) -> bool { + false + } + }, + extra_methods_impl: { + fn system_type(&self) -> Option { + (**self).system_type() + } + + fn is_anonymous(&self) -> bool { + (**self).is_anonymous() + } } -} +); -impl Clone for Box { - fn clone(&self) -> Self { - self.dyn_clone() - } -} +/// A shorthand for `Interned`. +pub type InternedSystemSet = Interned; +/// A shorthand for `Interned`. +pub type InternedScheduleLabel = Interned; /// A [`SystemSet`] grouping instances of the same function. /// @@ -108,6 +103,15 @@ impl SystemSet for SystemTypeSet { fn dyn_clone(&self) -> Box { Box::new(*self) } + + fn as_dyn_eq(&self) -> &dyn DynEq { + self + } + + fn dyn_hash(&self, mut state: &mut dyn Hasher) { + std::any::TypeId::of::().hash(&mut state); + self.hash(&mut state); + } } /// A [`SystemSet`] implicitly created when using @@ -116,11 +120,9 @@ impl SystemSet for SystemTypeSet { #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct AnonymousSet(usize); -static NEXT_ANONYMOUS_SET_ID: AtomicUsize = AtomicUsize::new(0); - impl AnonymousSet { - pub(crate) fn new() -> Self { - Self(NEXT_ANONYMOUS_SET_ID.fetch_add(1, Ordering::Relaxed)) + pub(crate) fn new(id: usize) -> Self { + Self(id) } } @@ -129,6 +131,15 @@ impl SystemSet for AnonymousSet { true } + fn as_dyn_eq(&self) -> &dyn DynEq { + self + } + + fn dyn_hash(&self, mut state: &mut dyn Hasher) { + std::any::TypeId::of::().hash(&mut state); + self.hash(&mut state); + } + fn dyn_clone(&self) -> Box { Box::new(*self) } @@ -189,7 +200,7 @@ mod tests { use super::*; #[test] - fn test_boxed_label() { + fn test_schedule_label() { use crate::{self as bevy_ecs, world::World}; #[derive(Resource)] @@ -198,20 +209,220 @@ mod tests { #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] struct A; + #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct B; + let mut world = World::new(); let mut schedule = Schedule::new(A); schedule.add_systems(|mut flag: ResMut| flag.0 = true); world.add_schedule(schedule); - let boxed: Box = Box::new(A); + let interned = A.intern(); world.insert_resource(Flag(false)); - world.run_schedule(&boxed); + world.run_schedule(interned); assert!(world.resource::().0); world.insert_resource(Flag(false)); - world.run_schedule(boxed); + world.run_schedule(interned); assert!(world.resource::().0); + + assert_ne!(A.intern(), B.intern()); + } + + #[test] + fn test_derive_schedule_label() { + use crate::{self as bevy_ecs}; + + #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct UnitLabel; + + #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct TupleLabel(u32, u32); + + #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct StructLabel { + a: u32, + b: u32, + } + + #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct EmptyTupleLabel(); + + #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct EmptyStructLabel {} + + #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + enum EnumLabel { + #[default] + Unit, + Tuple(u32, u32), + Struct { + a: u32, + b: u32, + }, + } + + #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct GenericLabel(PhantomData); + + assert_eq!(UnitLabel.intern(), UnitLabel.intern()); + assert_eq!(EnumLabel::Unit.intern(), EnumLabel::Unit.intern()); + assert_ne!(UnitLabel.intern(), EnumLabel::Unit.intern()); + assert_ne!(UnitLabel.intern(), TupleLabel(0, 0).intern()); + assert_ne!(EnumLabel::Unit.intern(), EnumLabel::Tuple(0, 0).intern()); + + assert_eq!(TupleLabel(0, 0).intern(), TupleLabel(0, 0).intern()); + assert_eq!( + EnumLabel::Tuple(0, 0).intern(), + EnumLabel::Tuple(0, 0).intern() + ); + assert_ne!(TupleLabel(0, 0).intern(), TupleLabel(0, 1).intern()); + assert_ne!( + EnumLabel::Tuple(0, 0).intern(), + EnumLabel::Tuple(0, 1).intern() + ); + assert_ne!(TupleLabel(0, 0).intern(), EnumLabel::Tuple(0, 0).intern()); + assert_ne!( + TupleLabel(0, 0).intern(), + StructLabel { a: 0, b: 0 }.intern() + ); + assert_ne!( + EnumLabel::Tuple(0, 0).intern(), + EnumLabel::Struct { a: 0, b: 0 }.intern() + ); + + assert_eq!( + StructLabel { a: 0, b: 0 }.intern(), + StructLabel { a: 0, b: 0 }.intern() + ); + assert_eq!( + EnumLabel::Struct { a: 0, b: 0 }.intern(), + EnumLabel::Struct { a: 0, b: 0 }.intern() + ); + assert_ne!( + StructLabel { a: 0, b: 0 }.intern(), + StructLabel { a: 0, b: 1 }.intern() + ); + assert_ne!( + EnumLabel::Struct { a: 0, b: 0 }.intern(), + EnumLabel::Struct { a: 0, b: 1 }.intern() + ); + assert_ne!( + StructLabel { a: 0, b: 0 }.intern(), + EnumLabel::Struct { a: 0, b: 0 }.intern() + ); + assert_ne!( + StructLabel { a: 0, b: 0 }.intern(), + EnumLabel::Struct { a: 0, b: 0 }.intern() + ); + assert_ne!(StructLabel { a: 0, b: 0 }.intern(), UnitLabel.intern(),); + assert_ne!( + EnumLabel::Struct { a: 0, b: 0 }.intern(), + EnumLabel::Unit.intern() + ); + + assert_eq!( + GenericLabel::(PhantomData).intern(), + GenericLabel::(PhantomData).intern() + ); + assert_ne!( + GenericLabel::(PhantomData).intern(), + GenericLabel::(PhantomData).intern() + ); + } + + #[test] + fn test_derive_system_set() { + use crate::{self as bevy_ecs}; + + #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct UnitSet; + + #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct TupleSet(u32, u32); + + #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct StructSet { + a: u32, + b: u32, + } + + #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct EmptyTupleSet(); + + #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct EmptyStructSet {} + + #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + enum EnumSet { + #[default] + Unit, + Tuple(u32, u32), + Struct { + a: u32, + b: u32, + }, + } + + #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + struct GenericSet(PhantomData); + + assert_eq!(UnitSet.intern(), UnitSet.intern()); + assert_eq!(EnumSet::Unit.intern(), EnumSet::Unit.intern()); + assert_ne!(UnitSet.intern(), EnumSet::Unit.intern()); + assert_ne!(UnitSet.intern(), TupleSet(0, 0).intern()); + assert_ne!(EnumSet::Unit.intern(), EnumSet::Tuple(0, 0).intern()); + + assert_eq!(TupleSet(0, 0).intern(), TupleSet(0, 0).intern()); + assert_eq!(EnumSet::Tuple(0, 0).intern(), EnumSet::Tuple(0, 0).intern()); + assert_ne!(TupleSet(0, 0).intern(), TupleSet(0, 1).intern()); + assert_ne!(EnumSet::Tuple(0, 0).intern(), EnumSet::Tuple(0, 1).intern()); + assert_ne!(TupleSet(0, 0).intern(), EnumSet::Tuple(0, 0).intern()); + assert_ne!(TupleSet(0, 0).intern(), StructSet { a: 0, b: 0 }.intern()); + assert_ne!( + EnumSet::Tuple(0, 0).intern(), + EnumSet::Struct { a: 0, b: 0 }.intern() + ); + + assert_eq!( + StructSet { a: 0, b: 0 }.intern(), + StructSet { a: 0, b: 0 }.intern() + ); + assert_eq!( + EnumSet::Struct { a: 0, b: 0 }.intern(), + EnumSet::Struct { a: 0, b: 0 }.intern() + ); + assert_ne!( + StructSet { a: 0, b: 0 }.intern(), + StructSet { a: 0, b: 1 }.intern() + ); + assert_ne!( + EnumSet::Struct { a: 0, b: 0 }.intern(), + EnumSet::Struct { a: 0, b: 1 }.intern() + ); + assert_ne!( + StructSet { a: 0, b: 0 }.intern(), + EnumSet::Struct { a: 0, b: 0 }.intern() + ); + assert_ne!( + StructSet { a: 0, b: 0 }.intern(), + EnumSet::Struct { a: 0, b: 0 }.intern() + ); + assert_ne!(StructSet { a: 0, b: 0 }.intern(), UnitSet.intern(),); + assert_ne!( + EnumSet::Struct { a: 0, b: 0 }.intern(), + EnumSet::Unit.intern() + ); + + assert_eq!( + GenericSet::(PhantomData).intern(), + GenericSet::(PhantomData).intern() + ); + assert_ne!( + GenericSet::(PhantomData).intern(), + GenericSet::(PhantomData).intern() + ); } } diff --git a/crates/bevy_ecs/src/system/adapter_system.rs b/crates/bevy_ecs/src/system/adapter_system.rs index 288e91138868b..228b5ddca2700 100644 --- a/crates/bevy_ecs/src/system/adapter_system.rs +++ b/crates/bevy_ecs/src/system/adapter_system.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use super::{ReadOnlySystem, System}; -use crate::world::unsafe_world_cell::UnsafeWorldCell; +use crate::{schedule::InternedSystemSet, world::unsafe_world_cell::UnsafeWorldCell}; /// Customizes the behavior of an [`AdapterSystem`] /// @@ -143,7 +143,7 @@ where self.system.set_last_run(last_run); } - fn default_system_sets(&self) -> Vec> { + fn default_system_sets(&self) -> Vec { self.system.default_system_sets() } } diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 1d20927ef995b..f70712711cc95 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -7,6 +7,7 @@ use crate::{ component::{ComponentId, Tick}, prelude::World, query::Access, + schedule::InternedSystemSet, world::unsafe_world_cell::UnsafeWorldCell, }; @@ -226,7 +227,7 @@ where self.b.set_last_run(last_run); } - fn default_system_sets(&self) -> Vec> { + fn default_system_sets(&self) -> Vec { let mut default_sets = self.a.default_system_sets(); default_sets.append(&mut self.b.default_system_sets()); default_sets diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index ef70330173068..1a06f1eb03d71 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -2,6 +2,7 @@ use crate::{ archetype::ArchetypeComponentId, component::{ComponentId, Tick}, query::Access, + schedule::{InternedSystemSet, SystemSet}, system::{ check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, In, IntoSystem, System, SystemMeta, @@ -150,9 +151,9 @@ where ); } - fn default_system_sets(&self) -> Vec> { + fn default_system_sets(&self) -> Vec { let set = crate::schedule::SystemTypeSet::::new(); - vec![Box::new(set)] + vec![set.intern()] } } diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 5a80dea4f2f16..5b6b68d7859e2 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -3,6 +3,7 @@ use crate::{ component::{ComponentId, Tick}, prelude::FromWorld, query::{Access, FilteredAccessSet}, + schedule::{InternedSystemSet, SystemSet}, system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem}, world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId}, }; @@ -528,9 +529,9 @@ where ); } - fn default_system_sets(&self) -> Vec> { + fn default_system_sets(&self) -> Vec { let set = crate::schedule::SystemTypeSet::::new(); - vec![Box::new(set)] + vec![set.intern()] } } diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 0c8c839f95e42..fd03b94fa2b55 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -2,6 +2,7 @@ use bevy_utils::tracing::warn; use core::fmt::Debug; use crate::component::Tick; +use crate::schedule::InternedSystemSet; use crate::world::unsafe_world_cell::UnsafeWorldCell; use crate::{archetype::ArchetypeComponentId, component::ComponentId, query::Access, world::World}; @@ -91,7 +92,7 @@ pub trait System: Send + Sync + 'static { fn check_change_tick(&mut self, change_tick: Tick); /// Returns the system's default [system sets](crate::schedule::SystemSet). - fn default_system_sets(&self) -> Vec> { + fn default_system_sets(&self) -> Vec { Vec::new() } diff --git a/crates/bevy_ecs/src/world/error.rs b/crates/bevy_ecs/src/world/error.rs index f3794bb03d61c..326b0310ba15c 100644 --- a/crates/bevy_ecs/src/world/error.rs +++ b/crates/bevy_ecs/src/world/error.rs @@ -2,11 +2,11 @@ use thiserror::Error; -use crate::schedule::BoxedScheduleLabel; +use crate::schedule::InternedScheduleLabel; /// The error type returned by [`World::try_run_schedule`] if the provided schedule does not exist. /// /// [`World::try_run_schedule`]: crate::world::World::try_run_schedule #[derive(Error, Debug)] #[error("The schedule with the label {0:?} was not found.")] -pub struct TryRunScheduleError(pub BoxedScheduleLabel); +pub struct TryRunScheduleError(pub InternedScheduleLabel); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 42042411bb9da..995b503788a84 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1993,15 +1993,15 @@ impl World { /// For other use cases, see the example on [`World::schedule_scope`]. pub fn try_schedule_scope( &mut self, - label: impl AsRef, + label: impl ScheduleLabel, f: impl FnOnce(&mut World, &mut Schedule) -> R, ) -> Result { - let label = label.as_ref(); + let label = label.intern(); let Some(mut schedule) = self .get_resource_mut::() .and_then(|mut s| s.remove(label)) else { - return Err(TryRunScheduleError(label.dyn_clone())); + return Err(TryRunScheduleError(label)); }; let value = f(self, &mut schedule); @@ -2053,7 +2053,7 @@ impl World { /// If the requested schedule does not exist. pub fn schedule_scope( &mut self, - label: impl AsRef, + label: impl ScheduleLabel, f: impl FnOnce(&mut World, &mut Schedule) -> R, ) -> R { self.try_schedule_scope(label, f) @@ -2069,7 +2069,7 @@ impl World { /// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead. pub fn try_run_schedule( &mut self, - label: impl AsRef, + label: impl ScheduleLabel, ) -> Result<(), TryRunScheduleError> { self.try_schedule_scope(label, |world, sched| sched.run(world)) } @@ -2084,7 +2084,7 @@ impl World { /// # Panics /// /// If the requested schedule does not exist. - pub fn run_schedule(&mut self, label: impl AsRef) { + pub fn run_schedule(&mut self, label: impl ScheduleLabel) { self.schedule_scope(label, |world, sched| sched.run(world)); } diff --git a/crates/bevy_macro_utils/src/label.rs b/crates/bevy_macro_utils/src/label.rs index e127b5ddc905a..7facb10d1bb1b 100644 --- a/crates/bevy_macro_utils/src/label.rs +++ b/crates/bevy_macro_utils/src/label.rs @@ -3,8 +3,6 @@ use quote::{quote, quote_spanned}; use rustc_hash::FxHashSet; use syn::{spanned::Spanned, Ident}; -use crate::BevyManifest; - /// Finds an identifier that will not conflict with the specified set of tokens. /// If the identifier is present in `haystack`, extra characters will be added /// to it until it no longer conflicts with anything. @@ -52,11 +50,24 @@ pub fn ensure_no_collision(value: Ident, haystack: TokenStream) -> Ident { /// # 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_boxed_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream { - let bevy_utils_path = BevyManifest::default().get_path("bevy_utils"); +/// - `trait_name`: Name of the label trait +/// - `trait_path`: The [path](`syn::Path`) to the label trait +/// - `dyn_eq_path`: The [path](`syn::Path`) to the `DynEq` trait +pub fn derive_label( + input: syn::DeriveInput, + trait_name: &str, + trait_path: &syn::Path, + dyn_eq_path: &syn::Path, +) -> TokenStream { + if let syn::Data::Union(_) = &input.data { + let message = format!("Cannot derive {trait_name} for unions."); + return quote_spanned! { + input.span() => compile_error!(#message); + } + .into(); + } - let ident = input.ident; + let ident = input.ident.clone(); 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(), @@ -68,141 +79,22 @@ pub fn derive_boxed_label(input: syn::DeriveInput, trait_path: &syn::Path) -> To }) .unwrap(), ); - (quote! { impl #impl_generics #trait_path for #ident #ty_generics #where_clause { - fn dyn_clone(&self) -> std::boxed::Box { - std::boxed::Box::new(std::clone::Clone::clone(self)) + fn dyn_clone(&self) -> ::std::boxed::Box { + ::std::boxed::Box::new(::std::clone::Clone::clone(self)) } - fn as_dyn_eq(&self) -> &dyn #bevy_utils_path::label::DynEq { + fn as_dyn_eq(&self) -> &dyn #dyn_eq_path { self } fn dyn_hash(&self, mut state: &mut dyn ::std::hash::Hasher) { - let ty_id = #trait_path::inner_type_id(self); + let ty_id = ::std::any::TypeId::of::(); ::std::hash::Hash::hash(&ty_id, &mut state); ::std::hash::Hash::hash(self, &mut state); } } - - impl #impl_generics ::std::convert::AsRef for #ident #ty_generics #where_clause { - fn as_ref(&self) -> &dyn #trait_path { - self - } - } - }) - .into() -} - -/// 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_label( - input: syn::DeriveInput, - trait_path: &syn::Path, - attr_name: &str, -) -> TokenStream { - // return true if the variant specified is an `ignore_fields` attribute - fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool { - if attr.path().get_ident().as_ref().unwrap() != &attr_name { - return false; - } - - syn::custom_keyword!(ignore_fields); - attr.parse_args_with(|input: syn::parse::ParseStream| { - let ignore = input.parse::>()?.is_some(); - Ok(ignore) - }) - .unwrap() - } - - let ident = input.ident.clone(); - - 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 }).unwrap()); - - let as_str = match input.data { - syn::Data::Struct(d) => { - // see if the user tried to ignore fields incorrectly - if let Some(attr) = d - .fields - .iter() - .flat_map(|f| &f.attrs) - .find(|a| is_ignore(a, attr_name)) - { - let err_msg = format!("`#[{attr_name}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration"); - return quote_spanned! { - attr.span() => compile_error!(#err_msg); - } - .into(); - } - // Structs must either be fieldless, or explicitly ignore the fields. - let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, attr_name)); - if matches!(d.fields, syn::Fields::Unit) || ignore_fields { - let lit = ident.to_string(); - quote! { #lit } - } else { - let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`"); - return quote_spanned! { - d.fields.span() => compile_error!(#err_msg); - } - .into(); - } - } - syn::Data::Enum(d) => { - // check if the user put #[label(ignore_fields)] in the wrong place - if let Some(attr) = input.attrs.iter().find(|a| is_ignore(a, attr_name)) { - let err_msg = format!("`#[{attr_name}(ignore_fields)]` can only be applied to enum variants or struct declarations"); - return quote_spanned! { - attr.span() => compile_error!(#err_msg); - } - .into(); - } - let arms = d.variants.iter().map(|v| { - // Variants must either be fieldless, or explicitly ignore the fields. - let ignore_fields = v.attrs.iter().any(|a| is_ignore(a, attr_name)); - if matches!(v.fields, syn::Fields::Unit) | ignore_fields { - let mut path = syn::Path::from(ident.clone()); - path.segments.push(v.ident.clone().into()); - let lit = format!("{ident}::{}", v.ident.clone()); - quote! { #path { .. } => #lit } - } else { - let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`"); - quote_spanned! { - v.fields.span() => _ => { compile_error!(#err_msg); } - } - } - }); - quote! { - match self { - #(#arms),* - } - } - } - syn::Data::Union(_) => { - return quote_spanned! { - input.span() => compile_error!("Unions cannot be used as labels."); - } - .into(); - } - }; - - (quote! { - impl #impl_generics #trait_path for #ident #ty_generics #where_clause { - fn as_str(&self) -> &'static str { - #as_str - } - } }) .into() } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 7f013c0a4fb28..4f06c11e0291d 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -406,7 +406,7 @@ unsafe fn initialize_render_app(app: &mut App) { app.init_resource::(); let mut render_app = App::empty(); - render_app.main_schedule_label = Box::new(Render); + render_app.main_schedule_label = Render.intern(); let mut extract_schedule = Schedule::new(ExtractSchedule); extract_schedule.set_apply_final_deferred(false); @@ -475,7 +475,7 @@ unsafe fn initialize_render_app(app: &mut App) { fn apply_extract_commands(render_world: &mut World) { render_world.resource_scope(|render_world, mut schedules: Mut| { schedules - .get_mut(&ExtractSchedule) + .get_mut(ExtractSchedule) .unwrap() .apply_deferred(render_world); }); diff --git a/crates/bevy_utils/src/intern.rs b/crates/bevy_utils/src/intern.rs new file mode 100644 index 0000000000000..27473ce471280 --- /dev/null +++ b/crates/bevy_utils/src/intern.rs @@ -0,0 +1,286 @@ +//! Provides types used to statically intern immutable values. +//! +//! Interning is a pattern used to save memory by deduplicating identical values, +//! speed up code by shrinking the stack size of large types, +//! and make comparisons for any type as fast as integers. + +use std::{ + fmt::Debug, + hash::Hash, + ops::Deref, + sync::{OnceLock, PoisonError, RwLock}, +}; + +use crate::HashSet; + +/// An interned value. Will stay valid until the end of the program and will not drop. +/// +/// For details on interning, see [the module level docs](self). +/// +/// # Comparisons +/// +/// Interned values use reference equality, meaning they implement [`Eq`] +/// and [`Hash`] regardless of whether `T` implements these traits. +/// Two interned values are only guaranteed to compare equal if they were interned using +/// the same [`Interner`] instance. +// NOTE: This type must NEVER implement Borrow since it does not obey that trait's invariants. +/// +/// ``` +/// # use bevy_utils::intern::*; +/// #[derive(PartialEq, Eq, Hash, Debug)] +/// struct Value(i32); +/// impl Internable for Value { +/// // ... +/// # fn leak(&self) -> &'static Self { Box::leak(Box::new(Value(self.0))) } +/// # fn ref_eq(&self, other: &Self) -> bool { std::ptr::eq(self, other ) } +/// # fn ref_hash(&self, state: &mut H) { std::ptr::hash(self, state); } +/// } +/// let interner_1 = Interner::new(); +/// let interner_2 = Interner::new(); +/// // Even though both values are identical, their interned forms do not +/// // compare equal as they use different interner instances. +/// assert_ne!(interner_1.intern(&Value(42)), interner_2.intern(&Value(42))); +/// ``` +pub struct Interned(pub &'static T); + +impl Deref for Interned { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl Clone for Interned { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Interned {} + +// Two Interned should only be equal if they are clones from the same instance. +// Therefore, we only use the pointer to determine equality. +impl PartialEq for Interned { + fn eq(&self, other: &Self) -> bool { + self.0.ref_eq(other.0) + } +} + +impl Eq for Interned {} + +// Important: This must be kept in sync with the PartialEq/Eq implementation +impl Hash for Interned { + fn hash(&self, state: &mut H) { + self.0.ref_hash(state); + } +} + +impl Debug for Interned { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl From<&Interned> for Interned { + fn from(value: &Interned) -> Self { + *value + } +} + +/// A trait for internable values. +/// +/// This is used by [`Interner`] to create static references for values that are interned. +pub trait Internable: Hash + Eq { + /// Creates a static reference to `self`, possibly leaking memory. + fn leak(&self) -> &'static Self; + + /// Returns `true` if the two references point to the same value. + fn ref_eq(&self, other: &Self) -> bool; + + /// Feeds the reference to the hasher. + fn ref_hash(&self, state: &mut H); +} + +impl Internable for str { + fn leak(&self) -> &'static Self { + let str = self.to_owned().into_boxed_str(); + Box::leak(str) + } + + fn ref_eq(&self, other: &Self) -> bool { + self.as_ptr() == other.as_ptr() && self.len() == other.len() + } + + fn ref_hash(&self, state: &mut H) { + self.len().hash(state); + self.as_ptr().hash(state); + } +} + +/// A thread-safe interner which can be used to create [`Interned`] from `&T` +/// +/// For details on interning, see [the module level docs](self). +/// +/// The implementation ensures that two equal values return two equal [`Interned`] values. +/// +/// To use an [`Interner`], `T` must implement [`Internable`]. +pub struct Interner(OnceLock>>); + +impl Interner { + /// Creates a new empty interner + pub const fn new() -> Self { + Self(OnceLock::new()) + } +} + +impl Interner { + /// Return the [`Interned`] corresponding to `value`. + /// + /// If it is called the first time for `value`, it will possibly leak the value and return an + /// [`Interned`] using the obtained static reference. Subsequent calls for the same `value` + /// will return [`Interned`] using the same static reference. + pub fn intern(&self, value: &T) -> Interned { + let lock = self.0.get_or_init(Default::default); + { + let set = lock.read().unwrap_or_else(PoisonError::into_inner); + if let Some(value) = set.get(value) { + return Interned(*value); + } + } + { + let mut set = lock.write().unwrap_or_else(PoisonError::into_inner); + if let Some(value) = set.get(value) { + Interned(*value) + } else { + let leaked = value.leak(); + set.insert(leaked); + Interned(leaked) + } + } + } +} + +impl Default for Interner { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + }; + + use crate::intern::{Internable, Interned, Interner}; + + #[test] + fn zero_sized_type() { + #[derive(PartialEq, Eq, Hash, Debug)] + pub struct A; + + impl Internable for A { + fn leak(&self) -> &'static Self { + &A + } + + fn ref_eq(&self, other: &Self) -> bool { + std::ptr::eq(self, other) + } + + fn ref_hash(&self, state: &mut H) { + std::ptr::hash(self, state); + } + } + + let interner = Interner::default(); + let x = interner.intern(&A); + let y = interner.intern(&A); + assert_eq!(x, y); + } + + #[test] + fn fieldless_enum() { + #[derive(PartialEq, Eq, Hash, Debug, Clone)] + pub enum A { + X, + Y, + } + + impl Internable for A { + fn leak(&self) -> &'static Self { + match self { + A::X => &A::X, + A::Y => &A::Y, + } + } + + fn ref_eq(&self, other: &Self) -> bool { + std::ptr::eq(self, other) + } + + fn ref_hash(&self, state: &mut H) { + std::ptr::hash(self, state); + } + } + + let interner = Interner::default(); + let x = interner.intern(&A::X); + let y = interner.intern(&A::Y); + assert_ne!(x, y); + } + + #[test] + fn static_sub_strings() { + let str = "ABC ABC"; + let a = &str[0..3]; + let b = &str[4..7]; + // Same contents + assert_eq!(a, b); + let x = Interned(a); + let y = Interned(b); + // Different pointers + assert_ne!(x, y); + let interner = Interner::default(); + let x = interner.intern(a); + let y = interner.intern(b); + // Same pointers returned by interner + assert_eq!(x, y); + } + + #[test] + fn same_interned_instance() { + let a = Interned("A"); + let b = a; + + assert_eq!(a, b); + + let mut hasher = DefaultHasher::default(); + a.hash(&mut hasher); + let hash_a = hasher.finish(); + + let mut hasher = DefaultHasher::default(); + b.hash(&mut hasher); + let hash_b = hasher.finish(); + + assert_eq!(hash_a, hash_b); + } + + #[test] + fn same_interned_content() { + let a = Interned::(Box::leak(Box::new("A".to_string()))); + let b = Interned::(Box::leak(Box::new("A".to_string()))); + + assert_ne!(a, b); + } + + #[test] + fn different_interned_content() { + let a = Interned::("A"); + let b = Interned::("B"); + + assert_ne!(a, b); + } +} diff --git a/crates/bevy_utils/src/label.rs b/crates/bevy_utils/src/label.rs index 9d993c5993809..20b63f8850b72 100644 --- a/crates/bevy_utils/src/label.rs +++ b/crates/bevy_utils/src/label.rs @@ -65,154 +65,134 @@ where /// # Example /// /// ``` -/// # use bevy_utils::define_boxed_label; -/// define_boxed_label!(MyNewLabelTrait); +/// # use bevy_utils::define_label; +/// define_label!( +/// /// Documentation of label trait +/// MyNewLabelTrait, +/// MY_NEW_LABEL_TRAIT_INTERNER +/// ); +/// +/// define_label!( +/// /// Documentation of another label trait +/// MyNewExtendedLabelTrait, +/// MY_NEW_EXTENDED_LABEL_TRAIT_INTERNER, +/// extra_methods: { +/// // Extra methods for the trait can be defined here +/// fn additional_method(&self) -> i32; +/// }, +/// extra_methods_impl: { +/// // Implementation of the extra methods for Interned +/// fn additional_method(&self) -> i32 { +/// 0 +/// } +/// } +/// ); /// ``` #[macro_export] -macro_rules! define_boxed_label { - ($label_trait_name:ident) => { - /// A strongly-typed label. +macro_rules! define_label { + ( + $(#[$label_attr:meta])* + $label_trait_name:ident, + $interner_name:ident + ) => { + $crate::define_label!( + $(#[$label_attr])* + $label_trait_name, + $interner_name, + extra_methods: {}, + extra_methods_impl: {} + ); + }; + ( + $(#[$label_attr:meta])* + $label_trait_name:ident, + $interner_name:ident, + extra_methods: { $($trait_extra_methods:tt)* }, + extra_methods_impl: { $($interned_extra_methods_impl:tt)* } + ) => { + + $(#[$label_attr])* pub trait $label_trait_name: 'static + Send + Sync + ::std::fmt::Debug { - /// Return's the [`TypeId`] of this label, or the ID of the - /// wrapped label type for `Box` - /// - /// [TypeId]: std::any::TypeId - fn inner_type_id(&self) -> ::std::any::TypeId { - std::any::TypeId::of::() - } + + $($trait_extra_methods)* /// Clones this ` #[doc = stringify!($label_trait_name)] - /// ` - fn dyn_clone(&self) -> Box; + ///`. + fn dyn_clone(&self) -> ::std::boxed::Box; /// Casts this value to a form where it can be compared with other type-erased values. - fn as_dyn_eq(&self) -> &dyn ::bevy_utils::label::DynEq; + fn as_dyn_eq(&self) -> &dyn $crate::label::DynEq; /// Feeds this value into the given [`Hasher`]. /// /// [`Hasher`]: std::hash::Hasher fn dyn_hash(&self, state: &mut dyn ::std::hash::Hasher); - } - impl PartialEq for dyn $label_trait_name { - fn eq(&self, other: &Self) -> bool { - self.as_dyn_eq().dyn_eq(other.as_dyn_eq()) + /// Returns an [`Interned`](bevy_utils::intern::Interned) value corresponding to `self`. + fn intern(&self) -> $crate::intern::Interned + where Self: Sized { + $interner_name.intern(self) } } - impl Eq for dyn $label_trait_name {} + impl $label_trait_name for $crate::intern::Interned { - impl ::std::hash::Hash for dyn $label_trait_name { - fn hash(&self, state: &mut H) { - self.dyn_hash(state); - } - } - - impl ::std::convert::AsRef for dyn $label_trait_name { - #[inline] - fn as_ref(&self) -> &Self { - self - } - } - - impl Clone for Box { - fn clone(&self) -> Self { - self.dyn_clone() - } - } - - impl $label_trait_name for Box { - fn inner_type_id(&self) -> ::std::any::TypeId { - (**self).inner_type_id() - } + $($interned_extra_methods_impl)* - fn dyn_clone(&self) -> Box { - // Be explicit that we want to use the inner value - // to avoid infinite recursion. + fn dyn_clone(&self) -> ::std::boxed::Box { (**self).dyn_clone() } - fn as_dyn_eq(&self) -> &dyn ::bevy_utils::label::DynEq { + /// Casts this value to a form where it can be compared with other type-erased values. + fn as_dyn_eq(&self) -> &dyn $crate::label::DynEq { (**self).as_dyn_eq() } fn dyn_hash(&self, state: &mut dyn ::std::hash::Hasher) { (**self).dyn_hash(state); } - } - }; -} - -/// Macro to define a new label trait -/// -/// # Example -/// -/// ``` -/// # use bevy_utils::define_label; -/// define_label!( -/// /// A class of labels. -/// MyNewLabelTrait, -/// /// Identifies a value that implements `MyNewLabelTrait`. -/// MyNewLabelId, -/// ); -/// ``` -#[macro_export] -macro_rules! define_label { - ( - $(#[$label_attr:meta])* - $label_name:ident, - $(#[$id_attr:meta])* - $id_name:ident $(,)? - ) => { - $(#[$id_attr])* - #[derive(Clone, Copy, PartialEq, Eq, Hash)] - pub struct $id_name(::core::any::TypeId, &'static str); - - impl ::core::fmt::Debug for $id_name { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - write!(f, "{}", self.1) + fn intern(&self) -> Self { + *self } } - $(#[$label_attr])* - pub trait $label_name: Send + Sync + 'static { - /// Converts this type into an opaque, strongly-typed label. - fn as_label(&self) -> $id_name { - let id = self.type_id(); - let label = self.as_str(); - $id_name(id, label) - } - /// Returns the [`TypeId`] used to differentiate labels. - fn type_id(&self) -> ::core::any::TypeId { - ::core::any::TypeId::of::() + impl PartialEq for dyn $label_trait_name { + fn eq(&self, other: &Self) -> bool { + self.as_dyn_eq().dyn_eq(other.as_dyn_eq()) } - /// Returns the representation of this label as a string literal. - /// - /// In cases where you absolutely need a label to be determined at runtime, - /// you can use [`Box::leak`] to get a `'static` reference. - fn as_str(&self) -> &'static str; } - impl $label_name for $id_name { - fn as_label(&self) -> Self { - *self + impl Eq for dyn $label_trait_name {} + + impl ::std::hash::Hash for dyn $label_trait_name { + fn hash(&self, state: &mut H) { + self.dyn_hash(state); } - fn type_id(&self) -> ::core::any::TypeId { - self.0 + } + + impl $crate::intern::Internable for dyn $label_trait_name { + fn leak(&self) -> &'static Self { + Box::leak(self.dyn_clone()) } - fn as_str(&self) -> &'static str { - self.1 + + fn ref_eq(&self, other: &Self) -> bool { + if self.as_dyn_eq().type_id() == other.as_dyn_eq().type_id() { + (self as *const Self as *const ()) == (other as *const Self as *const ()) + } else { + false + } } - } - impl $label_name for &'static str { - fn as_str(&self) -> Self { - self + fn ref_hash(&self, state: &mut H) { + use ::std::hash::Hash; + self.as_dyn_eq().type_id().hash(state); + (self as *const Self as *const ()).hash(state); } } + + static $interner_name: $crate::intern::Interner = + $crate::intern::Interner::new(); }; } diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 85e70e60bc541..fc0ec1c125c73 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -21,6 +21,7 @@ pub mod syncunsafecell; mod cow_arc; mod default; mod float_ord; +pub mod intern; pub use ahash::{AHasher, RandomState}; pub use bevy_utils_proc_macros::*;