bevyengine / bevyengine/bevy

🐢 Eliminate `!Send` resources by supporting `Send` and `!Send` `World` in the same binary

Open
#17,667 8 comments 3 reactions 0 assignees View on GitHub
A-ECS C-Code-Quality D-Complex S-Waiting-on-SME X-Needs-SME
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 16h
Merged PRs (30d)
171

Description

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

This issue is a replacement and extension of #17517, which attempts to solve the same problem by creating an optionally `!Send` `World` using generics. The problems with `!Send` resources listed in that issue are the same issues we are trying to solve here:
> `NonSend` resources are confusing (they don't impl `Resource`!), badly named ([#13481](https://github.com/bevyengine/bevy/pull/13481)), require API duplication and hamper us when implementing valuable new ECS features like [#17485](https://github.com/bevyengine/bevy/pull/17485).
>
> That said, not everything we might want to stick in and access from the ECS is threadsafe! Audio, window handles, scripting language integrations and more are all inherently !Send. Simply removing them is not an option. Ultimately, the problem is that our model here is muddy, hampering our ability to teach, reason about and safely improve these areas of code.
>
> Simultaneously, users have requested support for [`!Send` components](https://discord.com/channels/691052431525675048/1331346448662397059/1331817541202477197), particularly for use with web or scripting languages.

Through [discussion on Discord](https://discord.com/channels/691052431525675048/1332109626962874468) and experimentation, we have determined that making `World` optionally `!Send` using generics is a bit messy and hacky, and we want a more long-term solution rather than a work-around.

In summary, we need to solve these problems:
1. Eliminate `!Send` resources so the [resources-as-components](#17485) effort can be unblocked, and...
2. Find a place to store the data from those `!Send` resources while still supporting their `!Send` status

## What solution would you like?

### Short term

Short term, we can replace `!Send` resources with `thread_local!`. This will unblock the resources-as-components effort. However, `thread_local!` requires `std`, which conflicts with Bevy's support for `no_std`, so this is not a good long-term solution.

Long term, @bushrat011899 brought up supporting both `Send` and `!Send` `World`s in the same binary. This would allow us to store the data that is currently in `!Send` resources in it's own `!Send` `World` as `!Send` components (since resources-as-components aims to remove the concept of resources altogether). If data needs to be shared from the `Send` `World` to the `!Send` `World`, @maniwani has suggested an API feature for sending functions to be run on other worlds. Usage might look something like this:
```rust
fn send_something_to_foo_world(
mut worlds: Worlds,
foo_world: Res,
) {
let id: WorldId = *foo_world.0;
worlds.get_mut(id).block_on(move |mut world| {
// Something to run on the world with non-send Foo stuff
});
}
```

### Long term

The long-term plan looks like this:
1. We make non-send resources `thread_local!` so that resources-as-components is unblocked #17682
2. Create inter-thread executor that allows a system to execute arbitrary code on the main thread. This is needed because currently the only way to ensure a System runs on the main thread is to use `!Send` system params, which is getting removed. We need to ensure any `thread_local!`s are running on the same thread they're created in. #18172
3. Create official support for `Send` and `!Send` `World` in a single binary a. More details to follow
4. Move `thread_local!` global vars into a new `!Send` `World` as `!Send` componentsMore issues will be created to break this work out further## What alternative(s) have you considered?
5. Move main `World` to a thread that is not main thread

Some ideas we have discussed:
1. Only officially supporting `Send` `World` (not supporting `!Send` `World`) and requiring that users store their own `!Send` data. However, that would come at the cost of worse UX
2. Only supporting *either* a `Send` `World` *or* a `!Send` `World` in the same binary, but not both simultaneously and using a separate data store for `!Send` global data to replace our `!Send` resource usage. However, this would require the creation of a new `!Send` data store, which can be solved automatically by just creating support for `Send` and `!Send` `World`s to co-exist. Imo, it is cleaner to use a `!Send` `World` as a `!Send` data store and doesn't require us to re-engineer something we already have implemented. Plus having a `Send` and `!Send` `World` co-exist in the same binary is more dynamically useful (it's more general and has potential to be more generally useful to end users and/or other bevy teams to solve a variety of other problems).

## Why 🐢 turtle emoji?

Because that's the name of the [Discord work group](https://discord.com/channels/691052431525675048/1332109626962874468) that discusses this issue. And turtles are cute.

Contributor guide

Open the contributing guide

Research direction

Start by reading the replaced issue #17517 and the long-term work items #17682 and #18172, then review the discussion and experimentation referenced here. The issue is complete only when Bevy officially supports Send and !Send World instances in one binary while preserving the stated no_std and thread-local requirements.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
game-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.