bevyengine / bevyengine/bevy

Eliminate `NonSend` resources by making `World` optionally `!Send` using generics

Open
#17,517 0 comments 7 reactions 0 assignees View on GitHub
A-ECS C-Feature D-Complex S-Ready-For-Implementation X-Blessed
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
4d 8m
Merged PRs (30d)
147

Description

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

`NonSend` resources are confusing (they don't impl `Resource`!), badly named (#13481), require API duplication and hamper us when implementing valuable new ECS features like 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.

## What solution would you like?

As discussed in the [resources as entities](https://discord.com/channels/691052431525675048/1331346448662397059/1331813217537757238) Discord thread (and proposed by @bushrat011899, we should support `!Send` `World`s.

We already *have* a powerful, type-erased storage. We should take advantage of it, expanding the possibilities for users while clarifying the semantics and ensuring that our objects are either clearly `Send` or `!Send`.

### PR 0: Send-generic World

To do this, we can add a generic parameter to `World`, which conditionally holds a `PhantomData` which makes it !Send. See [this playground link for a proof of concept](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d3483beb6394417238b7a941b7e4c713). To make that nice, we probably want a sealed trait, meaningful names and an associated constant on that trait that you can check at runtime. Steal from the immutable components design.

### PR 1: Move `Send+Sync` trait bounds

Currently, the `Resource` and `Component` traits require their types to be both `Send` and `Sync`. In this brave new world, this is no longer required! Nonsend resources can be actual resources! We should remove those bounds, and add them to the dangerous methods (hi parallel executor + parallel query iteration) which actually require them.

### PR 2: `App` stores a `!Send` world

As laid out by @maniwani, make all apps regardless of platform would come with a world (thread-safe world) and a centralized thread-local storage (thread-local world).

### PR 3: Thread-safe worlds can access thread-local data

Allow systems in the normal world to access non-send data, stored in the thread-local storage. Normal systems never directly touch non-send data (except on web). The mechanism will look [something like this](https://discord.com/channels/691052431525675048/1331346448662397059/1332094803147620444).

```rust
fn system(mut thread: ThreadLocal) {
thread.run(|tls| {
let x = tls.resource::();
/* ... */
});
}
```

The thread-local world should live to service requests from the thread-safe (main) world, although users with particularly unusual needs could operate with only a thread-local world or add and run systems to it.

### PR 4: Migrate internals to new system

Move all of Bevy's `NonSend` usages to the new strategy, and make sure everything works.

### PR 5: yeet `NonSend`

Remove all of the internal now-redundant machinery that is currently used to implement !Send resources.

## Future work

- better multiworld APIs
- multiple thread-local worlds by default? Audio might want a dedicated storage and thread for performance
- resources-as-entities

## What alternative(s) have you considered?

See the following previous attempts at this flavor of work:

- https://github.com/bevyengine/bevy/pull/3519
- https://github.com/bevyengine/bevy/pull/5135
- https://github.com/bevyengine/bevy/pull/9122

This solution is powerful, elegant and minimizes API duplication.

Using a const boolean would work, as seen in this [playground proof of concept](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=91f3fe2f12229d9fc6e9e666e8378293), but results in uglier and less explicit APIs.

Once `const-adt`s is stabilized, moving to a custom enum will give us a simpler, cleaner solution without sacrificing clarity.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.