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

Relax Resource: Sync bound #6572

Closed
wants to merge 13 commits into from
Closed

Conversation

JoJoJet
Copy link
Member

@JoJoJet JoJoJet commented Nov 13, 2022

Objective

Allow non-Sync types to be used as resources, without having to resort to NonSend<>.

Currently, non-Sync types are conflated with non-Send types, which results in overly restrictive scheduling. Any system that uses non-Send types must be run on the main thread, which increases thread contention and prevents parallelization. This is unnecessary, as Send + !Sync resources could be accessed from any thread, as long as it's only one at a time.

Solution

  • Relax the bound Resource: Sync.
  • Resource trait tracks whether each type is Sync. The scheduler ensures that shared access to non-Sync types does not occur.

Example

// This type can't be shared between threads, but it *can* be sent.
#[derive(Resource, Deref)]
#[resource(is_sync = false)] // compile fails if you forget this
pub struct MyResource(std::cell::Cell<u32>);

fn my_system(x: Res<MyResource>) {
    x.set(x.get() + 1);
}

Future Work

For the time being, systems with read-only access to a non-Sync resource are run on the main thread. This is overly restrictive: we will need to update our Access model to properly support non-Sync reads.


Changelog

  • Resource now supports types that do not implement std::marker::Sync.

Migration Guide

The Resource trait is now unsafe to implement manually. Use of the derive macro is recommended.

// Before
struct MyResource(...);
impl Resource for MyResource {}

// After
struct MyResource(...);
unsafe impl Resource for MyResource {
    // Undefined Behavior if this is wrong. 
    const IS_SYNC: bool = true;
}

Any generic code that relied on Resource: Sync will now need to specify the Sync bound.

// Before:
fn my_system<T: Resource>(
    my_resource: Res<T>,
    q: Query<...>,
) {
    // Since we access `my_resource` across threads (because of par_for_each), we need it to be `Sync`.
    q.par_for_each(|...| {
        do_something_with(&my_resource);
    })
}

// After:
fn my_system<T: Resource + Sync>( // Just add this bound.
    ...
) {
    ...
}

@BoxyUwU BoxyUwU self-requested a review November 13, 2022 01:39
@maniwani
Copy link
Contributor

maniwani commented Nov 13, 2022

Can we avoid having to specify the Sync bound if Access knows which ComponentId or ArchetypeComponentId are Sync?

(Edit: Nevermind, I missed the "Future Work" section.)

@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Performance A change motivated by improving speed, memory usage or compile times C-Usability A simple quality-of-life change that makes Bevy easier to use D-Complex Quite challenging from either a design or technical perspective. Ask for help! labels Nov 14, 2022
@JoJoJet JoJoJet marked this pull request as ready for review November 15, 2022 01:19
@JoJoJet
Copy link
Member Author

JoJoJet commented Nov 15, 2022

Should be ready for review now. Note that this PR is blocked by #6603 .

