diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index 95e480493a..5f78dc76c6 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -4,7 +4,7 @@ use crate::{ }; use bevy_ecs::prelude::*; use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath}; -use bevy_utils::get_short_name; +use bevy_utils::ShortName; use crossbeam_channel::{Receiver, Sender}; use std::{ any::TypeId, @@ -206,7 +206,7 @@ impl Default for Handle { impl std::fmt::Debug for Handle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let name = get_short_name(std::any::type_name::()); + let name = ShortName::of::(); match self { Handle::Strong(handle) => { write!( diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index dad5d99c56..b9ffa1b914 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -1608,7 +1608,7 @@ impl ScheduleGraph { } }; if self.settings.use_shortnames { - name = bevy_utils::get_short_name(&name); + name = bevy_utils::ShortName(&name).to_string(); } name } diff --git a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs index 21cde05f52..b190885723 100644 --- a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs +++ b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use crate::Parent; use bevy_ecs::prelude::*; #[cfg(feature = "bevy_app")] -use bevy_utils::{get_short_name, HashSet}; +use bevy_utils::{HashSet, ShortName}; /// When enabled, runs [`check_hierarchy_component_has_valid_parent`]. /// @@ -67,7 +67,7 @@ pub fn check_hierarchy_component_has_valid_parent( bevy_utils::tracing::warn!( "warning[B0004]: {name} with the {ty_name} component has a parent without {ty_name}.\n\ This will cause inconsistent behaviors! See: https://bevyengine.org/learn/errors/b0004", - ty_name = get_short_name(std::any::type_name::()), + ty_name = ShortName::of::(), name = name.map_or_else(|| format!("Entity {}", entity), |s| format!("The {s} entity")), ); } diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 5505bad824..8685fb07d8 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -2366,9 +2366,7 @@ bevy_reflect::tests::Test { fn short_type_path() -> &'static str { static CELL: GenericTypePathCell = GenericTypePathCell::new(); - CELL.get_or_insert::(|| { - bevy_utils::get_short_name(std::any::type_name::()) - }) + CELL.get_or_insert::(|| bevy_utils::ShortName::of::().to_string()) } fn type_ident() -> Option<&'static str> { diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index d729dfa33d..3d231cf0cf 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -22,10 +22,8 @@ pub mod prelude { } pub mod futures; -#[cfg(feature = "alloc")] mod short_names; -#[cfg(feature = "alloc")] -pub use short_names::get_short_name; +pub use short_names::ShortName; pub mod synccell; pub mod syncunsafecell; diff --git a/crates/bevy_utils/src/short_names.rs b/crates/bevy_utils/src/short_names.rs index d78b9c5394..d14548b681 100644 --- a/crates/bevy_utils/src/short_names.rs +++ b/crates/bevy_utils/src/short_names.rs @@ -1,62 +1,115 @@ -use alloc::string::String; - -/// Shortens a type name to remove all module paths. +/// Lazily shortens a type name to remove all module paths. /// /// The short name of a type is its full name as returned by /// [`std::any::type_name`], but with the prefix of all paths removed. For /// example, the short name of `alloc::vec::Vec>` /// would be `Vec>`. -pub fn get_short_name(full_name: &str) -> String { - // Generics result in nested paths within <..> blocks. - // Consider "bevy_render::camera::camera::extract_cameras". - // To tackle this, we parse the string from left to right, collapsing as we go. - let mut index: usize = 0; - let end_of_string = full_name.len(); - let mut parsed_name = String::new(); +/// +/// Shortening is performed lazily without allocation. +#[cfg_attr( + feature = "alloc", + doc = r#" To get a [`String`] from this type, use the [`to_string`](`alloc::string::ToString::to_string`) method."# +)] +/// +/// # Examples +/// +/// ```rust +/// # use bevy_utils::ShortName; +/// # +/// # mod foo { +/// # pub mod bar { +/// # pub struct Baz; +/// # } +/// # } +/// // Baz +/// let short_name = ShortName::of::(); +/// ``` +#[derive(Clone, Copy)] +pub struct ShortName<'a>(pub &'a str); - while index < end_of_string { - let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default(); - - // Collapse everything up to the next special character, - // then skip over it - if let Some(special_character_index) = rest_of_string.find(|c: char| { - (c == ' ') - || (c == '<') - || (c == '>') - || (c == '(') - || (c == ')') - || (c == '[') - || (c == ']') - || (c == ',') - || (c == ';') - }) { - let segment_to_collapse = rest_of_string - .get(0..special_character_index) - .unwrap_or_default(); - parsed_name += collapse_type_name(segment_to_collapse); - // Insert the special character - let special_character = - &rest_of_string[special_character_index..=special_character_index]; - parsed_name.push_str(special_character); - - match special_character { - ">" | ")" | "]" - if rest_of_string[special_character_index + 1..].starts_with("::") => - { - parsed_name.push_str("::"); - // Move the index past the "::" - index += special_character_index + 3; - } - // Move the index just past the special character - _ => index += special_character_index + 1, - } - } else { - // If there are no special characters left, we're done! - parsed_name += collapse_type_name(rest_of_string); - index = end_of_string; - } +impl ShortName<'static> { + /// Gets a shortened version of the name of the type `T`. + pub fn of() -> Self { + Self(core::any::type_name::()) + } +} + +impl<'a> ShortName<'a> { + /// Gets the original name before shortening. + pub const fn original(&self) -> &'a str { + self.0 + } +} + +impl<'a> From<&'a str> for ShortName<'a> { + fn from(value: &'a str) -> Self { + Self(value) + } +} + +impl<'a> core::fmt::Debug for ShortName<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let &ShortName(full_name) = self; + // Generics result in nested paths within <..> blocks. + // Consider "bevy_render::camera::camera::extract_cameras". + // To tackle this, we parse the string from left to right, collapsing as we go. + let mut index: usize = 0; + let end_of_string = full_name.len(); + + while index < end_of_string { + let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default(); + + // Collapse everything up to the next special character, + // then skip over it + if let Some(special_character_index) = rest_of_string.find(|c: char| { + (c == ' ') + || (c == '<') + || (c == '>') + || (c == '(') + || (c == ')') + || (c == '[') + || (c == ']') + || (c == ',') + || (c == ';') + }) { + let segment_to_collapse = rest_of_string + .get(0..special_character_index) + .unwrap_or_default(); + + f.write_str(collapse_type_name(segment_to_collapse))?; + + // Insert the special character + let special_character = + &rest_of_string[special_character_index..=special_character_index]; + + f.write_str(special_character)?; + + match special_character { + ">" | ")" | "]" + if rest_of_string[special_character_index + 1..].starts_with("::") => + { + f.write_str("::")?; + // Move the index past the "::" + index += special_character_index + 3; + } + // Move the index just past the special character + _ => index += special_character_index + 1, + } + } else { + // If there are no special characters left, we're done! + f.write_str(collapse_type_name(rest_of_string))?; + index = end_of_string; + } + } + + Ok(()) + } +} + +impl<'a> core::fmt::Display for ShortName<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + ::fmt(self, f) } - parsed_name } #[inline(always)] @@ -77,49 +130,52 @@ fn collapse_type_name(string: &str) -> &str { } } -#[cfg(test)] +#[cfg(all(test, feature = "alloc"))] mod name_formatting_tests { - use super::get_short_name; + use super::ShortName; #[test] fn trivial() { - assert_eq!(get_short_name("test_system"), "test_system"); + assert_eq!(ShortName("test_system").to_string(), "test_system"); } #[test] fn path_separated() { assert_eq!( - get_short_name("bevy_prelude::make_fun_game"), + ShortName("bevy_prelude::make_fun_game").to_string(), "make_fun_game" ); } #[test] fn tuple_type() { - assert_eq!(get_short_name("(String, String)"), "(String, String)"); + assert_eq!( + ShortName("(String, String)").to_string(), + "(String, String)" + ); } #[test] fn array_type() { - assert_eq!(get_short_name("[i32; 3]"), "[i32; 3]"); + assert_eq!(ShortName("[i32; 3]").to_string(), "[i32; 3]"); } #[test] fn trivial_generics() { - assert_eq!(get_short_name("a"), "a"); + assert_eq!(ShortName("a").to_string(), "a"); } #[test] fn multiple_type_parameters() { - assert_eq!(get_short_name("a"), "a"); + assert_eq!(ShortName("a").to_string(), "a"); } #[test] fn enums() { - assert_eq!(get_short_name("Option::None"), "Option::None"); - assert_eq!(get_short_name("Option::Some(2)"), "Option::Some(2)"); + assert_eq!(ShortName("Option::None").to_string(), "Option::None"); + assert_eq!(ShortName("Option::Some(2)").to_string(), "Option::Some(2)"); assert_eq!( - get_short_name("bevy_render::RenderSet::Prepare"), + ShortName("bevy_render::RenderSet::Prepare").to_string(), "RenderSet::Prepare" ); } @@ -127,7 +183,7 @@ mod name_formatting_tests { #[test] fn generics() { assert_eq!( - get_short_name("bevy_render::camera::camera::extract_cameras"), + ShortName("bevy_render::camera::camera::extract_cameras").to_string(), "extract_cameras" ); } @@ -135,7 +191,7 @@ mod name_formatting_tests { #[test] fn nested_generics() { assert_eq!( - get_short_name("bevy::mad_science::do_mad_science, bavy::TypeSystemAbuse>"), + ShortName("bevy::mad_science::do_mad_science, bavy::TypeSystemAbuse>").to_string(), "do_mad_science, TypeSystemAbuse>" ); } @@ -143,13 +199,16 @@ mod name_formatting_tests { #[test] fn sub_path_after_closing_bracket() { assert_eq!( - get_short_name("bevy_asset::assets::Assets::asset_event_system"), + ShortName("bevy_asset::assets::Assets::asset_event_system").to_string(), "Assets::asset_event_system" ); assert_eq!( - get_short_name("(String, String)::default"), + ShortName("(String, String)::default").to_string(), "(String, String)::default" ); - assert_eq!(get_short_name("[i32; 16]::default"), "[i32; 16]::default"); + assert_eq!( + ShortName("[i32; 16]::default").to_string(), + "[i32; 16]::default" + ); } }