Skip to content

Commit ad1a6ca

Browse files
committed
allow arbitrary formatting using a fn pointer
1 parent 1cd6031 commit ad1a6ca

File tree

1 file changed

+82
-21
lines changed

1 file changed

+82
-21
lines changed

crates/bevy_utils/src/label.rs

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use std::{
55
hash::{Hash, Hasher},
66
};
77

8+
use crate::Interner;
9+
810
pub trait DynEq: Any {
911
fn as_any(&self) -> &dyn Any;
1012

@@ -47,6 +49,30 @@ where
4749
}
4850
}
4951

52+
#[doc(hidden)]
53+
pub struct VTable {
54+
// FIXME: When const TypeId stabilizes, inline the type instead of using a fn pointer for indirection.
55+
pub ty: fn() -> ::std::any::TypeId,
56+
pub fmt: fn(u64, &mut ::std::fmt::Formatter) -> ::std::fmt::Result,
57+
}
58+
59+
impl PartialEq for VTable {
60+
#[inline]
61+
fn eq(&self, other: &Self) -> bool {
62+
(self.ty)() == (other.ty)()
63+
}
64+
}
65+
impl Eq for VTable {}
66+
67+
impl Hash for VTable {
68+
fn hash<H: Hasher>(&self, state: &mut H) {
69+
(self.ty)().hash(state);
70+
}
71+
}
72+
73+
#[doc(hidden)]
74+
pub static STR_INTERN: Interner<&str> = Interner::new();
75+
5076
/// Macro to define a new label trait
5177
///
5278
/// # Example
@@ -71,48 +97,83 @@ macro_rules! define_label {
7197
) => {
7298
$(#[$id_attr])*
7399
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
74-
pub struct $id_name(::core::any::TypeId, &'static str);
100+
pub struct $id_name {
101+
data: u64,
102+
vtable: &'static $crate::label::VTable,
103+
}
75104

76-
impl ::core::fmt::Debug for $id_name {
77-
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
78-
write!(f, "{}", self.1)
105+
impl ::std::fmt::Debug for $id_name {
106+
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
107+
(self.vtable.fmt)(self.data, f)
79108
}
80109
}
81110

82111
$(#[$label_attr])*
83112
pub trait $label_name: 'static {
84113
/// Converts this type into an opaque, strongly-typed label.
114+
#[inline]
85115
fn as_label(&self) -> $id_name {
86-
let id = self.type_id();
87-
let label = self.as_str();
88-
$id_name(id, label)
89-
}
90-
/// Returns the [`TypeId`] used to differentiate labels.
91-
fn type_id(&self) -> ::core::any::TypeId {
92-
::core::any::TypeId::of::<Self>()
116+
// This is just machinery that lets us store the TypeId and formatter fn in the same static reference.
117+
struct VTables<L: ?::std::marker::Sized>(L);
118+
impl<L: $label_name + ?::std::marker::Sized> VTables<L> {
119+
const VTABLE: $crate::label::VTable = $crate::label::VTable {
120+
ty: || ::std::any::TypeId::of::<L>(),
121+
fmt: <L as $label_name>::fmt,
122+
};
123+
}
124+
125+
let data = self.data();
126+
$id_name { data, vtable: &VTables::<Self>::VTABLE }
93127
}
94-
/// Returns the representation of this label as a string literal.
128+
/// Returns a number used to distinguish different labels of the same type.
129+
fn data(&self) -> u64;
130+
/// Writes debug info for a label of the current type.
131+
/// * `data`: the result of calling [`data()`](#method.data) on an instance of this type.
95132
///
96-
/// In cases where you absolutely need a label to be determined at runtime,
97-
/// you can use [`Box::leak`] to get a `'static` reference.
98-
fn as_str(&self) -> &'static str;
133+
/// You should not call this method directly, as it may panic for some types;
134+
/// use [`as_label`](#method.as_label) instead.
135+
fn fmt(data: u64, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result;
99136
}
100137

101138
impl $label_name for $id_name {
139+
#[inline]
102140
fn as_label(&self) -> Self {
103141
*self
104142
}
105-
fn type_id(&self) -> ::core::any::TypeId {
106-
self.0
143+
#[inline]
144+
fn data(&self) -> u64 {
145+
self.data
107146
}
108-
fn as_str(&self) -> &'static str {
109-
self.1
147+
#[track_caller]
148+
fn fmt(data: u64, f: &mut ::std::fmt::Formatter) -> std::fmt::Result {
149+
let label = stringify!($label_name);
150+
::std::unimplemented!("do not call `{label}::fmt` directly -- use the result of `as_label()` for formatting instead")
151+
}
152+
}
153+
154+
impl $id_name {
155+
/// Returns the [`TypeId`] of the label from which this ID was constructed.
156+
///
157+
/// [`TypeId`]: ::std::any::TypeId
158+
#[inline]
159+
pub fn type_id(self) -> ::std::any::TypeId {
160+
(self.vtable.ty)()
161+
}
162+
/// Returns true if this label was constructed from an instance of type `L`.
163+
pub fn is<L: $label_name>(self) -> bool {
164+
self.type_id() == ::std::any::TypeId::of::<L>()
110165
}
111166
}
112167

113168
impl $label_name for &'static str {
114-
fn as_str(&self) -> Self {
115-
self
169+
fn data(&self) -> u64 {
170+
$crate::label::STR_INTERN.intern(self) as u64
171+
}
172+
fn fmt(idx: u64, f: &mut std::fmt::Formatter) -> std::fmt::Result {
173+
let s = $crate::label::STR_INTERN
174+
.get(idx as usize)
175+
.ok_or(::std::fmt::Error)?;
176+
write!(f, "{s}")
116177
}
117178
}
118179
};

0 commit comments

Comments
 (0)