crates/bevy_tasks/src/task_pool.rs Outdated Show resolved Hide resolved
crates/bevy_transform/src/systems.rs Outdated Show resolved Hide resolved
@@ -371,6 +378,13 @@ impl<'a, T: Resource> SystemParam for Res<'a, T> {
// conflicts with any prior access, a panic will occur.
unsafe impl<T: Resource> SystemParamState for ResState<T> {
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
// If the resource can't be shared between threads, make sure it runs on the main thread.
// FIXME: We should be able to register access as 'exclusive reads' to prevent shared access
// without forcing the system to run on the main thread.
Copy link
Member

Choose a reason for hiding this comment

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

IMO, there's no point to merging this PR as is without this. You would otherwise just use a NonSend for the same effect.

You can treat a Res<T: !Sync> the same as ResMut<T: !Sync>. Move the if !T::IS_SYNC down below, and change the non-Sync branch to add a write instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

IMO, there's no point to merging this PR as is without this. You would otherwise just use a NonSend for the same effect.

I want to keep this PR as small as possible, so it's easier to review. If this gets merged, I'll open a follow-up PR that makes the Access changes.

These changes are easily separable, so I see no reason to roll them up into one PR. Especially since the Access changes are just an optimization.

Also, this already allows ResMut<T: !Sync> to be used off of the main thread.

You can treat a Res<T: !Sync> the same as ResMut<T: !Sync>. Move the if !T::IS_SYNC down below, and change the non-Sync branch to add a write instead.

This won't work, it will mess up ambiguity detection.

crates/bevy_ecs/src/world/mod.rs Outdated Show resolved Hide resolved
impl #impl_generics #bevy_ecs_path::system::Resource for #struct_name #type_generics #where_clause {
// SAFETY: The trait bounds on this impl guarantee that the non-`Sync` types will not have `IS_SYNC = true`.
unsafe impl #impl_generics #bevy_ecs_path::system::Resource for #struct_name #type_generics #where_clause {
const IS_SYNC: bool = #is_sync;
Copy link
Member

Choose a reason for hiding this comment

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

These should definitely have some compile fail tests to ensure that this breaks when this contract is broken.

Separately, we should see if the compile failures are readable by normal users.

Copy link
Member Author

@JoJoJet JoJoJet Nov 21, 2022

Choose a reason for hiding this comment

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

I added a compile fail test. The error message isn't great, but it isn't any worse than before this PR.

I tried a few ways of providing a more descriptive error message, but they all caused problems in edge cases.

Copy link
Member Author

Choose a reason for hiding this comment

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

After investigating further, the error message is actually good for concrete types. It shows up when you define the struct, and points you directly to the field that makes the struct !Sync.

It's less good for generic types, because the error doesn't occur until you try to use the resource.

// Concrete types:

#[derive(Resource)] // error message shows up here, right where we want it.
struct MyResource(Cell<u32>);
// Generic types:

#[derive(Resource)]
struct MyResource<T>(Cell<T>);

// The error message doesn't show up until here, which is un-ideal.
fn my_system(_: Res<MyResource<u32>>) {}

IMO this isn't perfect, but it's perfectly acceptable.

@JoJoJet
Copy link
Member Author

JoJoJet commented Nov 21, 2022

This is no longer blocked by #6603.

@JoJoJet
Copy link
Member Author

JoJoJet commented Dec 5, 2022

I'm closing this in favor of #6864 -- this all seems a bit unnecessary when you could just wrap your !Sync types in a SyncCell.

@JoJoJet JoJoJet closed this Dec 5, 2022
@james7132
Copy link
Member

For what it's worth, I do think !Sync components by themselves are valuable. SyncCell forces mutable access which may force users into triggering change detection, even when it's really just for read-only access. This is bypasssable, but it's not exactly great UX.

@hymm
Copy link
Contributor

hymm commented Dec 7, 2022

could we create our own wrapper that would only trigger change detection on mutable access to the inner value?

@james7132
Copy link
Member

.We'd need to be aware that the type is !Sync in some way. Not sure how to do that in a workable way in Mut<T>.

bors bot pushed a commit that referenced this pull request Dec 11, 2022
# Objective
It's not clear to users how to handle `!Sync` types as components and resources in the absence of engine level support for them. 

## Solution
Added a section to `Component`'s and `Resource`'s type level docs on available options for making a type `Sync` when it holds `!Sync` fields, linking `bevy_utils::synccell::SyncCell` and the currently unstable `std::sync::Exclusive`.

Also added a compile_fail doctest that illustrates how to apply `SyncCell`. These will break when/if #6572 gets merged, at which point these docs should be updated.
alradish pushed a commit to alradish/bevy that referenced this pull request Jan 22, 2023
…ine#6864)

# Objective
It's not clear to users how to handle `!Sync` types as components and resources in the absence of engine level support for them. 

## Solution
Added a section to `Component`'s and `Resource`'s type level docs on available options for making a type `Sync` when it holds `!Sync` fields, linking `bevy_utils::synccell::SyncCell` and the currently unstable `std::sync::Exclusive`.

Also added a compile_fail doctest that illustrates how to apply `SyncCell`. These will break when/if bevyengine#6572 gets merged, at which point these docs should be updated.
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
…ine#6864)

# Objective
It's not clear to users how to handle `!Sync` types as components and resources in the absence of engine level support for them. 

## Solution
Added a section to `Component`'s and `Resource`'s type level docs on available options for making a type `Sync` when it holds `!Sync` fields, linking `bevy_utils::synccell::SyncCell` and the currently unstable `std::sync::Exclusive`.

Also added a compile_fail doctest that illustrates how to apply `SyncCell`. These will break when/if bevyengine#6572 gets merged, at which point these docs should be updated.
@JoJoJet JoJoJet deleted the non-sync-resource branch September 20, 2023 05:20
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-Performance A change motivated by improving speed, memory usage or compile times C-Usability A simple quality-of-life change that makes Bevy easier to use D-Complex Quite challenging from either a design or technical perspective. Ask for help!
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants