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

adding reflection for Cow<'static, [T]> #7454

Merged

Conversation

EliasPrescott
Copy link
Contributor

Objective

Solution

  • Implementing Reflect, Typed, GetTypeRegistration, and FromReflect for Cow<'static, [T]>

Notes

I have not used bevy_reflection much yet, so I may not fully understand all the use cases. This is also my first attempt at contributing, so I would appreciate any feedback or recommendations for changes. I tried to add cases for using Cow<'static, str> and Cow<'static, [u8]> to some of the bevy_reflect tests, but I can't guarantee those tests are comprehensive enough.

Verified

This commit was signed with the committer’s verified signature.
JamesNK James Newton-King
@github-actions
Copy link
Contributor

github-actions bot commented Feb 1, 2023

Welcome, new contributor!

Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨

@MrGVSV MrGVSV added C-Usability A targeted quality-of-life change that makes Bevy easier to use A-Reflection Runtime information about types labels Feb 1, 2023
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.

Welcome and thank you for your contribution @EliasPrescott (especially for taking the time to add tests)! Left a few comments 🙂

@@ -1039,6 +1039,130 @@ impl FromReflect for Cow<'static, str> {
}
}

impl<T: Clone + std::marker::Sync + std::marker::Send + std::hash::Hash + std::cmp::PartialEq>
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I don't think we need to fully qualify these since they're already in-scope (same for the other impls). By pure convention, I think we really only qualify std::any::type_name.

Suggested change
impl<T: Clone + std::marker::Sync + std::marker::Send + std::hash::Hash + std::cmp::PartialEq>
impl<T: Clone + Sync + Send + Hash + PartialEq>

Comment on lines 1091 to 1101
fn reflect_ref(&self) -> ReflectRef {
ReflectRef::Value(self)
}

fn reflect_mut(&mut self) -> ReflectMut {
ReflectMut::Value(self)
}

fn reflect_owned(self: Box<Self>) -> ReflectOwned {
ReflectOwned::Value(self)
}
Copy link
Member

Choose a reason for hiding this comment

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

I think we should consider making this implement Array and return Reflect***::Array instead. A slice acts more like an array than a pure value (you could argue that str should also work like this, but I think that's a special enough case).

This also means we should be able to replace the Clone, Hash, and PartialEq bounds on T with a simple Reflect bound (and FromReflect for the FromReflect impl). I'm not 100% sure if doing that will work as easily as I'm making it sound haha, but I believe that going the Array route is probably the better option.

Verified

This commit was signed with the committer’s verified signature.
JamesNK James Newton-King

Verified

This commit was signed with the committer’s verified signature.
JamesNK James Newton-King
@@ -1039,6 +1039,131 @@ impl FromReflect for Cow<'static, str> {
}
}

impl<T: FromReflect + Clone + Send + Sync> Array for Cow<'static, [T]> {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think the Clone bound is necessary (for any of the impls). I'd recommend taking a look at the trait implementations for [T; N].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought Clone would be necessary, because otherwise I get the error: [T]: ToOwned is not satisfied in Cow<'static, [T]>.

Here are the docs on Cow where it mentions the ToOwned bound. I would like to remove the Clone bounds if possible, but that's the only way I've been able to sneak past the type checker so far.

Copy link
Contributor

Choose a reason for hiding this comment

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

Are you sure that the Send and Sync bounds are necessary?

Copy link
Member

Choose a reason for hiding this comment

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

Are you sure that the Send and Sync bounds are necessary?

Good point, they should be added by the FromReflect bound already.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing that out. I removed them in the latest commit. I think I added them before the FromReflect bound was there and I didn't realize they were no longer needed.

impl<T: FromReflect + Clone + Send + Sync> Typed for Cow<'static, [T]> {
fn type_info() -> &'static TypeInfo {
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
CELL.get_or_set(|| TypeInfo::Array(ArrayInfo::new::<Self, T>(0)))
Copy link
Member

Choose a reason for hiding this comment

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

Hm, it's kinda weird that we'd set the capacity to 0 here. I wonder if there's a better way of doing this? 🤔

Copy link
Contributor Author

@EliasPrescott EliasPrescott Feb 1, 2023

Choose a reason for hiding this comment

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

Yeah I was going to ask about that. I thought a slice type didn't have a capacity, unless we consider the hardware's max isize as a capacity.

Trying to implement this almost made me feel like it'd be more natural to be returning a Reflect***::List instead of the Array case. I haven't tried that yet, but that could keep us from deciding on a random capacity.

Copy link
Member

