viridia / quill_v1

Reactive UI framework for Bevy game engine
MIT License
60 stars 7 forks source link

Cx.use_resource should not require a mutable borrow #10

Closed viridia closed 9 months ago

viridia commented 9 months ago

Currently cx.use_resource() does a mutable borrow on the World, which makes it impossible to access two resources in the same presenter:

fn example(cx: Cx) -> impl View {
  let res1 = cx.use_resource::<Res1>();
  let res2 = cx.use_resource::<Res2>(); // ERROR
}

The reason this doesn't compile is that both calls to use_resource do a mutable borrow on cx, and return a value whose lifetime is tied to that borrow, which leads to a borrowing conflict. There are actually two reasons why use_resource needs mutable access.

The first reason is that use_resource internally calls self.vc.add_tracked_resource::<T>(), which inserts an entry into the TrackedResources ECS component. In short, it needs mutable access to the world.

The second reason is that use_resource and use_resource_mut call world.get_resource() and world.get_resource_mut(). The first only needs an immutable borrow, but the second requires a mutable one.

Now, from a user perspective, there's no reason why these calls should require a mutable borrow. Like reference counts, the tracking context is, and should be, invisible to the user; the fact that there's mutation going on under the hood should not impact the API.

Unfortunately, adding interior mutability (such as RefCell or Mutex) won't work here. These work fine for updating the tracking context, since the mutation doesn't require any long-lived borrows. But they won't help for returning the resource reference, because when you pull a reference out of something in a RefCell or Mutex, the lifetime is only as long as the lock() or borrow(). This means you can't return a long-lived object.

The right answer here would be to use WorldCell, which allows access to multiple resources. Unfortunately, WorldCell only handles resources (and events) and not entities/components. If it did, then we could modify Cx to simply hold on to a WorldCell instead of holding on to a World reference. However, because WorldCell doesn't currently support entities, we still need a World in order to mutate the tracking context, and we can't hold a mutable World and a WorldCell at the same time.

Apparently there's an open RFC to add entity support to WorldCell which would solve this problem.