bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
35.86k stars 3.54k forks source link

Create an example showcasing disjoint mutable access to the world via `unsafe_world_cell` #12967

Open cBournhonesque opened 6 months ago

cBournhonesque commented 6 months ago

What problem does this solve or what need does it fill?

I recently discovered that bevy allows you to have disjoint mutable access to the world by using unsafe_world_cell (the user has to make sure that they don't access to conflicting queries at once).

This seems like a very powerful pattern which would be nice to showcase in an example.

For instance, you could want to display some egui ui, and have access to &mut World inside your ui to perform other operations; something like:

pub(crate) fn lobby_ui(world: &mut World, mut contexts: &mut SystemState<EguiContexts, ResMut<LobbyTable>>) {
    let cell = world.as_unsafe_world_cell();
    let (mut contexts, mut lobby_table) = contexts.get_mut(cell.world_mut());
    let world = cell.world_mut();
    egui::Window::new("Lobby").show(contexts.ctx_mut(), |ui| {
        lobby_table.table_ui(ui, world);
    });
}

The SystemState docs show a pattern where you can use them in combination with &mut World in an exclusive system, but they only show usages where you first use your system_state, and then you use &mut World, but never both in parallel. This example would showcase accessing both in parallel.

Themayu commented 6 months ago

This example appears to be unsound!

UnsafeWorldCell::world_mut is unsafe, and also requires that the &mut World instance returned by it is the only way of accessing the world during its lifetime. The example lacks unsafe annotations to allow world_mut to be called to begin with, and also has a more serious issue:

If we call the world borrow returned by contexts.get_mut(cell.world_mut()) world'1, and the world borrow stored in world by let world = cell.world_mut(); world'2, then this example appears to violate that requirement by calling contexts.ctx_mut() (which returns a mutable borrow obtained from world'1) while world'2 is alive.

cBournhonesque commented 6 months ago

Yes the example just provides a general schema of how using the api would look like, I haven't added all the required unsafes, etc.

I think the goal is to show that if the user can guarantee by themselves (in an unsafe manner) that the world'1 and world'2 won't have conflicting accesses. (in my example, the world'2 would be needed to initialize a connection and inserting new resources into the world, while the world'1 is simply needed to drive the egui ui)