Skip to content
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

use UnsafeWorldCell internally #6972

Conversation

jakobhellermann
Copy link
Contributor

@jakobhellermann jakobhellermann commented Dec 17, 2022

builds on #6404 (and #6402)

important commits:

Objective

Solution

  • use UnsafeWorldCell whenever we use &World as shared mutable
    • this is in the System machinery and for Query/WorldQuery

Changelog

bevy now uses the UnsafeWorldCell type in places where we previously used &World to mean "shared mutable access, but ensured to be distinct by systems".
This means that systems and queries now get a UnsafeWorldCell where every method is unsafe, so you need to reason correctly about every access.

Usually this is done like

// SAFETY: must only be called with read/write access to resource A and B, and component C
unsafe fn get_unsafe_world_cell(world: UnsafeWorldCell) {
  // SAFETY: caller promises read access, no other references
  let a = world.get_resource::<A>();
}

@james7132 james7132 added A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change labels Jan 2, 2023
@alice-i-cecile
Copy link
Member

@jakobhellermann can you rebase this? I'd like to prioritize this for review and merging due to its impact on ECS internals clarity.

@alice-i-cecile alice-i-cecile added this to the 0.10 milestone Jan 8, 2023
@alice-i-cecile alice-i-cecile added D-Complex Quite challenging from either a design or technical perspective. Ask for help! P-Unsound A bug that results in undefined compiler behavior labels Jan 8, 2023
@jakobhellermann jakobhellermann force-pushed the interior-mutable-world-internal-usage branch from 5ce7cbe to 7cc5b86 Compare January 13, 2023 15:07
@jakobhellermann jakobhellermann force-pushed the interior-mutable-world-internal-usage branch from 7cc5b86 to ebc7198 Compare January 24, 2023 15:39
@jakobhellermann jakobhellermann force-pushed the interior-mutable-world-internal-usage branch 4 times, most recently from 8abb3cf to 93dbca5 Compare January 27, 2023 14:23
@jakobhellermann jakobhellermann changed the title use InteriorMutableWorld internally use UnsafeWorldCell internally Jan 27, 2023
@jakobhellermann jakobhellermann force-pushed the interior-mutable-world-internal-usage branch from 93dbca5 to f7606e9 Compare February 9, 2023 15:49
@alice-i-cecile alice-i-cecile modified the milestones: 0.10, 0.11 Feb 13, 2023
@alice-i-cecile
Copy link
Member

I'm unsure if we can get this reviewed and merged appropriately without stopping the train. Impact is mostly internal, so I've bumped it from the 0.10 milestone.

@@ -158,8 +162,18 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
}
}

fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell<'_>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell<'_>) {
pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell<'_>) {

and also implement update_archetypes in terms of this method rather than the way it is now perhaps

self.validate_world_unsafe_world_cell(world.as_unsafe_world_cell_readonly())
}
#[inline]
pub fn validate_world_unsafe_world_cell(&self, world: UnsafeWorldCell<'_>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn validate_world_unsafe_world_cell(&self, world: UnsafeWorldCell<'_>) {
pub fn validate_unsafe_world_cell(&self, world: UnsafeWorldCell<'_>) {

@@ -309,6 +310,9 @@ impl MultiThreadedExecutor {
break;
}

// TODO: do this earlier?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you want to replace the cell: &'scope SyncUnsafeCell<World> with cell: UnsafeWorldCell<'scope> I believe.

.sparse_sets
.get(component_id)
.debug_checked_unwrap()
// SAFETY: TODO
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be a non-TODO comment.

.sparse_sets
.get(component_id)
.debug_checked_unwrap()
// SAFETY: world has access to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This placeholder needs to be fully written.

#[inline]
pub fn validate_world(&self, world: &World) {
self.validate_world_unsafe_world_cell(world.as_unsafe_world_cell_readonly())
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}

self.validate_world_unsafe_world_cell(world.as_unsafe_world_cell_readonly())
}
#[inline]
pub fn validate_world_unsafe_world_cell(&self, world: UnsafeWorldCell<'_>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is to be public, please document what this function does. Alternatively, we could just have validate_world take a WorldId instead, which should be accessible from both.

@alice-i-cecile
Copy link
Member

Closing in favor of #8833.

@jakobhellermann jakobhellermann deleted the interior-mutable-world-internal-usage branch June 13, 2023 14:18
alice-i-cecile pushed a commit that referenced this pull request Jun 15, 2023
# Objective

Follow-up to #6404 and #8292.

Mutating the world through a shared reference is surprising, and it
makes the meaning of `&World` unclear: sometimes it gives read-only
access to the entire world, and sometimes it gives interior mutable
access to only part of it.

This is an up-to-date version of #6972.

## Solution

Use `UnsafeWorldCell` for all interior mutability. Now, `&World`
*always* gives you read-only access to the entire world.

---

## Changelog

TODO - do we still care about changelogs?

## Migration Guide

Mutating any world data using `&World` is now considered unsound -- the
type `UnsafeWorldCell` must be used to achieve interior mutability. The
following methods now accept `UnsafeWorldCell` instead of `&World`:

- `QueryState`: `get_unchecked`, `iter_unchecked`,
`iter_combinations_unchecked`, `for_each_unchecked`,
`get_single_unchecked`, `get_single_unchecked_manual`.
- `SystemState`: `get_unchecked_manual`

```rust
let mut world = World::new();
let mut query = world.query::<&mut T>();

// Before:
let t1 = query.get_unchecked(&world, entity_1);
let t2 = query.get_unchecked(&world, entity_2);

// After:
let world_cell = world.as_unsafe_world_cell();
let t1 = query.get_unchecked(world_cell, entity_1);
let t2 = query.get_unchecked(world_cell, entity_2);
```

The methods `QueryState::validate_world` and
`SystemState::matches_world` now take a `WorldId` instead of `&World`:

```rust
// Before:
query_state.validate_world(&world);

// After:
query_state.validate_world(world.id());
```

The methods `QueryState::update_archetypes` and
`SystemState::update_archetypes` now take `UnsafeWorldCell` instead of
`&World`:

```rust
// Before:
query_state.update_archetypes(&world);

// After:
query_state.update_archetypes(world.as_unsafe_world_cell_readonly());
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change D-Complex Quite challenging from either a design or technical perspective. Ask for help! P-Unsound A bug that results in undefined compiler behavior
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants