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

Do not incorrectly impl Send for World #3519

Closed
wants to merge 4 commits into from

Conversation

DJMcNab
Copy link
Member

@DJMcNab DJMcNab commented Jan 1, 2022

Fixes #3310

For actual-pipelining, it might be required to change RenderWorld to contain a Turtle instead, but this version currently works.
I suspect that would also cause mutability problems, and Turtle would need to defer most of the non-non_send methods.

As it happens, those checks would panic at runtime anyway, but we could remove the checks now that this change has happened.

@github-actions github-actions bot added the S-Needs-Triage This issue needs to be labelled label Jan 1, 2022
@DJMcNab DJMcNab added A-ECS Entities, components, systems, and events C-Bug An unexpected or incorrect behavior P-High This is particularly urgent, and deserves immediate attention and removed S-Needs-Triage This issue needs to be labelled labels Jan 1, 2022
/// A world which does not contain any [`!Send`] resources, and therefore
/// can be safely sent between threads.
///
/// The name turtle is derived from this being something which is moving a
Copy link
Member

Choose a reason for hiding this comment

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

🤔 I... don't hate it? Very cute. Not sure it's terribly descriptive.

What about ThreadSafeWorld or something?

Copy link
Member Author

Choose a reason for hiding this comment

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

It is quite a niche type, to be fair.

I hoped I'd be able to sneak a bit of whimsy through.

(I will claim I didn't just create this PR for this joke; whether you believe that is your choice)

Copy link
Member

Choose a reason for hiding this comment

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

it is turtles all the way down

Copy link
Contributor

Choose a reason for hiding this comment

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

I have to leave a comment in support of Turtles. It's personal for me, given my avatar.

Copy link
Contributor

Choose a reason for hiding this comment

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

amazing

self.world
}

/// A view on the
Copy link
Member

Choose a reason for hiding this comment

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

Sentence fragment.

@@ -347,6 +347,13 @@ impl Components {

ComponentId(*index)
}

