Move get_short_name utility method from bevy_reflect into bevy_utils (#5174)

# Summary

This method strips a long type name like `bevy::render:📷:PerspectiveCameraBundle` down into the bare type name (`PerspectiveCameraBundle`). This is generally useful utility method, needed by #4299 and #5121.

As a result:

- This method was moved to `bevy_utils` for easier reuse.
- The legibility and robustness of this method has been significantly improved.
- Harder test cases have been added.

This change was split out of #4299 to unblock it and make merging / reviewing the rest of those changes easier.

## Changelog

- added `bevy_utils::get_short_name`, which strips the path from a type name for convenient display.
- removed the `TypeRegistry::get_short_name` method. Use the function in `bevy_utils` instead.
This commit is contained in:
Alice Cecile 2022-07-02 18:30:45 +00:00
parent de92054bbe
commit 8f721d8d0a
5 changed files with 120 additions and 90 deletions

View file

@ -231,7 +231,7 @@ impl TypeRegistryArc {
/// a [`TypeData`] which can be used to downcast [`Reflect`] trait objects of /// a [`TypeData`] which can be used to downcast [`Reflect`] trait objects of
/// this type to trait objects of the relevant trait. /// this type to trait objects of the relevant trait.
/// ///
/// [short name]: TypeRegistration::get_short_name /// [short name]: bevy_utils::get_short_name
/// [`TypeInfo`]: crate::TypeInfo /// [`TypeInfo`]: crate::TypeInfo
/// [0]: crate::Reflect /// [0]: crate::Reflect
/// [1]: crate::Reflect /// [1]: crate::Reflect
@ -287,14 +287,14 @@ impl TypeRegistration {
let type_name = std::any::type_name::<T>(); let type_name = std::any::type_name::<T>();
Self { Self {
data: HashMap::default(), data: HashMap::default(),
short_name: Self::get_short_name(type_name), short_name: bevy_utils::get_short_name(type_name),
type_info: T::type_info(), type_info: T::type_info(),
} }
} }
/// Returns the [short name] of the type. /// Returns the [short name] of the type.
/// ///
/// [short name]: TypeRegistration::get_short_name /// [short name]: bevy_utils::get_short_name
pub fn short_name(&self) -> &str { pub fn short_name(&self) -> &str {
&self.short_name &self.short_name
} }
@ -305,49 +305,6 @@ impl TypeRegistration {
pub fn type_name(&self) -> &'static str { pub fn type_name(&self) -> &'static str {
self.type_info.type_name() self.type_info.type_name()
} }
/// Calculates the short name of a type.
///
/// 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<core::option::Option<u32>>`
/// would be `Vec<Option<u32>>`.
pub fn get_short_name(full_name: &str) -> String {
let mut short_name = String::new();
{
// A typename may be a composition of several other type names (e.g. generic parameters)
// separated by the characters that we try to find below.
// Then, each individual typename is shortened to its last path component.
//
// Note: Instead of `find`, `split_inclusive` would be nice but it's still unstable...
let mut remainder = full_name;
while let Some(index) = remainder.find(&['<', '>', '(', ')', '[', ']', ',', ';'][..]) {
let (path, new_remainder) = remainder.split_at(index);
// Push the shortened path in front of the found character
short_name.push_str(path.rsplit(':').next().unwrap());
// Push the character that was found
let character = new_remainder.chars().next().unwrap();
short_name.push(character);
// Advance the remainder
if character == ',' || character == ';' {
// A comma or semicolon is always followed by a space
short_name.push(' ');
remainder = &new_remainder[2..];
} else {
remainder = &new_remainder[1..];
}
}
// The remainder will only be non-empty if there were no matches at all
if !remainder.is_empty() {
// Then, the full typename is a path that has to be shortened
short_name.push_str(remainder.rsplit(':').next().unwrap());
}
}
short_name
}
} }
impl Clone for TypeRegistration { impl Clone for TypeRegistration {
@ -459,42 +416,6 @@ mod test {
use crate::TypeRegistration; use crate::TypeRegistration;
use bevy_utils::HashMap; use bevy_utils::HashMap;
#[test]
fn test_get_short_name() {
assert_eq!(
TypeRegistration::get_short_name(std::any::type_name::<f64>()),
"f64"
);
assert_eq!(
TypeRegistration::get_short_name(std::any::type_name::<String>()),
"String"
);
assert_eq!(
TypeRegistration::get_short_name(std::any::type_name::<(u32, f64)>()),
"(u32, f64)"
);
assert_eq!(
TypeRegistration::get_short_name(std::any::type_name::<(String, String)>()),
"(String, String)"
);
assert_eq!(
TypeRegistration::get_short_name(std::any::type_name::<[f64]>()),
"[f64]"
);
assert_eq!(
TypeRegistration::get_short_name(std::any::type_name::<[String]>()),
"[String]"
);
assert_eq!(
TypeRegistration::get_short_name(std::any::type_name::<[f64; 16]>()),
"[f64; 16]"
);
assert_eq!(
TypeRegistration::get_short_name(std::any::type_name::<[String; 16]>()),
"[String; 16]"
);
}
#[test] #[test]
fn test_property_type_registration() { fn test_property_type_registration() {
assert_eq!( assert_eq!(

View file

@ -4,6 +4,8 @@ pub mod prelude {
pub mod futures; pub mod futures;
pub mod label; pub mod label;
mod short_names;
pub use short_names::get_short_name;
mod default; mod default;
mod float_ord; mod float_ord;

View file

@ -0,0 +1,110 @@
/// 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<core::option::Option<u32>>`
/// would be `Vec<Option<u32>>`.
pub fn get_short_name(full_name: &str) -> String {
// Generics result in nested paths within <..> blocks.
// Consider "bevy_render::camera::camera::extract_cameras<bevy_render::camera::bundle::Camera3d>".
// 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();
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);
// 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;
}
}
parsed_name
}
#[inline(always)]
fn collapse_type_name(string: &str) -> &str {
string.split("::").last().unwrap()
}
#[cfg(test)]
mod name_formatting_tests {
use super::get_short_name;
#[test]
fn trivial() {
assert_eq!(get_short_name("test_system"), "test_system");
}
#[test]
fn path_seperated() {
assert_eq!(
get_short_name("bevy_prelude::make_fun_game"),
"make_fun_game".to_string()
);
}
#[test]
fn tuple_type() {
assert_eq!(
get_short_name("(String, String)"),
"(String, String)".to_string()
);
}
#[test]
fn array_type() {
assert_eq!(get_short_name("[i32; 3]"), "[i32; 3]".to_string());
}
#[test]
fn trivial_generics() {
assert_eq!(get_short_name("a<B>"), "a<B>".to_string());
}
#[test]
fn multiple_type_parameters() {
assert_eq!(get_short_name("a<B, C>"), "a<B, C>".to_string());
}
#[test]
fn generics() {
assert_eq!(
get_short_name("bevy_render::camera::camera::extract_cameras<bevy_render::camera::bundle::Camera3d>"),
"extract_cameras<Camera3d>".to_string()
);
}
#[test]
fn nested_generics() {
assert_eq!(
get_short_name("bevy::mad_science::do_mad_science<mad_science::Test<mad_science::Tube>, bavy::TypeSystemAbuse>"),
"do_mad_science<Test<Tube>, TypeSystemAbuse>".to_string()
);
}
}

View file

@ -12,5 +12,5 @@ serde = { version = "1.0", features = ["derive"] }
clap = { version = "3.2", features = ["derive"] } clap = { version = "3.2", features = ["derive"] }
regex = "1.5" regex = "1.5"
termcolor = "1.1" termcolor = "1.1"
bevy_reflect = { path = "../../crates/bevy_reflect", version = "0.8.0-dev" } bevy_utils = { path = "../../crates/bevy_utils", version = "0.8.0-dev" }
lazy_static = "1.4" lazy_static = "1.4"

View file

@ -1,4 +1,4 @@
use bevy_reflect::TypeRegistration; use bevy_utils::get_short_name;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use termcolor::{Color, ColorSpec, StandardStream, WriteColor}; use termcolor::{Color, ColorSpec, StandardStream, WriteColor};
@ -206,21 +206,18 @@ lazy_static! {
pub fn simplify_name(name: &str) -> String { pub fn simplify_name(name: &str) -> String {
if let Some(captures) = SYSTEM_NAME.captures(name) { if let Some(captures) = SYSTEM_NAME.captures(name) {
return format!( return format!(r#"system: name="{}""#, get_short_name(&captures[1]));
r#"system: name="{}""#,
TypeRegistration::get_short_name(&captures[1])
);
} }
if let Some(captures) = SYSTEM_OVERHEAD.captures(name) { if let Some(captures) = SYSTEM_OVERHEAD.captures(name) {
return format!( return format!(
r#"system overhead: name="{}""#, r#"system overhead: name="{}""#,
TypeRegistration::get_short_name(&captures[1]) get_short_name(&captures[1])
); );
} }
if let Some(captures) = SYSTEM_COMMANDS.captures(name) { if let Some(captures) = SYSTEM_COMMANDS.captures(name) {
return format!( return format!(
r#"system_commands: name="{}""#, r#"system_commands: name="{}""#,
TypeRegistration::get_short_name(&captures[1]) get_short_name(&captures[1])
); );
} }
name.to_string() name.to_string()