From 47c34fbba4d34d66b65fba2b48eb98ee353deed9 Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 2 Mar 2022 05:37:18 -0800 Subject: [PATCH 01/25] Refactor GetPath's parser --- crates/bevy_reflect/src/path.rs | 489 ++++++++++++++++---------------- 1 file changed, 243 insertions(+), 246 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 17cbb36e33dc1..9fc649aebe566 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -1,6 +1,6 @@ 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. @@ -15,6 +15,13 @@ pub enum ReflectPathError<'a> { 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,12 +32,14 @@ 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. @@ -106,61 +115,15 @@ impl GetPath for T { impl GetPath for dyn Reflect { fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { - let mut index = 0; 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) { + match access { + Ok(access) => { + current = access.read_field(current, current_index)?; } + Err(err) => return Err(err), } } - Ok(current) } @@ -168,243 +131,280 @@ impl GetPath for dyn Reflect { &'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, - }); - } - - 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_mut(current, value, current_index)?; + for (access, current_index) in PathParser::new(path) { + match access { + Ok(access) => { + current = access.read_field_mut(current, current_index)?; } + Err(err) => return Err(err), } } - Ok(current) } } -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, - }) -} - -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, - }) +#[derive(Debug)] +enum Access<'a> { + Field(&'a str), + 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> Access<'a> { + fn read_field<'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)) => Ok(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::TupleIndex(tuple_index), ReflectRef::TupleStruct(reflect_struct)) => { + Ok(reflect_struct.field(*tuple_index).ok_or( + ReflectPathError::InvalidTupleStructIndex { + index: current_index, + tuple_struct_index: *tuple_index, + }, + )?) + } + (Self::ListIndex(list_index), ReflectRef::List(reflect_list)) => Ok(reflect_list + .get(*list_index) + .ok_or(ReflectPathError::InvalidListIndex { 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 { + list_index: *list_index, + })?), + (Self::ListIndex(list_index), ReflectRef::Array(reflect_list)) => Ok(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 => { + Ok(reflect_enum + .field(field) + .ok_or(ReflectPathError::InvalidField { + index: current_index, + field, + })?) + } + _ => Err(ReflectPathError::ExpectedStructVariant { index: current_index, - field, - })?) + }), + } } - VariantType::Tuple => { - let tuple_index = field.parse::()?; - Ok(reflect_enum - .field_at(tuple_index) - .ok_or(ReflectPathError::InvalidField { + (Self::TupleIndex(tuple_variant_index), ReflectRef::Enum(reflect_enum)) => { + match reflect_enum.variant_type() { + VariantType::Tuple => Ok(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_field_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)) => Ok(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::TupleIndex(tuple_index), ReflectMut::TupleStruct(reflect_struct)) => { + Ok(reflect_struct.field_mut(*tuple_index).ok_or( + ReflectPathError::InvalidTupleStructIndex { + index: current_index, + tuple_struct_index: *tuple_index, + }, + )?) + } + (Self::ListIndex(list_index), ReflectMut::List(reflect_list)) => Ok(reflect_list + .get_mut(*list_index) + .ok_or(ReflectPathError::InvalidListIndex { 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 { + list_index: *list_index, + })?), + (Self::ListIndex(list_index), ReflectMut::Array(reflect_list)) => Ok(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 => { + Ok(reflect_enum + .field_mut(field) + .ok_or(ReflectPathError::InvalidField { + index: current_index, + field, + })?) + } + _ => Err(ReflectPathError::ExpectedStructVariant { index: current_index, - field, - })?) + }), + } } - VariantType::Tuple => { - let tuple_index = field.parse::()?; - Ok(reflect_enum.field_at_mut(tuple_index).ok_or( - ReflectPathError::InvalidField { + (Self::TupleIndex(tuple_variant_index), ReflectMut::Enum(reflect_enum)) => { + match reflect_enum.variant_type() { + VariantType::Tuple => Ok(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, - field, - }, - )?) + }), + } } - _ => Err(ReflectPathError::InvalidVariantAccess { + _ => 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() { + '.' => { + self.index += 1; + return Some(Token::Dot); + } + '[' => { + self.index += 1; + return Some(Token::OpenBracket); + } + ']' => { + 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 { + '.' | '[' | ']' => { + 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() { + if let Ok(tuple_index) = value.parse::() { + Ok(Access::TupleIndex(tuple_index)) + } else { + Ok(Access::Field(value)) + } + } else { + Err(ReflectPathError::ExpectedIdent { + index: current_index, + }) + } + } + Token::OpenBracket => { + let access = if let Some(Token::Ident(value)) = self.next_token() { + Access::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: "]", + }); + } + + Ok(access) + } + Token::CloseBracket => Err(ReflectPathError::UnexpectedToken { + index: current_index, + token: "]", + }), + Token::Ident(value) => { + if let Ok(tuple_index) = value.parse::() { + Ok(Access::TupleIndex(tuple_index)) + } else { + Ok(Access::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 { + if let Some(token) = self.next_token() { + let index = self.index; + Some((self.token_to_access(token), index)) + } else { + None + } + } +} + +enum Token<'a> { + Dot, + OpenBracket, + CloseBracket, + Ident(&'a str), } #[cfg(test)] @@ -532,10 +532,7 @@ mod tests { assert_eq!( a.path("unit_variant.0").err().unwrap(), - ReflectPathError::InvalidVariantAccess { - index: 13, - accessor: "0" - } + ReflectPathError::ExpectedTupleVariant { index: 13 } ); assert_eq!( From 5da1f0d3d25d0d73d52a17525653ac4da7d8b9e6 Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 2 Mar 2022 06:11:42 -0800 Subject: [PATCH 02/25] Add FieldPath and Access --- crates/bevy_reflect/src/path.rs | 95 +++++++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 9fc649aebe566..1b75bee123df1 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -56,6 +56,9 @@ pub enum ReflectPathError<'a> { /// 2-tuples (like a `Vec<(T, U)>`), the path string `foo[3].0` would access tuple /// element 0 of element 3 of `foo`. /// +/// Using these functions repeatedly with the same string requires parsing the +/// string every time. To avoid this cost, construct a [`FieldPath`] instead. +/// /// [`Struct`]: crate::Struct /// [`TupleStruct`]: crate::TupleStruct /// [`Tuple`]: crate::Tuple @@ -117,12 +120,7 @@ impl GetPath for dyn Reflect { fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { let mut current: &dyn Reflect = self; for (access, current_index) in PathParser::new(path) { - match access { - Ok(access) => { - current = access.read_field(current, current_index)?; - } - Err(err) => return Err(err), - } + current = access?.read_field(current, current_index)?; } Ok(current) } @@ -133,25 +131,80 @@ impl GetPath for dyn Reflect { ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { let mut current: &mut dyn Reflect = self; for (access, current_index) in PathParser::new(path) { - match access { - Ok(access) => { - current = access.read_field_mut(current, current_index)?; - } - Err(err) => return Err(err), - } + current = access?.read_field_mut(current, current_index)?; + } + Ok(current) + } +} + +#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub struct FieldPath(Box<[(Access, usize)]>); + +impl FieldPath { + pub fn parse<'a>(string: &'a 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())) + } + + pub fn field<'r, 'p>( + &'p self, + root: &'r impl Reflect, + ) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { + let mut current: &dyn Reflect = root; + for (access, current_index) in self.0.iter() { + current = access.to_ref().read_field(current, *current_index)?; + } + Ok(current) + } + + pub fn field_mut<'r, 'p>( + &'p mut self, + root: &'r mut impl Reflect, + ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { + let mut current: &mut dyn Reflect = root; + for (access, current_index) in self.0.iter() { + current = access.to_ref().read_field_mut(current, *current_index)?; } Ok(current) } } +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +enum Access { + Field(String), + TupleIndex(usize), + ListIndex(usize), +} + +impl Access { + fn to_ref(&self) -> AccessRef<'_> { + match self { + Self::Field(value) => AccessRef::Field(&value), + Self::TupleIndex(value) => AccessRef::TupleIndex(*value), + Self::ListIndex(value) => AccessRef::ListIndex(*value), + } + } +} + #[derive(Debug)] -enum Access<'a> { +enum AccessRef<'a> { Field(&'a str), TupleIndex(usize), ListIndex(usize), } -impl<'a> Access<'a> { +impl<'a> AccessRef<'a> { + fn to_owned(&self) -> Access { + match self { + Self::Field(value) => Access::Field(value.to_string()), + Self::TupleIndex(value) => Access::TupleIndex(*value), + Self::ListIndex(value) => Access::ListIndex(*value), + } + } + fn read_field<'r>( &self, current: &'r dyn Reflect, @@ -338,15 +391,15 @@ impl<'a> PathParser<'a> { Some(ident) } - fn token_to_access(&mut self, token: Token<'a>) -> Result, ReflectPathError<'a>> { + 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() { if let Ok(tuple_index) = value.parse::() { - Ok(Access::TupleIndex(tuple_index)) + Ok(AccessRef::TupleIndex(tuple_index)) } else { - Ok(Access::Field(value)) + Ok(AccessRef::Field(value)) } } else { Err(ReflectPathError::ExpectedIdent { @@ -356,7 +409,7 @@ impl<'a> PathParser<'a> { } Token::OpenBracket => { let access = if let Some(Token::Ident(value)) = self.next_token() { - Access::ListIndex(value.parse::()?) + AccessRef::ListIndex(value.parse::()?) } else { return Err(ReflectPathError::ExpectedIdent { index: current_index, @@ -378,9 +431,9 @@ impl<'a> PathParser<'a> { }), Token::Ident(value) => { if let Ok(tuple_index) = value.parse::() { - Ok(Access::TupleIndex(tuple_index)) + Ok(AccessRef::TupleIndex(tuple_index)) } else { - Ok(Access::Field(value)) + Ok(AccessRef::Field(value)) } } } @@ -388,7 +441,7 @@ impl<'a> PathParser<'a> { } impl<'a> Iterator for PathParser<'a> { - type Item = (Result, ReflectPathError<'a>>, usize); + type Item = (Result, ReflectPathError<'a>>, usize); fn next(&mut self) -> Option { if let Some(token) = self.next_token() { From b2d9cf065ba5f8a96cb64bbd9132b4a0db8e5723 Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 2 Mar 2022 06:18:31 -0800 Subject: [PATCH 03/25] Darn clippy --- crates/bevy_reflect/src/path.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 1b75bee123df1..674bef69ed202 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -141,7 +141,7 @@ impl GetPath for dyn Reflect { pub struct FieldPath(Box<[(Access, usize)]>); impl FieldPath { - pub fn parse<'a>(string: &'a str) -> Result> { + pub fn parse(string: &str) -> Result> { let mut parts = Vec::new(); for (access, idx) in PathParser::new(string) { parts.push((access?.to_owned(), idx)); @@ -182,7 +182,7 @@ enum Access { impl Access { fn to_ref(&self) -> AccessRef<'_> { match self { - Self::Field(value) => AccessRef::Field(&value), + Self::Field(value) => AccessRef::Field(value), Self::TupleIndex(value) => AccessRef::TupleIndex(*value), Self::ListIndex(value) => AccessRef::ListIndex(*value), } From b1a52c6fbc4d4a0608d55235cea5d25d86304a71 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 4 Mar 2022 00:44:36 -0800 Subject: [PATCH 04/25] Clean up code and add doc comments --- crates/bevy_reflect/src/path.rs | 80 +++++++++++++++++---------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 674bef69ed202..5ac0562ac0794 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -137,10 +137,14 @@ impl GetPath for dyn Reflect { } } +/// A path to a field within a type. Can be used like [`GetPath`] functions to get +/// references to the inner fields of a type. #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] pub struct FieldPath(Box<[(Access, usize)]>); impl FieldPath { + /// Parses a [`FieldPath`] from a string. For the exact format, see [`GetPath`]. + /// Returns an error if the string does not represent a valid path to a field. pub fn parse(string: &str) -> Result> { let mut parts = Vec::new(); for (access, idx) in PathParser::new(string) { @@ -149,6 +153,8 @@ impl FieldPath { Ok(Self(parts.into_boxed_slice())) } + /// Gets a read-only reference of given field. + /// Returns an error if the path is invalid for the provided type. pub fn field<'r, 'p>( &'p self, root: &'r impl Reflect, @@ -160,6 +166,8 @@ impl FieldPath { Ok(current) } + /// Gets a mutable reference of given field. + /// Returns an error if the path is invalid for the provided type. pub fn field_mut<'r, 'p>( &'p mut self, root: &'r mut impl Reflect, @@ -211,44 +219,44 @@ impl<'a> AccessRef<'a> { current_index: usize, ) -> Result<&'r dyn Reflect, ReflectPathError<'a>> { match (self, current.reflect_ref()) { - (Self::Field(field), ReflectRef::Struct(reflect_struct)) => Ok(reflect_struct + (Self::Field(field), ReflectRef::Struct(reflect_struct)) => reflect_struct .field(field) .ok_or(ReflectPathError::InvalidField { index: current_index, field, - })?), + }), (Self::TupleIndex(tuple_index), ReflectRef::TupleStruct(reflect_struct)) => { - Ok(reflect_struct.field(*tuple_index).ok_or( + reflect_struct.field(*tuple_index).ok_or( ReflectPathError::InvalidTupleStructIndex { index: current_index, tuple_struct_index: *tuple_index, }, - )?) + ) } - (Self::ListIndex(list_index), ReflectRef::List(reflect_list)) => Ok(reflect_list + (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)) => Ok(reflect_list + }), + (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 => { - Ok(reflect_enum + reflect_enum .field(field) .ok_or(ReflectPathError::InvalidField { index: current_index, field, - })?) + }) } _ => Err(ReflectPathError::ExpectedStructVariant { index: current_index, @@ -257,12 +265,12 @@ impl<'a> AccessRef<'a> { } (Self::TupleIndex(tuple_variant_index), ReflectRef::Enum(reflect_enum)) => { match reflect_enum.variant_type() { - VariantType::Tuple => Ok(reflect_enum.field_at(*tuple_variant_index).ok_or( + 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, }), @@ -280,44 +288,44 @@ impl<'a> AccessRef<'a> { current_index: usize, ) -> Result<&'r mut dyn Reflect, ReflectPathError<'a>> { match (self, current.reflect_mut()) { - (Self::Field(field), ReflectMut::Struct(reflect_struct)) => Ok(reflect_struct + (Self::Field(field), ReflectMut::Struct(reflect_struct)) => reflect_struct .field_mut(field) .ok_or(ReflectPathError::InvalidField { index: current_index, field, - })?), + }), (Self::TupleIndex(tuple_index), ReflectMut::TupleStruct(reflect_struct)) => { - Ok(reflect_struct.field_mut(*tuple_index).ok_or( + reflect_struct.field_mut(*tuple_index).ok_or( ReflectPathError::InvalidTupleStructIndex { index: current_index, tuple_struct_index: *tuple_index, }, - )?) + ) } - (Self::ListIndex(list_index), ReflectMut::List(reflect_list)) => Ok(reflect_list + (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)) => Ok(reflect_list + }), + (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 => { - Ok(reflect_enum + reflect_enum .field_mut(field) .ok_or(ReflectPathError::InvalidField { index: current_index, field, - })?) + }) } _ => Err(ReflectPathError::ExpectedStructVariant { index: current_index, @@ -326,12 +334,12 @@ impl<'a> AccessRef<'a> { } (Self::TupleIndex(tuple_variant_index), ReflectMut::Enum(reflect_enum)) => { match reflect_enum.variant_type() { - VariantType::Tuple => Ok(reflect_enum - .field_at_mut(*tuple_variant_index) - .ok_or(ReflectPathError::InvalidTupleVariantIndex { + 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, }), @@ -396,11 +404,10 @@ impl<'a> PathParser<'a> { match token { Token::Dot => { if let Some(Token::Ident(value)) = self.next_token() { - if let Ok(tuple_index) = value.parse::() { - Ok(AccessRef::TupleIndex(tuple_index)) - } else { - Ok(AccessRef::Field(value)) - } + value + .parse::() + .map(|idx| AccessRef::TupleIndex(idx)) + .or(Ok(AccessRef::Field(value))) } else { Err(ReflectPathError::ExpectedIdent { index: current_index, @@ -429,13 +436,10 @@ impl<'a> PathParser<'a> { index: current_index, token: "]", }), - Token::Ident(value) => { - if let Ok(tuple_index) = value.parse::() { - Ok(AccessRef::TupleIndex(tuple_index)) - } else { - Ok(AccessRef::Field(value)) - } - } + Token::Ident(value) => value + .parse::() + .map(|idx| AccessRef::TupleIndex(idx)) + .or(Ok(AccessRef::Field(value))), } } } From e773d8e34220760717d17a6c790dba92267c788a Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 4 Mar 2022 13:52:31 -0800 Subject: [PATCH 05/25] Clippy --- crates/bevy_reflect/src/path.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 5ac0562ac0794..2b0b38b38bc42 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -406,7 +406,7 @@ impl<'a> PathParser<'a> { if let Some(Token::Ident(value)) = self.next_token() { value .parse::() - .map(|idx| AccessRef::TupleIndex(idx)) + .map(AccessRef::TupleIndex) .or(Ok(AccessRef::Field(value))) } else { Err(ReflectPathError::ExpectedIdent { @@ -438,7 +438,7 @@ impl<'a> PathParser<'a> { }), Token::Ident(value) => value .parse::() - .map(|idx| AccessRef::TupleIndex(idx)) + .map(AccessRef::TupleIndex) .or(Ok(AccessRef::Field(value))), } } From 3a61215f7fc0010a2a42e83986027f1f8b0efc1a Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 4 Mar 2022 14:07:52 -0800 Subject: [PATCH 06/25] Cleanup code a bit more --- crates/bevy_reflect/src/path.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 2b0b38b38bc42..ef034c4241322 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -448,12 +448,9 @@ impl<'a> Iterator for PathParser<'a> { type Item = (Result, ReflectPathError<'a>>, usize); fn next(&mut self) -> Option { - if let Some(token) = self.next_token() { - let index = self.index; - Some((self.token_to_access(token), index)) - } else { - None - } + let token = self.next_token()?; + let index = self.index; + Some((self.token_to_access(token), index)) } } From c15bbb558e8c8aa464371381f676e2f45dc60447 Mon Sep 17 00:00:00 2001 From: james7132 Date: Mon, 7 Mar 2022 02:15:51 -0800 Subject: [PATCH 07/25] Handle field number parsing --- crates/bevy_reflect/src/path.rs | 60 ++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index ef034c4241322..c97823f5d2a5d 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -10,6 +10,8 @@ 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, @@ -183,6 +185,7 @@ impl FieldPath { #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] enum Access { Field(String), + FieldIndex(usize), TupleIndex(usize), ListIndex(usize), } @@ -191,6 +194,7 @@ 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), } @@ -200,6 +204,7 @@ impl Access { #[derive(Debug)] enum AccessRef<'a> { Field(&'a str), + FieldIndex(usize), TupleIndex(usize), ListIndex(usize), } @@ -208,6 +213,7 @@ 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), } @@ -225,6 +231,12 @@ impl<'a> AccessRef<'a> { index: current_index, field, }), + (Self::FieldIndex(field_index), ReflectRef::Struct(reflect_struct)) => reflect_struct + .field_at(*field_index) + .ok_or(ReflectPathError::InvalidFieldIndex { + index: current_index, + field_index: *field_index, + }), (Self::TupleIndex(tuple_index), ReflectRef::TupleStruct(reflect_struct)) => { reflect_struct.field(*tuple_index).ok_or( ReflectPathError::InvalidTupleStructIndex { @@ -263,6 +275,19 @@ impl<'a> AccessRef<'a> { }), } } + (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( @@ -294,6 +319,12 @@ impl<'a> AccessRef<'a> { index: current_index, field, }), + (Self::FieldIndex(field_index), ReflectMut::Struct(reflect_struct)) => reflect_struct + .field_at_mut(*field_index) + .ok_or(ReflectPathError::InvalidFieldIndex { + index: current_index, + field_index: *field_index, + }), (Self::TupleIndex(tuple_index), ReflectMut::TupleStruct(reflect_struct)) => { reflect_struct.field_mut(*tuple_index).ok_or( ReflectPathError::InvalidTupleStructIndex { @@ -332,6 +363,19 @@ impl<'a> AccessRef<'a> { }), } } + (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( @@ -372,6 +416,10 @@ impl<'a> PathParser<'a> { self.index += 1; return Some(Token::Dot); } + '#' => { + self.index += 1; + return Some(Token::CrossHatch); + } '[' => { self.index += 1; return Some(Token::OpenBracket); @@ -386,7 +434,7 @@ impl<'a> PathParser<'a> { // we can assume we are parsing an ident now for (char_index, character) in self.path[self.index..].chars().enumerate() { match character { - '.' | '[' | ']' => { + '.' | '#' | '[' | ']' => { let ident = Token::Ident(&self.path[self.index..self.index + char_index]); self.index += char_index; return Some(ident); @@ -414,6 +462,15 @@ impl<'a> PathParser<'a> { }) } } + 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::()?) @@ -456,6 +513,7 @@ impl<'a> Iterator for PathParser<'a> { enum Token<'a> { Dot, + CrossHatch, OpenBracket, CloseBracket, Ident(&'a str), From 9d7f10c2f7294b00a6fb448169533920b29c04b2 Mon Sep 17 00:00:00 2001 From: james7132 Date: Mon, 7 Mar 2022 02:21:09 -0800 Subject: [PATCH 08/25] Update path tests --- crates/bevy_reflect/src/path.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index c97823f5d2a5d..6e7705ad7efec 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -623,6 +623,8 @@ mod tests { 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.get_path::("x#0").unwrap(), 10); + assert_eq!(*a.get_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); From 0763808c6b2bc35fe976706c986af8b65fb5ede2 Mon Sep 17 00:00:00 2001 From: james7132 Date: Mon, 7 Mar 2022 02:30:30 -0800 Subject: [PATCH 09/25] Add a Display implementation for FieldPath --- crates/bevy_reflect/src/path.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 6e7705ad7efec..f0d0da20ab7a4 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::num::ParseIntError; use crate::{Reflect, ReflectMut, ReflectRef, VariantType}; @@ -182,6 +183,37 @@ impl FieldPath { } } +impl fmt::Display for FieldPath { + 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 { + f.write_str(".")?; + } + f.write_str(field.as_str())?; + } + Access::FieldIndex(index) => { + f.write_str("#")?; + index.fmt(f)?; + } + Access::TupleIndex(index) => { + if idx != 0 { + f.write_str(".")?; + } + index.fmt(f)?; + } + Access::ListIndex(index) => { + f.write_str("[")?; + index.fmt(f)?; + f.write_str("]")?; + } + } + } + Ok(()) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] enum Access { Field(String), From 3ad5ace36d07a4b857068e461020e855923cdf72 Mon Sep 17 00:00:00 2001 From: james7132 Date: Mon, 7 Mar 2022 02:42:59 -0800 Subject: [PATCH 10/25] Add documentation for field index path s --- crates/bevy_reflect/src/path.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index f0d0da20ab7a4..0bcfa03e7fd16 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -51,6 +51,10 @@ pub enum ReflectPathError<'a> { /// - [`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]` +/// - Field indexes within [`Struct`] can also be optionally used instead: `#0` for +/// the first field. This can speed up fetches at runtime (no string matching) +/// but can be much more fragile to keeping code and string paths in sync. Storing +/// these paths in persistent storage (i.e. game assets) is strongly discouraged. /// /// If the initial path element is a field of a struct, tuple struct, or tuple, /// the initial '.' may be omitted. From 0caf893b6c57b5237402002ca0f27ecf8d78a695 Mon Sep 17 00:00:00 2001 From: James Liu Date: Mon, 7 Mar 2022 21:24:53 -0500 Subject: [PATCH 11/25] Clarify doc comment on field index paths. Co-authored-by: MrGVSV <49806985+MrGVSV@users.noreply.github.com> --- crates/bevy_reflect/src/path.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 0bcfa03e7fd16..bad3f975efb2c 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -53,8 +53,8 @@ pub enum ReflectPathError<'a> { /// - [`List`] items are accessed with brackets: `[0]` /// - Field indexes within [`Struct`] can also be optionally used instead: `#0` for /// the first field. This can speed up fetches at runtime (no string matching) -/// but can be much more fragile to keeping code and string paths in sync. Storing -/// these paths in persistent storage (i.e. game assets) is strongly discouraged. +/// but can be much more fragile to keeping code and string paths in sync since +/// the order of fields could be easily changed. Storing these paths in persistent /// storage (i.e. game assets) is strongly discouraged. /// /// If the initial path element is a field of a struct, tuple struct, or tuple, /// the initial '.' may be omitted. From 085c965e50a891be51f2a4d1343171695bdf06c3 Mon Sep 17 00:00:00 2001 From: james7132 Date: Mon, 7 Mar 2022 18:31:15 -0800 Subject: [PATCH 12/25] impl -> dyn --- crates/bevy_reflect/src/path.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index bad3f975efb2c..88c51c4adf702 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -164,9 +164,9 @@ impl FieldPath { /// Returns an error if the path is invalid for the provided type. pub fn field<'r, 'p>( &'p self, - root: &'r impl Reflect, + root: &'r dyn Reflect, ) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { - let mut current: &dyn Reflect = root; + let mut current = root; for (access, current_index) in self.0.iter() { current = access.to_ref().read_field(current, *current_index)?; } @@ -177,9 +177,9 @@ impl FieldPath { /// Returns an error if the path is invalid for the provided type. pub fn field_mut<'r, 'p>( &'p mut self, - root: &'r mut impl Reflect, + root: &'r mut dyn Reflect, ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { - let mut current: &mut dyn Reflect = root; + let mut current = root; for (access, current_index) in self.0.iter() { current = access.to_ref().read_field_mut(current, *current_index)?; } From 20d89bb03ba1b4676c3edce5c2a3f9ef6e46698b Mon Sep 17 00:00:00 2001 From: james7132 Date: Mon, 7 Mar 2022 18:54:14 -0800 Subject: [PATCH 13/25] Add tests --- crates/bevy_reflect/src/path.rs | 189 ++++++++++++++++++++++++++------ 1 file changed, 153 insertions(+), 36 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 88c51c4adf702..bf6402ef11c95 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -185,6 +185,26 @@ impl FieldPath { } Ok(current) } + + pub fn get_field<'r, 'p, T: Reflect>( + &'p self, + root: &'r dyn Reflect, + ) -> Result<&'r T, ReflectPathError<'p>> { + self.field(root).and_then(|p| { + p.downcast_ref::() + .ok_or(ReflectPathError::InvalidDowncast) + }) + } + + pub fn get_field_mut<'r, 'p, T: Reflect>( + &'p mut self, + root: &'r mut dyn Reflect, + ) -> Result<&'r mut T, ReflectPathError<'p>> { + self.field_mut(root).and_then(|p| { + p.downcast_mut::() + .ok_or(ReflectPathError::InvalidDowncast) + }) + } } impl fmt::Display for FieldPath { @@ -558,10 +578,141 @@ enum Token<'a> { #[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, + } + + #[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 field_path_parse() { + assert_eq!( + &*FieldPath::parse("w").unwrap().0, + &[(Access::Field("w".to_string()), 1)] + ); + assert_eq!( + &*FieldPath::parse("x.foo").unwrap().0, + &[ + (Access::Field("x".to_string()), 1), + (Access::Field("foo".to_string()), 2) + ] + ); + assert_eq!( + &*FieldPath::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!( + &*FieldPath::parse("y[1].baz").unwrap().0, + &[ + (Access::Field("y".to_string()), 1), + (Access::ListIndex(1), 2), + (Access::Field("baz".to_string()), 5) + ] + ); + assert_eq!( + &*FieldPath::parse("z.0.1").unwrap().0, + &[ + (Access::Field("z".to_string()), 1), + (Access::TupleIndex(0), 2), + (Access::TupleIndex(1), 4), + ] + ); + assert_eq!( + &*FieldPath::parse("x#0").unwrap().0, + &[ + (Access::Field("x".to_string()), 1), + (Access::FieldIndex(0), 2), + ] + ); + assert_eq!( + &*FieldPath::parse("x#0#1").unwrap().0, + &[ + (Access::Field("x".to_string()), 1), + (Access::FieldIndex(0), 2), + (Access::FieldIndex(1), 4) + ] + ); + } + + #[test] + fn field_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' }, + }; + + let b = FieldPath::parse("w").unwrap(); + let c = FieldPath::parse("x.foo").unwrap(); + let d = FieldPath::parse("x.bar.baz").unwrap(); + let e = FieldPath::parse("y[1].baz").unwrap(); + let f = FieldPath::parse("z.0.1").unwrap(); + let g = FieldPath::parse("x#0").unwrap(); + let h = FieldPath::parse("x#1#0").unwrap(); + let i = FieldPath::parse("unit_variant").unwrap(); + let j = FieldPath::parse("tuple_variant.1").unwrap(); + let k = FieldPath::parse("struct_variant.value").unwrap(); + let l = FieldPath::parse("struct_variant#0").unwrap(); + + for _ in 0..30 { + assert_eq!(*b.get_field::(&a).unwrap(), 1); + assert_eq!(*c.get_field::(&a).unwrap(), 10); + assert_eq!(*d.get_field::(&a).unwrap(), 3.14); + assert_eq!(*e.get_field::(&a).unwrap(), 2.0); + assert_eq!(*f.get_field::(&a).unwrap(), 42); + assert_eq!(*g.get_field::(&a).unwrap(), 10); + assert_eq!(*h.get_field::(&a).unwrap(), 3.14); + assert_eq!(*i.get_field::(&a).unwrap(), F::Unit); + assert_eq!(*j.get_field::(&a).unwrap(), 321); + assert_eq!(*k.get_field::(&a).unwrap(), 'm'); + assert_eq!(*l.get_field::(&a).unwrap(), 'm'); + } + } + #[test] fn reflect_array_behaves_like_list() { #[derive(Reflect)] @@ -606,41 +757,6 @@ mod tests { #[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 { @@ -665,6 +781,7 @@ mod tests { 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.get_path::("struct_variant#0").unwrap(), 'm'); *a.get_path_mut::("y[1].baz").unwrap() = 3.0; assert_eq!(a.y[1].baz, 3.0); From 8c1afb478a3e14348b834b66842cf3735a8eb0bd Mon Sep 17 00:00:00 2001 From: James Liu Date: Mon, 16 May 2022 12:06:32 -0700 Subject: [PATCH 14/25] Update docs. Co-authored-by: Alice Cecile --- crates/bevy_reflect/src/path.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index bf6402ef11c95..cdad0cb1df2f7 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -53,7 +53,7 @@ pub enum ReflectPathError<'a> { /// - [`List`] items are accessed with brackets: `[0]` /// - Field indexes within [`Struct`] can also be optionally used instead: `#0` for /// the first field. This can speed up fetches at runtime (no string matching) -/// but can be much more fragile to keeping code and string paths in sync since +/// but can be much more fragile as code and string paths must be kept in sync since /// the order of fields could be easily changed. Storing these paths in persistent /// storage (i.e. game assets) is strongly discouraged. /// /// If the initial path element is a field of a struct, tuple struct, or tuple, From e8e225bc4bfa738cf85c955e82b06ad31e87429f Mon Sep 17 00:00:00 2001 From: James Liu Date: Mon, 16 May 2022 12:06:39 -0700 Subject: [PATCH 15/25] Update docs. Co-authored-by: Alice Cecile --- crates/bevy_reflect/src/path.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index cdad0cb1df2f7..834d9cff46055 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -54,7 +54,8 @@ pub enum ReflectPathError<'a> { /// - Field indexes within [`Struct`] can also be optionally used instead: `#0` for /// the first field. This can speed up fetches at runtime (no string matching) /// but can be much more fragile as code and string paths must be kept in sync since -/// the order of fields could be easily changed. Storing these paths in persistent /// storage (i.e. game assets) is strongly discouraged. +/// the order of fields could be easily changed. Storing these paths in persistent +/// storage (i.e. game assets) is strongly discouraged. /// /// If the initial path element is a field of a struct, tuple struct, or tuple, /// the initial '.' may be omitted. From 9a14693a5de4ed86d6c64e3dd31db540fe762d3d Mon Sep 17 00:00:00 2001 From: James Liu Date: Mon, 16 May 2022 12:06:54 -0700 Subject: [PATCH 16/25] Update docs. Co-authored-by: Alice Cecile --- crates/bevy_reflect/src/path.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 834d9cff46055..709bd1e987d5a 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -161,7 +161,8 @@ impl FieldPath { Ok(Self(parts.into_boxed_slice())) } - /// Gets a read-only reference of given field. + /// Gets a read-only reference to a given field. + /// /// Returns an error if the path is invalid for the provided type. pub fn field<'r, 'p>( &'p self, From 40f81bde06e5da218a0bdc7e34a4f679a766e27d Mon Sep 17 00:00:00 2001 From: James Liu Date: Mon, 16 May 2022 12:07:02 -0700 Subject: [PATCH 17/25] Update docs. Co-authored-by: Alice Cecile --- crates/bevy_reflect/src/path.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 709bd1e987d5a..c2ff42b49b506 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -175,7 +175,8 @@ impl FieldPath { Ok(current) } - /// Gets a mutable reference of given field. + /// Gets a mutable reference to a given field. + /// /// Returns an error if the path is invalid for the provided type. pub fn field_mut<'r, 'p>( &'p mut self, From 01f87fbfcc2734e58ac3e18aa3a185087e4e4c48 Mon Sep 17 00:00:00 2001 From: james7132 Date: Thu, 26 May 2022 04:24:46 -0700 Subject: [PATCH 18/25] Use associated constants --- crates/bevy_reflect/src/path.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index c2ff42b49b506..5586d72b04d29 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -471,19 +471,19 @@ impl<'a> PathParser<'a> { } 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); } @@ -493,7 +493,7 @@ impl<'a> PathParser<'a> { // 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); @@ -542,7 +542,7 @@ impl<'a> PathParser<'a> { if !matches!(self.next_token(), Some(Token::CloseBracket)) { return Err(ReflectPathError::ExpectedToken { index: current_index, - token: "]", + token: Token::OPEN_BRACKET_STR, }); } @@ -550,7 +550,7 @@ impl<'a> PathParser<'a> { } Token::CloseBracket => Err(ReflectPathError::UnexpectedToken { index: current_index, - token: "]", + token: Token::CLOSE_BRACKET_STR, }), Token::Ident(value) => value .parse::() @@ -578,6 +578,15 @@ enum Token<'a> { 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 { From aa1124df4b6d5ee2c253c6d034eb573b8be80d5b Mon Sep 17 00:00:00 2001 From: james7132 Date: Thu, 26 May 2022 05:08:01 -0700 Subject: [PATCH 19/25] Add some doc strings --- crates/bevy_reflect/src/path.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 5586d72b04d29..890e30356f136 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -145,6 +145,8 @@ impl GetPath for dyn Reflect { } } +// This stores an access and a start index for the identity. The index is only really +// used for errors. /// A path to a field within a type. Can be used like [`GetPath`] functions to get /// references to the inner fields of a type. #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] @@ -241,6 +243,10 @@ impl fmt::Display for FieldPath { } } +/// A singular owned field access within a path. Can be applied +/// to a `dyn Reflect` to get a reference to the targetted field. +/// +/// A path is composed of multiple accesses in sequence. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] enum Access { Field(String), @@ -260,6 +266,11 @@ impl Access { } } +/// A singular borrowed field access within a path. Can be applied +/// to a `dyn Reflect` to get a reference to the targetted field. +/// +/// 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), From 3f00df76f914360283b4e2545e16ea307e7a1bce Mon Sep 17 00:00:00 2001 From: james7132 Date: Thu, 26 May 2022 05:13:47 -0700 Subject: [PATCH 20/25] Use the constants more --- crates/bevy_reflect/src/path.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 890e30356f136..0c1a6a233df4d 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -218,24 +218,24 @@ impl fmt::Display for FieldPath { match access { Access::Field(field) => { if idx != 0 { - f.write_str(".")?; + Token::DOT.fmt(f)?; } f.write_str(field.as_str())?; } Access::FieldIndex(index) => { - f.write_str("#")?; + Token::CROSSHATCH.fmt(f)?; index.fmt(f)?; } Access::TupleIndex(index) => { if idx != 0 { - f.write_str(".")?; + Token::DOT.fmt(f)?; } index.fmt(f)?; } Access::ListIndex(index) => { - f.write_str("[")?; + Token::OPEN_BRACKET.fmt(f)?; index.fmt(f)?; - f.write_str("]")?; + Token::CLOSE_BRACKET.fmt(f)?; } } } From 3b2ae9bfcbed7d6ccd132154f165f40a8be5ec04 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Fri, 20 Jan 2023 23:23:44 -0700 Subject: [PATCH 21/25] Add more array path tests --- crates/bevy_reflect/src/path.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 0c1a6a233df4d..93346ce9befe2 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -245,7 +245,7 @@ impl fmt::Display for FieldPath { /// A singular owned field access within a path. Can be applied /// to a `dyn Reflect` to get a reference to the targetted field. -/// +/// /// A path is composed of multiple accesses in sequence. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] enum Access { @@ -268,7 +268,7 @@ impl Access { /// A singular borrowed field access within a path. Can be applied /// to a `dyn Reflect` to get a reference to the targetted field. -/// +/// /// Does not own the backing store it's sourced from. For an owned /// version, you can convert one to an [`Access`]. #[derive(Debug)] @@ -614,6 +614,7 @@ mod tests { unit_variant: F, tuple_variant: F, struct_variant: F, + array: [i32; 3], } #[derive(Reflect)] @@ -707,6 +708,7 @@ mod tests { unit_variant: F::Unit, tuple_variant: F::Tuple(123, 321), struct_variant: F::Struct { value: 'm' }, + array: [86, 75, 309], }; let b = FieldPath::parse("w").unwrap(); @@ -720,6 +722,7 @@ mod tests { let j = FieldPath::parse("tuple_variant.1").unwrap(); let k = FieldPath::parse("struct_variant.value").unwrap(); let l = FieldPath::parse("struct_variant#0").unwrap(); + let m = FieldPath::parse("array[2]").unwrap(); for _ in 0..30 { assert_eq!(*b.get_field::(&a).unwrap(), 1); @@ -733,6 +736,7 @@ mod tests { assert_eq!(*j.get_field::(&a).unwrap(), 321); assert_eq!(*k.get_field::(&a).unwrap(), 'm'); assert_eq!(*l.get_field::(&a).unwrap(), 'm'); + assert_eq!(*m.get_field::(&a).unwrap(), 309); } } @@ -791,6 +795,7 @@ 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); @@ -806,6 +811,8 @@ mod tests { assert_eq!(*a.get_path::("struct_variant.value").unwrap(), 'm'); assert_eq!(*a.get_path::("struct_variant#0").unwrap(), 'm'); + assert_eq!(*a.get_path::("array[2]").unwrap(), 309); + *a.get_path_mut::("y[1].baz").unwrap() = 3.0; assert_eq!(a.y[1].baz, 3.0); From 99bc3120656af157f7d10ffdcdce5bee7092a5fe Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 21 Jan 2023 01:04:14 -0700 Subject: [PATCH 22/25] Update docs --- crates/bevy_reflect/src/path.rs | 222 +++++++++++++++++++++++++++----- 1 file changed, 188 insertions(+), 34 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 93346ce9befe2..36916aa67b9c0 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -45,32 +45,145 @@ pub enum ReflectPathError<'a> { InvalidDowncast, } -/// 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. /// -/// 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]` -/// - Field indexes within [`Struct`] can also be optionally used instead: `#0` for -/// the first field. This can speed up fetches at runtime (no string matching) -/// but can be much more fragile as code and string paths must be kept in sync since -/// the order of fields could be easily changed. Storing these paths in persistent -/// storage (i.e. game assets) is strongly discouraged. +/// Using these functions repeatedly with the same string requires parsing the string every time. +/// To avoid this cost, it's recommended to construct a [`FieldPath`] instead. /// -/// If the initial path element is a field of a struct, tuple struct, or tuple, -/// the initial '.' may be omitted. +/// # Syntax /// -/// 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`. +/// ## Structs /// -/// Using these functions repeatedly with the same string requires parsing the -/// string every time. To avoid this cost, construct a [`FieldPath`] instead. +/// Field paths for [`Struct`] items 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.get_path::(".value").unwrap(), &123); +/// // Access via field index +/// assert_eq!(my_struct.get_path::("#0").unwrap(), &123); +/// ``` +/// +/// ## Tuples and Tuple Structs +/// +/// [`Tuple`] and [`TupleStruct`] items 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.get_path::(".0").unwrap(), &123); +/// ``` +/// +/// ## Lists and Arrays +/// +/// [`List`] and [`Array`] items are accessed with brackets: `[0]`. +/// +/// ### Example +/// ``` +/// # use bevy_reflect::{GetPath}; +/// let my_list: Vec = vec![1, 2, 3]; +/// assert_eq!(my_list.get_path::("[2]").unwrap(), &3); +/// ``` +/// +/// ## Enums +/// +/// Pathing for [`Enum`] items 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.get_path::(".0").unwrap(), &true); +/// +/// let struct_variant = MyEnum::Struct { value: 123 }; +/// // Access via field name +/// assert_eq!(struct_variant.get_path::(".value").unwrap(), &123); +/// // Access via field index +/// assert_eq!(struct_variant.get_path::("#0").unwrap(), &123); +/// +/// // Error: Expected struct variant +/// assert!(matches!(tuple_variant.get_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.get_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`. /// @@ -88,6 +201,12 @@ pub trait GetPath { ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>>; /// Returns a statically typed 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 get_path<'r, 'p, T: Reflect>( &'r self, path: &'p str, @@ -98,8 +217,13 @@ pub trait GetPath { }) } - /// Returns a statically typed mutable reference to the value specified by - /// `path`. + /// 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 get_path_mut<'r, 'p, T: Reflect>( &'r mut self, path: &'p str, @@ -145,15 +269,29 @@ impl GetPath for dyn Reflect { } } -// This stores an access and a start index for the identity. The index is only really -// used for errors. -/// A path to a field within a type. Can be used like [`GetPath`] functions to get -/// references to the inner fields of a type. +/// A pre-parsed path to a field 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 FieldPath(Box<[(Access, usize)]>); +pub struct FieldPath( + /// 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 FieldPath { - /// Parses a [`FieldPath`] from a string. For the exact format, see [`GetPath`]. + /// Parses a [`FieldPath`] from a string. + /// + /// See [`GetPath`] for the exact path format. + /// /// Returns an error if the string does not represent a valid path to a field. pub fn parse(string: &str) -> Result> { let mut parts = Vec::new(); @@ -163,9 +301,11 @@ impl FieldPath { Ok(Self(parts.into_boxed_slice())) } - /// Gets a read-only reference to a given field. + /// Gets a read-only reference to the specified field on the given [`Reflect`] object. /// /// Returns an error if the path is invalid for the provided type. + /// + /// See [`field_mut`](Self::field_mut) for a typed version of this method. pub fn field<'r, 'p>( &'p self, root: &'r dyn Reflect, @@ -177,9 +317,11 @@ impl FieldPath { Ok(current) } - /// Gets a mutable reference to a given field. + /// Gets a mutable reference to the specified field on the given [`Reflect`] object. /// /// Returns an error if the path is invalid for the provided type. + /// + /// See [`get_field_mut`](Self::get_field_mut) for a typed version of this method. pub fn field_mut<'r, 'p>( &'p mut self, root: &'r mut dyn Reflect, @@ -191,6 +333,11 @@ impl FieldPath { Ok(current) } + /// Gets a typed, read-only reference to the specified field on the given [`Reflect`] object. + /// + /// Returns an error if the path is invalid for the provided type. + /// + /// See [`field`](Self::field) for an untyped version of this method. pub fn get_field<'r, 'p, T: Reflect>( &'p self, root: &'r dyn Reflect, @@ -201,6 +348,11 @@ impl FieldPath { }) } + /// Gets a typed, read-only reference to the specified field on the given [`Reflect`] object. + /// + /// Returns an error if the path is invalid for the provided type. + /// + /// See [`field_mut`](Self::field_mut) for an untyped version of this method. pub fn get_field_mut<'r, 'p, T: Reflect>( &'p mut self, root: &'r mut dyn Reflect, @@ -243,8 +395,9 @@ impl fmt::Display for FieldPath { } } -/// A singular owned field access within a path. Can be applied -/// to a `dyn Reflect` to get a reference to the targetted field. +/// A singular owned field access within a path. +/// +/// Can be applied to a `dyn Reflect` to get a reference to the targeted field. /// /// A path is composed of multiple accesses in sequence. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -266,11 +419,12 @@ impl Access { } } -/// A singular borrowed field access within a path. Can be applied -/// to a `dyn Reflect` to get a reference to the targetted field. +/// A singular borrowed field access within a path. +/// +/// Can be applied to a `dyn Reflect` to get a reference to the targeted field. /// -/// Does not own the backing store it's sourced from. For an owned -/// version, you can convert one to an [`Access`]. +/// 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), From bb7aa1fa206b5d290fe8df606a3a7a1c291a7f9c Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 21 Jan 2023 08:38:32 -0700 Subject: [PATCH 23/25] Rename FieldPath to ParsedPath --- crates/bevy_reflect/src/path.rs | 136 ++++++++++++++++---------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 36916aa67b9c0..8b10f757ce4cb 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -48,13 +48,13 @@ pub enum ReflectPathError<'a> { /// 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 [`FieldPath`] instead. +/// To avoid this cost, it's recommended to construct a [`ParsedPath`] instead. /// /// # Syntax /// /// ## Structs /// -/// Field paths for [`Struct`] items use the standard Rust field access syntax of +/// 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. @@ -89,7 +89,7 @@ pub enum ReflectPathError<'a> { /// /// ## Tuples and Tuple Structs /// -/// [`Tuple`] and [`TupleStruct`] items also follow a conventional Rust syntax. +/// [`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, @@ -107,7 +107,7 @@ pub enum ReflectPathError<'a> { /// /// ## Lists and Arrays /// -/// [`List`] and [`Array`] items are accessed with brackets: `[0]`. +/// [`List`] and [`Array`] elements are accessed with brackets: `[0]`. /// /// ### Example /// ``` @@ -118,7 +118,7 @@ pub enum ReflectPathError<'a> { /// /// ## Enums /// -/// Pathing for [`Enum`] items works a bit differently than in normal Rust. +/// 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. @@ -252,7 +252,7 @@ impl GetPath for dyn Reflect { fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { let mut current: &dyn Reflect = self; for (access, current_index) in PathParser::new(path) { - current = access?.read_field(current, current_index)?; + current = access?.read_element(current, current_index)?; } Ok(current) } @@ -263,13 +263,13 @@ impl GetPath for dyn Reflect { ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { let mut current: &mut dyn Reflect = self; for (access, current_index) in PathParser::new(path) { - current = access?.read_field_mut(current, current_index)?; + current = access?.read_element_mut(current, current_index)?; } Ok(current) } } -/// A pre-parsed path to a field within a type. +/// 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. @@ -277,7 +277,7 @@ impl GetPath for dyn Reflect { /// 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 FieldPath( +pub struct ParsedPath( /// This is the boxed slice of pre-parsed accesses. /// /// Each item in the slice contains the access along with the character @@ -287,12 +287,12 @@ pub struct FieldPath( Box<[(Access, usize)]>, ); -impl FieldPath { - /// Parses a [`FieldPath`] from a string. +impl ParsedPath { + /// Parses a [`ParsedPath`] from a string. /// /// See [`GetPath`] for the exact path format. /// - /// Returns an error if the string does not represent a valid path to a field. + /// Returns an error if the string does not represent a valid path to an element. pub fn parse(string: &str) -> Result> { let mut parts = Vec::new(); for (access, idx) in PathParser::new(string) { @@ -301,70 +301,70 @@ impl FieldPath { Ok(Self(parts.into_boxed_slice())) } - /// Gets a read-only reference to the specified field on the given [`Reflect`] object. + /// 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 [`field_mut`](Self::field_mut) for a typed version of this method. - pub fn field<'r, 'p>( + /// See [`element_mut`](Self::element_mut) for a typed version of this method. + pub fn 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_field(current, *current_index)?; + current = access.to_ref().read_element(current, *current_index)?; } Ok(current) } - /// Gets a mutable reference to the specified field on the given [`Reflect`] object. + /// 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 [`get_field_mut`](Self::get_field_mut) for a typed version of this method. - pub fn field_mut<'r, 'p>( + /// See [`get_element_mut`](Self::get_element_mut) for a typed version of this method. + pub fn 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_field_mut(current, *current_index)?; + current = access.to_ref().read_element_mut(current, *current_index)?; } Ok(current) } - /// Gets a typed, read-only reference to the specified field on the given [`Reflect`] object. + /// 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 [`field`](Self::field) for an untyped version of this method. - pub fn get_field<'r, 'p, T: Reflect>( + /// See [`element`](Self::element) for an untyped version of this method. + pub fn get_element<'r, 'p, T: Reflect>( &'p self, root: &'r dyn Reflect, ) -> Result<&'r T, ReflectPathError<'p>> { - self.field(root).and_then(|p| { + self.element(root).and_then(|p| { p.downcast_ref::() .ok_or(ReflectPathError::InvalidDowncast) }) } - /// Gets a typed, read-only reference to the specified field on the given [`Reflect`] object. + /// 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 [`field_mut`](Self::field_mut) for an untyped version of this method. - pub fn get_field_mut<'r, 'p, T: Reflect>( + /// See [`element_mut`](Self::element_mut) for an untyped version of this method. + pub fn get_element_mut<'r, 'p, T: Reflect>( &'p mut self, root: &'r mut dyn Reflect, ) -> Result<&'r mut T, ReflectPathError<'p>> { - self.field_mut(root).and_then(|p| { + self.element_mut(root).and_then(|p| { p.downcast_mut::() .ok_or(ReflectPathError::InvalidDowncast) }) } } -impl fmt::Display for FieldPath { +impl fmt::Display for ParsedPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for (idx, (access, _)) in self.0.iter().enumerate() { match access { @@ -395,9 +395,9 @@ impl fmt::Display for FieldPath { } } -/// A singular owned field access within a path. +/// A singular owned element access within a path. /// -/// Can be applied to a `dyn Reflect` to get a reference to the targeted field. +/// 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)] @@ -419,9 +419,9 @@ impl Access { } } -/// A singular borrowed field access within a path. +/// A singular borrowed element access within a path. /// -/// Can be applied to a `dyn Reflect` to get a reference to the targeted field. +/// 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`]. @@ -443,7 +443,7 @@ impl<'a> AccessRef<'a> { } } - fn read_field<'r>( + fn read_element<'r>( &self, current: &'r dyn Reflect, current_index: usize, @@ -531,7 +531,7 @@ impl<'a> AccessRef<'a> { } } - fn read_field_mut<'r>( + fn read_element_mut<'r>( &self, current: &'r mut dyn Reflect, current_index: usize, @@ -796,20 +796,20 @@ mod tests { } #[test] - fn field_path_parse() { + fn parsed_path_parse() { assert_eq!( - &*FieldPath::parse("w").unwrap().0, + &*ParsedPath::parse("w").unwrap().0, &[(Access::Field("w".to_string()), 1)] ); assert_eq!( - &*FieldPath::parse("x.foo").unwrap().0, + &*ParsedPath::parse("x.foo").unwrap().0, &[ (Access::Field("x".to_string()), 1), (Access::Field("foo".to_string()), 2) ] ); assert_eq!( - &*FieldPath::parse("x.bar.baz").unwrap().0, + &*ParsedPath::parse("x.bar.baz").unwrap().0, &[ (Access::Field("x".to_string()), 1), (Access::Field("bar".to_string()), 2), @@ -817,7 +817,7 @@ mod tests { ] ); assert_eq!( - &*FieldPath::parse("y[1].baz").unwrap().0, + &*ParsedPath::parse("y[1].baz").unwrap().0, &[ (Access::Field("y".to_string()), 1), (Access::ListIndex(1), 2), @@ -825,7 +825,7 @@ mod tests { ] ); assert_eq!( - &*FieldPath::parse("z.0.1").unwrap().0, + &*ParsedPath::parse("z.0.1").unwrap().0, &[ (Access::Field("z".to_string()), 1), (Access::TupleIndex(0), 2), @@ -833,14 +833,14 @@ mod tests { ] ); assert_eq!( - &*FieldPath::parse("x#0").unwrap().0, + &*ParsedPath::parse("x#0").unwrap().0, &[ (Access::Field("x".to_string()), 1), (Access::FieldIndex(0), 2), ] ); assert_eq!( - &*FieldPath::parse("x#0#1").unwrap().0, + &*ParsedPath::parse("x#0#1").unwrap().0, &[ (Access::Field("x".to_string()), 1), (Access::FieldIndex(0), 2), @@ -850,7 +850,7 @@ mod tests { } #[test] - fn field_path_get_field() { + fn parsed_path_get_field() { let a = A { w: 1, x: B { @@ -865,32 +865,32 @@ mod tests { array: [86, 75, 309], }; - let b = FieldPath::parse("w").unwrap(); - let c = FieldPath::parse("x.foo").unwrap(); - let d = FieldPath::parse("x.bar.baz").unwrap(); - let e = FieldPath::parse("y[1].baz").unwrap(); - let f = FieldPath::parse("z.0.1").unwrap(); - let g = FieldPath::parse("x#0").unwrap(); - let h = FieldPath::parse("x#1#0").unwrap(); - let i = FieldPath::parse("unit_variant").unwrap(); - let j = FieldPath::parse("tuple_variant.1").unwrap(); - let k = FieldPath::parse("struct_variant.value").unwrap(); - let l = FieldPath::parse("struct_variant#0").unwrap(); - let m = FieldPath::parse("array[2]").unwrap(); + 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.get_field::(&a).unwrap(), 1); - assert_eq!(*c.get_field::(&a).unwrap(), 10); - assert_eq!(*d.get_field::(&a).unwrap(), 3.14); - assert_eq!(*e.get_field::(&a).unwrap(), 2.0); - assert_eq!(*f.get_field::(&a).unwrap(), 42); - assert_eq!(*g.get_field::(&a).unwrap(), 10); - assert_eq!(*h.get_field::(&a).unwrap(), 3.14); - assert_eq!(*i.get_field::(&a).unwrap(), F::Unit); - assert_eq!(*j.get_field::(&a).unwrap(), 321); - assert_eq!(*k.get_field::(&a).unwrap(), 'm'); - assert_eq!(*l.get_field::(&a).unwrap(), 'm'); - assert_eq!(*m.get_field::(&a).unwrap(), 309); + assert_eq!(*b.get_element::(&a).unwrap(), 1); + assert_eq!(*c.get_element::(&a).unwrap(), 10); + assert_eq!(*d.get_element::(&a).unwrap(), 3.14); + assert_eq!(*e.get_element::(&a).unwrap(), 2.0); + assert_eq!(*f.get_element::(&a).unwrap(), 42); + assert_eq!(*g.get_element::(&a).unwrap(), 10); + assert_eq!(*h.get_element::(&a).unwrap(), 3.14); + assert_eq!(*i.get_element::(&a).unwrap(), F::Unit); + assert_eq!(*j.get_element::(&a).unwrap(), 321); + assert_eq!(*k.get_element::(&a).unwrap(), 'm'); + assert_eq!(*l.get_element::(&a).unwrap(), 'm'); + assert_eq!(*m.get_element::(&a).unwrap(), 309); } } From 8ac1f0f01585e0eb7da4d38a11f0a50da8c143a1 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 21 Jan 2023 08:53:44 -0700 Subject: [PATCH 24/25] Rename GetPath/ParsedPath methods --- crates/bevy_reflect/src/lib.rs | 10 +- crates/bevy_reflect/src/path.rs | 162 +++++++++++++++++--------------- 2 files changed, 92 insertions(+), 80 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 8b10f757ce4cb..44f6266f787b1 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -82,9 +82,9 @@ pub enum ReflectPathError<'a> { /// /// let my_struct = MyStruct { value: 123 }; /// // Access via field name -/// assert_eq!(my_struct.get_path::(".value").unwrap(), &123); +/// assert_eq!(my_struct.path::(".value").unwrap(), &123); /// // Access via field index -/// assert_eq!(my_struct.get_path::("#0").unwrap(), &123); +/// assert_eq!(my_struct.path::("#0").unwrap(), &123); /// ``` /// /// ## Tuples and Tuple Structs @@ -102,7 +102,7 @@ pub enum ReflectPathError<'a> { /// struct MyTupleStruct(u32); /// /// let my_tuple_struct = MyTupleStruct(123); -/// assert_eq!(my_tuple_struct.get_path::(".0").unwrap(), &123); +/// assert_eq!(my_tuple_struct.path::(".0").unwrap(), &123); /// ``` /// /// ## Lists and Arrays @@ -113,7 +113,7 @@ pub enum ReflectPathError<'a> { /// ``` /// # use bevy_reflect::{GetPath}; /// let my_list: Vec = vec![1, 2, 3]; -/// assert_eq!(my_list.get_path::("[2]").unwrap(), &3); +/// assert_eq!(my_list.path::("[2]").unwrap(), &3); /// ``` /// /// ## Enums @@ -144,16 +144,16 @@ pub enum ReflectPathError<'a> { /// } /// /// let tuple_variant = MyEnum::Tuple(true); -/// assert_eq!(tuple_variant.get_path::(".0").unwrap(), &true); +/// assert_eq!(tuple_variant.path::(".0").unwrap(), &true); /// /// let struct_variant = MyEnum::Struct { value: 123 }; /// // Access via field name -/// assert_eq!(struct_variant.get_path::(".value").unwrap(), &123); +/// assert_eq!(struct_variant.path::(".value").unwrap(), &123); /// // Access via field index -/// assert_eq!(struct_variant.get_path::("#0").unwrap(), &123); +/// assert_eq!(struct_variant.path::("#0").unwrap(), &123); /// /// // Error: Expected struct variant -/// assert!(matches!(tuple_variant.get_path::(".value"), Err(_))); +/// assert!(matches!(tuple_variant.path::(".value"), Err(_))); /// ``` /// /// # Chaining @@ -173,7 +173,7 @@ pub enum ReflectPathError<'a> { /// value: vec![None, None, Some(123)], /// }; /// assert_eq!( -/// my_struct.get_path::(".value[2].0").unwrap(), +/// my_struct.path::(".value[2].0").unwrap(), /// &123, /// ); /// ``` @@ -188,14 +188,17 @@ 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>>; @@ -207,11 +210,8 @@ pub trait GetPath { /// (which may be the case when using dynamic types like [`DynamicStruct`]). /// /// [`DynamicStruct`]: crate::DynamicStruct - fn get_path<'r, 'p, T: Reflect>( - &'r self, - path: &'p str, - ) -> Result<&'r T, ReflectPathError<'p>> { - self.path(path).and_then(|p| { + 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) }) @@ -224,11 +224,11 @@ pub trait GetPath { /// (which may be the case when using dynamic types like [`DynamicStruct`]). /// /// [`DynamicStruct`]: crate::DynamicStruct - fn get_path_mut<'r, 'p, T: Reflect>( + 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) }) @@ -236,20 +236,26 @@ 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>> { + fn reflect_path<'r, 'p>( + &'r self, + path: &'p str, + ) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { let mut current: &dyn Reflect = self; for (access, current_index) in PathParser::new(path) { current = access?.read_element(current, current_index)?; @@ -257,7 +263,7 @@ impl GetPath for dyn Reflect { 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>> { @@ -305,8 +311,8 @@ impl ParsedPath { /// /// 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 element<'r, 'p>( + /// 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>> { @@ -321,8 +327,8 @@ impl ParsedPath { /// /// Returns an error if the path is invalid for the provided type. /// - /// See [`get_element_mut`](Self::get_element_mut) for a typed version of this method. - pub fn element_mut<'r, 'p>( + /// 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>> { @@ -337,12 +343,12 @@ impl ParsedPath { /// /// Returns an error if the path is invalid for the provided type. /// - /// See [`element`](Self::element) for an untyped version of this method. - pub fn get_element<'r, 'p, T: Reflect>( + /// 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.element(root).and_then(|p| { + self.reflect_element(root).and_then(|p| { p.downcast_ref::() .ok_or(ReflectPathError::InvalidDowncast) }) @@ -352,12 +358,12 @@ impl ParsedPath { /// /// Returns an error if the path is invalid for the provided type. /// - /// See [`element_mut`](Self::element_mut) for an untyped version of this method. - pub fn get_element_mut<'r, 'p, T: Reflect>( + /// 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.element_mut(root).and_then(|p| { + self.reflect_element_mut(root).and_then(|p| { p.downcast_mut::() .ok_or(ReflectPathError::InvalidDowncast) }) @@ -879,18 +885,18 @@ mod tests { let m = ParsedPath::parse("array[2]").unwrap(); for _ in 0..30 { - assert_eq!(*b.get_element::(&a).unwrap(), 1); - assert_eq!(*c.get_element::(&a).unwrap(), 10); - assert_eq!(*d.get_element::(&a).unwrap(), 3.14); - assert_eq!(*e.get_element::(&a).unwrap(), 2.0); - assert_eq!(*f.get_element::(&a).unwrap(), 42); - assert_eq!(*g.get_element::(&a).unwrap(), 10); - assert_eq!(*h.get_element::(&a).unwrap(), 3.14); - assert_eq!(*i.get_element::(&a).unwrap(), F::Unit); - assert_eq!(*j.get_element::(&a).unwrap(), 321); - assert_eq!(*k.get_element::(&a).unwrap(), 'm'); - assert_eq!(*l.get_element::(&a).unwrap(), 'm'); - assert_eq!(*m.get_element::(&a).unwrap(), 309); + 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); } } @@ -907,10 +913,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] @@ -926,14 +932,14 @@ 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] @@ -952,29 +958,29 @@ mod tests { 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.get_path::("x#0").unwrap(), 10); - assert_eq!(*a.get_path::("x#1#0").unwrap(), 3.14); + 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.get_path::("struct_variant#0").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'); - assert_eq!(*a.get_path::("array[2]").unwrap(), 309); + assert_eq!(*a.path::("array[2]").unwrap(), 309); - *a.get_path_mut::("y[1].baz").unwrap() = 3.0; + *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" @@ -982,27 +988,27 @@ mod tests { ); assert_eq!( - a.path("unit_variant.0").err().unwrap(), + 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(_)) )); } From 93c35a40e40d06c1056e3e40d2f898f381b37129 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 21 Jan 2023 21:31:47 -0700 Subject: [PATCH 25/25] Improve docs for ParsedPath::parse --- crates/bevy_reflect/src/path.rs | 44 +++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 44f6266f787b1..e43a45fea31ee 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -296,9 +296,49 @@ pub struct ParsedPath( impl ParsedPath { /// Parses a [`ParsedPath`] from a string. /// - /// See [`GetPath`] for the exact path format. - /// /// 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) {