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 ReflectFromWorld and replace the FromWorld requirement on ReflectComponent and ReflectBundle with FromReflect #9623

Merged
merged 12 commits into from
Jan 19, 2024

Conversation

SkiFire13
Copy link
Contributor

@SkiFire13 SkiFire13 commented Aug 29, 2023

Objective

  • FromType<T> for ReflectComponent and ReflectBundle currently require T: FromWorld for two reasons:
    • they include a from_world method;
    • they create dummy Ts using FromWorld and then apply a &dyn Reflect to it to simulate FromReflect.
  • However FromWorld/Default may be difficult/weird/impractical to implement, while FromReflect is easier and also more natural for the job.
  • See also https://discord.com/channels/691052431525675048/1146022009554337792

Solution

  • Split from_world from ReflectComponent and ReflectBundle into its own ReflectFromWorld struct.
  • Replace the requirement on FromWorld in ReflectComponent and ReflectBundle with FromReflect

Changelog

  • ReflectComponent and ReflectBundle no longer offer a from_world method.
  • ReflectComponent and ReflectBundle's FromType<T> implementation no longer requires T: FromWorld, but now requires FromReflect.
  • ReflectComponent::insert, ReflectComponent::apply_or_insert and ReflectComponent::copy now take an extra &TypeRegistry parameter.
  • There is now a new ReflectFromWorld struct.

Migration Guide

  • Existing uses of ReflectComponent::from_world and ReflectBundle::from_world will have to be changed to ReflectFromWorld::from_world.
  • Users of #[reflect(Component)] and #[reflect(Bundle)] will need to also implement/derive FromReflect.
  • Users of #[reflect(Component)] and #[reflect(Bundle)] may now want to also add FromWorld to the list of reflected traits in case their FromReflect implementation may fail.
  • Users of ReflectComponent will now need to pass a &TypeRegistry to its insert, apply_or_insert and copy methods.

@SkiFire13
Copy link
Contributor Author

Mhm that didn't happen when I ran cargo run -p ci on my Windows machine...

Anyway, look like the error occurs because a Component wants to be only partially deserialized (using #[reflect(skip_serializing)]) and then expects FromWorld to be used first when being deserialized. IMO this is not exactly straightforward to use, though look a valid usecase.

In the end the error is caused by bevy_scene calling ReflectComponent::apply_or_insert with a "partial" &dyn Reflect, while this PR expects a "complete" one, at least for types that don't implement Default.

One way to fix could be to fall back to using FromWorld (through ReflectFromWorld) if FromReflect fails. It adds yet another layer, but should have the wanted semantics. Ideally though apply_or_insert would only receive "full" &dyn Reflect and the additional initialization required could be performed by a different dedicated mechanism.

@alice-i-cecile alice-i-cecile added C-Usability A simple quality-of-life change that makes Bevy easier to use A-Reflection Runtime information about types labels Aug 29, 2023

use crate::world::{FromWorld, World};

/// A struct used to operate on reflected [`FromWorld`] of a type.
Copy link
Member

Choose a reason for hiding this comment

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

This line isn't clear to me. It feels like we're missing a noun after FromWorld.

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 blindly copied the documentation for ReflectComponent and ReflectBundle. I guess their documentation should also be updated?

Copy link
Member

Choose a reason for hiding this comment

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

Yes please :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also FromWorld already feel kinda a noun, so I'm not so sure what to add after it. Any idea?

@alice-i-cecile
Copy link
Member

I like not requiring Default (implicitly) on everything :) The general direction of this change makes a lot of sense to me now that we have FromReflect.

Copy link
Contributor Author

@SkiFire13 SkiFire13 left a comment

Choose a reason for hiding this comment

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

I have rebased and fixed the merge conflicts. I'll leave here an auto-review to highlight some changes where some feedback would be welcome.

/// Function pointer implementing [`ReflectComponent::insert()`].
pub insert: fn(&mut EntityWorldMut, &dyn Reflect),
pub insert: fn(&mut EntityWorldMut, &dyn Reflect, &TypeRegistry),
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 have added a &TypeRegistry parameter to ReflectComponent's functions, mimicking the ones on ReflectBundle. Now the TypeRegistry in the World is no longer implicitly accessed.

