Skip to content
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

Add a pre-parsed FieldPath for faster repeated uses of GetPath calls #4081

Closed
wants to merge 21 commits into from

Conversation

james7132
Copy link
Member

@james7132 james7132 commented Mar 2, 2022

Objective

Fixes #4080. Implement a pre-parsed version of GetPath functions.

Solution

Implement FieldPath a struct that parses a field path ahead of time and caches the sequence of field accesses to avoid string parsing when fetching a field. Added a few additional changes:

  • Clean up some of the duplicated code in that file.
  • Extended the string format to allow for specifying normal struct field indexes instead of normal names. This can be faster to use for FieldPath lookups, but it can be much more fragile when serialized to assets. Can be used to transform string lookups into index lookups static type information.

Future Directions

If we get non-reference bound type information, we can further optimize this by looking up field indexes during parsing using a TypeRegistry.

@github-actions github-actions bot added the S-Needs-Triage This issue needs to be labelled label Mar 2, 2022
@james7132 james7132 added A-Animation Make things move and change over time A-Reflection Runtime information about types C-Feature A new feature, making something new possible and removed S-Needs-Triage This issue needs to be labelled labels Mar 2, 2022
@james7132 james7132 added the C-Performance A change motivated by improving speed, memory usage or compile times label Mar 2, 2022
@james7132 james7132 changed the title Parse path Add FieldPath, a pre-parsed field path for faster repeated uses of GetPath Mar 2, 2022
@james7132 james7132 changed the title Add FieldPath, a pre-parsed field path for faster repeated uses of GetPath Add a pre-parsed FieldPath for faster repeated uses of GetPath calls Mar 2, 2022
Copy link
Member

@MrGVSV MrGVSV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall. I think it would nice to add some tests for FieldPath, though, to make sure it works properly (especially after multiple uses) in the future.

crates/bevy_reflect/src/path.rs Outdated Show resolved Hide resolved
crates/bevy_reflect/src/path.rs Show resolved Hide resolved
crates/bevy_reflect/src/path.rs Outdated Show resolved Hide resolved
james7132 and others added 3 commits March 7, 2022 21:24
Co-authored-by: MrGVSV <49806985+MrGVSV@users.noreply.github.com>
crates/bevy_reflect/src/path.rs Show resolved Hide resolved
crates/bevy_reflect/src/path.rs Outdated Show resolved Hide resolved
crates/bevy_reflect/src/path.rs Show resolved Hide resolved
@alice-i-cecile alice-i-cecile added C-Code-Quality A section of code that is hard to understand or change and removed C-Feature A new feature, making something new possible labels Apr 25, 2022
Ok(current)
}

pub fn get_field<'r, 'p, T: Reflect>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs docs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Externally, it's very hard to tell the difference between the field method and the get_field. I would have expected this to be the fallible version based on the name, but they both return results and have the same type signature.

@DJMcNab comments that get_field appears to do automatic downcasting.

The naming scheme needs to be fixed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mirrors the same API for GetPath, if we update it here, we should update it in both places. The main difference is that one has a concrete type and the other returns a dyn Reflect reference.

})
}

pub fn get_field_mut<'r, 'p, T: Reflect>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs.

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the direction here, but have some docs and code quality nits that should be addressed.

Ok(current)
}

pub fn get_field<'r, 'p, T: Reflect>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Externally, it's very hard to tell the difference between the field method and the get_field. I would have expected this to be the fallible version based on the name, but they both return results and have the same type signature.

@DJMcNab comments that get_field appears to do automatic downcasting.

The naming scheme needs to be fixed.

});
impl fmt::Display for FieldPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (idx, (access, _)) in self.0.iter().enumerate() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be pulled out into a utility function and tested IMO.

crates/bevy_reflect/src/path.rs Show resolved Hide resolved
crates/bevy_reflect/src/path.rs Show resolved Hide resolved
crates/bevy_reflect/src/path.rs Outdated Show resolved Hide resolved
james7132 and others added 7 commits May 16, 2022 12:06
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
@Weibye Weibye added the S-Adopt-Me The original PR author has no intent to complete this work. Pick me up! label Aug 10, 2022
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would still like more docs, but I won't block on them.

@alice-i-cecile
Copy link
Member

@james7132 @MrGVSV now that #6560 is merged, we should clean this up and merge it as James pointed out there.

@MrGVSV
Copy link
Member

MrGVSV commented Jan 12, 2023

bors try

@bors
Copy link
Contributor

bors bot commented Jan 12, 2023

try

Merge conflict.

@MrGVSV
Copy link
Member

MrGVSV commented Jan 12, 2023

@james7132 if you're too busy to do so, I can adopt this PR, fix the merge conflicts, and address any remaining issues/reviews. Just let me know! :)

@james7132
Copy link
Member Author

If you could ,that'd be great. My focus has been elsewhere.

@MrGVSV
Copy link
Member

MrGVSV commented Jan 21, 2023

Closing this as it's now been adopted by #7321.

