Skip to content

Commit d75abf4

Browse files
committed
Intern complex label types
1 parent bf41520 commit d75abf4

File tree

3 files changed

+131
-17
lines changed

3 files changed

+131
-17
lines changed

crates/bevy_derive/src/lib.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,14 @@ pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream {
8282

8383
/// Generates an impl of the `AppLabel` trait.
8484
///
85-
/// This works only for unit structs, or enums with only unit variants.
86-
/// You may force a struct or variant to behave as if it were fieldless with `#[app_label(ignore_fields)]`.
85+
/// For unit structs and enums with only unit variants, a cheap implementation can easily be created.
86+
///
87+
/// More complex types must be boxed and interned
88+
/// - opt in to this by annotating the entire item with `#[app_label(intern)]`.
89+
///
90+
/// Alternatively, you may force a struct or variant to behave as if
91+
/// it were fieldless with `#[app_label(ignore_fields)]`.
92+
/// This is especially useful for [`PhantomData`](core::marker::PhantomData) fields.
8793
#[proc_macro_derive(AppLabel, attributes(app_label))]
8894
pub fn derive_app_label(input: TokenStream) -> TokenStream {
8995
let input = syn::parse_macro_input!(input as syn::DeriveInput);

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -436,8 +436,14 @@ pub fn derive_world_query(input: TokenStream) -> TokenStream {
436436

437437
/// Generates an impl of the `SystemLabel` trait.
438438
///
439-
/// This works only for unit structs, or enums with only unit variants.
440-
/// You may force a struct or variant to behave as if it were fieldless with `#[system_label(ignore_fields)]`.
439+
/// For unit structs and enums with only unit variants, a cheap implementation can easily be created.
440+
///
441+
/// More complex types must be boxed and interned
442+
/// - opt in to this by annotating the entire item with `#[system_label(intern)]`.
443+
///
444+
/// Alternatively, you may force a struct or variant to behave as if
445+
/// it were fieldless with `#[system_label(ignore_fields)]`.
446+
/// This is especially useful for [`PhantomData`](core::marker::PhantomData) fields.
441447
#[proc_macro_derive(SystemLabel, attributes(system_label))]
442448
pub fn derive_system_label(input: TokenStream) -> TokenStream {
443449
let input = parse_macro_input!(input as DeriveInput);
@@ -451,8 +457,14 @@ pub fn derive_system_label(input: TokenStream) -> TokenStream {
451457

452458
/// Generates an impl of the `StageLabel` trait.
453459
///
454-
/// This works only for unit structs, or enums with only unit variants.
455-
/// You may force a struct or variant to behave as if it were fieldless with `#[stage_label(ignore_fields)]`.
460+
/// For unit structs and enums with only unit variants, a cheap implementation can easily be created.
461+
///
462+
/// More complex types must be boxed and interned
463+
/// - opt in to this by annotating the entire item with `#[stage_label(intern)]`.
464+
///
465+
/// Alternatively, you may force a struct or variant to behave as if
466+
/// it were fieldless with `#[stage_label(ignore_fields)]`.
467+
/// This is especially useful for [`PhantomData`](core::marker::PhantomData) fields.
456468
#[proc_macro_derive(StageLabel, attributes(stage_label))]
457469
pub fn derive_stage_label(input: TokenStream) -> TokenStream {
458470
let input = parse_macro_input!(input as DeriveInput);
@@ -464,8 +476,14 @@ pub fn derive_stage_label(input: TokenStream) -> TokenStream {
464476

465477
/// Generates an impl of the `AmbiguitySetLabel` trait.
466478
///
467-
/// This works only for unit structs, or enums with only unit variants.
468-
/// You may force a struct or variant to behave as if it were fieldless with `#[ambiguity_set_label(ignore_fields)]`.
479+
/// For unit structs and enums with only unit variants, a cheap implementation can easily be created.
480+
///
481+
/// More complex types must be boxed and interned
482+
/// - opt in to this by annotating the entire item with `#[ambiguity_set_label(intern)]`.
483+
///
484+
/// Alternatively, you may force a struct or variant to behave as if
485+
/// it were fieldless with `#[ambiguity_set_label(ignore_fields)]`.
486+
/// This is especially useful for [`PhantomData`](core::marker::PhantomData) fields.
469487
#[proc_macro_derive(AmbiguitySetLabel, attributes(ambiguity_set_label))]
470488
pub fn derive_ambiguity_set_label(input: TokenStream) -> TokenStream {
471489
let input = parse_macro_input!(input as DeriveInput);
@@ -479,8 +497,14 @@ pub fn derive_ambiguity_set_label(input: TokenStream) -> TokenStream {
479497

480498
/// Generates an impl of the `RunCriteriaLabel` trait.
481499
///
482-
/// This works only for unit structs, or enums with only unit variants.
483-
/// You may force a struct or variant to behave as if it were fieldless with `#[run_criteria_label(ignore_fields)]`.
500+
/// For unit structs and enums with only unit variants, a cheap implementation can easily be created.
501+
///
502+
/// More complex types must be boxed and interned
503+
/// - opt in to this by annotating the entire item with `#[run_criteria_label(intern)]`.
504+
///
505+
/// Alternatively, you may force a struct or variant to behave as if
506+
/// it were fieldless with `#[run_criteria_label(ignore_fields)]`.
507+
/// This is especially useful for [`PhantomData`](core::marker::PhantomData) fields.
484508
#[proc_macro_derive(RunCriteriaLabel, attributes(run_criteria_label))]
485509
pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream {
486510
let input = parse_macro_input!(input as DeriveInput);

crates/bevy_macro_utils/src/lib.rs

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub use symbol::*;
1010

1111
use proc_macro::TokenStream;
1212
use proc_macro2::{Span, TokenStream as TokenStream2};
13-
use quote::quote;
13+
use quote::{format_ident, quote};
1414
use std::{env, path::PathBuf};
1515
use syn::spanned::Spanned;
1616
use toml::{map::Map, Value};
@@ -110,6 +110,7 @@ impl BevyManifest {
110110
/// in the form e.g. `#[system_label(..)]`.
111111
#[derive(Default)]
112112
struct LabelAttrs {
113+
intern: Option<Span>,
113114
ignore_fields: Option<Span>,
114115
}
115116

@@ -132,10 +133,14 @@ impl LabelAttrs {
132133
// Parse the argument/s to the attribute.
133134
attr.parse_args_with(|input: syn::parse::ParseStream| {
134135
loop {
136+
syn::custom_keyword!(intern);
135137
syn::custom_keyword!(ignore_fields);
136138

137139
let next = input.lookahead1();
138-
if next.peek(ignore_fields) {
140+
if next.peek(intern) {
141+
let kw: intern = input.parse()?;
142+
this.intern = Some(kw.span);
143+
} else if next.peek(ignore_fields) {
139144
let kw: ignore_fields = input.parse()?;
140145
this.ignore_fields = Some(kw.span);
141146
} else {
@@ -171,9 +176,14 @@ pub fn derive_label(
171176
Err(e) => return e.into_compile_error().into(),
172177
};
173178

174-
derive_named_label(input, &item_attrs, trait_path, attr_name)
175-
.unwrap_or_else(syn::Error::into_compile_error)
176-
.into()
179+
// We use entirely different derives for interned and named labels.
180+
if item_attrs.intern.is_some() {
181+
derive_interned_label(input, trait_path, attr_name)
182+
} else {
183+
derive_named_label(input, &item_attrs, trait_path, attr_name)
184+
}
185+
.unwrap_or_else(syn::Error::into_compile_error)
186+
.into()
177187
}
178188

179189
fn with_static_bound(where_clause: Option<&syn::WhereClause>) -> syn::WhereClause {
@@ -209,6 +219,13 @@ fn derive_named_label(
209219
);
210220
return Err(syn::Error::new(attr, err_msg));
211221
}
222+
if let Some(attr) = all_field_attrs.intern {
223+
let err_msg = format!(
224+
r#"`#[{attr_name}(intern)]` cannot be applied to fields individually:
225+
try adding it to the struct declaration"#
226+
);
227+
return Err(syn::Error::new(attr, err_msg));
228+
}
212229
// Structs must either be fieldless, or explicitly ignore the fields.
213230
let ignore_fields = item_attrs.ignore_fields.is_some();
214231
if d.fields.is_empty() || ignore_fields {
@@ -217,7 +234,11 @@ fn derive_named_label(
217234
let as_str = quote! { write!(f, #lit) };
218235
(data, as_str)
219236
} else {
220-
let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`");
237+
let err_msg = format!(
238+
r#"Simple labels cannot contain data, unless the whole type is boxed
239+
by marking the type with `#[{attr_name}(intern)]`.
240+
Alternatively, you can make this label behave as if it were fieldless with `#[{attr_name}(ignore_fields)]`."#
241+
);
221242
return Err(syn::Error::new(d.fields.span(), err_msg));
222243
}
223244
}
@@ -245,7 +266,11 @@ fn derive_named_label(
245266
let lit = format!("{ident}::{}", v.ident.clone());
246267
fmt_arms.push(quote! { #i => { write!(f, #lit) } });
247268
} else {
248-
let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`");
269+
let err_msg = format!(
270+
r#"Simple labels only allow unit variants -- more complex types must be boxed
271+
by marking the whole type with `#[{attr_name}(intern)]`.
272+
Alternatively, you can make the variant act fieldless using `#[{attr_name}(ignore_fields)]`."#
273+
);
249274
return Err(syn::Error::new(v.fields.span(), err_msg));
250275
}
251276
}
@@ -302,3 +327,62 @@ fn derive_named_label(
302327
}
303328
})
304329
}
330+
331+
fn derive_interned_label(
332+
input: syn::DeriveInput,
333+
trait_path: &syn::Path,
334+
_attr_name: &str,
335+
) -> syn::Result<TokenStream2> {
336+
let manifest = BevyManifest::default();
337+
338+
let ident = input.ident;
339+
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
340+
let mut where_clause = with_static_bound(where_clause);
341+
where_clause.predicates.push(
342+
syn::parse2(quote! {
343+
Self: ::std::clone::Clone + ::std::cmp::Eq + ::std::hash::Hash + ::std::fmt::Debug
344+
+ ::std::marker::Send + ::std::marker::Sync
345+
})
346+
.unwrap(),
347+
);
348+
349+
let is_generic = !input.generics.params.is_empty();
350+
351+
let interner_type_path = {
352+
let mut path = manifest.get_path("bevy_utils");
353+
// If the type is generic, we have to store all monomorphizations
354+
// in the same global due to Rust restrictions.
355+
if is_generic {
356+
path.segments.push(format_ident!("AnyInterner").into());
357+
} else {
358+
path.segments.push(format_ident!("Interner").into());
359+
}
360+
path
361+
};
362+
let interner_type_expr = if is_generic {
363+
quote! { #interner_type_path }
364+
} else {
365+
quote! { #interner_type_path <#ident> }
366+
};
367+
let guard_type_path = {
368+
let mut path = manifest.get_path("bevy_utils");
369+
path.segments.push(format_ident!("InternGuard").into());
370+
path
371+
};
372+
let interner_ident = format_ident!("{}_INTERN", ident.to_string().to_uppercase());
373+
374+
Ok(quote! {
375+
static #interner_ident : #interner_type_expr = #interner_type_path::new();
376+
377+
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
378+
#[inline]
379+
fn data(&self) -> u64 {
380+
#interner_ident .intern(self) as u64
381+
}
382+
fn fmt(idx: u64, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
383+
let val: #guard_type_path <Self> = #interner_ident .get(idx as usize).ok_or(::std::fmt::Error)?;
384+
::std::fmt::Debug::fmt(&*val, f)
385+
}
386+
}
387+
})
388+
}

0 commit comments

Comments
 (0)