From 8cd59b6a0344e20e6d0e0dcfd26eb7c4d485545e Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sun, 22 Jan 2023 23:35:33 +0000 Subject: [PATCH] bevy_reflect: Pre-parsed paths (#7321) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective > ℹ️ **This is an adoption of #4081 by @james7132** Fixes #4080. Provide a way to pre-parse reflection paths so as to avoid having to parse at each call to `GetPath::path` (or similar method). ## Solution Adds the `ParsedPath` struct (named `FieldPath` in the original PR) that parses and caches the sequence of accesses to a reflected element. This is functionally similar to the `GetPath` trait, but removes the need to parse an unchanged path more than once. ### Additional Changes Included in this PR from the original is cleaner code as well as the introduction of a new pathing operation: field access by index. This allows struct and struct variant fields to be accessed in a more performant (albeit more fragile) way if needed. This operation is faster due to not having to perform string matching. As an example, if we wanted the third field on a struct, we'd write `#2`—where `#` denotes indexed access and `2` denotes the desired field index. This PR also contains improved documentation for `GetPath` and friends, including renaming some of the methods to be more clear to the end-user with a reduced risk of getting them mixed up. ### Future Work There are a few things that could be done as a separate PR (order doesn't matter— they could be followup PRs or done in parallel). These are: - [x] ~~Add support for `Tuple`. Currently, we hint that they work but they do not.~~ See #7324 - [ ] Cleanup `ReflectPathError`. I think it would be nicer to give `ReflectPathError` two variants: `ReflectPathError::ParseError` and `ReflectPathError::AccessError`, with all current variants placed within one of those two. It's not obvious when one might expect to receive one type of error over the other, so we can help by explicitly categorizing them. --- ## Changelog - Cleaned up `GetPath` logic - Added `ParsedPath` for cached reflection paths - Added new reflection path syntax: struct field access by index (example syntax: `foo#1`) - Renamed methods on `GetPath`: - `path` -> `reflect_path` - `path_mut` -> `reflect_path_mut` - `get_path` -> `path` - `get_path_mut` -> `path_mut` ## Migration Guide `GetPath` methods have been renamed according to the following: - `path` -> `reflect_path` - `path_mut` -> `reflect_path_mut` - `get_path` -> `path` - `get_path_mut` -> `path_mut` Co-authored-by: Gino Valente --- crates/bevy_reflect/src/lib.rs | 10 +- crates/bevy_reflect/src/path.rs | 1164 ++++++++++++++++++++++--------- 2 files changed, 837 insertions(+), 337 deletions(-) diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index dcf2d85861722..d0bf81058442e 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 17cbb36e33dc1..e43a45fea31ee 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(_)) )); }