@MrGVSV MrGVSV closed this Jan 21, 2023
bors bot pushed a commit that referenced this pull request Jan 22, 2023
# Objective

> ℹ️ **This is an adoption of #4081 by @james7132**

Fixes #4080.

Provide a way to pre-parse reflection paths so as to avoid having to parse at each call to `GetPath::path` (or similar method).

## Solution

Adds the `ParsedPath` struct (named `FieldPath` in the original PR) that parses and caches the sequence of accesses to a reflected element. This is functionally similar to the `GetPath` trait, but removes the need to parse an unchanged path more than once.

### Additional Changes

Included in this PR from the original is cleaner code as well as the introduction of a new pathing operation: field access by index. This allows struct and struct variant fields to be accessed in a more performant (albeit more fragile) way if needed. This operation is faster due to not having to perform string matching. As an example, if we wanted the third field on a struct, we'd write `#2`—where `#` denotes indexed access and `2` denotes the desired field index.

This PR also contains improved documentation for `GetPath` and friends, including renaming some of the methods to be more clear to the end-user with a reduced risk of getting them mixed up.

### Future Work

There are a few things that could be done as a separate PR (order doesn't matter— they could be followup PRs or done in parallel). These are:

- [x] ~~Add support for `Tuple`. Currently, we hint that they work but they do not.~~ See #7324
- [ ] Cleanup `ReflectPathError`. I think it would be nicer to give `ReflectPathError` two variants: `ReflectPathError::ParseError` and `ReflectPathError::AccessError`, with all current variants placed within one of those two. It's not obvious when one might expect to receive one type of error over the other, so we can help by explicitly categorizing them.

---

## Changelog

- Cleaned up `GetPath` logic
- Added `ParsedPath` for cached reflection paths
- Added new reflection path syntax: struct field access by index (example syntax: `foo#1`)
- Renamed methods on `GetPath`:
  - `path` -> `reflect_path`
  - `path_mut` -> `reflect_path_mut`
  - `get_path` -> `path`
  - `get_path_mut` -> `path_mut`

## Migration Guide

`GetPath` methods have been renamed according to the following:
- `path` -> `reflect_path`
- `path_mut` -> `reflect_path_mut`
- `get_path` -> `path`
- `get_path_mut` -> `path_mut`


Co-authored-by: Gino Valente <gino.valente.code@gmail.com>
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
# Objective

> ℹ️ **This is an adoption of bevyengine#4081 by @james7132**

Fixes bevyengine#4080.

Provide a way to pre-parse reflection paths so as to avoid having to parse at each call to `GetPath::path` (or similar method).

## Solution

Adds the `ParsedPath` struct (named `FieldPath` in the original PR) that parses and caches the sequence of accesses to a reflected element. This is functionally similar to the `GetPath` trait, but removes the need to parse an unchanged path more than once.

### Additional Changes

Included in this PR from the original is cleaner code as well as the introduction of a new pathing operation: field access by index. This allows struct and struct variant fields to be accessed in a more performant (albeit more fragile) way if needed. This operation is faster due to not having to perform string matching. As an example, if we wanted the third field on a struct, we'd write `bevyengine#2`—where `#` denotes indexed access and `2` denotes the desired field index.

This PR also contains improved documentation for `GetPath` and friends, including renaming some of the methods to be more clear to the end-user with a reduced risk of getting them mixed up.

### Future Work

There are a few things that could be done as a separate PR (order doesn't matter— they could be followup PRs or done in parallel). These are:

- [x] ~~Add support for `Tuple`. Currently, we hint that they work but they do not.~~ See bevyengine#7324
- [ ] Cleanup `ReflectPathError`. I think it would be nicer to give `ReflectPathError` two variants: `ReflectPathError::ParseError` and `ReflectPathError::AccessError`, with all current variants placed within one of those two. It's not obvious when one might expect to receive one type of error over the other, so we can help by explicitly categorizing them.

---

## Changelog

- Cleaned up `GetPath` logic
- Added `ParsedPath` for cached reflection paths
- Added new reflection path syntax: struct field access by index (example syntax: `foo#1`)
- Renamed methods on `GetPath`:
  - `path` -> `reflect_path`
  - `path_mut` -> `reflect_path_mut`
  - `get_path` -> `path`
  - `get_path_mut` -> `path_mut`

## Migration Guide

`GetPath` methods have been renamed according to the following:
- `path` -> `reflect_path`
- `path_mut` -> `reflect_path_mut`
- `get_path` -> `path`
- `get_path_mut` -> `path_mut`


Co-authored-by: Gino Valente <gino.valente.code@gmail.com>
@james7132 james7132 deleted the parse-path branch March 14, 2023 04:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Animation Make things move and change over time A-Reflection Runtime information about types C-Code-Quality A section of code that is hard to understand or change C-Performance A change motivated by improving speed, memory usage or compile times S-Adopt-Me The original PR author has no intent to complete this work. Pick me up!
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Pre-parsed paths for GetPath functions
4 participants