-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
Motivation
The essential rule for aliasing in rust is that you can have either multiple shared & references to a location, or a unique &mut reference.
This is sometimes too restrictive, e.g. when you have two systems that both share a reference to a World but you know that they don't access the same data. As an escape hatch rust provides the UnsafeCell<T> type (and safe wrappers around it), which is special in that it lets you get a &mut T from a &UnsafeCell<T>, provided that you manually uphold the safety requirements. Notable, this doesn't change anything about the fact that you can't have two aliasing &muts. The only thing that this allows, and that we make use of, is building abstractions like these:
struct World {
storages: UnsafeCell<ActualStorage>
...
}where you can pass a &World around and expose some unsafe fn World::get_unchecked_mut<T>(&World, Entity) -> Option<&mut T>.
Why is this not enough?
This works if you carefully document the unchecked_mut safety contracts and take care to not violate them. It is still error prone, because when you have a &World that only lets you access resource Foo, safe code can do bad things.
This is because all the methods on World like World::get_resource are safe, which is usually corrent when treating &World as an immutable world reference. This isn't the case anymore if it is used as a access restricted kinda-mutable world.
Solution
Add a new wrapper type around &World (here InteriorMutableWorld, name up for discussion). Using this type documents intent better and makes writing unsafe code easier than using a &World directly.
Proposed API
struct World { .. }
impl World {
fn as_interior_mutable(&'w self) -> InteriorMutableWorld<'w>;
}
#[derive(Clone, Copy)]
struct InteriorMutableWorld<'w>(&'w World);
impl<'w> InteriorMutableWorld {
unsafe fn world(&self) -> &World;
fn get_entity(&self) -> InteriorMutableEntityRef<'w>;
unsafe fn get_resource<T>(&self) -> Option<&'w T>;
unsafe fn get_resource_by_id(&self, ComponentId) -> Option<&'w T>;
unsafe fn get_resource_mut<T>(&self) -> Option<Mut<'w, T>>;
unsafe fn get_resource_mut_by_id<T>(&self) -> Option<MutUntyped<'w>>;
// not included: remove, remove_resource, despawn, anything that might change archetypes
}
struct InteriorMutableEntityRef<'w> { .. }
impl InteriorMutableEntityRef<'w> {
unsafe fn get<T>(&self, Entity) -> Option<&'w T>;
unsafe fn get_by_id(&self, Entity, ComponentId) -> Option<Ptr<'w>>;
unsafe fn get_mut<T>(&self, Entity) -> Option<Mut<'w, T>>;
unsafe fn get_mut_by_id(&self, Entity, ComponentId) -> Option<MutUntyped<'w>>;
unsafe fn get_change_ticks<T>(&self, Entity) -> Option<Mut<'w, T>>;
unsafe fn get_mut_by_id(&self, Entity, ComponentId) -> Option<MutUntyped<'w>>;
}After this abstraction has been built, we should update our internal use of &World to InteriorMutableWorld, for example
trait System {
- unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out;
+ unsafe fn run_unsafe(&mut self, input: Self::In, world: &InteriorMutableWorld) -> Self::Out;
...
}Prior Art
#5588 builds something similar, but custom built for the QueryState use case.
Implementation Questions
When we have World::get_resource_mut and InteriorMutableWorld::get_resource_mut we don't want code duplication.
Do we
- define
get_resource_mut_inner(&self)onWorldand use it in both or - just define the method in
InteriorMutableWorldand callself.as_interior_mutable().get_resource_mut(self)inWorld
Same question for get_resource, but here we could also define it in World and let InteriorMutableWorld just call self.0.get_resource.