Skip to content

Commit 46d4226

Browse files
committed
derive boxed labels
1 parent 5889238 commit 46d4226

File tree

3 files changed

+136
-46
lines changed

3 files changed

+136
-46
lines changed

crates/bevy_ecs/examples/derive_label.rs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
use std::{hash::Hash, marker::PhantomData};
22

3-
use bevy_ecs::{
4-
prelude::*,
5-
schedule::{LabelGuard, Labels, SystemLabelId},
6-
};
3+
use bevy_ecs::prelude::*;
74

85
fn main() {
96
// Unit labels are always equal.
@@ -91,29 +88,8 @@ pub struct BadLabel2 {
9188
x: (),
9289
}*/
9390

94-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
91+
#[derive(Debug, Clone, PartialEq, Eq, Hash, SystemLabel)]
92+
#[system_label(hash)]
9593
pub struct ComplexLabel {
9694
people: Vec<&'static str>,
9795
}
98-
99-
static MAP: Labels<ComplexLabel> = Labels::new();
100-
101-
impl SystemLabel for ComplexLabel {
102-
fn data(&self) -> u64 {
103-
let hash = bevy_utils::compute_fixed_hash(self);
104-
MAP.intern(hash, || self.clone());
105-
hash
106-
}
107-
fn fmt(hash: u64, f: &mut std::fmt::Formatter) -> std::fmt::Result {
108-
MAP.scope(hash, |val| write!(f, "{val:?}"))
109-
.ok_or_else(Default::default)?
110-
}
111-
}
112-
113-
impl bevy_utils::label::LabelDowncast<SystemLabelId> for ComplexLabel {
114-
type Output = LabelGuard<'static, ComplexLabel>;
115-
fn downcast_from(label: SystemLabelId) -> Option<Self::Output> {
116-
let hash = label.data();
117-
MAP.get(hash)
118-
}
119-
}

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,10 @@ pub fn derive_system_label(input: TokenStream) -> TokenStream {
446446
trait_path
447447
.segments
448448
.push(format_ident!("SystemLabel").into());
449-
derive_label(input, &trait_path, "system_label")
449+
let mut id_path = bevy_ecs_path();
450+
id_path.segments.push(format_ident!("schedule").into());
451+
id_path.segments.push(format_ident!("SystemLabelId").into());
452+
derive_label(input, &trait_path, &id_path, "system_label")
450453
}
451454

452455
/// Generates an impl of the `StageLabel` trait.
@@ -459,7 +462,10 @@ pub fn derive_stage_label(input: TokenStream) -> TokenStream {
459462
let mut trait_path = bevy_ecs_path();
460463
trait_path.segments.push(format_ident!("schedule").into());
461464
trait_path.segments.push(format_ident!("StageLabel").into());
462-
derive_label(input, &trait_path, "stage_label")
465+
let mut id_path = bevy_ecs_path();
466+
id_path.segments.push(format_ident!("schedule").into());
467+
id_path.segments.push(format_ident!("StageLabelId").into());
468+
derive_label(input, &trait_path, &id_path, "stage_label")
463469
}
464470

465471
/// Generates an impl of the `AmbiguitySetLabel` trait.
@@ -474,7 +480,12 @@ pub fn derive_ambiguity_set_label(input: TokenStream) -> TokenStream {
474480
trait_path
475481
.segments
476482
.push(format_ident!("AmbiguitySetLabel").into());
477-
derive_label(input, &trait_path, "ambiguity_set_label")
483+
let mut id_path = bevy_ecs_path();
484+
id_path.segments.push(format_ident!("schedule").into());
485+
id_path
486+
.segments
487+
.push(format_ident!("AmbiguitySetLabelId").into());
488+
derive_label(input, &trait_path, &id_path, "ambiguity_set_label")
478489
}
479490

480491
/// Generates an impl of the `RunCriteriaLabel` trait.
@@ -489,7 +500,12 @@ pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream {
489500
trait_path
490501
.segments
491502
.push(format_ident!("RunCriteriaLabel").into());
492-
derive_label(input, &trait_path, "run_criteria_label")
503+
let mut id_path = bevy_ecs_path();
504+
id_path.segments.push(format_ident!("schedule").into());
505+
id_path
506+
.segments
507+
.push(format_ident!("RunCriteriaLabelId").into());
508+
derive_label(input, &trait_path, &id_path, "run_criteria_label")
493509
}
494510

495511
pub(crate) fn bevy_ecs_path() -> syn::Path {

crates/bevy_macro_utils/src/lib.rs

Lines changed: 113 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub use shape::*;
99
pub use symbol::*;
1010

1111
use proc_macro::TokenStream;
12-
use quote::{quote, quote_spanned};
12+
use quote::{format_ident, quote, quote_spanned};
1313
use std::{env, path::PathBuf};
1414
use syn::spanned::Spanned;
1515
use toml::{map::Map, Value};
@@ -105,6 +105,22 @@ impl BevyManifest {
105105
}
106106
}
107107

108+
fn is_attr<T: syn::parse::Parse + syn::token::CustomToken>(
109+
attr: &syn::Attribute,
110+
attr_name: &str,
111+
) -> bool {
112+
if attr.path.get_ident().as_ref().unwrap() != &attr_name {
113+
return false;
114+
}
115+
116+
attr.parse_args_with(|input: syn::parse::ParseStream| {
117+
let ignore = input.parse::<Option<T>>()?.is_some();
118+
Ok(ignore)
119+
})
120+
.ok()
121+
== Some(true)
122+
}
123+
108124
/// Derive a label trait
109125
///
110126
/// # Args
@@ -114,32 +130,49 @@ impl BevyManifest {
114130
pub fn derive_label(
115131
input: syn::DeriveInput,
116132
trait_path: &syn::Path,
133+
id_path: &syn::Path,
117134
attr_name: &str,
118135
) -> TokenStream {
119-
// return true if the variant specified is an `ignore_fields` attribute
120-
fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool {
121-
if attr.path.get_ident().as_ref().unwrap() != &attr_name {
122-
return false;
123-
}
124-
125-
syn::custom_keyword!(ignore_fields);
126-
attr.parse_args_with(|input: syn::parse::ParseStream| {
127-
let ignore = input.parse::<Option<ignore_fields>>()?.is_some();
128-
Ok(ignore)
129-
})
130-
.unwrap()
136+
fn is_hash_attr(attr: &syn::Attribute, attr_name: &str) -> bool {
137+
syn::custom_keyword!(hash);
138+
is_attr::<hash>(attr, attr_name)
131139
}
132140

133-
let ident = input.ident.clone();
141+
// If the label is marked with `#[label(hash)]`.
142+
let hash_attr = input.attrs.iter().any(|a| is_hash_attr(a, attr_name));
134143

135-
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
144+
if hash_attr {
145+
derive_hashed_label(input, trait_path, id_path, attr_name)
146+
} else {
147+
derive_named_label(input, trait_path, attr_name)
148+
}
149+
}
150+
151+
fn with_static_bound(where_clause: Option<&syn::WhereClause>) -> syn::WhereClause {
136152
let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
137153
where_token: Default::default(),
138154
predicates: Default::default(),
139155
});
140156
where_clause
141157
.predicates
142158
.push(syn::parse2(quote! { Self: 'static }).unwrap());
159+
where_clause
160+
}
161+
162+
fn derive_named_label(
163+
input: syn::DeriveInput,
164+
trait_path: &syn::Path,
165+
attr_name: &str,
166+
) -> TokenStream {
167+
// return true if the variant specified is an `ignore_fields` attribute
168+
fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool {
169+
syn::custom_keyword!(ignore_fields);
170+
is_attr::<ignore_fields>(attr, attr_name)
171+
}
172+
173+
let ident = input.ident.clone();
174+
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
175+
let where_clause = with_static_bound(where_clause);
143176

144177
let (data, mut fmt) = match input.data {
145178
syn::Data::Struct(d) => {
@@ -260,3 +293,68 @@ pub fn derive_label(
260293
})
261294
.into()
262295
}
296+
297+
fn derive_hashed_label(
298+
input: syn::DeriveInput,
299+
trait_path: &syn::Path,
300+
id_path: &syn::Path,
301+
_attr_name: &str,
302+
) -> TokenStream {
303+
let manifest = BevyManifest::default();
304+
305+
let ident = input.ident;
306+
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
307+
let where_clause = with_static_bound(where_clause);
308+
309+
let compute_hash_path = {
310+
let mut path = manifest.get_path("bevy_utils");
311+
path.segments
312+
.push(format_ident!("compute_fixed_hash").into());
313+
path
314+
};
315+
let interner_type_path = {
316+
let mut path = manifest.get_path("bevy_ecs");
317+
path.segments.push(format_ident!("schedule").into());
318+
path.segments.push(format_ident!("Labels").into());
319+
path
320+
};
321+
let guard_type_path = {
322+
let mut path = manifest.get_path("bevy_ecs");
323+
path.segments.push(format_ident!("schedule").into());
324+
path.segments.push(format_ident!("LabelGuard").into());
325+
path
326+
};
327+
let interner_ident = format_ident!("{}_INTERN", ident.to_string().to_uppercase());
328+
let downcast_trait_path = {
329+
let mut path = manifest.get_path("bevy_utils");
330+
path.segments.push(format_ident!("label").into());
331+
path.segments.push(format_ident!("LabelDowncast").into());
332+
path
333+
};
334+
335+
quote! {
336+
static #interner_ident : #interner_type_path <#ident #ty_generics> = #interner_type_path::new();
337+
338+
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
339+
#[inline]
340+
fn data(&self) -> u64 {
341+
let hash = #compute_hash_path(self);
342+
#interner_ident .intern(hash, || ::std::clone::Clone::clone(self));
343+
hash
344+
}
345+
fn fmt(hash: u64, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
346+
#interner_ident
347+
.scope(hash, |val| ::std::fmt::Debug::fmt(val, f))
348+
.ok_or(::std::fmt::Error)?
349+
}
350+
}
351+
352+
impl #impl_generics #downcast_trait_path <#id_path> for #ident #ty_generics #where_clause {
353+
type Output = #guard_type_path <'static, Self>;
354+
fn downcast_from(label: #id_path) -> Option<Self::Output> {
355+
let hash = <#id_path as #trait_path>::data(&label);
356+
#interner_ident .get(hash)
357+
}
358+
}
359+
}.into()
360+
}

0 commit comments

Comments
 (0)