-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor path
module of bevy_reflect
#8887
Conversation
I like the split a lot, and think this is an improvement in terms of code organization. |
# Objective `ParsedPath` does not need to be mut to access a field of a `Reflect`. Be that access mutable or not. Yet `element_mut` requires a mutable borrow on `self`. ## Solution - Make `element_mut` take a `&self` over a `&mut self`. #8887 fixes this, but this is a major limitation in the API and I'd rather see it merged before 0.11. --- ## Changelog - `ParsedPath::element_mut` and `ParsedPath::reflect_element_mut` now accept a non-mutable `ParsedPath` (only the accessed `Reflect` needs to be mutable)
@alice-i-cecile Those types are only public because they are part of I created a wrapper type so that I don't want to document them, it's difficult to come up with a good vocabulary, and it would require renaming every types in the module. And all of this because of fields of an error value. |
Sounds good, I think that's a sensible solution <3 |
# Objective `ParsedPath` does not need to be mut to access a field of a `Reflect`. Be that access mutable or not. Yet `element_mut` requires a mutable borrow on `self`. ## Solution - Make `element_mut` take a `&self` over a `&mut self`. bevyengine#8887 fixes this, but this is a major limitation in the API and I'd rather see it merged before 0.11. --- ## Changelog - `ParsedPath::element_mut` and `ParsedPath::reflect_element_mut` now accept a non-mutable `ParsedPath` (only the accessed `Reflect` needs to be mutable)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome job! This is much cleaner!
Just some nits, but overall looks good!
pub(super) enum Type { | ||
Struct, | ||
TupleStruct, | ||
Tuple, | ||
List, | ||
Array, | ||
Map, | ||
Enum, | ||
Value, | ||
Unit, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty similar to this code from #7075 and is something I think deserves its own small PR due to how much it seems to want to crop up in these code de-duplications.
bevy/crates/bevy_reflect/src/reflect.rs
Lines 14 to 33 in 7974319
/// A simple enumeration of [kinds](ReflectKind) of type, without any associated object. | |
/// | |
/// All types implementing [`Reflect`] are categorized into "kinds". They help to group types that | |
/// behave similarly and provide methods specific to its kind. These kinds directly correspond to | |
/// the traits [`Struct`], [`TupleStruct`], [`Tuple`], [`List`], [`Array`], [`Map`] and [`Enum`]; | |
/// which means that a type implementing any one of the above traits will be of the corresponding | |
/// kind. All the remaining types will be `ReflectKind::Value`. | |
/// | |
/// A `ReflectKind` is obtained via [`Reflect::reflect_kind`]. | |
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] | |
pub enum ReflectKind { | |
Struct, | |
TupleStruct, | |
Tuple, | |
List, | |
Array, | |
Map, | |
Enum, | |
Value, | |
} |
The only difference is that your code here includes a Unit
variant (not sure how important it is to keep or not).
And I bring this up more as a note rather than something this PR needs to be blocked on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'd really like a TypeShape
of sort. I think I copied the ReflectRef definition and removed the values from the variants to make the Type
enum. I added Unit
later to account for mismatched enum variants (Unit
is the variant without value).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah and actually I wonder if TypeShape
is a clearer terminology. We'll probably explore this more in a future PR.
``` field[0].foo.bar ``` vs ``` .field[0].foo.bar ``` - Both are valid as input for parsing - The one with the leading dot is more consistant, as in the current implementation, the hatch will be present if the leading access is a field index. - IMO having the leading dot demonstrates better we are accessing the field `field`, rather than index 0 of variable `field`. - The logic for displaying with the leading dot is simpler, we don't need to discriminate the first element. Also implement Display for Access.
This removes 8 bytes to the size of `Access`.
The root doc for bevy_reflect doesn't mention `GetPath`. It's a fairly useful featuree and I've seen people be surprised to learn it exists.
This commit: - Splits the `path.rs` module of `bevy_reflect` in two. the `Access` structs are now defined in their own submodules - Revamps error handling - Separate Parse error from access errors - rename "index" to offset. "offset" is more commonly used to designate parsing position, and it can't be confused with the other kind of indexing done in this module - Instead of having one error variant per possible mismatching type, create a single error with a "expected" and "actual" type. - The error values now contain more details about the nature of the error. - Refactor the `read_element{_mut}` methods - They are now about 70 lines, while previously they were 188 lines - They are much more readable now. - Rename them to `element{_mut}`, since the "read" is a bit misleading. - They accept a `self` rather than `&self`, since AccessRef is Copy now. - We also rewrite Display for Access in term of write! rather than sequential write.fmt() - Rename `to_ref` to `as_ref` - Make AccessRef Copy: because it's a very small type. The downside is that it doesn't benefit from niching in the error type anymore.
Co-authored-by: Alice Cecile <[email protected]>
Co-authored-by: Gino Valente <[email protected]>
@MrGVSV I found a nice way to merge Also thank you for the review, it helped improve the code a lot I think. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MrGVSV I found a nice way to merge
Access
andAccessRef
. According tocargo asm
, it is also a bit faster, and we also get a bonusparse_static
method that removes all but one allocation when buildingParsedPath
!Also thank you for the review, it helped improve the code a lot I think.
Awesome! Glad it worked out and great job cleaning this up! 😄
)] | ||
Access { ty: TypeShape, access: Access<'a> }, | ||
|
||
#[error("invalid type shape: expected {expected} but found a reflect {actual}")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replying to this comment:
yeah, but
Type
here is not really a "type" but a "type shape", The error message would read as follow: "expected struct but found tuple". I don't think there is such a thing as a "struct-like" it's either a struct or not a struct.
Well I think it would be more useful for TypeShape::Tuple
and TypeShape::List
, since those also work for tuple structs and arrays, respectively. It's not required we use -like
haha, I just think it would be clearer to users that we're not expecting them to pass only Struct
, Tuple
, and List
types.
# Objective - Follow up to #8887 - The parsing code in `bevy_reflect/src/path/mod.rs` could also do with some cleanup ## Solution - Create the `parse.rs` module, move all parsing code to this module - The parsing errors also now keep track of the whole parsed string, and are much more fine-grained ### Detailed changes - Move `PathParser` to `parse.rs` submodule - Rename `token_to_access` to `access_following` (yep, goes from 132 lines to 16) - Move parsing tests into the `parse.rs` file
Objective
path
module was getting fairly large.AccessRef::read_element
and mut equivalent was very complex and difficult to understand.ReflectPathError
had a lot of variants, and was difficult to read.Solution
access
now has its own moduleread_element
methods, they were ~200 lines long, they are now ~70 lines long — I didn't change any of the logic. It's really just the same code, but error handling is separated.ReflectPathError
errorAccessRef
andAccess
Fully detailed change list
Display
impl ofParsedPath
now includes prefix dots — this allows simplifying its implementation, and IMO.path.to.field
is a better way to express a "path" thanpath.to.field
which could suggest we are reading theto
field of a variable namedpath
Access
andAccessRef
, using aCow<'a, str>
. Generated code seems to agree with this decision (ParsedPath::parse
sheds 5% of instructions)Access::as_ref
since there is no such thing as anAccessRef
anymore.AccessRef::to_owned
intoAccessRef::into_owned()
since it takes ownership ofself
now.parse_static
that doesn't allocate new strings for named fields!bevy_reflect
crate root doc — I saw a few people that weren't aware of path reflection, so I thought it was pertinent to add it to the root docindex
tooffset
when it refers to offset in the path string — There is no more confusion with the other kind of indices in this context, also it's a common naming convention for parsing.read_element
methods toelement
— shorter, but alsoread_element_mut
was a fairly poor nameGetPath
trait methods.Change log
ParsedPath::parse_static
method, avoids allocating when parsing&'static str
.Migration Guide
If you were matching on the
Err(ReflectPathError)
value returned byGetPath
andParsedPath
methods, now only the parse-related errors and the offset are publicly accessible. You can always use thefmt::Display
to get a clear error message, but if you need programmatic access to the error types, please open an issue.