diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index dcf2d85861722b..d0bf81058442ed 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -1274,9 +1274,15 @@ bevy_reflect::tests::should_reflect_debug::Test { fn vec3_path_access() { let mut v = vec3(1.0, 2.0, 3.0); - assert_eq!(*v.path("x").unwrap().downcast_ref::().unwrap(), 1.0); + assert_eq!( + *v.reflect_path("x").unwrap().downcast_ref::().unwrap(), + 1.0 + ); - *v.path_mut("y").unwrap().downcast_mut::().unwrap() = 6.0; + *v.reflect_path_mut("y") + .unwrap() + .downcast_mut::() + .unwrap() = 6.0; assert_eq!(v.y, 6.0); } diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 17cbb36e33dc1c..e43a45fea31eeb 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -1,6 +1,7 @@ +use std::fmt; use std::num::ParseIntError; -use crate::{Array, Reflect, ReflectMut, ReflectRef, VariantType}; +use crate::{Reflect, ReflectMut, ReflectRef, VariantType}; use thiserror::Error; /// An error returned from a failed path string query. @@ -10,11 +11,20 @@ pub enum ReflectPathError<'a> { ExpectedIdent { index: usize }, #[error("the current struct doesn't have a field with the name `{field}`")] InvalidField { index: usize, field: &'a str }, + #[error("the current struct doesn't have a field at the given index")] + InvalidFieldIndex { index: usize, field_index: usize }, #[error("the current tuple struct doesn't have a field with the index {tuple_struct_index}")] InvalidTupleStructIndex { index: usize, tuple_struct_index: usize, }, + #[error("the current struct variant doesn't have a field with the name `{field}`")] + InvalidStructVariantField { index: usize, field: &'a str }, + #[error("the current tuple variant doesn't have a field with the index {tuple_variant_index}")] + InvalidTupleVariantIndex { + index: usize, + tuple_variant_index: usize, + }, #[error("the current list doesn't have a value at the index {list_index}")] InvalidListIndex { index: usize, list_index: usize }, #[error("encountered an unexpected token `{token}`")] @@ -25,66 +35,200 @@ pub enum ReflectPathError<'a> { ExpectedStruct { index: usize }, #[error("expected a list, but found a different reflect value")] ExpectedList { index: usize }, + #[error("expected a struct variant, but found a different reflect value")] + ExpectedStructVariant { index: usize }, + #[error("expected a tuple variant, but found a different reflect value")] + ExpectedTupleVariant { index: usize }, #[error("failed to parse a usize")] IndexParseError(#[from] ParseIntError), #[error("failed to downcast to the path result to the given type")] InvalidDowncast, - #[error("expected either a struct variant or tuple variant, but found a unit variant")] - InvalidVariantAccess { index: usize, accessor: &'a str }, } -/// A trait which allows nested values to be retrieved with path strings. +/// A trait which allows nested [`Reflect`] values to be retrieved with path strings. +/// +/// Using these functions repeatedly with the same string requires parsing the string every time. +/// To avoid this cost, it's recommended to construct a [`ParsedPath`] instead. +/// +/// # Syntax +/// +/// ## Structs +/// +/// Field paths for [`Struct`] elements use the standard Rust field access syntax of +/// dot and field name: `.field_name`. +/// +/// Additionally, struct fields may be accessed by their index within the struct's definition. +/// This is accomplished by using the hash symbol (`#`) in place of the standard dot: `#0`. +/// +/// Accessing a struct's field by index can speed up fetches at runtime due to the removed +/// need for string matching. +/// And while this can be more performant, it's best to keep in mind the tradeoffs when +/// utilizing such optimizations. +/// For example, this can result in fairly fragile code as the string paths will need to be +/// kept in sync with the struct definitions since the order of fields could be easily changed. +/// Because of this, storing these kinds of paths in persistent storage (i.e. game assets) +/// is strongly discouraged. +/// +/// Note that a leading dot (`.`) or hash (`#`) token is implied for the first item in a path, +/// and may therefore be omitted. +/// +/// ### Example +/// ``` +/// # use bevy_reflect::{GetPath, Reflect}; +/// #[derive(Reflect)] +/// struct MyStruct { +/// value: u32 +/// } +/// +/// let my_struct = MyStruct { value: 123 }; +/// // Access via field name +/// assert_eq!(my_struct.path::(".value").unwrap(), &123); +/// // Access via field index +/// assert_eq!(my_struct.path::("#0").unwrap(), &123); +/// ``` +/// +/// ## Tuples and Tuple Structs +/// +/// [`Tuple`] and [`TupleStruct`] elements also follow a conventional Rust syntax. +/// Fields are accessed with a dot and the field index: `.0`. +/// +/// Note that a leading dot (`.`) token is implied for the first item in a path, +/// and may therefore be omitted. +/// +/// ### Example +/// ``` +/// # use bevy_reflect::{GetPath, Reflect}; +/// #[derive(Reflect)] +/// struct MyTupleStruct(u32); +/// +/// let my_tuple_struct = MyTupleStruct(123); +/// assert_eq!(my_tuple_struct.path::(".0").unwrap(), &123); +/// ``` +/// +/// ## Lists and Arrays +/// +/// [`List`] and [`Array`] elements are accessed with brackets: `[0]`. /// -/// Path strings use Rust syntax: -/// - [`Struct`] items are accessed with a dot and a field name: `.field_name` -/// - [`TupleStruct`] and [`Tuple`] items are accessed with a dot and a number: `.0` -/// - [`List`] items are accessed with brackets: `[0]` +/// ### Example +/// ``` +/// # use bevy_reflect::{GetPath}; +/// let my_list: Vec = vec![1, 2, 3]; +/// assert_eq!(my_list.path::("[2]").unwrap(), &3); +/// ``` /// -/// If the initial path element is a field of a struct, tuple struct, or tuple, -/// the initial '.' may be omitted. +/// ## Enums /// -/// For example, given a struct with a field `foo` which is a reflected list of -/// 2-tuples (like a `Vec<(T, U)>`), the path string `foo[3].0` would access tuple -/// element 0 of element 3 of `foo`. +/// Pathing for [`Enum`] elements works a bit differently than in normal Rust. +/// Usually, you would need to pattern match an enum, branching off on the desired variants. +/// Paths used by this trait do not have any pattern matching capabilities; +/// instead, they assume the variant is already known ahead of time. +/// +/// The syntax used, therefore, depends on the variant being accessed: +/// - Struct variants use the struct syntax (outlined above) +/// - Tuple variants use the tuple syntax (outlined above) +/// - Unit variants have no fields to access +/// +/// If the variant cannot be known ahead of time, the path will need to be split up +/// and proper enum pattern matching will need to be handled manually. +/// +/// ### Example +/// ``` +/// # use bevy_reflect::{GetPath, Reflect}; +/// #[derive(Reflect)] +/// enum MyEnum { +/// Unit, +/// Tuple(bool), +/// Struct { +/// value: u32 +/// } +/// } +/// +/// let tuple_variant = MyEnum::Tuple(true); +/// assert_eq!(tuple_variant.path::(".0").unwrap(), &true); +/// +/// let struct_variant = MyEnum::Struct { value: 123 }; +/// // Access via field name +/// assert_eq!(struct_variant.path::(".value").unwrap(), &123); +/// // Access via field index +/// assert_eq!(struct_variant.path::("#0").unwrap(), &123); +/// +/// // Error: Expected struct variant +/// assert!(matches!(tuple_variant.path::(".value"), Err(_))); +/// ``` +/// +/// # Chaining +/// +/// Using the aforementioned syntax, path items may be chained one after another +/// to create a full path to a nested element. +/// +/// ## Example +/// ``` +/// # use bevy_reflect::{GetPath, Reflect}; +/// #[derive(Reflect)] +/// struct MyStruct { +/// value: Vec> +/// } +/// +/// let my_struct = MyStruct { +/// value: vec![None, None, Some(123)], +/// }; +/// assert_eq!( +/// my_struct.path::(".value[2].0").unwrap(), +/// &123, +/// ); +/// ``` /// /// [`Struct`]: crate::Struct -/// [`TupleStruct`]: crate::TupleStruct /// [`Tuple`]: crate::Tuple +/// [`TupleStruct`]: crate::TupleStruct /// [`List`]: crate::List +/// [`Array`]: crate::Array +/// [`Enum`]: crate::Enum pub trait GetPath { /// Returns a reference to the value specified by `path`. /// /// To retrieve a statically typed reference, use - /// [`get_path`][GetPath::get_path]. - fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>>; + /// [`path`][GetPath::path]. + fn reflect_path<'r, 'p>( + &'r self, + path: &'p str, + ) -> Result<&'r dyn Reflect, ReflectPathError<'p>>; /// Returns a mutable reference to the value specified by `path`. /// /// To retrieve a statically typed mutable reference, use - /// [`get_path_mut`][GetPath::get_path_mut]. - fn path_mut<'r, 'p>( + /// [`path_mut`][GetPath::path_mut]. + fn reflect_path_mut<'r, 'p>( &'r mut self, path: &'p str, ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>>; /// Returns a statically typed reference to the value specified by `path`. - fn get_path<'r, 'p, T: Reflect>( - &'r self, - path: &'p str, - ) -> Result<&'r T, ReflectPathError<'p>> { - self.path(path).and_then(|p| { + /// + /// This will automatically handle downcasting to type `T`. + /// The downcast will fail if this value is not of type `T` + /// (which may be the case when using dynamic types like [`DynamicStruct`]). + /// + /// [`DynamicStruct`]: crate::DynamicStruct + fn path<'r, 'p, T: Reflect>(&'r self, path: &'p str) -> Result<&'r T, ReflectPathError<'p>> { + self.reflect_path(path).and_then(|p| { p.downcast_ref::() .ok_or(ReflectPathError::InvalidDowncast) }) } - /// Returns a statically typed mutable reference to the value specified by - /// `path`. - fn get_path_mut<'r, 'p, T: Reflect>( + /// Returns a statically typed mutable reference to the value specified by `path`. + /// + /// This will automatically handle downcasting to type `T`. + /// The downcast will fail if this value is not of type `T` + /// (which may be the case when using dynamic types like [`DynamicStruct`]). + /// + /// [`DynamicStruct`]: crate::DynamicStruct + fn path_mut<'r, 'p, T: Reflect>( &'r mut self, path: &'p str, ) -> Result<&'r mut T, ReflectPathError<'p>> { - self.path_mut(path).and_then(|p| { + self.reflect_path_mut(path).and_then(|p| { p.downcast_mut::() .ok_or(ReflectPathError::InvalidDowncast) }) @@ -92,328 +236,710 @@ pub trait GetPath { } impl GetPath for T { - fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { - (self as &dyn Reflect).path(path) + fn reflect_path<'r, 'p>( + &'r self, + path: &'p str, + ) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { + (self as &dyn Reflect).reflect_path(path) } - fn path_mut<'r, 'p>( + fn reflect_path_mut<'r, 'p>( &'r mut self, path: &'p str, ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { - (self as &mut dyn Reflect).path_mut(path) + (self as &mut dyn Reflect).reflect_path_mut(path) } } impl GetPath for dyn Reflect { - fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { - let mut index = 0; + fn reflect_path<'r, 'p>( + &'r self, + path: &'p str, + ) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { let mut current: &dyn Reflect = self; - while let Some(token) = next_token(path, &mut index) { - let current_index = index; - match token { - Token::Dot => { - if let Some(Token::Ident(value)) = next_token(path, &mut index) { - current = read_field(current, value, current_index)?; - } else { - return Err(ReflectPathError::ExpectedIdent { - index: current_index, - }); - } - } - Token::OpenBracket => { - if let Some(Token::Ident(value)) = next_token(path, &mut index) { - match current.reflect_ref() { - ReflectRef::List(reflect_list) => { - current = read_array_entry(reflect_list, value, current_index)?; - } - ReflectRef::Array(reflect_arr) => { - current = read_array_entry(reflect_arr, value, current_index)?; - } - _ => { - return Err(ReflectPathError::ExpectedList { - index: current_index, - }) - } - } - } else { - return Err(ReflectPathError::ExpectedIdent { - index: current_index, - }); - } - - if let Some(Token::CloseBracket) = next_token(path, &mut index) { - } else { - return Err(ReflectPathError::ExpectedToken { - index: current_index, - token: "]", - }); - } - } - Token::CloseBracket => { - return Err(ReflectPathError::UnexpectedToken { - index: current_index, - token: "]", - }) - } - Token::Ident(value) => { - current = read_field(current, value, current_index)?; - } - } + for (access, current_index) in PathParser::new(path) { + current = access?.read_element(current, current_index)?; } - Ok(current) } - fn path_mut<'r, 'p>( + fn reflect_path_mut<'r, 'p>( &'r mut self, path: &'p str, ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { - let mut index = 0; let mut current: &mut dyn Reflect = self; - while let Some(token) = next_token(path, &mut index) { - let current_index = index; - match token { - Token::Dot => { - if let Some(Token::Ident(value)) = next_token(path, &mut index) { - current = read_field_mut(current, value, current_index)?; - } else { - return Err(ReflectPathError::ExpectedIdent { - index: current_index, - }); - } - } - Token::OpenBracket => { - if let Some(Token::Ident(value)) = next_token(path, &mut index) { - match current.reflect_mut() { - ReflectMut::List(reflect_list) => { - current = read_array_entry_mut(reflect_list, value, current_index)?; - } - ReflectMut::Array(reflect_arr) => { - current = read_array_entry_mut(reflect_arr, value, current_index)?; - } - _ => { - return Err(ReflectPathError::ExpectedStruct { - index: current_index, - }) - } - } - } else { - return Err(ReflectPathError::ExpectedIdent { - index: current_index, - }); - } + for (access, current_index) in PathParser::new(path) { + current = access?.read_element_mut(current, current_index)?; + } + Ok(current) + } +} - if let Some(Token::CloseBracket) = next_token(path, &mut index) { - } else { - return Err(ReflectPathError::ExpectedToken { - index: current_index, - token: "]", - }); +/// A pre-parsed path to an element within a type. +/// +/// This struct may be used like [`GetPath`] but removes the cost of parsing the path +/// string at each element access. +/// +/// It's recommended to use this in place of `GetPath` when the path string is +/// unlikely to be changed and will be accessed repeatedly. +#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub struct ParsedPath( + /// This is the boxed slice of pre-parsed accesses. + /// + /// Each item in the slice contains the access along with the character + /// index of the start of the access within the parsed path string. + /// + /// The index is mainly used for more helpful error reporting. + Box<[(Access, usize)]>, +); + +impl ParsedPath { + /// Parses a [`ParsedPath`] from a string. + /// + /// Returns an error if the string does not represent a valid path to an element. + /// + /// The exact format for path strings can be found in the documentation for [`GetPath`]. + /// In short, though, a path consists of one or more chained accessor strings. + /// These are: + /// - Named field access (`.field`) + /// - Unnamed field access (`.1`) + /// - Field index access (`#0`) + /// - Sequence access (`[2]`) + /// + /// # Example + /// ``` + /// # use bevy_reflect::{ParsedPath, Reflect}; + /// #[derive(Reflect)] + /// struct Foo { + /// bar: Bar, + /// } + /// + /// #[derive(Reflect)] + /// struct Bar { + /// baz: Baz, + /// } + /// + /// #[derive(Reflect)] + /// struct Baz(f32, Vec>); + /// + /// let foo = Foo { + /// bar: Bar { + /// baz: Baz(3.14, vec![None, None, Some(123)]) + /// }, + /// }; + /// + /// let parsed_path = ParsedPath::parse("bar#0.1[2].0").unwrap(); + /// // Breakdown: + /// // "bar" - Access struct field named "bar" + /// // "#0" - Access struct field at index 0 + /// // ".1" - Access tuple struct field at index 1 + /// // "[2]" - Access list element at index 2 + /// // ".0" - Access tuple variant field at index 0 + /// + /// assert_eq!(parsed_path.element::(&foo).unwrap(), &123); + /// ``` + /// + pub fn parse(string: &str) -> Result> { + let mut parts = Vec::new(); + for (access, idx) in PathParser::new(string) { + parts.push((access?.to_owned(), idx)); + } + Ok(Self(parts.into_boxed_slice())) + } + + /// Gets a read-only reference to the specified element on the given [`Reflect`] object. + /// + /// Returns an error if the path is invalid for the provided type. + /// + /// See [`element_mut`](Self::reflect_element_mut) for a typed version of this method. + pub fn reflect_element<'r, 'p>( + &'p self, + root: &'r dyn Reflect, + ) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { + let mut current = root; + for (access, current_index) in self.0.iter() { + current = access.to_ref().read_element(current, *current_index)?; + } + Ok(current) + } + + /// Gets a mutable reference to the specified element on the given [`Reflect`] object. + /// + /// Returns an error if the path is invalid for the provided type. + /// + /// See [`element_mut`](Self::element_mut) for a typed version of this method. + pub fn reflect_element_mut<'r, 'p>( + &'p mut self, + root: &'r mut dyn Reflect, + ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { + let mut current = root; + for (access, current_index) in self.0.iter() { + current = access.to_ref().read_element_mut(current, *current_index)?; + } + Ok(current) + } + + /// Gets a typed, read-only reference to the specified element on the given [`Reflect`] object. + /// + /// Returns an error if the path is invalid for the provided type. + /// + /// See [`reflect_element`](Self::reflect_element) for an untyped version of this method. + pub fn element<'r, 'p, T: Reflect>( + &'p self, + root: &'r dyn Reflect, + ) -> Result<&'r T, ReflectPathError<'p>> { + self.reflect_element(root).and_then(|p| { + p.downcast_ref::() + .ok_or(ReflectPathError::InvalidDowncast) + }) + } + + /// Gets a typed, read-only reference to the specified element on the given [`Reflect`] object. + /// + /// Returns an error if the path is invalid for the provided type. + /// + /// See [`reflect_element_mut`](Self::reflect_element_mut) for an untyped version of this method. + pub fn element_mut<'r, 'p, T: Reflect>( + &'p mut self, + root: &'r mut dyn Reflect, + ) -> Result<&'r mut T, ReflectPathError<'p>> { + self.reflect_element_mut(root).and_then(|p| { + p.downcast_mut::() + .ok_or(ReflectPathError::InvalidDowncast) + }) + } +} + +impl fmt::Display for ParsedPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (idx, (access, _)) in self.0.iter().enumerate() { + match access { + Access::Field(field) => { + if idx != 0 { + Token::DOT.fmt(f)?; } + f.write_str(field.as_str())?; } - Token::CloseBracket => { - return Err(ReflectPathError::UnexpectedToken { - index: current_index, - token: "]", - }) + Access::FieldIndex(index) => { + Token::CROSSHATCH.fmt(f)?; + index.fmt(f)?; + } + Access::TupleIndex(index) => { + if idx != 0 { + Token::DOT.fmt(f)?; + } + index.fmt(f)?; } - Token::Ident(value) => { - current = read_field_mut(current, value, current_index)?; + Access::ListIndex(index) => { + Token::OPEN_BRACKET.fmt(f)?; + index.fmt(f)?; + Token::CLOSE_BRACKET.fmt(f)?; } } } - - Ok(current) + Ok(()) } } -fn read_array_entry<'r, 'p, T>( - list: &'r T, - value: &'p str, - current_index: usize, -) -> Result<&'r dyn Reflect, ReflectPathError<'p>> -where - T: Array + ?Sized, -{ - let list_index = value.parse::()?; - list.get(list_index) - .ok_or(ReflectPathError::InvalidListIndex { - index: current_index, - list_index, - }) +/// A singular owned element access within a path. +/// +/// Can be applied to a `dyn Reflect` to get a reference to the targeted element. +/// +/// A path is composed of multiple accesses in sequence. +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +enum Access { + Field(String), + FieldIndex(usize), + TupleIndex(usize), + ListIndex(usize), } -fn read_array_entry_mut<'r, 'p, T>( - list: &'r mut T, - value: &'p str, - current_index: usize, -) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> -where - T: Array + ?Sized, -{ - let list_index = value.parse::()?; - list.get_mut(list_index) - .ok_or(ReflectPathError::InvalidListIndex { - index: current_index, - list_index, - }) +impl Access { + fn to_ref(&self) -> AccessRef<'_> { + match self { + Self::Field(value) => AccessRef::Field(value), + Self::FieldIndex(value) => AccessRef::FieldIndex(*value), + Self::TupleIndex(value) => AccessRef::TupleIndex(*value), + Self::ListIndex(value) => AccessRef::ListIndex(*value), + } + } +} + +/// A singular borrowed element access within a path. +/// +/// Can be applied to a `dyn Reflect` to get a reference to the targeted element. +/// +/// Does not own the backing store it's sourced from. +/// For an owned version, you can convert one to an [`Access`]. +#[derive(Debug)] +enum AccessRef<'a> { + Field(&'a str), + FieldIndex(usize), + TupleIndex(usize), + ListIndex(usize), } -fn read_field<'r, 'p>( - current: &'r dyn Reflect, - field: &'p str, - current_index: usize, -) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { - match current.reflect_ref() { - ReflectRef::Struct(reflect_struct) => { - Ok(reflect_struct +impl<'a> AccessRef<'a> { + fn to_owned(&self) -> Access { + match self { + Self::Field(value) => Access::Field(value.to_string()), + Self::FieldIndex(value) => Access::FieldIndex(*value), + Self::TupleIndex(value) => Access::TupleIndex(*value), + Self::ListIndex(value) => Access::ListIndex(*value), + } + } + + fn read_element<'r>( + &self, + current: &'r dyn Reflect, + current_index: usize, + ) -> Result<&'r dyn Reflect, ReflectPathError<'a>> { + match (self, current.reflect_ref()) { + (Self::Field(field), ReflectRef::Struct(reflect_struct)) => reflect_struct .field(field) .ok_or(ReflectPathError::InvalidField { index: current_index, field, - })?) - } - ReflectRef::TupleStruct(reflect_struct) => { - let tuple_index = field.parse::()?; - Ok(reflect_struct.field(tuple_index).ok_or( - ReflectPathError::InvalidTupleStructIndex { + }), + (Self::FieldIndex(field_index), ReflectRef::Struct(reflect_struct)) => reflect_struct + .field_at(*field_index) + .ok_or(ReflectPathError::InvalidFieldIndex { index: current_index, - tuple_struct_index: tuple_index, - }, - )?) - } - ReflectRef::Enum(reflect_enum) => match reflect_enum.variant_type() { - VariantType::Struct => { - Ok(reflect_enum - .field(field) - .ok_or(ReflectPathError::InvalidField { + field_index: *field_index, + }), + (Self::TupleIndex(tuple_index), ReflectRef::TupleStruct(reflect_struct)) => { + reflect_struct.field(*tuple_index).ok_or( + ReflectPathError::InvalidTupleStructIndex { index: current_index, - field, - })?) + tuple_struct_index: *tuple_index, + }, + ) } - VariantType::Tuple => { - let tuple_index = field.parse::()?; - Ok(reflect_enum - .field_at(tuple_index) - .ok_or(ReflectPathError::InvalidField { + (Self::ListIndex(list_index), ReflectRef::List(reflect_list)) => reflect_list + .get(*list_index) + .ok_or(ReflectPathError::InvalidListIndex { + index: current_index, + list_index: *list_index, + }), + (Self::ListIndex(list_index), ReflectRef::Array(reflect_list)) => reflect_list + .get(*list_index) + .ok_or(ReflectPathError::InvalidListIndex { + index: current_index, + list_index: *list_index, + }), + (Self::ListIndex(_), _) => Err(ReflectPathError::ExpectedList { + index: current_index, + }), + (Self::Field(field), ReflectRef::Enum(reflect_enum)) => { + match reflect_enum.variant_type() { + VariantType::Struct => { + reflect_enum + .field(field) + .ok_or(ReflectPathError::InvalidField { + index: current_index, + field, + }) + } + _ => Err(ReflectPathError::ExpectedStructVariant { + index: current_index, + }), + } + } + (Self::FieldIndex(field_index), ReflectRef::Enum(reflect_enum)) => { + match reflect_enum.variant_type() { + VariantType::Struct => reflect_enum.field_at(*field_index).ok_or( + ReflectPathError::InvalidFieldIndex { + index: current_index, + field_index: *field_index, + }, + ), + _ => Err(ReflectPathError::ExpectedStructVariant { + index: current_index, + }), + } + } + (Self::TupleIndex(tuple_variant_index), ReflectRef::Enum(reflect_enum)) => { + match reflect_enum.variant_type() { + VariantType::Tuple => reflect_enum.field_at(*tuple_variant_index).ok_or( + ReflectPathError::InvalidTupleVariantIndex { + index: current_index, + tuple_variant_index: *tuple_variant_index, + }, + ), + _ => Err(ReflectPathError::ExpectedTupleVariant { index: current_index, - field, - })?) + }), + } } - _ => Err(ReflectPathError::InvalidVariantAccess { + _ => Err(ReflectPathError::ExpectedStruct { index: current_index, - accessor: field, }), - }, - _ => Err(ReflectPathError::ExpectedStruct { - index: current_index, - }), + } } -} -fn read_field_mut<'r, 'p>( - current: &'r mut dyn Reflect, - field: &'p str, - current_index: usize, -) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { - match current.reflect_mut() { - ReflectMut::Struct(reflect_struct) => { - Ok(reflect_struct + fn read_element_mut<'r>( + &self, + current: &'r mut dyn Reflect, + current_index: usize, + ) -> Result<&'r mut dyn Reflect, ReflectPathError<'a>> { + match (self, current.reflect_mut()) { + (Self::Field(field), ReflectMut::Struct(reflect_struct)) => reflect_struct .field_mut(field) .ok_or(ReflectPathError::InvalidField { index: current_index, field, - })?) - } - ReflectMut::TupleStruct(reflect_struct) => { - let tuple_index = field.parse::()?; - Ok(reflect_struct.field_mut(tuple_index).ok_or( - ReflectPathError::InvalidTupleStructIndex { + }), + (Self::FieldIndex(field_index), ReflectMut::Struct(reflect_struct)) => reflect_struct + .field_at_mut(*field_index) + .ok_or(ReflectPathError::InvalidFieldIndex { index: current_index, - tuple_struct_index: tuple_index, - }, - )?) - } - ReflectMut::Enum(reflect_enum) => match reflect_enum.variant_type() { - VariantType::Struct => { - Ok(reflect_enum - .field_mut(field) - .ok_or(ReflectPathError::InvalidField { + field_index: *field_index, + }), + (Self::TupleIndex(tuple_index), ReflectMut::TupleStruct(reflect_struct)) => { + reflect_struct.field_mut(*tuple_index).ok_or( + ReflectPathError::InvalidTupleStructIndex { index: current_index, - field, - })?) + tuple_struct_index: *tuple_index, + }, + ) } - VariantType::Tuple => { - let tuple_index = field.parse::()?; - Ok(reflect_enum.field_at_mut(tuple_index).ok_or( - ReflectPathError::InvalidField { + (Self::ListIndex(list_index), ReflectMut::List(reflect_list)) => reflect_list + .get_mut(*list_index) + .ok_or(ReflectPathError::InvalidListIndex { + index: current_index, + list_index: *list_index, + }), + (Self::ListIndex(list_index), ReflectMut::Array(reflect_list)) => reflect_list + .get_mut(*list_index) + .ok_or(ReflectPathError::InvalidListIndex { + index: current_index, + list_index: *list_index, + }), + (Self::ListIndex(_), _) => Err(ReflectPathError::ExpectedList { + index: current_index, + }), + (Self::Field(field), ReflectMut::Enum(reflect_enum)) => { + match reflect_enum.variant_type() { + VariantType::Struct => { + reflect_enum + .field_mut(field) + .ok_or(ReflectPathError::InvalidField { + index: current_index, + field, + }) + } + _ => Err(ReflectPathError::ExpectedStructVariant { index: current_index, - field, - }, - )?) + }), + } } - _ => Err(ReflectPathError::InvalidVariantAccess { + (Self::FieldIndex(field_index), ReflectMut::Enum(reflect_enum)) => { + match reflect_enum.variant_type() { + VariantType::Struct => reflect_enum.field_at_mut(*field_index).ok_or( + ReflectPathError::InvalidFieldIndex { + index: current_index, + field_index: *field_index, + }, + ), + _ => Err(ReflectPathError::ExpectedStructVariant { + index: current_index, + }), + } + } + (Self::TupleIndex(tuple_variant_index), ReflectMut::Enum(reflect_enum)) => { + match reflect_enum.variant_type() { + VariantType::Tuple => reflect_enum.field_at_mut(*tuple_variant_index).ok_or( + ReflectPathError::InvalidTupleVariantIndex { + index: current_index, + tuple_variant_index: *tuple_variant_index, + }, + ), + _ => Err(ReflectPathError::ExpectedTupleVariant { + index: current_index, + }), + } + } + _ => Err(ReflectPathError::ExpectedStruct { index: current_index, - accessor: field, }), - }, - _ => Err(ReflectPathError::ExpectedStruct { - index: current_index, - }), + } } } -enum Token<'a> { - Dot, - OpenBracket, - CloseBracket, - Ident(&'a str), +struct PathParser<'a> { + path: &'a str, + index: usize, } -fn next_token<'a>(path: &'a str, index: &mut usize) -> Option> { - if *index >= path.len() { - return None; +impl<'a> PathParser<'a> { + fn new(path: &'a str) -> Self { + Self { path, index: 0 } } - match path[*index..].chars().next().unwrap() { - '.' => { - *index += 1; - return Some(Token::Dot); + fn next_token(&mut self) -> Option> { + if self.index >= self.path.len() { + return None; } - '[' => { - *index += 1; - return Some(Token::OpenBracket); + + match self.path[self.index..].chars().next().unwrap() { + Token::DOT => { + self.index += 1; + return Some(Token::Dot); + } + Token::CROSSHATCH => { + self.index += 1; + return Some(Token::CrossHatch); + } + Token::OPEN_BRACKET => { + self.index += 1; + return Some(Token::OpenBracket); + } + Token::CLOSE_BRACKET => { + self.index += 1; + return Some(Token::CloseBracket); + } + _ => {} } - ']' => { - *index += 1; - return Some(Token::CloseBracket); + + // we can assume we are parsing an ident now + for (char_index, character) in self.path[self.index..].chars().enumerate() { + match character { + Token::DOT | Token::CROSSHATCH | Token::OPEN_BRACKET | Token::CLOSE_BRACKET => { + let ident = Token::Ident(&self.path[self.index..self.index + char_index]); + self.index += char_index; + return Some(ident); + } + _ => {} + } } - _ => {} + let ident = Token::Ident(&self.path[self.index..]); + self.index = self.path.len(); + Some(ident) } - // we can assume we are parsing an ident now - for (char_index, character) in path[*index..].chars().enumerate() { - match character { - '.' | '[' | ']' => { - let ident = Token::Ident(&path[*index..*index + char_index]); - *index += char_index; - return Some(ident); + fn token_to_access(&mut self, token: Token<'a>) -> Result, ReflectPathError<'a>> { + let current_index = self.index; + match token { + Token::Dot => { + if let Some(Token::Ident(value)) = self.next_token() { + value + .parse::() + .map(AccessRef::TupleIndex) + .or(Ok(AccessRef::Field(value))) + } else { + Err(ReflectPathError::ExpectedIdent { + index: current_index, + }) + } } - _ => {} + Token::CrossHatch => { + if let Some(Token::Ident(value)) = self.next_token() { + Ok(AccessRef::FieldIndex(value.parse::()?)) + } else { + Err(ReflectPathError::ExpectedIdent { + index: current_index, + }) + } + } + Token::OpenBracket => { + let access = if let Some(Token::Ident(value)) = self.next_token() { + AccessRef::ListIndex(value.parse::()?) + } else { + return Err(ReflectPathError::ExpectedIdent { + index: current_index, + }); + }; + + if !matches!(self.next_token(), Some(Token::CloseBracket)) { + return Err(ReflectPathError::ExpectedToken { + index: current_index, + token: Token::OPEN_BRACKET_STR, + }); + } + + Ok(access) + } + Token::CloseBracket => Err(ReflectPathError::UnexpectedToken { + index: current_index, + token: Token::CLOSE_BRACKET_STR, + }), + Token::Ident(value) => value + .parse::() + .map(AccessRef::TupleIndex) + .or(Ok(AccessRef::Field(value))), } } - let ident = Token::Ident(&path[*index..]); - *index = path.len(); - Some(ident) +} + +impl<'a> Iterator for PathParser<'a> { + type Item = (Result, ReflectPathError<'a>>, usize); + + fn next(&mut self) -> Option { + let token = self.next_token()?; + let index = self.index; + Some((self.token_to_access(token), index)) + } +} + +enum Token<'a> { + Dot, + CrossHatch, + OpenBracket, + CloseBracket, + Ident(&'a str), +} + +impl<'a> Token<'a> { + const DOT: char = '.'; + const CROSSHATCH: char = '#'; + const OPEN_BRACKET: char = '['; + const CLOSE_BRACKET: char = ']'; + const OPEN_BRACKET_STR: &'static str = "["; + const CLOSE_BRACKET_STR: &'static str = "]"; } #[cfg(test)] #[allow(clippy::float_cmp, clippy::approx_constant)] mod tests { - use super::GetPath; + use super::*; use crate as bevy_reflect; use crate::*; + #[derive(Reflect)] + struct A { + w: usize, + x: B, + y: Vec, + z: D, + unit_variant: F, + tuple_variant: F, + struct_variant: F, + array: [i32; 3], + } + + #[derive(Reflect)] + struct B { + foo: usize, + bar: C, + } + + #[derive(Reflect, FromReflect)] + struct C { + baz: f32, + } + + #[derive(Reflect)] + struct D(E); + + #[derive(Reflect)] + struct E(f32, usize); + + #[derive(Reflect, FromReflect, PartialEq, Debug)] + enum F { + Unit, + Tuple(u32, u32), + Struct { value: char }, + } + + #[test] + fn parsed_path_parse() { + assert_eq!( + &*ParsedPath::parse("w").unwrap().0, + &[(Access::Field("w".to_string()), 1)] + ); + assert_eq!( + &*ParsedPath::parse("x.foo").unwrap().0, + &[ + (Access::Field("x".to_string()), 1), + (Access::Field("foo".to_string()), 2) + ] + ); + assert_eq!( + &*ParsedPath::parse("x.bar.baz").unwrap().0, + &[ + (Access::Field("x".to_string()), 1), + (Access::Field("bar".to_string()), 2), + (Access::Field("baz".to_string()), 6) + ] + ); + assert_eq!( + &*ParsedPath::parse("y[1].baz").unwrap().0, + &[ + (Access::Field("y".to_string()), 1), + (Access::ListIndex(1), 2), + (Access::Field("baz".to_string()), 5) + ] + ); + assert_eq!( + &*ParsedPath::parse("z.0.1").unwrap().0, + &[ + (Access::Field("z".to_string()), 1), + (Access::TupleIndex(0), 2), + (Access::TupleIndex(1), 4), + ] + ); + assert_eq!( + &*ParsedPath::parse("x#0").unwrap().0, + &[ + (Access::Field("x".to_string()), 1), + (Access::FieldIndex(0), 2), + ] + ); + assert_eq!( + &*ParsedPath::parse("x#0#1").unwrap().0, + &[ + (Access::Field("x".to_string()), 1), + (Access::FieldIndex(0), 2), + (Access::FieldIndex(1), 4) + ] + ); + } + + #[test] + fn parsed_path_get_field() { + let a = A { + w: 1, + x: B { + foo: 10, + bar: C { baz: 3.14 }, + }, + y: vec![C { baz: 1.0 }, C { baz: 2.0 }], + z: D(E(10.0, 42)), + unit_variant: F::Unit, + tuple_variant: F::Tuple(123, 321), + struct_variant: F::Struct { value: 'm' }, + array: [86, 75, 309], + }; + + let b = ParsedPath::parse("w").unwrap(); + let c = ParsedPath::parse("x.foo").unwrap(); + let d = ParsedPath::parse("x.bar.baz").unwrap(); + let e = ParsedPath::parse("y[1].baz").unwrap(); + let f = ParsedPath::parse("z.0.1").unwrap(); + let g = ParsedPath::parse("x#0").unwrap(); + let h = ParsedPath::parse("x#1#0").unwrap(); + let i = ParsedPath::parse("unit_variant").unwrap(); + let j = ParsedPath::parse("tuple_variant.1").unwrap(); + let k = ParsedPath::parse("struct_variant.value").unwrap(); + let l = ParsedPath::parse("struct_variant#0").unwrap(); + let m = ParsedPath::parse("array[2]").unwrap(); + + for _ in 0..30 { + assert_eq!(*b.element::(&a).unwrap(), 1); + assert_eq!(*c.element::(&a).unwrap(), 10); + assert_eq!(*d.element::(&a).unwrap(), 3.14); + assert_eq!(*e.element::(&a).unwrap(), 2.0); + assert_eq!(*f.element::(&a).unwrap(), 42); + assert_eq!(*g.element::(&a).unwrap(), 10); + assert_eq!(*h.element::(&a).unwrap(), 3.14); + assert_eq!(*i.element::(&a).unwrap(), F::Unit); + assert_eq!(*j.element::(&a).unwrap(), 321); + assert_eq!(*k.element::(&a).unwrap(), 'm'); + assert_eq!(*l.element::(&a).unwrap(), 'm'); + assert_eq!(*m.element::(&a).unwrap(), 309); + } + } + #[test] fn reflect_array_behaves_like_list() { #[derive(Reflect)] @@ -427,10 +953,10 @@ mod tests { array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], }; - assert_eq!(*a.get_path::("list[5]").unwrap(), 5); - assert_eq!(*a.get_path::("array[5]").unwrap(), 5); - assert_eq!(*a.get_path::("list[0]").unwrap(), 0); - assert_eq!(*a.get_path::("array[0]").unwrap(), 0); + assert_eq!(*a.path::("list[5]").unwrap(), 5); + assert_eq!(*a.path::("array[5]").unwrap(), 5); + assert_eq!(*a.path::("list[0]").unwrap(), 0); + assert_eq!(*a.path::("array[0]").unwrap(), 0); } #[test] @@ -446,53 +972,18 @@ mod tests { array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], }; - assert_eq!(*a.get_path_mut::("list[5]").unwrap(), 5); - assert_eq!(*a.get_path_mut::("array[5]").unwrap(), 5); + assert_eq!(*a.path_mut::("list[5]").unwrap(), 5); + assert_eq!(*a.path_mut::("array[5]").unwrap(), 5); - *a.get_path_mut::("list[5]").unwrap() = 10; - *a.get_path_mut::("array[5]").unwrap() = 10; + *a.path_mut::("list[5]").unwrap() = 10; + *a.path_mut::("array[5]").unwrap() = 10; - assert_eq!(*a.get_path_mut::("list[5]").unwrap(), 10); - assert_eq!(*a.get_path_mut::("array[5]").unwrap(), 10); + assert_eq!(*a.path_mut::("list[5]").unwrap(), 10); + assert_eq!(*a.path_mut::("array[5]").unwrap(), 10); } #[test] fn reflect_path() { - #[derive(Reflect)] - struct A { - w: usize, - x: B, - y: Vec, - z: D, - unit_variant: F, - tuple_variant: F, - struct_variant: F, - } - - #[derive(Reflect)] - struct B { - foo: usize, - bar: C, - } - - #[derive(Reflect, FromReflect)] - struct C { - baz: f32, - } - - #[derive(Reflect)] - struct D(E); - - #[derive(Reflect)] - struct E(f32, usize); - - #[derive(Reflect, FromReflect, PartialEq, Debug)] - enum F { - Unit, - Tuple(u32, u32), - Struct { value: char }, - } - let mut a = A { w: 1, x: B { @@ -504,26 +995,32 @@ mod tests { unit_variant: F::Unit, tuple_variant: F::Tuple(123, 321), struct_variant: F::Struct { value: 'm' }, + array: [86, 75, 309], }; - assert_eq!(*a.get_path::("w").unwrap(), 1); - assert_eq!(*a.get_path::("x.foo").unwrap(), 10); - assert_eq!(*a.get_path::("x.bar.baz").unwrap(), 3.14); - assert_eq!(*a.get_path::("y[1].baz").unwrap(), 2.0); - assert_eq!(*a.get_path::("z.0.1").unwrap(), 42); + assert_eq!(*a.path::("w").unwrap(), 1); + assert_eq!(*a.path::("x.foo").unwrap(), 10); + assert_eq!(*a.path::("x.bar.baz").unwrap(), 3.14); + assert_eq!(*a.path::("y[1].baz").unwrap(), 2.0); + assert_eq!(*a.path::("z.0.1").unwrap(), 42); + assert_eq!(*a.path::("x#0").unwrap(), 10); + assert_eq!(*a.path::("x#1#0").unwrap(), 3.14); - assert_eq!(*a.get_path::("unit_variant").unwrap(), F::Unit); - assert_eq!(*a.get_path::("tuple_variant.1").unwrap(), 321); - assert_eq!(*a.get_path::("struct_variant.value").unwrap(), 'm'); + assert_eq!(*a.path::("unit_variant").unwrap(), F::Unit); + assert_eq!(*a.path::("tuple_variant.1").unwrap(), 321); + assert_eq!(*a.path::("struct_variant.value").unwrap(), 'm'); + assert_eq!(*a.path::("struct_variant#0").unwrap(), 'm'); - *a.get_path_mut::("y[1].baz").unwrap() = 3.0; + assert_eq!(*a.path::("array[2]").unwrap(), 309); + + *a.path_mut::("y[1].baz").unwrap() = 3.0; assert_eq!(a.y[1].baz, 3.0); - *a.get_path_mut::("tuple_variant.0").unwrap() = 1337; + *a.path_mut::("tuple_variant.0").unwrap() = 1337; assert_eq!(a.tuple_variant, F::Tuple(1337, 321)); assert_eq!( - a.path("x.notreal").err().unwrap(), + a.reflect_path("x.notreal").err().unwrap(), ReflectPathError::InvalidField { index: 2, field: "notreal" @@ -531,30 +1028,27 @@ mod tests { ); assert_eq!( - a.path("unit_variant.0").err().unwrap(), - ReflectPathError::InvalidVariantAccess { - index: 13, - accessor: "0" - } + a.reflect_path("unit_variant.0").err().unwrap(), + ReflectPathError::ExpectedTupleVariant { index: 13 } ); assert_eq!( - a.path("x..").err().unwrap(), + a.reflect_path("x..").err().unwrap(), ReflectPathError::ExpectedIdent { index: 2 } ); assert_eq!( - a.path("x[0]").err().unwrap(), + a.reflect_path("x[0]").err().unwrap(), ReflectPathError::ExpectedList { index: 2 } ); assert_eq!( - a.path("y.x").err().unwrap(), + a.reflect_path("y.x").err().unwrap(), ReflectPathError::ExpectedStruct { index: 2 } ); assert!(matches!( - a.path("y[badindex]"), + a.reflect_path("y[badindex]"), Err(ReflectPathError::IndexParseError(_)) )); }