Provide a MapEntities implementation that doesn't require mutable access to the EntityMapper
What problem does this solve or what need does it fill?
The MapEntities trait (https://github.com/bevyengine/bevy/blob/main/crates/bevy_ecs/src/entity/map_entities.rs#L46)
pub trait MapEntities {
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M);
}
requires a mutable access to the EntityMapper: &mut M
This is because the SceneEntityMapper might generate a new Entity, as seen here: https://github.com/bevyengine/bevy/blob/main/crates/bevy_ecs/src/entity/map_entities.rs#L81
but there are a lot of cases where you might want to just map an entity using an immutable HashMap, in which case you only need a &dyn EntityMapper.
I run into cases where the parallelism of my systems is lowered because I am forced to provide a mutable access to the EntityMapper even though a non-mutable access would be okay. I would appreciate having a way to call map_entities without requiring a mutable access to entity_mapper
What solution would you like?
Maybe the MapEntities trait could have 2 functions map_entities_mut and map_entities? I haven't thought this through.
It would be convenient, but not sure how to solve it...
Having map_entities_mut and map_entities will require users to provide both implementations which is not convenient :(
+1 for this being convenient, I'm currently cloning EntityMapper to avoid having to think about how to satisfy the borrow checker in https://github.com/aristaeus/bevy_mod_desync
Thinking about this more, we can do something like Fn/FnMut in the standard library:
- Provide
MapEntitiesandMapEntitiesMut, with a blanketimpl<T: MapEntities> MapEntitesMut for T - Rename all current usage to
map_entities_mutetc (would result in a lot of short term churn) - Users only need to implement one trait
I can prepare a PR if others think this is a good strategy
This seems like a good idea to me!
And for things like the SceneEntityMap, we would only implement the MapEntitiesMut trait?
Yes, exactly. I'll work on a PR in the next couple of days.
And for things like the SceneEntityMap, we would only implement the MapEntitiesMut trait?
SceneEntityMap is a mapper, MapEntities(Mut) is for components.
I think splitting into two traits and providing a blanked imp won't solve the problem. All components should be used in scenes, so they all need MapEntitiesMut that call map_entity_mut on a mapper, so blanked impl that calls map_entity won't work for them. And if you implement MapEntitiesMut, you won't be able to implement MapEntities due to the blanked impl.
Yeah, I ran into some trouble trying this. Splitting EntityMapper and adding a blanket impl is easy, but there's no correct way to write down MapEntities(Mut).
I've noticed that components don't really care whether the entity mapper is mutable or not, so a more controversial option is to pad out the trait like so;
trait MapEntities {
fn map_entities_internal(&mut self, entity_mapper: MutErasedEntityMapper);
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &M) {
self.map_entities_internal(...);
}
fn map_entities_mut<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
interior_mutableise_entity_mapper();
self.map_entities_internal(...);
}
}
Components can them impl map_entities_internal, and users can choose whether or not they need mutability in context. map_entities may need some way of conveying that mapping failed because an entity needed to be created and couldn't be?
Interior mutability would solve the problem. But you can keep a single non-mutable method and require mappers use interior mutability if needed.