Choose a reason for hiding this comment

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

Can you insert/remove data on a slice? If so then yeah List would be better. Otherwise, I’m not sure of the best approach...

Copy link
Contributor

Choose a reason for hiding this comment

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

You can't insert/remove data on a regular slice, but you can on a Cow slice by doing a to_mut and then inserting/removing.

Copy link
Contributor

Choose a reason for hiding this comment

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

Basically a Cow<'static, [T]> is a Vec<T> that can optionally store ALL of its data in static memory. Therefore it's closer to being a Vec<T> than [T; N], so it should be treated as a List in reflection.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ItsDoot I added your implementation of List for Cow<'static, [T]> from #7455 and switched the Array reflections to use the List implementation. By the way, I'm sorry if I hijacked your issue. I should have left a comment asking to claim it first.

Verified

This commit was signed with the committer’s verified signature.
JamesNK James Newton-King

Verified

This commit was signed with the committer’s verified signature.
JamesNK James Newton-King
Co-authored-by: MinerSebas <66798382+MinerSebas@users.noreply.github.com>
Copy link
Contributor

@ItsDoot ItsDoot left a comment

Choose a reason for hiding this comment

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

Looks fine overall, just one major issue and a few nits.

}

fn drain(self: Box<Self>) -> Vec<Box<dyn Reflect>> {
self.iter().map(Reflect::clone_value).collect()
Copy link
Contributor

Choose a reason for hiding this comment

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

You can save it from doing an unnecessary clone if you have a Cow::Owned by switching this to self.into_owned().into_iter(). That would only clone the data if it's currently borrowed, otherwise it would reuse the owned data. Although that currently triggers a false-positive in clippy, so it will need an #[allow] tag added.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing that out! Also, thank you for the allow tag idea. I was having lots of trouble getting around that Clippy lint.

@@ -1039,6 +1039,131 @@ impl FromReflect for Cow<'static, str> {
}
}

impl<T: FromReflect + Clone + Send + Sync> Array for Cow<'static, [T]> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you sure that the Send and Sync bounds are necessary?

impl<T: FromReflect + Clone + Send + Sync> Typed for Cow<'static, [T]> {
fn type_info() -> &'static TypeInfo {
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
CELL.get_or_set(|| TypeInfo::Array(ArrayInfo::new::<Self, T>(0)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Basically a Cow<'static, [T]> is a Vec<T> that can optionally store ALL of its data in static memory. Therefore it's closer to being a Vec<T> than [T; N], so it should be treated as a List in reflection.

Copy link
Contributor

@ItsDoot ItsDoot left a comment

Choose a reason for hiding this comment

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

One more.

}
}

impl<T: FromReflect + Clone + Send + Sync> GetTypeRegistration for Cow<'static, [T]> {
Copy link
Contributor

Choose a reason for hiding this comment

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

ReflectDeserialize and ReflectSerialize require that the type implements Deserialize and Serialize, respectively.

Suggested change
impl<T: FromReflect + Clone + Send + Sync> GetTypeRegistration for Cow<'static, [T]> {
impl<'de, T: FromReflect + Clone + Serialize + Deserialize<'de>> GetTypeRegistration for Cow<'static, [T]> {

Copy link
Contributor

Choose a reason for hiding this comment

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

However... what if we want to be able to reflect one that isn't serializable? Now that's not possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added the Deserialize and Serialize trait bounds. Thankfully, I had just read this post that talks about higher-ranked trait bounds, because that came in very handy here.

Copy link
Member

@MrGVSV MrGVSV Feb 2, 2023

Choose a reason for hiding this comment

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

I disagree that we should add Serialize and Deserialize bounds. Again, I recommend looking at the implementation for [T; N] rather than for Cow<'static, str>. The reason we don't have those trait bounds there is because it really limits what T can be.

ReflectSerialize and ReflectDeserialize can always be registered manually (like we do with many other types).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes sense. I changed the GetTypeRegistration impl to match the other generic types in std.rs.

Verified

This commit was signed with the committer’s verified signature.
JamesNK James Newton-King
…unds

Verified

This commit was signed with the committer’s verified signature.
JamesNK James Newton-King

impl<T: FromReflect + Clone> Typed for Cow<'static, [T]> {
fn type_info() -> &'static TypeInfo {
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
Copy link
Member

Choose a reason for hiding this comment

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

I think this should use the GenericTypeInfoCell, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I believe so. Thanks for pointing that out!

@github-actions
Copy link
Contributor

github-actions bot commented Feb 3, 2023

