Skip to content

Commit ac7cfba

Browse files
committed
Support downcasting interned labels
1 parent 3ade879 commit ac7cfba

File tree

3 files changed

+72
-1
lines changed

3 files changed

+72
-1
lines changed

crates/bevy_ecs/examples/derive_label.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,38 @@ fn main() {
4141
format!("{id:?}"),
4242
r#"ComplexLabel { people: ["John", "William", "Sharon"] }"#
4343
);
44+
// Try to downcast it back to its concrete type.
45+
if let Some(complex_label) = id.downcast::<ComplexLabel>() {
46+
assert_eq!(complex_label.people, vec!["John", "William", "Sharon"]);
47+
} else {
48+
// The downcast will never fail in this example, since the label is always
49+
// created from a value of type `ComplexLabel`.
50+
unreachable!();
51+
}
4452

4553
// Generic heap-allocated labels.
4654
let id = WrapLabel(1_i128).as_label();
4755
assert_eq!(format!("{id:?}"), "WrapLabel(1)");
56+
assert!(id.downcast::<WrapLabel<usize>>().is_none());
57+
if let Some(label) = id.downcast::<WrapLabel<i128>>() {
58+
assert_eq!(label.0, 1);
59+
} else {
60+
unreachable!();
61+
}
4862

4963
// Different types with the same type constructor.
5064
let id2 = WrapLabel(1_u32).as_label();
5165
// The debug representations are the same...
5266
assert_eq!(format!("{id:?}"), format!("{id2:?}"));
53-
// ...but they do not compare equal.
67+
// ...but they do not compare equal...
5468
assert_ne!(id, id2);
69+
// ...nor can you downcast between monomorphizations.
70+
assert!(id2.downcast::<WrapLabel<i128>>().is_none());
71+
if let Some(label) = id2.downcast::<WrapLabel<u32>>() {
72+
assert_eq!(label.0, 1);
73+
} else {
74+
unreachable!();
75+
}
5576
}
5677

5778
#[derive(SystemLabel)]

crates/bevy_macro_utils/src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,12 @@ fn derive_interned_label(
370370
path
371371
};
372372
let interner_ident = format_ident!("{}_INTERN", ident.to_string().to_uppercase());
373+
let downcast_trait_path = {
374+
let mut path = manifest.get_path("bevy_utils");
375+
path.segments.push(format_ident!("label").into());
376+
path.segments.push(format_ident!("LabelDowncast").into());
377+
path
378+
};
373379

374380
Ok(quote! {
375381
static #interner_ident : #interner_type_expr = #interner_type_path::new();
@@ -384,5 +390,12 @@ fn derive_interned_label(
384390
::std::fmt::Debug::fmt(&*val, f)
385391
}
386392
}
393+
394+
impl #impl_generics #downcast_trait_path for #ident #ty_generics #where_clause {
395+
type Output = #guard_type_path <'static, Self>;
396+
fn downcast_from(idx: u64) -> Option<Self::Output> {
397+
#interner_ident .get(idx as usize)
398+
}
399+
}
387400
})
388401
}

crates/bevy_utils/src/label.rs

+37
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::{
44
any::Any,
55
hash::{Hash, Hasher},
6+
ops::Deref,
67
};
78

89
use crate::Interner;
@@ -49,6 +50,15 @@ where
4950
}
5051
}
5152

53+
/// Trait for implementors of `*Label` types that support downcasting.
54+
pub trait LabelDowncast {
55+
// FIXME: use "return position impl Trait in traits" when that stabilizes.
56+
/// The type returned from [`downcast_from`](#method.downcast_from).
57+
type Output: Deref<Target = Self>;
58+
/// Attempts to convert data from a label to type `Self`. Returns a reference-like type.
59+
fn downcast_from(data: u64) -> Option<Self::Output>;
60+
}
61+
5262
#[doc(hidden)]
5363
pub struct VTable {
5464
// FIXME: When const TypeId stabilizes, inline the type instead of using a fn pointer for indirection.
@@ -163,6 +173,33 @@ macro_rules! define_label {
163173
pub fn is<L: $label_name>(self) -> bool {
164174
self.type_id() == ::std::any::TypeId::of::<L>()
165175
}
176+
/// Attempts to downcast this label to type `L`.
177+
///
178+
/// As an anti-footgun measure, the returned reference-like type is `!Send + !Sync`
179+
/// -- often it is a mutex guard type, so it should be contained to one thread,
180+
/// and should not be held onto for very long.
181+
///
182+
/// This method is not available for all types of labels.
183+
pub fn downcast<L>(self) -> Option<impl ::std::ops::Deref<Target = L>>
184+
where
185+
L: $label_name + $crate::label::LabelDowncast
186+
{
187+
// Wraps a deref type and forces it to be !Send + !Sync
188+
struct NonSendSyncDeref<L>(L, ::std::marker::PhantomData<*mut u8>);
189+
impl<L: ::std::ops::Deref> ::std::ops::Deref for NonSendSyncDeref<L> {
190+
type Target = <L as ::std::ops::Deref>::Target;
191+
fn deref(&self) -> &Self::Target {
192+
&*self.0
193+
}
194+
}
195+
196+
if self.is::<L>() {
197+
let val = L::downcast_from(self.data())?;
198+
Some(NonSendSyncDeref(val, ::std::marker::PhantomData))
199+
} else {
200+
None
201+
}
202+
}
166203
}
167204

168205
impl $label_name for &'static str {

0 commit comments

Comments
 (0)