pub(crate) fn non_send_components(&'_ self) -> impl Iterator<Item = ComponentId> + '_ {
Copy link
Member

Choose a reason for hiding this comment

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

Interesting. This will be useful if we want to relax the bounds on Component later too.

@@ -1159,9 +1181,50 @@ impl fmt::Debug for World {
}
}

unsafe impl Send for World {}
unsafe impl Sync for World {}
Copy link
Member

Choose a reason for hiding this comment

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

Just so I understand: why do we still want the Sync impl for World?

Copy link
Member Author

Choose a reason for hiding this comment

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

The entire idea of our world abstraction is that it is accessed in a shared way across multiple threads.

///
/// [`!Send`]: Send

pub fn turtle(self) -> Turtle {
Copy link
Member

Choose a reason for hiding this comment

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

Hmm. I wonder if this method or a From impl is better...

Copy link
Member Author

Choose a reason for hiding this comment

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

It has the potential to be a relatively expensive operation, so I don't want it to be so implicit.

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

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

Correct, well-scoped and important as a prerequisite. None of my musings are blocking, so LGTM.

Cart can decide on the 🐢 name :p

/// A view on this [`Turtle`]'s [`World`], which contains no [`!Send`] resources
///
/// [`!Send`]: Send
// Safety: NonSend resources cannot be added using a raw reference
Copy link
Member

Choose a reason for hiding this comment

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

this is a _really_ sketchy safety comment imo, its relying on things not being possible in the current API which could definitely be added in the future. Do we actually need this function? It doesn't seem like it from the changes in this PR

Copy link
Member Author

Choose a reason for hiding this comment

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

How would having a raw reference to the world ever allow adding !Send resources.

Also how would you propose we access the world in a scene without this method?

Copy link
Member

Choose a reason for hiding this comment

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

Well resources have no need to fragment archetypes, which is the main problem with &World allowed add/removes on components.

After looking over the code again I realise that you have &Turtle not Turtle so yeah I dunno what to do here but I think this safety comment is not going to end well

Copy link
Member

Choose a reason for hiding this comment

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

Also, "raw" reference feels like poor wording, maybe immutable reference or just reference would work better?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, not sure why I used the term raw here.

Also, in my view, the main challenge of &World allowing adds and removes is actually synchronisation; currently the synchronisation is outside of the World (for good reason). You seem to be suggesting that there are plans to add the synchronisation back into the World?

@alice-i-cecile alice-i-cecile added P-Critical This must be fixed immediately or contributors or users will be severely impacted and removed P-High This is particularly urgent, and deserves immediate attention labels Jan 13, 2022
@alice-i-cecile
Copy link
Member

Bumping to Critical, as clippy is now causing CI to fail because this Send impl is unsound.

@alice-i-cecile
Copy link
Member

bors try

bors bot added a commit that referenced this pull request Jan 13, 2022
@bors
Copy link
Contributor

bors bot commented Jan 13, 2022

try

Build failed:

@alice-i-cecile alice-i-cecile added P-High This is particularly urgent, and deserves immediate attention and removed P-Critical This must be fixed immediately or contributors or users will be severely impacted labels Jan 14, 2022
@alice-i-cecile
Copy link
Member

Back down to High: #3667 is a much lower risk immediate fix.

@alice-i-cecile
Copy link
Member

Ping @bevyengine/ecs-team; unsoundness is not good.

@alice-i-cecile
Copy link
Member

@DJMcNab can you rebase?

@alice-i-cecile alice-i-cecile added P-Unsound A bug that results in undefined compiler behavior and removed P-High This is particularly urgent, and deserves immediate attention labels May 5, 2022
@alice-i-cecile alice-i-cecile added the X-Controversial There is active debate or serious implications around merging this PR label May 13, 2022
@DJMcNab
Copy link
Member Author

DJMcNab commented May 23, 2022

I'm not convinced this is quite the correct changeset. The current impl Send for World is definitely wrong, but I don't think Turtle is unambiguously the correct API here.

Broadly I think we might need to have variants of World, each differently constructed for Send and not Send
I.e. a SendWorld has static requirements that all component types are Send, and crucially a SendWorld cannot be created from an existing World (the main use for this is Assets, just like Turtle in this PR, but it's about constraining the API more correctly).

As it currently stands, this is equivalent to just not supporting the NonSend apis, so the temptation may be to implement World in terms of SendWorld (i.e. literally as a DerefMut); however this falls down once we relax the bounds on Component, which we're likely to do eventually.

I guess we can still call it 🐢.

@alice-i-cecile alice-i-cecile removed this from the Bevy 0.8 milestone Jun 28, 2022
bors bot pushed a commit that referenced this pull request Jul 8, 2022
# Objective

- Currently, the `Extract` `RenderStage` is executed on the main world, with the render world available as a resource.
- However, when needing access to resources in the render world (e.g. to mutate them), the only way to do so was to get exclusive access to the whole `RenderWorld` resource.
- This meant that effectively only one extract which wrote to resources could run at a time.
- We didn't previously make `Extract`ing writing to the world a non-happy path, even though we want to discourage that.

## Solution

- Move the extract stage to run on the render world.
- Add the main world as a `MainWorld` resource.
- Add an `Extract` `SystemParam` as a convenience to access a (read only) `SystemParam` in the main world during `Extract`.

## Future work

It should be possible to avoid needing to use `get_or_spawn` for the render commands, since now the `Commands`' `Entities` matches up with the world being executed on.
We need to determine how this interacts with #3519
It's theoretically possible to remove the need for the `value` method on `Extract`. However, that requires slightly changing the `SystemParam` interface, which would make it more complicated. That would probably mess up the `SystemState` api too.

## Todo
I still need to add doc comments to `Extract`.

---

## Changelog

### Changed
- The `Extract` `RenderStage` now runs on the render world (instead of the main world as before).
   You must use the `Extract` `SystemParam` to access the main world during the extract phase.
   Resources on the render world can now be accessed using `ResMut` during extract.

### Removed
- `Commands::spawn_and_forget`. Use `Commands::get_or_spawn(e).insert_bundle(bundle)` instead

## Migration Guide

The `Extract` `RenderStage` now runs on the render world (instead of the main world as before).
You must use the `Extract` `SystemParam` to access the main world during the extract phase. `Extract` takes a single type parameter, which is any system parameter (such as `Res`, `Query` etc.). It will extract this from the main world, and returns the result of this extraction when `value` is called on it.

For example, if previously your extract system looked like:
```rust
fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) {
    for cloud in clouds.iter() {
        commands.get_or_spawn(cloud).insert(Cloud);
    }
}
```
the new version would be:
```rust
fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) {
    for cloud in clouds.value().iter() {
        commands.get_or_spawn(cloud).insert(Cloud);
    }
}
```
The diff is:
```diff
--- a/src/clouds.rs
+++ b/src/clouds.rs
@@ -1,5 +1,5 @@
-fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) {
-    for cloud in clouds.iter() {
+fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) {
+    for cloud in clouds.value().iter() {
         commands.get_or_spawn(cloud).insert(Cloud);
     }
 }
```
You can now also access resources from the render world using the normal system parameters during `Extract`:
```rust
fn extract_assets(mut render_assets: ResMut<MyAssets>, source_assets: Extract<Res<MyAssets>>) {
     *render_assets = source_assets.clone();
}
```
Please note that all existing extract systems need to be updated to match this new style; even if they currently compile they will not run as expected. A warning will be emitted on a best-effort basis if this is not met.

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
@DJMcNab DJMcNab closed this Aug 1, 2022
inodentry pushed a commit to IyesGames/bevy that referenced this pull request Aug 8, 2022
# Objective

- Currently, the `Extract` `RenderStage` is executed on the main world, with the render world available as a resource.
- However, when needing access to resources in the render world (e.g. to mutate them), the only way to do so was to get exclusive access to the whole `RenderWorld` resource.
- This meant that effectively only one extract which wrote to resources could run at a time.
- We didn't previously make `Extract`ing writing to the world a non-happy path, even though we want to discourage that.

## Solution

- Move the extract stage to run on the render world.
- Add the main world as a `MainWorld` resource.
- Add an `Extract` `SystemParam` as a convenience to access a (read only) `SystemParam` in the main world during `Extract`.

## Future work

It should be possible to avoid needing to use `get_or_spawn` for the render commands, since now the `Commands`' `Entities` matches up with the world being executed on.
We need to determine how this interacts with bevyengine#3519
It's theoretically possible to remove the need for the `value` method on `Extract`. However, that requires slightly changing the `SystemParam` interface, which would make it more complicated. That would probably mess up the `SystemState` api too.

## Todo
I still need to add doc comments to `Extract`.

---

## Changelog

### Changed
- The `Extract` `RenderStage` now runs on the render world (instead of the main world as before).
   You must use the `Extract` `SystemParam` to access the main world during the extract phase.
   Resources on the render world can now be accessed using `ResMut` during extract.

### Removed
- `Commands::spawn_and_forget`. Use `Commands::get_or_spawn(e).insert_bundle(bundle)` instead

## Migration Guide

The `Extract` `RenderStage` now runs on the render world (instead of the main world as before).
You must use the `Extract` `SystemParam` to access the main world during the extract phase. `Extract` takes a single type parameter, which is any system parameter (such as `Res`, `Query` etc.). It will extract this from the main world, and returns the result of this extraction when `value` is called on it.

For example, if previously your extract system looked like:
```rust
fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) {
    for cloud in clouds.iter() {
        commands.get_or_spawn(cloud).insert(Cloud);
    }
}
```
the new version would be:
```rust
fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) {
    for cloud in clouds.value().iter() {
        commands.get_or_spawn(cloud).insert(Cloud);
    }
}
```
The diff is:
```diff
--- a/src/clouds.rs
+++ b/src/clouds.rs
@@ -1,5 +1,5 @@
-fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) {
-    for cloud in clouds.iter() {
+fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) {
+    for cloud in clouds.value().iter() {
         commands.get_or_spawn(cloud).insert(Cloud);
     }
 }
```
You can now also access resources from the render world using the normal system parameters during `Extract`:
```rust
fn extract_assets(mut render_assets: ResMut<MyAssets>, source_assets: Extract<Res<MyAssets>>) {
     *render_assets = source_assets.clone();
}
```
Please note that all existing extract systems need to be updated to match this new style; even if they currently compile they will not run as expected. A warning will be emitted on a best-effort basis if this is not met.

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
james7132 pushed a commit to james7132/bevy that referenced this pull request Oct 28, 2022
# Objective

- Currently, the `Extract` `RenderStage` is executed on the main world, with the render world available as a resource.
- However, when needing access to resources in the render world (e.g. to mutate them), the only way to do so was to get exclusive access to the whole `RenderWorld` resource.
- This meant that effectively only one extract which wrote to resources could run at a time.
- We didn't previously make `Extract`ing writing to the world a non-happy path, even though we want to discourage that.

## Solution

- Move the extract stage to run on the render world.
- Add the main world as a `MainWorld` resource.
- Add an `Extract` `SystemParam` as a convenience to access a (read only) `SystemParam` in the main world during `Extract`.

## Future work

It should be possible to avoid needing to use `get_or_spawn` for the render commands, since now the `Commands`' `Entities` matches up with the world being executed on.
We need to determine how this interacts with bevyengine#3519
It's theoretically possible to remove the need for the `value` method on `Extract`. However, that requires slightly changing the `SystemParam` interface, which would make it more complicated. That would probably mess up the `SystemState` api too.

## Todo
I still need to add doc comments to `Extract`.

---

## Changelog

### Changed
- The `Extract` `RenderStage` now runs on the render world (instead of the main world as before).
   You must use the `Extract` `SystemParam` to access the main world during the extract phase.
   Resources on the render world can now be accessed using `ResMut` during extract.

### Removed
- `Commands::spawn_and_forget`. Use `Commands::get_or_spawn(e).insert_bundle(bundle)` instead

## Migration Guide

The `Extract` `RenderStage` now runs on the render world (instead of the main world as before).
You must use the `Extract` `SystemParam` to access the main world during the extract phase. `Extract` takes a single type parameter, which is any system parameter (such as `Res`, `Query` etc.). It will extract this from the main world, and returns the result of this extraction when `value` is called on it.

For example, if previously your extract system looked like:
```rust
fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) {
    for cloud in clouds.iter() {
        commands.get_or_spawn(cloud).insert(Cloud);
    }
}
```
the new version would be:
```rust
fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) {
    for cloud in clouds.value().iter() {
        commands.get_or_spawn(cloud).insert(Cloud);
    }
}
```
The diff is:
```diff
--- a/src/clouds.rs
+++ b/src/clouds.rs
@@ -1,5 +1,5 @@
-fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) {
-    for cloud in clouds.iter() {
+fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) {
+    for cloud in clouds.value().iter() {
         commands.get_or_spawn(cloud).insert(Cloud);
     }
 }
```
You can now also access resources from the render world using the normal system parameters during `Extract`:
```rust
fn extract_assets(mut render_assets: ResMut<MyAssets>, source_assets: Extract<Res<MyAssets>>) {
     *render_assets = source_assets.clone();
}
```
Please note that all existing extract systems need to be updated to match this new style; even if they currently compile they will not run as expected. A warning will be emitted on a best-effort basis if this is not met.

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
# Objective

- Currently, the `Extract` `RenderStage` is executed on the main world, with the render world available as a resource.
- However, when needing access to resources in the render world (e.g. to mutate them), the only way to do so was to get exclusive access to the whole `RenderWorld` resource.
- This meant that effectively only one extract which wrote to resources could run at a time.
- We didn't previously make `Extract`ing writing to the world a non-happy path, even though we want to discourage that.

## Solution

- Move the extract stage to run on the render world.
- Add the main world as a `MainWorld` resource.
- Add an `Extract` `SystemParam` as a convenience to access a (read only) `SystemParam` in the main world during `Extract`.

## Future work

It should be possible to avoid needing to use `get_or_spawn` for the render commands, since now the `Commands`' `Entities` matches up with the world being executed on.
We need to determine how this interacts with bevyengine#3519
It's theoretically possible to remove the need for the `value` method on `Extract`. However, that requires slightly changing the `SystemParam` interface, which would make it more complicated. That would probably mess up the `SystemState` api too.

## Todo
I still need to add doc comments to `Extract`.

---

## Changelog

### Changed
- The `Extract` `RenderStage` now runs on the render world (instead of the main world as before).
   You must use the `Extract` `SystemParam` to access the main world during the extract phase.
   Resources on the render world can now be accessed using `ResMut` during extract.

### Removed
- `Commands::spawn_and_forget`. Use `Commands::get_or_spawn(e).insert_bundle(bundle)` instead

## Migration Guide

The `Extract` `RenderStage` now runs on the render world (instead of the main world as before).
You must use the `Extract` `SystemParam` to access the main world during the extract phase. `Extract` takes a single type parameter, which is any system parameter (such as `Res`, `Query` etc.). It will extract this from the main world, and returns the result of this extraction when `value` is called on it.

For example, if previously your extract system looked like:
```rust
fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) {
    for cloud in clouds.iter() {
        commands.get_or_spawn(cloud).insert(Cloud);
    }
}
```
the new version would be:
```rust
fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) {
    for cloud in clouds.value().iter() {
        commands.get_or_spawn(cloud).insert(Cloud);
    }
}
```
The diff is:
```diff
--- a/src/clouds.rs
+++ b/src/clouds.rs
@@ -1,5 +1,5 @@
-fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) {
-    for cloud in clouds.iter() {
+fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) {
+    for cloud in clouds.value().iter() {
         commands.get_or_spawn(cloud).insert(Cloud);
     }
 }
```
You can now also access resources from the render world using the normal system parameters during `Extract`:
```rust
fn extract_assets(mut render_assets: ResMut<MyAssets>, source_assets: Extract<Res<MyAssets>>) {
     *render_assets = source_assets.clone();
}
```
Please note that all existing extract systems need to be updated to match this new style; even if they currently compile they will not run as expected. A warning will be emitted on a best-effort basis if this is not met.

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
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-Bug An unexpected or incorrect behavior P-Unsound A bug that results in undefined compiler behavior X-Controversial There is active debate or serious implications around merging this PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Dropping NonSend resources is unsound
7 participants