Example alien_cake_addict failed to run, please try running it locally and check the result.

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.

LGTM! Thanks for working on this! 😄

@MrGVSV
Copy link
Member

MrGVSV commented Feb 3, 2023

bors try

bors bot added a commit that referenced this pull request Feb 3, 2023
@EliasPrescott
Copy link
Contributor Author

LGTM! Thanks for working on this! 😄

Thank you so much for helping me! Also, thank you to @ItsDoot and @MinerSebas for reviewing and suggesting changes. You were all very helpful and I learned a lot!

@MrGVSV MrGVSV requested review from ItsDoot and MinerSebas and removed request for ItsDoot and MinerSebas February 13, 2023 06:45
@MrGVSV
Copy link
Member

MrGVSV commented Mar 28, 2023

@EliasPrescott Sorry for the delay with this one. Could you rebase this PR on current main? I think it might be broken after #7467

@EliasPrescott
Copy link
Contributor Author

@EliasPrescott Sorry for the delay with this one. Could you rebase this PR on current main? I think it might be broken after #7467

No worries! I synced my fork with main and updated the impl<T: FromReflect + Clone> List for Cow<'static, [T]> block to match the changes. Let me know if you see anything else that needs changed. Thanks!

@MrGVSV MrGVSV added this to the 0.11 milestone Apr 23, 2023
@MrGVSV MrGVSV added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label May 30, 2023
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jun 5, 2023
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to a conflict with the base branch Jun 5, 2023
@alice-i-cecile
Copy link
Member

Fixed the merge conflicts for you. Once I have another approval that I didn't screw that up I'll merge this.

@MrGVSV
Copy link
Member

MrGVSV commented Jun 12, 2023

@alice-i-cecile there still seems to be some CI errors

@alice-i-cecile
Copy link
Member

Ah, formatting issues. @EliasPrescott want to format and push for us?

Copy link
Contributor

@ItsDoot ItsDoot 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 otherwise, from what I can recall.

Comment on lines +1222 to +1239
impl<T: PathOnly> PathOnly for [T] where [T]: ToOwned {}

impl<T: TypePath> TypePath for [T]
where
[T]: ToOwned,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| format!("[{}]", <T>::type_path()))
}

fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| format!("[{}]", <T>::short_type_path()))
}
}

impl<T: ToOwned> PathOnly for T {}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@MrGVSV @ItsDoot

This is new code that I had to add after merging the latest changes into my fork. I want feedback on it especially because I'm not sure if adding blanket impls like this is a good practice or not.

Also, I wanted to ask if I need to expand the type returned from type_path() for [T]. As far as I know, the slice type doesn't actually live in any namespace, but if it did, we could return something like core::slice::<[T]> instead.

Copy link
Member

Choose a reason for hiding this comment

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

This looks good to me. The blanket impl should be fine as far as I can tell. I'll be honest, I'm not sure why we have this trait over just using ToOwned directly, but either way this should be fine.

Comment on lines +1222 to +1239
impl<T: PathOnly> PathOnly for [T] where [T]: ToOwned {}

impl<T: TypePath> TypePath for [T]
where
[T]: ToOwned,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| format!("[{}]", <T>::type_path()))
}

fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| format!("[{}]", <T>::short_type_path()))
}
}

impl<T: ToOwned> PathOnly for T {}
Copy link
Member

Choose a reason for hiding this comment

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

This looks good to me. The blanket impl should be fine as far as I can tell. I'll be honest, I'm not sure why we have this trait over just using ToOwned directly, but either way this should be fine.

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jun 19, 2023
Merged via the queue into bevyengine:main with commit e6b655f Jun 19, 2023
james7132 pushed a commit to james7132/bevy that referenced this pull request Jun 19, 2023
# Objective

- Implementing reflection for Cow<'static, [T]>
- Hopefully fixes bevyengine#7429

## Solution

- Implementing Reflect, Typed, GetTypeRegistration, and FromReflect for
Cow<'static, [T]>

---

## Notes

I have not used bevy_reflection much yet, so I may not fully understand
all the use cases. This is also my first attempt at contributing, so I
would appreciate any feedback or recommendations for changes. I tried to
add cases for using Cow<'static, str> and Cow<'static, [u8]> to some of
the bevy_reflect tests, but I can't guarantee those tests are
comprehensive enough.

---------

Co-authored-by: MinerSebas <66798382+MinerSebas@users.noreply.github.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Reflection Runtime information about types C-Usability A targeted quality-of-life change that makes Bevy easier to use S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

bevy_reflect: Support Cow<'static, [T]>
5 participants