Comment on lines +326 to +336
// FIXME: once we have unique reflect, use `TypePath`.
std::any::type_name::<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.

Here I kinda copied what it's done in ReflectBundle. The alternative would be requiring TypePath in the bounds, but I guess that's probably better done in another PR.

Comment on lines +125 to +128
if let Some(reflect_component) =
registry.get_type_data::<ReflectComponent>(TypeId::of::<B>())
{
reflect_component.apply(entity, reflected_bundle);
} else {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

While reworking insert_field I realized it would make sense to check if the Bundle being inserted is a single Component, in which case it shouldn't insert each field as if it was itself a bundle/component (which would be wrong). I guess usually ReflectComponent should be checked before ReflectBundle, but if someone made a mistake we should catch it. This won't work though if someone only registers ReflectBundle and not ReflectComponent. It's also a technically a change in behaviour (though I would argue any case where a change is observable was likely behaving wrongly before).

@SkiFire13
Copy link
Contributor Author

Rebased and fixed merge conflicts.

@@ -0,0 +1,84 @@
//! Definitions for [`FromWorld`] reflection.
Copy link
Member

Choose a reason for hiding this comment

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

This could use a sentence or two of docs about why this is useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that this is a private module. I tried to expand the documentation a bit anyway, though I'm not fully convinced. I also expanded a bit the documentation of the bundle and component modules to keep the same "structure".

Related: I just noticed that the component module has some kinda extensive documentation, but unfortunately it is not visible to users due to the module being private.

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 have nits about the documentation, but this change ultimately makes sense to me and seems both correct and necessary.

Copy link
Contributor

@soqb soqb left a comment

Choose a reason for hiding this comment

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

i strongly agree with the broad strokes of this change and the implementation is solid too.

i wonder if it would be possible in a followup to automatically register ReflectFromWorld when we write #[reflect(Default)].

@SkiFire13
Copy link
Contributor Author

i wonder if it would be possible in a followup to automatically register ReflectFromWorld when we write #[reflect(Default)].

It should be possible, though I wonder whether there is a more general way to do this that covers any Trait that has a blanket implementation impl<T: OtherTrait> Trait for T

@alice-i-cecile alice-i-cecile 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 Jan 19, 2024
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jan 19, 2024
Merged via the queue into bevyengine:main with commit eff96e2 Jan 19, 2024
23 checks passed
@SkiFire13 SkiFire13 deleted the fromworld-reflectcomponent branch January 19, 2024 18:53
@soqb
Copy link
Contributor

soqb commented Jan 20, 2024

It should be possible, though I wonder whether there is a more general way to do this that covers any Trait that has a blanket implementation impl<T: OtherTrait> Trait for T

probably not without linkme/inventory-like shenanigans, which has no wasm support.

edit: unless we made this part of the definition of #[reflect_trait], e.g. #[reflect_trait(impl FromWorld)] on Default trait.

github-merge-queue bot pushed a commit that referenced this pull request Feb 27, 2024
…ource` for `State<S>` (#12136)

# Objective

- In #9623 I forgot to change the `FromWorld` requirement for
`ReflectResource`, fix that;
- Fix #12129

## Solution

- Use the same approach as in #9623 to try using `FromReflect` and
falling back to the `ReflectFromWorld` contained in the `TypeRegistry`
provided
- Just reflect `Resource` on `State<S>` since now that's possible
without introducing new bounds.

---

## Changelog

- `ReflectResource`'s `FromType<T>` implementation no longer requires
`T: FromWorld`, but instead now requires `FromReflect`.
- `ReflectResource::insert`, `ReflectResource::apply_or_insert` and
`ReflectResource::copy` now take an extra `&TypeRegistry` parameter.

## Migration Guide

- Users of `#[reflect(Resource)]` will need to also implement/derive
`FromReflect` (should already be the default).
- Users of `#[reflect(Resource)]` may now want to also add `FromWorld`
to the list of reflected traits in case their `FromReflect`
implementation may fail.
- Users of `ReflectResource` will now need to pass a `&TypeRegistry` to
its `insert`, `apply_or_insert` and `copy` methods.
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 simple 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
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants