Skip to content

Commit 1a1dae6

Browse files
committed
use a fn pointer for formatting
1 parent 4454de1 commit 1a1dae6

File tree

6 files changed

+49
-134
lines changed

6 files changed

+49
-134
lines changed

benches/benches/bevy_ecs/scheduling/schedule.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,18 @@ pub fn build_schedule(criterion: &mut Criterion) {
6464
// Use multiple different kinds of label to ensure that dynamic dispatch
6565
// doesn't somehow get optimized away.
6666
#[derive(Debug, Clone, Copy)]
67-
struct NumLabel(u16);
67+
struct NumLabel(u64);
6868
#[derive(Debug, Clone, Copy, SystemLabel)]
6969
struct DummyLabel;
7070

7171
impl SystemLabel for NumLabel {
7272
#[inline]
73-
fn data(&self) -> u16 {
73+
fn data(&self) -> u64 {
7474
self.0
7575
}
76-
fn as_str(&self) -> &'static str {
77-
let s = self.0.to_string();
78-
Box::leak(s.into_boxed_str())
76+
#[inline]
77+
fn dbg(&self) -> fn(u64, &mut std::fmt::Formatter) -> std::fmt::Result {
78+
|x, f| write!(f, "NumLabel({x})")
7979
}
8080
}
8181

@@ -89,7 +89,7 @@ pub fn build_schedule(criterion: &mut Criterion) {
8989
// Also, we are performing the `as_label` operation outside of the loop since that
9090
// requires an allocation and a leak. This is not something that would be necessary in a
9191
// real scenario, just a contrivance for the benchmark.
92-
let labels: Vec<_> = (0..1000).map(|i| NumLabel(i as u16).as_label()).collect();
92+
let labels: Vec<_> = (0..1000).map(|i| NumLabel(i).as_label()).collect();
9393

9494
// Benchmark graphs of different sizes.
9595
for graph_size in [100, 500, 1000] {

crates/bevy_app/src/app.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,7 @@ impl App {
901901
pub fn sub_app_mut(&mut self, label: impl AppLabel) -> &mut App {
902902
match self.get_sub_app_mut(label) {
903903
Ok(app) => app,
904-
Err(label) => panic!("Sub-App with label '{:?}' does not exist", label.as_str()),
904+
Err(label) => panic!("Sub-App with label '{:?}' does not exist", label.as_label()),
905905
}
906906
}
907907

@@ -923,7 +923,7 @@ impl App {
923923
pub fn sub_app(&self, label: impl AppLabel) -> &App {
924924
match self.get_sub_app(label) {
925925
Ok(app) => app,
926-
Err(label) => panic!("Sub-App with label '{:?}' does not exist", label.as_str()),
926+
Err(label) => panic!("Sub-App with label '{:?}' does not exist", label.as_label()),
927927
}
928928
}
929929

crates/bevy_ecs/src/schedule/state.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,27 @@ enum ScheduledOperation<T: StateData> {
5252
Push(T),
5353
}
5454

55-
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
56-
struct DriverLabel(TypeId, &'static str);
55+
struct DriverLabel(
56+
TypeId,
57+
fn(u64, &mut std::fmt::Formatter) -> std::fmt::Result,
58+
);
5759
impl RunCriteriaLabel for DriverLabel {
5860
fn type_id(&self) -> core::any::TypeId {
5961
self.0
6062
}
61-
fn data(&self) -> u16 {
63+
fn data(&self) -> u64 {
6264
0
6365
}
64-
fn as_str(&self) -> &'static str {
66+
fn dbg(&self) -> fn(u64, &mut std::fmt::Formatter) -> std::fmt::Result {
6567
self.1
6668
}
6769
}
6870

6971
impl DriverLabel {
7072
fn of<T: 'static>() -> Self {
71-
Self(TypeId::of::<T>(), std::any::type_name::<T>())
73+
Self(TypeId::of::<T>(), |_, f| {
74+
write!(f, "{}", std::any::type_name::<T>())
75+
})
7276
}
7377
}
7478

crates/bevy_ecs/src/system/function_system.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ use crate::{
1212
world::{World, WorldId},
1313
};
1414
use bevy_ecs_macros::all_tuples;
15-
use std::{borrow::Cow, fmt::Debug, marker::PhantomData};
15+
use std::{
16+
borrow::Cow,
17+
fmt::{self, Debug},
18+
marker::PhantomData,
19+
};
1620

1721
/// The metadata of a [`System`].
1822
#[derive(Clone)]
@@ -454,17 +458,17 @@ pub struct SystemTypeIdLabel<T: 'static>(PhantomData<fn() -> T>);
454458

455459
impl<T: 'static> SystemLabel for SystemTypeIdLabel<T> {
456460
#[inline]
457-
fn as_str(&self) -> &'static str {
458-
std::any::type_name::<T>()
461+
fn data(&self) -> u64 {
462+
0
459463
}
460464
#[inline]
461-
fn data(&self) -> u16 {
462-
0
465+
fn dbg(&self) -> fn(u64, &mut fmt::Formatter) -> fmt::Result {
466+
|_, f| write!(f, "{}", std::any::type_name::<T>())
463467
}
464468
}
465469

466470
impl<T> Debug for SystemTypeIdLabel<T> {
467-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468472
f.debug_tuple("SystemTypeIdLabel")
469473
.field(&std::any::type_name::<T>())
470474
.finish()

crates/bevy_macro_utils/src/lib.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ pub fn derive_label(
161161
if matches!(d.fields, syn::Fields::Unit) || ignore_fields {
162162
let lit = ident.to_string();
163163
let data = quote! { 0 };
164-
let as_str = quote! { #lit };
164+
let as_str = quote! { |_, f| write!(f, #lit) };
165165
(data, as_str)
166166
} else {
167167
let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`");
@@ -191,12 +191,11 @@ pub fn derive_label(
191191
let mut path = syn::Path::from(ident.clone());
192192
path.segments.push(v.ident.clone().into());
193193

194-
// hopefully no one has more than 60 thousand variants
195-
let i = i as u16;
194+
let i = i as u64;
196195
data_arms.push(quote! { #path { .. } => #i });
197196

198197
let lit = format!("{ident}::{}", v.ident.clone());
199-
str_arms.push(quote! { #path { .. } => #lit });
198+
str_arms.push(quote! { #path { .. } => { |_, f| write!(f, #lit) } });
200199
} else {
201200
let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`");
202201
return quote_spanned! {
@@ -229,10 +228,10 @@ pub fn derive_label(
229228
(quote! {
230229
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
231230
#[inline]
232-
fn data(&self) -> u16 {
231+
fn data(&self) -> u64 {
233232
#data
234233
}
235-
fn as_str(&self) -> &'static str {
234+
fn dbg(&self) -> fn(u64, &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
236235
#as_str
237236
}
238237
}

crates/bevy_utils/src/label.rs

Lines changed: 17 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
use std::{
44
any::Any,
55
hash::{Hash, Hasher},
6-
marker::PhantomData,
76
};
87

98
pub trait DynEq: Any {
@@ -48,79 +47,6 @@ where
4847
}
4948
}
5049

51-
#[doc(hidden)]
52-
/// A string slice, stuffed with a payload of data `16` bits long.
53-
/// This means that the maximum length of the string is `2^(size-16)`,
54-
/// where `size` is either 32 or 64 depending on your computer.
55-
#[derive(Clone, Copy)]
56-
pub struct StuffedStr<'a> {
57-
ptr: *const u8,
58-
marker: PhantomData<&'a str>,
59-
60-
/// lower 16 bits stores the data,
61-
/// upper 48 or 16 bits stores the length of the string.
62-
/// This layout optimizes for calling `.data()`
63-
meta: usize,
64-
}
65-
66-
unsafe impl<'a> Send for StuffedStr<'a> {}
67-
unsafe impl<'a> Sync for StuffedStr<'a> {}
68-
69-
/// Truncates the passed string slice to be of length `len`, or shorter.
70-
#[cold]
71-
#[inline(never)]
72-
fn truncate(str: &str, mut len: usize) -> &str {
73-
// Just keep lowering the expected length until we hit a unicode boundary.
74-
// This is messy but if the string is long enough that we need to truncate it,
75-
// it's too long for anyone to notice what the end looks like.
76-
loop {
77-
if let Some(str) = str.get(..len) {
78-
break str;
79-
}
80-
// In practice this won't be able to underflow, since `str.get(..0)` will always succeed.
81-
len -= 1;
82-
}
83-
}
84-
85-
impl<'a> StuffedStr<'a> {
86-
const DATA_MASK: usize = !Self::LEN_MASK;
87-
const LEN_MASK: usize = !0 << 16;
88-
89-
pub fn new(mut str: &'a str, data: u16) -> Self {
90-
// Make sure there's enough room to store the data.
91-
if str.len().leading_zeros() < 16 {
92-
str = truncate(str, Self::LEN_MASK >> 16);
93-
debug_assert!(str.len().leading_zeros() < 16);
94-
}
95-
let ptr = str.as_ptr();
96-
let meta = data as usize | str.len() << 16;
97-
Self {
98-
ptr,
99-
marker: PhantomData,
100-
meta,
101-
}
102-
}
103-
104-
#[inline]
105-
fn len(&self) -> usize {
106-
self.meta >> 16
107-
}
108-
#[inline]
109-
pub fn data(&self) -> u16 {
110-
let data = self.meta & Self::DATA_MASK;
111-
data as u16
112-
}
113-
#[inline]
114-
pub fn as_str(&self) -> &'a str {
115-
// SAFETY: this instance was constructed from a string slice of length `len`, and lifetime `'a`.
116-
// It is sound to convert it back to a slice.
117-
unsafe {
118-
let slice = std::slice::from_raw_parts(self.ptr, self.len());
119-
std::str::from_utf8_unchecked(slice)
120-
}
121-
}
122-
}
123-
12450
/// Macro to define a new label trait
12551
///
12652
/// # Example
@@ -145,11 +71,12 @@ macro_rules! define_label {
14571
) => {
14672
$(#[$id_attr])*
14773
#[derive(Clone, Copy)]
148-
pub struct $id_name(::core::any::TypeId, $crate::label::StuffedStr<'static>);
74+
pub struct $id_name(::std::any::TypeId, u64, fn(u64, &mut ::std::fmt::Formatter) -> ::std::fmt::Result);
14975

150-
impl ::core::fmt::Debug for $id_name {
151-
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
152-
write!(f, "{}", self.as_str())
76+
impl ::std::fmt::Debug for $id_name {
77+
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
78+
let data = self.data();
79+
self.dbg()(data, f)
15380
}
15481
}
15582

@@ -159,22 +86,17 @@ macro_rules! define_label {
15986
fn as_label(&self) -> $id_name {
16087
let ty = self.type_id();
16188
let data = self.data();
162-
let text = self.as_str();
163-
let stuffed = $crate::label::StuffedStr::new(text, data);
164-
$id_name(ty, stuffed)
89+
let dbg = self.dbg();
90+
$id_name(ty, data, dbg)
16591
}
16692
/// Returns the [`TypeId`] used to differentiate labels.
16793
#[inline]
168-
fn type_id(&self) -> ::core::any::TypeId {
169-
::core::any::TypeId::of::<Self>()
94+
fn type_id(&self) -> ::std::any::TypeId {
95+
::std::any::TypeId::of::<Self>()
17096
}
17197
/// Returns a number used to distinguish different labels of the same type.
172-
fn data(&self) -> u16;
173-
/// Returns the representation of this label as a string literal.
174-
///
175-
/// In cases where you absolutely need a label to be determined at runtime,
176-
/// you can use [`Box::leak`] to get a `'static` reference.
177-
fn as_str(&self) -> &'static str;
98+
fn data(&self) -> u64;
99+
fn dbg(&self) -> fn(u64, &mut ::std::fmt::Formatter) -> ::std::fmt::Result;
178100
}
179101

180102
impl $label_name for $id_name {
@@ -183,15 +105,16 @@ macro_rules! define_label {
183105
*self
184106
}
185107
#[inline]
186-
fn type_id(&self) -> ::core::any::TypeId {
108+
fn type_id(&self) -> ::std::any::TypeId {
187109
self.0
188110
}
189111
#[inline]
190-
fn data(&self) -> u16 {
191-
self.1.data()
112+
fn data(&self) -> u64 {
113+
self.1
192114
}
193-
fn as_str(&self) -> &'static str {
194-
self.1.as_str()
115+
#[inline]
116+
fn dbg(&self) -> fn(u64, &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
117+
self.2
195118
}
196119
}
197120

@@ -212,18 +135,3 @@ macro_rules! define_label {
212135
}
213136
};
214137
}
215-
216-
#[cfg(test)]
217-
mod tests {
218-
use super::*;
219-
220-
#[test]
221-
fn test_truncate() {
222-
// Slice at byte '4'
223-
assert_eq!(truncate("Hello, World!", 4), "Hell");
224-
225-
// Slicing off at byte '3' would be inside of the emoji,
226-
// so instead the whole emoji gets sliced off.
227-
assert_eq!(truncate("x😂", 3), "x");
228-
}
229-
}

0 commit comments

Comments
 (0)