-
-
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
Change ReflectMapEntities to operate on components before insertion #14549
Conversation
Welcome, new contributor! Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨ |
8a82089
to
f8552ce
Compare
Let me know when this is ready for review :) I'm going to want a regression test or two: the logic here is tricky but important to get right. |
f410d21
to
d18b79c
Compare
Hmm, I added a test which correctly fails before/succeeds after my change, but in the process I noticed that I'm now causing |
d18b79c
to
0063b1b
Compare
Ope, I'm just an idiot 😅 Forgot to use the wrapped |
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.
Mostly happy with this, apart from all of the extra component copying that ends up happening. We might be able to get rid of the outer one in the event that we don't have to map entities via some copy-on-write shenanigans, but I'm not sure about the one incurred from the call to the concrete FromReflect
and the subsequent write back to the &mut dyn Reflect
.
}); | ||
} | ||
} | ||
|
||
impl<C: Component + MapEntities> FromType<C> for ReflectMapEntities { | ||
impl<C: Component + MapEntities + Reflect + FromReflect> FromType<C> for ReflectMapEntities { |
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 slightly more restrictive than previously, but I'm not sure that it really matters since anything reflecting MapEntities
should already be Reflect + FromReflect
.
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.
I think it's fine. Ideally we'd only use Reflect
and do a similar trick as ReflectComponent
, but I’m not sure that's possible without world access.
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.
World access shouldn't actually be a problem, at least for the two places we're currently using this. I actually had a &mut World
parameter previously before swapping the EntityHashMap<Entity>
for the &mut dyn DynEntityMapper
.
Would also need access to the type registry, which also shouldn't be an issue, but then the arguments are starting to feel cluttered.
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, works just fine. I could go either way 🤷
crates/bevy_scene/src/scene.rs
Outdated
reflect_component.apply_or_insert( | ||
&mut world.entity_mut(entity), | ||
&*component, | ||
&type_registry, | ||
); |
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.
I think this is equivalent to the previous reflect_component.copy
, but I'm not 100% certain. I don't use regular Scene
s all that often. Or at all, really.
0063b1b
to
13b2c68
Compare
map_entities: |component, entity_mapper| { | ||
let mut concrete = C::from_reflect(&*component).unwrap(); | ||
concrete.map_entities(&mut UnDynEntityMapper(entity_mapper)); | ||
component.apply(&concrete as _); |
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.
The only possible concern here is if the user has mapped fields marked with #[reflect(ignore)]
. The original implementation didn't rely on reflection so it was fine, but this is a new "hidden" requirement. We should probably document this somewhere.
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.
I feel like that's expected? If a field is invisible to reflection in general, it seems logical that it would be invisible to reflected traits.
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.
Hm fair point. And when used with scenes specifically I guess those fields won't even be serialized which means it's less of an issue than I originally thought.
00acaaa
to
31b8c97
Compare
@alice-i-cecile Ready for review! I'm satisfied that this change fixes the original scenes/observers/mapped entities issue, but I wonder if there isn't a more fundamental problem with observers and their immediacy. This fixes the issue of individual components being in an inconsistent state, but I think the overall scene will still be inconsistent when the observers fire, e.g. a |
Agreed, there's something deeper here that's bothering me but I haven't figured out exactly what yet. @cart's idea from #14520 (comment) to use a topologically sorted order of resolution might help this problem as well. |
Yeah, that sounds like it would definitely help, but it also feels like another band-aid. In my mind, the real issue is that observers very much look like systems, which have always run on either side of sync points and thus could never observe intermediate state. But in reality, observers can see this intermediate state since they run immediately. Things like Edit: Delved a little more deeply, and I think I'm conflating a couple of issues. The stack overflow is due to how command application recurses on |
31b8c97
to
d0a3349
Compare
Can we plz schedule this PR for an upcoming milestone?
|
Happy to get this rebased and mergeable, but I still think it's just a band-aid. Systems with exclusive |
Yeah, I'd like to get this merged :) I disagree with you on "should hooks and observers operate during exclusive world access", but that's a conversation for another thread. |
b7a52aa
to
6ed33e8
Compare
@mrchantey can I please have your review here? |
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.
Extra test coverage looks great and its really well documented. I left one comment about documenting panics, not sure if thats our style :)
// Fetch the entity with the given entity id from the `entity_map`. | ||
let entity = *entity_map | ||
.get(&scene_entity.entity) | ||
.expect("should have previously spawned an empty entity"); |
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.
I'm not 100% on Bevy docs style, are we meant to declare these panics in the function docs?
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.
Yes, it would be nice to add a #Panics
section to the docs.
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.
My understanding is that panics only need to be documented if they can be triggered by the caller due to invariants not being upheld. That isn't the case for this panic as it signals a bug in the method internals.
6ed33e8
to
a8d1796
Compare
Went through the referenced issues to see how relevant they are:
|
a8d1796
to
97d4d15
Compare
Merging #15405 instead, which resolves the same issue. |
@alice-i-cecile I think some wires got crossed - This PR and #15405 both resolve #14300, but only this one resolves #14465. The test it adds still fails before the fix. So this is still relevant and should be reopened. |
Sorry, I can't reopen this PR. Can you remake it? |
…15422) Previous PR #14549 was closed in error and couldn't be reopened since I had updated the branch :crying_cat_face: # Objective Fixes #14465 ## Solution `ReflectMapEntities` now works similarly to `MapEntities` in that it works on the reflected value itself rather than the component in the world after insertion. This makes it so that observers see the remapped entities on insertion rather than the entity IDs from the scene. `ReflectMapEntities` now works for both components and resources, so we only need the one. ## Testing * New unit test for `Observer`s + `DynamicScene`s * New unit test for `Observer`s + `Scene`s * Open to suggestions for other tests! --- ## Migration Guide - Consumers of `ReflectMapEntities` will need to call `map_entities` on values prior to inserting them into the world. - Implementors of `MapEntities` will need to remove the `mappings` method, which is no longer needed for `ReflectMapEntities` and has been removed from the trait. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Hennadii Chernyshchyk <genaloner@gmail.com>
…evyengine#15422) Previous PR bevyengine#14549 was closed in error and couldn't be reopened since I had updated the branch :crying_cat_face: # Objective Fixes bevyengine#14465 ## Solution `ReflectMapEntities` now works similarly to `MapEntities` in that it works on the reflected value itself rather than the component in the world after insertion. This makes it so that observers see the remapped entities on insertion rather than the entity IDs from the scene. `ReflectMapEntities` now works for both components and resources, so we only need the one. ## Testing * New unit test for `Observer`s + `DynamicScene`s * New unit test for `Observer`s + `Scene`s * Open to suggestions for other tests! --- ## Migration Guide - Consumers of `ReflectMapEntities` will need to call `map_entities` on values prior to inserting them into the world. - Implementors of `MapEntities` will need to remove the `mappings` method, which is no longer needed for `ReflectMapEntities` and has been removed from the trait. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Hennadii Chernyshchyk <genaloner@gmail.com>
Objective
Fixes #14465
Solution
Alters
ReflectMapEntities
to operate more likeMapEntities
by making it operate on the component itself rather than theWorld
post-insertion. As a result,Observers
never see the in-between state where the scene component has been added, but hasn't yet had its entities mapped.Also brings it more in-line with
MapEntities
by accepting a genericDynEntityMapper
rather than relying on theEntityHashMap
andSceneEntityMapper
implementation.Testing
Observer
s +DynamicScene
sObserver
s +Scene
sMigration Guide
map_entities
andmap_all_entities
methods have been renamed tomap_world_entities
andmap_all_world_entities
respectively and marked as deprecated.This is in a separate commit so that they'll be easy to fully remove later.Edit: Oops, I forgot I did this and squashed it away. Oh well, should still be easy to remove them if/when desired.