From aae31d8c0469b44861ade3540f8c441eda8ba53d Mon Sep 17 00:00:00 2001 From: Michael Hsu Date: Wed, 16 Nov 2022 12:23:08 -0800 Subject: [PATCH 1/6] add a thread marker system param --- crates/bevy_ecs/src/system/mod.rs | 2 + crates/bevy_ecs/src/system/thread_marker.rs | 91 +++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 crates/bevy_ecs/src/system/thread_marker.rs diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 0dcc806c6d44a..02d92f389dbd9 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -77,6 +77,7 @@ mod query; mod system; mod system_param; mod system_piping; +mod thread_marker; pub use commands::*; pub use exclusive_function_system::*; @@ -86,6 +87,7 @@ pub use query::*; pub use system::*; pub use system_param::*; pub use system_piping::*; +pub use thread_marker::*; /// Ensure that a given function is a system /// diff --git a/crates/bevy_ecs/src/system/thread_marker.rs b/crates/bevy_ecs/src/system/thread_marker.rs new file mode 100644 index 0000000000000..0419d0b377aa3 --- /dev/null +++ b/crates/bevy_ecs/src/system/thread_marker.rs @@ -0,0 +1,91 @@ +use std::ops::{Deref, DerefMut}; + +use crate::prelude::World; + +use super::{SystemMeta, SystemParam, SystemParamFetch, SystemParamItem, SystemParamState}; + +pub struct MainThread<'w, 's, T: SystemParam>(pub SystemParamItem<'w, 's, T>); +impl<'w, 's, T: SystemParam + Send + Sync + 'static> SystemParam for MainThread<'w, 's, T> { + type Fetch = MainThreadState; +} + +impl<'w, 's, T: SystemParam + Send + Sync> Deref for MainThread<'w, 's, T> { + type Target = SystemParamItem<'w, 's, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'w, 's, T: SystemParam + Send + Sync> DerefMut for MainThread<'w, 's, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'w, 's, T: SystemParam + Send + Sync> AsRef> + for MainThread<'w, 's, T> +{ + #[inline] + fn as_ref(&self) -> &SystemParamItem<'w, 's, T> { + self.deref() + } +} + +pub struct MainThreadState(T::Fetch); + +// SAFETY: this impl defers to `NonSendMutState`, which initializes +// and validates the correct world access +unsafe impl SystemParamState for MainThreadState { + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { + system_meta.set_non_send(); + Self(T::Fetch::init(world, system_meta)) + } +} + +impl<'w, 's, T: SystemParam + Send + Sync + 'static> SystemParamFetch<'w, 's> for MainThreadState +where + T::Fetch: SystemParamState, +{ + type Item = MainThread<'w, 's, T>; + + #[inline] + unsafe fn get_param( + state: &'s mut Self, + system_meta: &SystemMeta, + world: &'w World, + change_tick: u32, + ) -> Self::Item { + MainThread(T::Fetch::get_param( + &mut state.0, + system_meta, + world, + change_tick, + )) + } +} + +#[cfg(test)] +mod tests { + use crate as bevy_ecs; + use crate::prelude::World; + use crate::schedule::{Stage, SystemStage}; + use crate::system::{MainThread, ResMut, Resource}; + + #[derive(Resource)] + struct A(pub usize); + + #[test] + fn test() { + fn system(mut non_send_res: MainThread>) { + (*non_send_res).0 = 1; + } + let mut world = World::new(); + world.insert_resource(A(0)); + let mut stage = SystemStage::parallel(); + stage.add_system(system); + stage.run(&mut world); + let res = world.get_resource::().unwrap(); + assert_eq!(res.0, 1); + } +} From 41a93d7ce07bb7356b4a8abfdfc3e95c1b746060 Mon Sep 17 00:00:00 2001 From: Michael Hsu Date: Wed, 16 Nov 2022 13:31:59 -0800 Subject: [PATCH 2/6] make a tls abstraction and test on audio --- crates/bevy_audio/src/audio_output.rs | 8 ++- crates/bevy_audio/src/lib.rs | 4 +- crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/src/system/thread_marker.rs | 77 ++++++++++++++++++++- 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index f42db09df654c..b48a4854436f6 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -1,6 +1,6 @@ use crate::{Audio, AudioSource, Decodable}; use bevy_asset::{Asset, Assets}; -use bevy_ecs::system::{NonSend, Res, ResMut}; +use bevy_ecs::system::{MainThread, Res, ResMut, Tls}; use bevy_reflect::TypeUuid; use bevy_utils::tracing::warn; use rodio::{OutputStream, OutputStreamHandle, Sink, Source}; @@ -84,13 +84,15 @@ where /// Plays audio currently queued in the [`Audio`] resource through the [`AudioOutput`] resource pub fn play_queued_audio_system( - audio_output: NonSend>, + audio_output: MainThread>>>, audio_sources: Option>>, mut audio: ResMut>, mut sinks: ResMut>, ) { if let Some(audio_sources) = audio_sources { - audio_output.try_play_queued(&*audio_sources, &mut *audio, &mut sinks); + audio_output.get(|audio_output| { + audio_output.try_play_queued(&*audio_sources, &mut *audio, &mut sinks); + }); }; } diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index 7c4db0090c77c..6410df525fe06 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -41,6 +41,7 @@ pub use rodio::Sample; use bevy_app::prelude::*; use bevy_asset::AddAsset; +use bevy_ecs::system::Tls; /// Adds support for audio playback to a Bevy Application /// @@ -50,7 +51,8 @@ pub struct AudioPlugin; impl Plugin for AudioPlugin { fn build(&self, app: &mut App) { - app.init_non_send_resource::>() + let audio_output = Tls::new(AudioOutput::::default()); + app.insert_resource(audio_output) .add_asset::() .add_asset::() .init_resource::>() diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 000c2121a7d1c..88d48d40189d6 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -23,6 +23,7 @@ bevy_ecs_macros = { path = "macros", version = "0.9.0" } async-channel = "1.4" event-listener = "2.5" thread_local = "1.1.4" +thread-local-object = "0.1.0" fixedbitset = "0.4" fxhash = "0.2" downcast-rs = "1.2" diff --git a/crates/bevy_ecs/src/system/thread_marker.rs b/crates/bevy_ecs/src/system/thread_marker.rs index 0419d0b377aa3..d468df9ad0cb6 100644 --- a/crates/bevy_ecs/src/system/thread_marker.rs +++ b/crates/bevy_ecs/src/system/thread_marker.rs @@ -1,8 +1,15 @@ -use std::ops::{Deref, DerefMut}; +use std::{ + ops::{Deref, DerefMut}, + sync::Arc, +}; +use thread_local_object::ThreadLocal; +use crate as bevy_ecs; use crate::prelude::World; -use super::{SystemMeta, SystemParam, SystemParamFetch, SystemParamItem, SystemParamState}; +use super::{ + Resource, SystemMeta, SystemParam, SystemParamFetch, SystemParamItem, SystemParamState, +}; pub struct MainThread<'w, 's, T: SystemParam>(pub SystemParamItem<'w, 's, T>); impl<'w, 's, T: SystemParam + Send + Sync + 'static> SystemParam for MainThread<'w, 's, T> { @@ -65,6 +72,72 @@ where } } +#[derive(Resource)] +pub struct Tls(Arc>); + +impl Tls { + pub fn new(value: T) -> Self { + let tls = Arc::new(ThreadLocal::new()); + tls.set(value); + Tls(tls) + } + + pub fn set(&self, value: T) -> Option { + self.0.set(value) + } + + pub fn remove(&mut self) -> Option { + self.0.remove() + } + + pub fn get(&self, f: F) -> R + where + F: FnOnce(&T) -> R, + { + self.0.get(|t| + // TODO: add typename to error message. possibly add reference to NonSend System param + f(t.unwrap_or_else(|| + panic!( + "Requested non-send resource {} does not exist on this thread. + You may be on the wrong thread or need to call .set on the resource.", + std::any::type_name::() + ) + ))) + } + + pub fn get_mut(&mut self, f: F) -> R + where + F: FnOnce(&mut T) -> R, + { + self.0.get_mut(|t| + // TODO: add typename to error message. possibly add reference to NonSend System param + f(t.unwrap_or_else(|| + panic!( + "Requested non-send resource {} does not exist on this thread. + You may be on the wrong thread or need to call .set on the resource.", + std::any::type_name::() + ) + ))) + } +} + +// TODO: This drop impl is needed because AudioOutput was panicking when +// it was being dropped when the thread local storage was being dropped. +// This tries to drop the resource on the current thread, which fixes +// things for when the world is on the main thread, but would probably +// break if the world is moved to a different thread. We should figure +// out a more robust way of dropping the resource instead. +impl Drop for Tls { + fn drop(&mut self) { + self.remove(); + } +} + +// pretty sure this is safe as ThreadLocal just wraps a usize and a phatom data +// the usize is only written to on the call to ThreadLocal::new() +unsafe impl Send for Tls {} +unsafe impl Sync for Tls {} + #[cfg(test)] mod tests { use crate as bevy_ecs; From 37550f730d53185af2e07718c1079f9785a6c373 Mon Sep 17 00:00:00 2001 From: Michael Hsu Date: Wed, 16 Nov 2022 14:24:33 -0800 Subject: [PATCH 3/6] migrate plugins and delete nonsend --- crates/bevy_app/src/app.rs | 33 - crates/bevy_ecs/src/change_detection.rs | 58 +- crates/bevy_ecs/src/component.rs | 28 +- crates/bevy_ecs/src/lib.rs | 59 +- .../src/schedule/ambiguity_detection.rs | 5 +- .../src/schedule/executor_parallel.rs | 10 +- crates/bevy_ecs/src/storage/resource.rs | 22 +- crates/bevy_ecs/src/system/mod.rs | 23 +- crates/bevy_ecs/src/system/system_param.rs | 295 +------ crates/bevy_ecs/src/system/thread_marker.rs | 1 + crates/bevy_ecs/src/world/mod.rs | 202 +---- crates/bevy_ecs/src/world/world_cell.rs | 69 -- crates/bevy_gilrs/src/gilrs_system.rs | 102 +-- crates/bevy_gilrs/src/lib.rs | 4 +- crates/bevy_render/src/view/window.rs | 4 +- crates/bevy_winit/src/lib.rs | 747 +++++++++--------- 16 files changed, 473 insertions(+), 1189 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index c6613825591ec..d99226a1f73a2 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -707,29 +707,6 @@ impl App { self } - /// Inserts a non-send resource to the app. - /// - /// You usually want to use [`insert_resource`](Self::insert_resource), - /// but there are some special cases when a resource cannot be sent across threads. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// struct MyCounter { - /// counter: usize, - /// } - /// - /// App::new() - /// .insert_non_send_resource(MyCounter { counter: 0 }); - /// ``` - pub fn insert_non_send_resource(&mut self, resource: R) -> &mut Self { - self.world.insert_non_send_resource(resource); - self - } - /// Initialize a [`Resource`] with standard starting values by adding it to the [`World`]. /// /// If the [`Resource`] already exists, nothing happens. @@ -765,16 +742,6 @@ impl App { self } - /// Initialize a non-send [`Resource`] with standard starting values by adding it to the [`World`]. - /// - /// The [`Resource`] must implement the [`FromWorld`] trait. - /// If the [`Default`] trait is implemented, the [`FromWorld`] trait will use - /// the [`Default::default`] method to initialize the [`Resource`]. - pub fn init_non_send_resource(&mut self) -> &mut Self { - self.world.init_non_send_resource::(); - self - } - /// Sets the function that will be called when the app is run. /// /// The runner function `run_fn` is called only once by [`App::run`]. If the diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index b8d1f7c196d35..1aa991ce80d39 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -285,38 +285,6 @@ impl<'a, T: Resource> From> for Mut<'a, T> { } } -/// Unique borrow of a non-[`Send`] resource. -/// -/// Only [`Send`] resources may be accessed with the [`ResMut`] [`SystemParam`](crate::system::SystemParam). In case that the -/// resource does not implement `Send`, this `SystemParam` wrapper can be used. This will instruct -/// the scheduler to instead run the system on the main thread so that it doesn't send the resource -/// over to another thread. -/// -/// # Panics -/// -/// Panics when used as a `SystemParameter` if the resource does not exist. -/// -/// Use `Option>` instead if the resource might not always exist. -pub struct NonSendMut<'a, T: ?Sized + 'static> { - pub(crate) value: &'a mut T, - pub(crate) ticks: Ticks<'a>, -} - -change_detection_impl!(NonSendMut<'a, T>, T,); -impl_methods!(NonSendMut<'a, T>, T,); -impl_debug!(NonSendMut<'a, T>,); - -impl<'a, T: 'static> From> for Mut<'a, T> { - /// Convert this `NonSendMut` into a `Mut`. This allows keeping the change-detection feature of `Mut` - /// while losing the specificity of `NonSendMut`. - fn from(other: NonSendMut<'a, T>) -> Mut<'a, T> { - Mut { - value: other.value, - ticks: other.ticks, - } - } -} - /// Unique mutable borrow of an entity's component pub struct Mut<'a, T: ?Sized> { pub(crate) value: &'a mut T, @@ -430,7 +398,7 @@ mod tests { use crate::{ self as bevy_ecs, change_detection::{ - ComponentTicks, Mut, NonSendMut, ResMut, Ticks, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE, + ComponentTicks, Mut, ResMut, Ticks, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE, }, component::Component, query::ChangeTrackers, @@ -555,30 +523,6 @@ mod tests { assert_eq!(4, into_mut.ticks.change_tick); } - #[test] - fn mut_from_non_send_mut() { - let mut component_ticks = ComponentTicks { - added: 1, - changed: 2, - }; - let ticks = Ticks { - component_ticks: &mut component_ticks, - last_change_tick: 3, - change_tick: 4, - }; - let mut res = R {}; - let non_send_mut = NonSendMut { - value: &mut res, - ticks, - }; - - let into_mut: Mut = non_send_mut.into(); - assert_eq!(1, into_mut.ticks.component_ticks.added); - assert_eq!(2, into_mut.ticks.component_ticks.changed); - assert_eq!(3, into_mut.ticks.last_change_tick); - assert_eq!(4, into_mut.ticks.change_tick); - } - #[test] fn map_mut() { use super::*; diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index c59a1e2028737..4d2af6dd61bc6 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -7,12 +7,7 @@ use crate::{ }; pub use bevy_ecs_macros::Component; use bevy_ptr::OwningPtr; -use std::{ - alloc::Layout, - any::{Any, TypeId}, - borrow::Cow, - mem::needs_drop, -}; +use std::{alloc::Layout, any::TypeId, borrow::Cow, mem::needs_drop}; /// A data type that can be used to store data for an [entity]. /// @@ -335,17 +330,6 @@ impl ComponentDescriptor { } } - fn new_non_send(storage_type: StorageType) -> Self { - Self { - name: Cow::Borrowed(std::any::type_name::()), - storage_type, - is_send_and_sync: false, - type_id: Some(TypeId::of::()), - layout: Layout::new::(), - drop: needs_drop::().then_some(Self::drop_ptr:: as _), - } - } - #[inline] pub fn storage_type(&self) -> StorageType { self.storage_type @@ -482,16 +466,6 @@ impl Components { } } - #[inline] - pub fn init_non_send(&mut self) -> ComponentId { - // SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`] - unsafe { - self.get_or_insert_resource_with(TypeId::of::(), || { - ComponentDescriptor::new_non_send::(StorageType::default()) - }) - } - } - /// # Safety /// /// The [`ComponentDescriptor`] must match the [`TypeId`] diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index a91ed6634f1b6..6ace70fe3acc0 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -38,9 +38,9 @@ pub mod prelude { Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage, }, system::{ - adapter as system_adapter, Commands, In, IntoPipeSystem, IntoSystem, Local, NonSend, - NonSendMut, ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut, - Resource, System, SystemParamFunction, + adapter as system_adapter, Commands, In, IntoPipeSystem, IntoSystem, Local, + ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut, Resource, System, + SystemParamFunction, }, world::{FromWorld, Mut, World}, }; @@ -1173,27 +1173,6 @@ mod tests { ); } - #[test] - fn non_send_resource() { - let mut world = World::default(); - world.insert_non_send_resource(123i32); - world.insert_non_send_resource(456i64); - assert_eq!(*world.non_send_resource::(), 123); - assert_eq!(*world.non_send_resource_mut::(), 456); - } - - #[test] - #[should_panic] - fn non_send_resource_panic() { - let mut world = World::default(); - world.insert_non_send_resource(0i32); - std::thread::spawn(move || { - let _ = world.non_send_resource_mut::(); - }) - .join() - .unwrap(); - } - #[test] fn trackers_query() { let mut world = World::default(); @@ -1321,38 +1300,6 @@ mod tests { assert_eq!(world.resource::().0, 1); } - #[test] - fn non_send_resource_scope() { - let mut world = World::default(); - world.insert_non_send_resource(NonSendA::default()); - world.resource_scope(|world: &mut World, mut value: Mut| { - value.0 += 1; - assert!(!world.contains_resource::()); - }); - assert_eq!(world.non_send_resource::().0, 1); - } - - #[test] - #[should_panic( - expected = "attempted to access NonSend resource bevy_ecs::tests::NonSendA off of the main thread" - )] - fn non_send_resource_scope_from_different_thread() { - let mut world = World::default(); - world.insert_non_send_resource(NonSendA::default()); - - let thread = std::thread::spawn(move || { - // Accessing the non-send resource on a different thread - // Should result in a panic - world.resource_scope(|_: &mut World, mut value: Mut| { - value.0 += 1; - }); - }); - - if let Err(err) = thread.join() { - std::panic::resume_unwind(err); - } - } - #[test] fn insert_overwrite_drop() { let (dropck1, dropped1) = DropCk::new_pair(); diff --git a/crates/bevy_ecs/src/schedule/ambiguity_detection.rs b/crates/bevy_ecs/src/schedule/ambiguity_detection.rs index 558754c9dfcd5..11949ff63e480 100644 --- a/crates/bevy_ecs/src/schedule/ambiguity_detection.rs +++ b/crates/bevy_ecs/src/schedule/ambiguity_detection.rs @@ -282,6 +282,7 @@ mod tests { use crate as bevy_ecs; use crate::event::Events; use crate::prelude::*; + use crate::system::MainThread; #[derive(Resource)] struct R; @@ -298,8 +299,8 @@ mod tests { fn empty_system() {} fn res_system(_res: Res) {} fn resmut_system(_res: ResMut) {} - fn nonsend_system(_ns: NonSend) {} - fn nonsendmut_system(_ns: NonSendMut) {} + fn nonsend_system(_ns: MainThread>) {} + fn nonsendmut_system(_ns: MainThread>) {} fn read_component_system(_query: Query<&A>) {} fn write_component_system(_query: Query<&mut A>) {} fn with_filtered_component_system(_query: Query<&mut A, With>) {} diff --git a/crates/bevy_ecs/src/schedule/executor_parallel.rs b/crates/bevy_ecs/src/schedule/executor_parallel.rs index 68dd1f1ea798d..12825c42bea8c 100644 --- a/crates/bevy_ecs/src/schedule/executor_parallel.rs +++ b/crates/bevy_ecs/src/schedule/executor_parallel.rs @@ -401,7 +401,7 @@ mod tests { schedule::{ executor_parallel::scheduling_event::*, SingleThreadedExecutor, Stage, SystemStage, }, - system::{NonSend, Query, Res, ResMut, Resource}, + system::{MainThread, Query, Res, ResMut, Resource, Tls}, world::World, }; @@ -536,9 +536,11 @@ mod tests { fn non_send_resource() { use std::thread; let mut world = World::new(); - world.insert_non_send_resource(thread::current().id()); - fn non_send(thread_id: NonSend) { - assert_eq!(thread::current().id(), *thread_id); + world.insert_resource(Tls::new(thread::current().id())); + fn non_send(thread_id: MainThread>>) { + thread_id.get(|thread_id| { + assert_eq!(thread::current().id(), *thread_id); + }); } fn empty() {} let mut stage = SystemStage::parallel() diff --git a/crates/bevy_ecs/src/storage/resource.rs b/crates/bevy_ecs/src/storage/resource.rs index e047fb3c96bf8..8a903fd18b26b 100644 --- a/crates/bevy_ecs/src/storage/resource.rs +++ b/crates/bevy_ecs/src/storage/resource.rs @@ -53,11 +53,6 @@ impl ResourceData { /// /// # Safety /// `value` must be valid for the underlying type for the resource. - /// - /// The underlying type must be [`Send`] or be inserted from the main thread. - /// This can be validated with [`World::validate_non_send_access_untyped`]. - /// - /// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped #[inline] pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: u32) { if self.is_present() { @@ -72,11 +67,6 @@ impl ResourceData { /// /// # Safety /// `value` must be valid for the underlying type for the resource. - /// - /// The underlying type must be [`Send`] or be inserted from the main thread. - /// This can be validated with [`World::validate_non_send_access_untyped`]. - /// - /// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped #[inline] pub(crate) unsafe fn insert_with_ticks( &mut self, @@ -94,12 +84,7 @@ impl ResourceData { /// Removes a value from the resource, if present. /// /// # Safety - /// The underlying type must be [`Send`] or be removed from the main thread. - /// This can be validated with [`World::validate_non_send_access_untyped`]. - /// - /// The removed value must be used or dropped. - /// - /// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped + /// The underlying type must be [`Send`]. #[inline] #[must_use = "The returned pointer to the removed component should be used or dropped"] pub(crate) unsafe fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks)> { @@ -109,10 +94,7 @@ impl ResourceData { /// Removes a value from the resource, if present, and drops it. /// /// # Safety - /// The underlying type must be [`Send`] or be removed from the main thread. - /// This can be validated with [`World::validate_non_send_access_untyped`]. - /// - /// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped + /// The underlying type must be [`Send`]. #[inline] pub(crate) unsafe fn remove_and_drop(&mut self) { self.column.clear(); diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 02d92f389dbd9..9445de77976b9 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -108,6 +108,7 @@ mod tests { use crate::prelude::StageLabel; + use crate::system::{MainThread, Tls}; use crate::{ self as bevy_ecs, archetype::{ArchetypeComponentId, Archetypes}, @@ -118,8 +119,8 @@ mod tests { query::{Added, Changed, Or, With, Without}, schedule::{Schedule, Stage, SystemStage}, system::{ - Commands, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, QueryComponentError, - RemovedComponents, Res, ResMut, Resource, System, SystemState, + Commands, IntoSystem, Local, ParamSet, Query, QueryComponentError, RemovedComponents, + Res, ResMut, Resource, System, SystemState, }, world::{FromWorld, World}, }; @@ -513,14 +514,16 @@ mod tests { world.insert_resource(SystemRan::No); struct NotSend1(std::rc::Rc); struct NotSend2(std::rc::Rc); - world.insert_non_send_resource(NotSend1(std::rc::Rc::new(0))); + world.insert_resource(Tls::new(NotSend1(std::rc::Rc::new(0)))); fn sys( - op: Option>, - mut _op2: Option>, + op: MainThread>>>, + mut _op2: MainThread>>>, mut system_ran: ResMut, ) { - op.expect("NonSend should exist"); + // TODO: need to fix this. Probably should just implement SystemPAram + // for Option> + let MainThread(Some(_op)) = op else {panic!("blah");}; *system_ran = SystemRan::Yes; } @@ -537,12 +540,12 @@ mod tests { struct NotSend1(std::rc::Rc); struct NotSend2(std::rc::Rc); - world.insert_non_send_resource(NotSend1(std::rc::Rc::new(1))); - world.insert_non_send_resource(NotSend2(std::rc::Rc::new(2))); + world.insert_resource(Tls::new(NotSend1(std::rc::Rc::new(1)))); + world.insert_resource(Tls::new(NotSend2(std::rc::Rc::new(2)))); fn sys( - _op: NonSend, - mut _op2: NonSendMut, + _op: MainThread>>, + mut _op2: MainThread>>, mut system_ran: ResMut, ) { *system_ran = SystemRan::Yes; diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index acfdd8f7224e2..cd973d82cf4a8 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -1,4 +1,4 @@ -pub use crate::change_detection::{NonSendMut, ResMut}; +pub use crate::change_detection::ResMut; use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundles, @@ -887,299 +887,6 @@ impl<'w, 's, T: Component> SystemParamFetch<'w, 's> for RemovedComponentsState>` instead if the resource might not always exist. -pub struct NonSend<'w, T: 'static> { - pub(crate) value: &'w T, - ticks: ComponentTicks, - last_change_tick: u32, - change_tick: u32, -} - -// SAFETY: Only reads a single World non-send resource -unsafe impl ReadOnlySystemParamFetch for NonSendState {} - -impl<'w, T> Debug for NonSend<'w, T> -where - T: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("NonSend").field(&self.value).finish() - } -} - -impl<'w, T: 'static> NonSend<'w, T> { - /// Returns `true` if the resource was added after the system last ran. - pub fn is_added(&self) -> bool { - self.ticks.is_added(self.last_change_tick, self.change_tick) - } - - /// Returns `true` if the resource was added or mutably dereferenced after the system last ran. - pub fn is_changed(&self) -> bool { - self.ticks - .is_changed(self.last_change_tick, self.change_tick) - } -} - -impl<'w, T> Deref for NonSend<'w, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.value - } -} -impl<'a, T> From> for NonSend<'a, T> { - fn from(nsm: NonSendMut<'a, T>) -> Self { - Self { - value: nsm.value, - ticks: nsm.ticks.component_ticks.to_owned(), - change_tick: nsm.ticks.change_tick, - last_change_tick: nsm.ticks.last_change_tick, - } - } -} - -/// The [`SystemParamState`] of [`NonSend`]. -#[doc(hidden)] -pub struct NonSendState { - component_id: ComponentId, - marker: PhantomData T>, -} - -impl<'a, T: 'static> SystemParam for NonSend<'a, T> { - type Fetch = NonSendState; -} - -// SAFETY: NonSendComponentId and ArchetypeComponentId access is applied to SystemMeta. If this -// NonSend conflicts with any prior access, a panic will occur. -unsafe impl SystemParamState for NonSendState { - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { - system_meta.set_non_send(); - - let component_id = world.initialize_non_send_resource::(); - let combined_access = system_meta.component_access_set.combined_access(); - assert!( - !combined_access.has_write(component_id), - "error[B0002]: NonSend<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access.", - std::any::type_name::(), - system_meta.name, - ); - system_meta - .component_access_set - .add_unfiltered_read(component_id); - - let archetype_component_id = world - .get_resource_archetype_component_id(component_id) - .unwrap(); - system_meta - .archetype_component_access - .add_read(archetype_component_id); - Self { - component_id, - marker: PhantomData, - } - } -} - -impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendState { - type Item = NonSend<'w, T>; - - #[inline] - unsafe fn get_param( - state: &'s mut Self, - system_meta: &SystemMeta, - world: &'w World, - change_tick: u32, - ) -> Self::Item { - world.validate_non_send_access::(); - let (ptr, ticks) = world - .get_resource_with_ticks(state.component_id) - .unwrap_or_else(|| { - panic!( - "Non-send resource requested by {} does not exist: {}", - system_meta.name, - std::any::type_name::() - ) - }); - - NonSend { - value: ptr.deref(), - ticks: ticks.read(), - last_change_tick: system_meta.last_change_tick, - change_tick, - } - } -} - -/// The [`SystemParamState`] of [`Option>`]. -/// See: [`NonSend`] -#[doc(hidden)] -pub struct OptionNonSendState(NonSendState); - -impl<'w, T: 'static> SystemParam for Option> { - type Fetch = OptionNonSendState; -} - -// SAFETY: Only reads a single non-send resource -unsafe impl ReadOnlySystemParamFetch for OptionNonSendState {} - -// SAFETY: this impl defers to `NonSendState`, which initializes -// and validates the correct world access -unsafe impl SystemParamState for OptionNonSendState { - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { - Self(NonSendState::init(world, system_meta)) - } -} - -impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendState { - type Item = Option>; - - #[inline] - unsafe fn get_param( - state: &'s mut Self, - system_meta: &SystemMeta, - world: &'w World, - change_tick: u32, - ) -> Self::Item { - world.validate_non_send_access::(); - world - .get_resource_with_ticks(state.0.component_id) - .map(|(ptr, ticks)| NonSend { - value: ptr.deref(), - ticks: ticks.read(), - last_change_tick: system_meta.last_change_tick, - change_tick, - }) - } -} - -/// The [`SystemParamState`] of [`NonSendMut`]. -#[doc(hidden)] -pub struct NonSendMutState { - component_id: ComponentId, - marker: PhantomData T>, -} - -impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { - type Fetch = NonSendMutState; -} - -// SAFETY: NonSendMut ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this -// NonSendMut conflicts with any prior access, a panic will occur. -unsafe impl SystemParamState for NonSendMutState { - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { - system_meta.set_non_send(); - - let component_id = world.initialize_non_send_resource::(); - let combined_access = system_meta.component_access_set.combined_access(); - if combined_access.has_write(component_id) { - panic!( - "error[B0002]: NonSendMut<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access.", - std::any::type_name::(), system_meta.name); - } else if combined_access.has_read(component_id) { - panic!( - "error[B0002]: NonSendMut<{}> in system {} conflicts with a previous immutable resource access ({0}). Consider removing the duplicate access.", - std::any::type_name::(), system_meta.name); - } - system_meta - .component_access_set - .add_unfiltered_write(component_id); - - let archetype_component_id = world - .get_resource_archetype_component_id(component_id) - .unwrap(); - system_meta - .archetype_component_access - .add_write(archetype_component_id); - Self { - component_id, - marker: PhantomData, - } - } -} - -impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendMutState { - type Item = NonSendMut<'w, T>; - - #[inline] - unsafe fn get_param( - state: &'s mut Self, - system_meta: &SystemMeta, - world: &'w World, - change_tick: u32, - ) -> Self::Item { - world.validate_non_send_access::(); - let (ptr, ticks) = world - .get_resource_with_ticks(state.component_id) - .unwrap_or_else(|| { - panic!( - "Non-send resource requested by {} does not exist: {}", - system_meta.name, - std::any::type_name::() - ) - }); - NonSendMut { - value: ptr.assert_unique().deref_mut(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick: system_meta.last_change_tick, - change_tick, - }, - } - } -} - -/// The [`SystemParamState`] of [`Option>`]. -/// See: [`NonSendMut`] -#[doc(hidden)] -pub struct OptionNonSendMutState(NonSendMutState); - -impl<'a, T: 'static> SystemParam for Option> { - type Fetch = OptionNonSendMutState; -} - -// SAFETY: this impl defers to `NonSendMutState`, which initializes -// and validates the correct world access -unsafe impl SystemParamState for OptionNonSendMutState { - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { - Self(NonSendMutState::init(world, system_meta)) - } -} - -impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendMutState { - type Item = Option>; - - #[inline] - unsafe fn get_param( - state: &'s mut Self, - system_meta: &SystemMeta, - world: &'w World, - change_tick: u32, - ) -> Self::Item { - world.validate_non_send_access::(); - world - .get_resource_with_ticks(state.0.component_id) - .map(|(ptr, ticks)| NonSendMut { - value: ptr.assert_unique().deref_mut(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick: system_meta.last_change_tick, - change_tick, - }, - }) - } -} - impl<'a> SystemParam for &'a Archetypes { type Fetch = ArchetypesState; } diff --git a/crates/bevy_ecs/src/system/thread_marker.rs b/crates/bevy_ecs/src/system/thread_marker.rs index d468df9ad0cb6..e590af4a9fa29 100644 --- a/crates/bevy_ecs/src/system/thread_marker.rs +++ b/crates/bevy_ecs/src/system/thread_marker.rs @@ -105,6 +105,7 @@ impl Tls { ))) } + // this takes an &mut self to trigger change detection when we get a mutable value out of the tls pub fn get_mut(&mut self, f: F) -> R where F: FnOnce(&mut T) -> R, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 1aab56a9f4ae7..c8cc0e672eed5 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -60,7 +60,6 @@ pub struct World { pub(crate) removed_components: SparseSet>, /// Access cache used by [WorldCell]. pub(crate) archetype_component_access: ArchetypeComponentAccess, - main_thread_validator: MainThreadValidator, pub(crate) change_tick: AtomicU32, pub(crate) last_change_tick: u32, } @@ -76,7 +75,6 @@ impl Default for World { bundles: Default::default(), removed_components: Default::default(), archetype_component_access: Default::default(), - main_thread_validator: Default::default(), // Default value is `1`, and `last_change_tick`s default to `0`, such that changes // are detected on first system runs and for direct world queries. change_tick: AtomicU32::new(1), @@ -744,46 +742,6 @@ impl World { }); } - /// Inserts a new non-send resource with standard starting values. - /// - /// If the resource already exists, nothing happens. - /// - /// The value given by the [`FromWorld::from_world`] method will be used. - /// Note that any resource with the `Default` trait automatically implements `FromWorld`, - /// and those default values will be here instead. - /// - /// # Panics - /// - /// Panics if called from a thread other than the main thread. - #[inline] - pub fn init_non_send_resource(&mut self) { - if !self.contains_resource::() { - let resource = R::from_world(self); - self.insert_non_send_resource(resource); - } - } - - /// Inserts a new non-send resource with the given `value`. - /// - /// `NonSend` resources cannot be sent across threads, - /// and do not need the `Send + Sync` bounds. - /// Systems with `NonSend` resources are always scheduled on the main thread. - /// - /// # Panics - /// - /// Panics if called from a thread other than the main thread. - #[inline] - pub fn insert_non_send_resource(&mut self, value: R) { - self.validate_non_send_access::(); - let component_id = self.components.init_non_send::(); - OwningPtr::make(value, |ptr| { - // SAFETY: component_id was just initialized and corresponds to resource of type R - unsafe { - self.insert_resource_by_id(component_id, ptr); - } - }); - } - /// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None]. #[inline] pub fn remove_resource(&mut self) -> Option { @@ -791,13 +749,6 @@ impl World { unsafe { self.remove_resource_unchecked() } } - #[inline] - pub fn remove_non_send_resource(&mut self) -> Option { - self.validate_non_send_access::(); - // SAFETY: we are on main thread - unsafe { self.remove_resource_unchecked() } - } - #[inline] /// # Safety /// Only remove `NonSend` resources from the main thread @@ -927,75 +878,6 @@ impl World { self.get_resource_unchecked_mut_with_id(component_id) } - /// Gets an immutable reference to the non-send resource of the given type, if it exists. - /// - /// # Panics - /// - /// Panics if the resource does not exist. - /// Use [`get_non_send_resource`](World::get_non_send_resource) instead if you want to handle this case. - #[inline] - #[track_caller] - pub fn non_send_resource(&self) -> &R { - match self.get_non_send_resource() { - Some(x) => x, - None => panic!( - "Requested non-send resource {} does not exist in the `World`. - Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", - std::any::type_name::() - ), - } - } - - /// Gets a mutable reference to the non-send resource of the given type, if it exists. - /// - /// # Panics - /// - /// Panics if the resource does not exist. - /// Use [`get_non_send_resource_mut`](World::get_non_send_resource_mut) instead if you want to handle this case. - #[inline] - #[track_caller] - pub fn non_send_resource_mut(&mut self) -> Mut<'_, R> { - match self.get_non_send_resource_mut() { - Some(x) => x, - None => panic!( - "Requested non-send resource {} does not exist in the `World`. - Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", - std::any::type_name::() - ), - } - } - - /// Gets a reference to the non-send resource of the given type, if it exists. - /// Otherwise returns [None] - #[inline] - pub fn get_non_send_resource(&self) -> Option<&R> { - let component_id = self.components.get_resource_id(TypeId::of::())?; - // SAFETY: component id matches type T - unsafe { self.get_non_send_with_id(component_id) } - } - - /// Gets a mutable reference to the non-send resource of the given type, if it exists. - /// Otherwise returns [None] - #[inline] - pub fn get_non_send_resource_mut(&mut self) -> Option> { - // SAFETY: unique world access - unsafe { self.get_non_send_resource_unchecked_mut() } - } - - /// Gets a mutable reference to the non-send resource of the given type, if it exists. - /// Otherwise returns [None] - /// - /// # Safety - /// This will allow aliased mutable access to the given non-send resource type. The caller must - /// ensure that there is either only one mutable access or multiple immutable accesses at a time. - #[inline] - pub unsafe fn get_non_send_resource_unchecked_mut(&self) -> Option> { - let component_id = self.components.get_resource_id(TypeId::of::())?; - self.get_non_send_unchecked_mut_with_id(component_id) - } - // Shorthand helper function for getting the data and change ticks for a resource. #[inline] pub(crate) fn get_resource_with_ticks( @@ -1175,11 +1057,6 @@ impl World { .components .get_resource_id(TypeId::of::()) .unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::())); - // If the resource isn't send and sync, validate that we are on the main thread, so that we can access it. - let component_info = self.components().get_info(component_id).unwrap(); - if !component_info.is_send_and_sync() { - self.validate_non_send_access::(); - } let (ptr, mut ticks) = self .storages @@ -1281,29 +1158,6 @@ impl World { }) } - /// # Safety - /// `component_id` must be assigned to a component of type `R` - #[inline] - pub(crate) unsafe fn get_non_send_with_id( - &self, - component_id: ComponentId, - ) -> Option<&R> { - self.validate_non_send_access::(); - self.get_resource_with_id(component_id) - } - - /// # Safety - /// `component_id` must be assigned to a component of type `R`. - /// Caller must ensure this doesn't violate Rust mutability rules for the given resource. - #[inline] - pub(crate) unsafe fn get_non_send_unchecked_mut_with_id( - &self, - component_id: ComponentId, - ) -> Option> { - self.validate_non_send_access::(); - self.get_resource_unchecked_mut_with_id(component_id) - } - /// Inserts a new resource with the given `value`. Will replace the value if it already existed. /// /// **You should prefer to use the typed API [`World::insert_resource`] where possible and only @@ -1349,29 +1203,6 @@ impl World { component_id } - pub(crate) fn initialize_non_send_resource(&mut self) -> ComponentId { - let component_id = self.components.init_non_send::(); - // SAFETY: resource initialized above - unsafe { self.initialize_resource_internal(component_id) }; - component_id - } - - pub(crate) fn validate_non_send_access(&self) { - assert!( - self.main_thread_validator.is_main_thread(), - "attempted to access NonSend resource {} off of the main thread", - std::any::type_name::(), - ); - } - - pub(crate) fn validate_non_send_access_untyped(&self, name: &str) { - assert!( - self.main_thread_validator.is_main_thread(), - "attempted to access NonSend resource {} off of the main thread", - name - ); - } - /// Empties queued entities and adds them to the empty [Archetype](crate::archetype::Archetype). /// This should be called before doing operations that might operate on queued entities, /// such as inserting a [Component]. @@ -1435,10 +1266,6 @@ impl World { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_by_id(&self, component_id: ComponentId) -> Option> { - let info = self.components.get_info(component_id)?; - if !info.is_send_and_sync() { - self.validate_non_send_access_untyped(info.name()); - } self.storages.resources.get(component_id)?.get_data() } @@ -1450,11 +1277,6 @@ impl World { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - let info = self.components.get_info(component_id)?; - if !info.is_send_and_sync() { - self.validate_non_send_access_untyped(info.name()); - } - let (ptr, ticks) = self.get_resource_with_ticks(component_id)?; // SAFE: This function has exclusive access to the world so nothing aliases `ticks`. @@ -1479,11 +1301,7 @@ impl World { /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only /// use this in cases where the actual types are not known at compile time.** pub fn remove_resource_by_id(&mut self, component_id: ComponentId) -> Option<()> { - let info = self.components.get_info(component_id)?; - if !info.is_send_and_sync() { - self.validate_non_send_access_untyped(info.name()); - } - // SAFETY: The underlying type is Send and Sync or we've already validated we're on the main thread + // SAFETY: The underlying type is Send and Sync unsafe { self.storages .resources @@ -1570,24 +1388,6 @@ impl FromWorld for T { } } -struct MainThreadValidator { - main_thread: std::thread::ThreadId, -} - -impl MainThreadValidator { - fn is_main_thread(&self) -> bool { - self.main_thread == std::thread::current().id() - } -} - -impl Default for MainThreadValidator { - fn default() -> Self { - Self { - main_thread: std::thread::current().id(), - } - } -} - #[cfg(test)] mod tests { use super::World; diff --git a/crates/bevy_ecs/src/world/world_cell.rs b/crates/bevy_ecs/src/world/world_cell.rs index 39770890e2b0d..8d8692d767bad 100644 --- a/crates/bevy_ecs/src/world/world_cell.rs +++ b/crates/bevy_ecs/src/world/world_cell.rs @@ -252,75 +252,6 @@ impl<'w> WorldCell<'w> { } } - /// Gets an immutable reference to the non-send resource of the given type, if it exists. - pub fn get_non_send_resource(&self) -> Option> { - let component_id = self.world.components.get_resource_id(TypeId::of::())?; - let archetype_component_id = self - .world - .get_resource_archetype_component_id(component_id)?; - Some(WorldBorrow::new( - // SAFETY: ComponentId matches TypeId - unsafe { self.world.get_non_send_with_id(component_id)? }, - archetype_component_id, - self.access.clone(), - )) - } - - /// Gets an immutable reference to the non-send resource of the given type, if it exists. - /// - /// # Panics - /// - /// Panics if the resource does not exist. Use - /// [`get_non_send_resource`](WorldCell::get_non_send_resource) instead if you want to handle - /// this case. - pub fn non_send_resource(&self) -> WorldBorrow<'_, T> { - match self.get_non_send_resource() { - Some(x) => x, - None => panic!( - "Requested non-send resource {} does not exist in the `World`. - Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", - std::any::type_name::() - ), - } - } - - /// Gets a mutable reference to the non-send resource of the given type, if it exists. - pub fn get_non_send_resource_mut(&self) -> Option> { - let component_id = self.world.components.get_resource_id(TypeId::of::())?; - let archetype_component_id = self - .world - .get_resource_archetype_component_id(component_id)?; - Some(WorldBorrowMut::new( - // SAFETY: ComponentId matches TypeId and access is checked by WorldBorrowMut - unsafe { - self.world - .get_non_send_unchecked_mut_with_id(component_id)? - }, - archetype_component_id, - self.access.clone(), - )) - } - - /// Gets a mutable reference to the non-send resource of the given type, if it exists. - /// - /// # Panics - /// - /// Panics if the resource does not exist. Use - /// [`get_non_send_resource_mut`](WorldCell::get_non_send_resource_mut) instead if you want to - /// handle this case. - pub fn non_send_resource_mut(&self) -> WorldBorrowMut<'_, T> { - match self.get_non_send_resource_mut() { - Some(x) => x, - None => panic!( - "Requested non-send resource {} does not exist in the `World`. - Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", - std::any::type_name::() - ), - } - } - /// Sends an [`Event`](crate::event::Event). #[inline] pub fn send_event(&self, event: E) { diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index d0bb67c5c28c6..c9d977fc9eb50 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -1,66 +1,76 @@ use crate::converter::{convert_axis, convert_button, convert_gamepad_id}; use bevy_ecs::event::EventWriter; -use bevy_ecs::system::{NonSend, NonSendMut}; +use bevy_ecs::system::{MainThread, Res, ResMut, Tls}; use bevy_input::gamepad::GamepadInfo; use bevy_input::{gamepad::GamepadEventRaw, prelude::*}; use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter, Gilrs}; -pub fn gilrs_event_startup_system(gilrs: NonSend, mut events: EventWriter) { - for (id, gamepad) in gilrs.gamepads() { - let info = GamepadInfo { - name: gamepad.name().into(), - }; +pub fn gilrs_event_startup_system( + gilrs: MainThread>>, + mut events: EventWriter, +) { + gilrs.get(|gilrs| { + for (id, gamepad) in gilrs.gamepads() { + let info = GamepadInfo { + name: gamepad.name().into(), + }; - events.send(GamepadEventRaw::new( - convert_gamepad_id(id), - GamepadEventType::Connected(info), - )); - } + events.send(GamepadEventRaw::new( + convert_gamepad_id(id), + GamepadEventType::Connected(info), + )); + } + }); } -pub fn gilrs_event_system(mut gilrs: NonSendMut, mut events: EventWriter) { - while let Some(gilrs_event) = gilrs - .next_event() - .filter_ev(&axis_dpad_to_button, &mut gilrs) - { - gilrs.update(&gilrs_event); +pub fn gilrs_event_system( + mut gilrs: MainThread>>, + mut events: EventWriter, +) { + gilrs.get_mut(|mut gilrs| { + while let Some(gilrs_event) = gilrs + .next_event() + .filter_ev(&axis_dpad_to_button, &mut gilrs) + { + gilrs.update(&gilrs_event); - match gilrs_event.event { - EventType::Connected => { - let pad = gilrs.gamepad(gilrs_event.id); - let info = GamepadInfo { - name: pad.name().into(), - }; + match gilrs_event.event { + EventType::Connected => { + let pad = gilrs.gamepad(gilrs_event.id); + let info = GamepadInfo { + name: pad.name().into(), + }; - events.send(GamepadEventRaw::new( - convert_gamepad_id(gilrs_event.id), - GamepadEventType::Connected(info), - )); - } - EventType::Disconnected => { - events.send(GamepadEventRaw::new( - convert_gamepad_id(gilrs_event.id), - GamepadEventType::Disconnected, - )); - } - EventType::ButtonChanged(gilrs_button, value, _) => { - if let Some(button_type) = convert_button(gilrs_button) { events.send(GamepadEventRaw::new( convert_gamepad_id(gilrs_event.id), - GamepadEventType::ButtonChanged(button_type, value), + GamepadEventType::Connected(info), )); } - } - EventType::AxisChanged(gilrs_axis, value, _) => { - if let Some(axis_type) = convert_axis(gilrs_axis) { + EventType::Disconnected => { events.send(GamepadEventRaw::new( convert_gamepad_id(gilrs_event.id), - GamepadEventType::AxisChanged(axis_type, value), + GamepadEventType::Disconnected, )); } - } - _ => (), - }; - } - gilrs.inc(); + EventType::ButtonChanged(gilrs_button, value, _) => { + if let Some(button_type) = convert_button(gilrs_button) { + events.send(GamepadEventRaw::new( + convert_gamepad_id(gilrs_event.id), + GamepadEventType::ButtonChanged(button_type, value), + )); + } + } + EventType::AxisChanged(gilrs_axis, value, _) => { + if let Some(axis_type) = convert_axis(gilrs_axis) { + events.send(GamepadEventRaw::new( + convert_gamepad_id(gilrs_event.id), + GamepadEventType::AxisChanged(axis_type, value), + )); + } + } + _ => (), + }; + } + gilrs.inc(); + }); } diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 9002f10b476c0..3c800739c5cdd 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -2,7 +2,7 @@ mod converter; mod gilrs_system; use bevy_app::{App, CoreStage, Plugin, StartupStage}; -use bevy_ecs::schedule::IntoSystemDescriptor; +use bevy_ecs::{schedule::IntoSystemDescriptor, system::Tls}; use bevy_input::InputSystem; use bevy_utils::tracing::error; use gilrs::GilrsBuilder; @@ -19,7 +19,7 @@ impl Plugin for GilrsPlugin { .build() { Ok(gilrs) => { - app.insert_non_send_resource(gilrs) + app.insert_resource(Tls::new(gilrs)) .add_startup_system_to_stage( StartupStage::PreStartup, gilrs_event_startup_system, diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 8e6f82c66c911..5895923a5547a 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -4,7 +4,7 @@ use crate::{ Extract, RenderApp, RenderStage, }; use bevy_app::{App, Plugin}; -use bevy_ecs::prelude::*; +use bevy_ecs::{prelude::*, system::MainThread}; use bevy_utils::{tracing::debug, HashMap, HashSet}; use bevy_window::{ CompositeAlphaMode, PresentMode, RawHandleWrapper, WindowClosed, WindowId, Windows, @@ -166,7 +166,7 @@ pub struct WindowSurfaces { pub fn prepare_windows( // By accessing a NonSend resource, we tell the scheduler to put this system on the main thread, // which is necessary for some OS s - _marker: NonSend, + _marker: MainThread>, mut windows: ResMut, mut window_surfaces: ResMut, render_device: Res, diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 0b1b544eec17b..d19e53e2a7423 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -4,6 +4,7 @@ mod web_resize; mod winit_config; mod winit_windows; +use bevy_ecs::system::{MainThread, Tls}; use converters::convert_cursor_grab_mode; pub use winit_config::*; pub use winit_windows::*; @@ -38,7 +39,7 @@ pub struct WinitPlugin; impl Plugin for WinitPlugin { fn build(&self, app: &mut App) { - app.init_non_send_resource::() + app.insert_resource(Tls::new(WinitWindows::default())) .init_resource::() .set_runner(winit_runner) .add_system_to_stage(CoreStage::PostUpdate, change_window.label(ModifiesWindows)); @@ -56,199 +57,205 @@ impl Plugin for WinitPlugin { #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))] handle_create_window_events(&mut app.world, &event_loop, &mut create_window_reader.0); app.insert_resource(create_window_reader) - .insert_non_send_resource(event_loop); + .insert_resource(Tls::new(event_loop)); } } fn change_window( - mut winit_windows: NonSendMut, + mut winit_windows: MainThread>>, mut windows: ResMut, mut window_dpi_changed_events: EventWriter, mut window_close_events: EventWriter, ) { - let mut removed_windows = vec![]; - for bevy_window in windows.iter_mut() { - let id = bevy_window.id(); - for command in bevy_window.drain_commands() { - match command { - bevy_window::WindowCommand::SetWindowMode { - mode, - resolution: - UVec2 { - x: width, - y: height, - }, - } => { - let window = winit_windows.get_window(id).unwrap(); - match mode { - bevy_window::WindowMode::BorderlessFullscreen => { - window - .set_fullscreen(Some(winit::window::Fullscreen::Borderless(None))); + winit_windows.get_mut(|winit_windows| { + let mut removed_windows = vec![]; + for bevy_window in windows.iter_mut() { + let id = bevy_window.id(); + for command in bevy_window.drain_commands() { + match command { + bevy_window::WindowCommand::SetWindowMode { + mode, + resolution: + UVec2 { + x: width, + y: height, + }, + } => { + let window = winit_windows.get_window(id).unwrap(); + match mode { + bevy_window::WindowMode::BorderlessFullscreen => { + window.set_fullscreen(Some(winit::window::Fullscreen::Borderless( + None, + ))); + } + bevy_window::WindowMode::Fullscreen => { + window.set_fullscreen(Some(winit::window::Fullscreen::Exclusive( + get_best_videomode(&window.current_monitor().unwrap()), + ))); + } + bevy_window::WindowMode::SizedFullscreen => window.set_fullscreen( + Some(winit::window::Fullscreen::Exclusive(get_fitting_videomode( + &window.current_monitor().unwrap(), + width, + height, + ))), + ), + bevy_window::WindowMode::Windowed => window.set_fullscreen(None), } - bevy_window::WindowMode::Fullscreen => { - window.set_fullscreen(Some(winit::window::Fullscreen::Exclusive( - get_best_videomode(&window.current_monitor().unwrap()), - ))); + } + bevy_window::WindowCommand::SetTitle { title } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_title(&title); + } + bevy_window::WindowCommand::SetScaleFactor { scale_factor } => { + window_dpi_changed_events + .send(WindowScaleFactorChanged { id, scale_factor }); + } + bevy_window::WindowCommand::SetResolution { + logical_resolution: + Vec2 { + x: width, + y: height, + }, + scale_factor, + } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_inner_size( + winit::dpi::LogicalSize::new(width, height) + .to_physical::(scale_factor), + ); + } + bevy_window::WindowCommand::SetPresentMode { .. } => (), + bevy_window::WindowCommand::SetResizable { resizable } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_resizable(resizable); + } + bevy_window::WindowCommand::SetDecorations { decorations } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_decorations(decorations); + } + bevy_window::WindowCommand::SetCursorIcon { icon } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_cursor_icon(converters::convert_cursor_icon(icon)); + } + bevy_window::WindowCommand::SetCursorGrabMode { grab_mode } => { + let window = winit_windows.get_window(id).unwrap(); + window + .set_cursor_grab(convert_cursor_grab_mode(grab_mode)) + .unwrap_or_else(|e| error!("Unable to un/grab cursor: {}", e)); + } + bevy_window::WindowCommand::SetCursorVisibility { visible } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_cursor_visible(visible); + } + bevy_window::WindowCommand::SetCursorPosition { position } => { + let window = winit_windows.get_window(id).unwrap(); + let inner_size = + window.inner_size().to_logical::(window.scale_factor()); + window + .set_cursor_position(LogicalPosition::new( + position.x, + inner_size.height - position.y, + )) + .unwrap_or_else(|e| error!("Unable to set cursor position: {}", e)); + } + bevy_window::WindowCommand::SetMaximized { maximized } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_maximized(maximized); + } + bevy_window::WindowCommand::SetMinimized { minimized } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_minimized(minimized); + } + bevy_window::WindowCommand::SetPosition { + monitor_selection, + position, + } => { + let window = winit_windows.get_window(id).unwrap(); + + use bevy_window::MonitorSelection::*; + let maybe_monitor = match monitor_selection { + Current => window.current_monitor(), + Primary => window.primary_monitor(), + Index(i) => window.available_monitors().nth(i), + }; + if let Some(monitor) = maybe_monitor { + let monitor_position = DVec2::from(<(_, _)>::from(monitor.position())); + let position = monitor_position + position.as_dvec2(); + + window.set_outer_position(LogicalPosition::new(position.x, position.y)); + } else { + warn!("Couldn't get monitor selected with: {monitor_selection:?}"); } - bevy_window::WindowMode::SizedFullscreen => window.set_fullscreen(Some( - winit::window::Fullscreen::Exclusive(get_fitting_videomode( - &window.current_monitor().unwrap(), - width, - height, - )), - )), - bevy_window::WindowMode::Windowed => window.set_fullscreen(None), } - } - bevy_window::WindowCommand::SetTitle { title } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_title(&title); - } - bevy_window::WindowCommand::SetScaleFactor { scale_factor } => { - window_dpi_changed_events.send(WindowScaleFactorChanged { id, scale_factor }); - } - bevy_window::WindowCommand::SetResolution { - logical_resolution: - Vec2 { - x: width, - y: height, - }, - scale_factor, - } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_inner_size( - winit::dpi::LogicalSize::new(width, height) - .to_physical::(scale_factor), - ); - } - bevy_window::WindowCommand::SetPresentMode { .. } => (), - bevy_window::WindowCommand::SetResizable { resizable } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_resizable(resizable); - } - bevy_window::WindowCommand::SetDecorations { decorations } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_decorations(decorations); - } - bevy_window::WindowCommand::SetCursorIcon { icon } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_cursor_icon(converters::convert_cursor_icon(icon)); - } - bevy_window::WindowCommand::SetCursorGrabMode { grab_mode } => { - let window = winit_windows.get_window(id).unwrap(); - window - .set_cursor_grab(convert_cursor_grab_mode(grab_mode)) - .unwrap_or_else(|e| error!("Unable to un/grab cursor: {}", e)); - } - bevy_window::WindowCommand::SetCursorVisibility { visible } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_cursor_visible(visible); - } - bevy_window::WindowCommand::SetCursorPosition { position } => { - let window = winit_windows.get_window(id).unwrap(); - let inner_size = window.inner_size().to_logical::(window.scale_factor()); - window - .set_cursor_position(LogicalPosition::new( - position.x, - inner_size.height - position.y, - )) - .unwrap_or_else(|e| error!("Unable to set cursor position: {}", e)); - } - bevy_window::WindowCommand::SetMaximized { maximized } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_maximized(maximized); - } - bevy_window::WindowCommand::SetMinimized { minimized } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_minimized(minimized); - } - bevy_window::WindowCommand::SetPosition { - monitor_selection, - position, - } => { - let window = winit_windows.get_window(id).unwrap(); - - use bevy_window::MonitorSelection::*; - let maybe_monitor = match monitor_selection { - Current => window.current_monitor(), - Primary => window.primary_monitor(), - Index(i) => window.available_monitors().nth(i), - }; - if let Some(monitor) = maybe_monitor { - let monitor_position = DVec2::from(<(_, _)>::from(monitor.position())); - let position = monitor_position + position.as_dvec2(); - - window.set_outer_position(LogicalPosition::new(position.x, position.y)); - } else { - warn!("Couldn't get monitor selected with: {monitor_selection:?}"); + bevy_window::WindowCommand::Center(monitor_selection) => { + let window = winit_windows.get_window(id).unwrap(); + + use bevy_window::MonitorSelection::*; + let maybe_monitor = match monitor_selection { + Current => window.current_monitor(), + Primary => window.primary_monitor(), + Index(i) => window.available_monitors().nth(i), + }; + + if let Some(monitor) = maybe_monitor { + let monitor_size = monitor.size(); + let monitor_position = monitor.position().cast::(); + + let window_size = window.outer_size(); + + window.set_outer_position(PhysicalPosition { + x: monitor_size.width.saturating_sub(window_size.width) as f64 / 2. + + monitor_position.x, + y: monitor_size.height.saturating_sub(window_size.height) as f64 + / 2. + + monitor_position.y, + }); + } else { + warn!("Couldn't get monitor selected with: {monitor_selection:?}"); + } } - } - bevy_window::WindowCommand::Center(monitor_selection) => { - let window = winit_windows.get_window(id).unwrap(); - - use bevy_window::MonitorSelection::*; - let maybe_monitor = match monitor_selection { - Current => window.current_monitor(), - Primary => window.primary_monitor(), - Index(i) => window.available_monitors().nth(i), - }; - - if let Some(monitor) = maybe_monitor { - let monitor_size = monitor.size(); - let monitor_position = monitor.position().cast::(); - - let window_size = window.outer_size(); - - window.set_outer_position(PhysicalPosition { - x: monitor_size.width.saturating_sub(window_size.width) as f64 / 2. - + monitor_position.x, - y: monitor_size.height.saturating_sub(window_size.height) as f64 / 2. - + monitor_position.y, - }); - } else { - warn!("Couldn't get monitor selected with: {monitor_selection:?}"); + bevy_window::WindowCommand::SetResizeConstraints { resize_constraints } => { + let window = winit_windows.get_window(id).unwrap(); + let constraints = resize_constraints.check_constraints(); + let min_inner_size = LogicalSize { + width: constraints.min_width, + height: constraints.min_height, + }; + let max_inner_size = LogicalSize { + width: constraints.max_width, + height: constraints.max_height, + }; + + window.set_min_inner_size(Some(min_inner_size)); + if constraints.max_width.is_finite() && constraints.max_height.is_finite() { + window.set_max_inner_size(Some(max_inner_size)); + } } - } - bevy_window::WindowCommand::SetResizeConstraints { resize_constraints } => { - let window = winit_windows.get_window(id).unwrap(); - let constraints = resize_constraints.check_constraints(); - let min_inner_size = LogicalSize { - width: constraints.min_width, - height: constraints.min_height, - }; - let max_inner_size = LogicalSize { - width: constraints.max_width, - height: constraints.max_height, - }; - - window.set_min_inner_size(Some(min_inner_size)); - if constraints.max_width.is_finite() && constraints.max_height.is_finite() { - window.set_max_inner_size(Some(max_inner_size)); + bevy_window::WindowCommand::SetAlwaysOnTop { always_on_top } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_always_on_top(always_on_top); + } + bevy_window::WindowCommand::Close => { + // Since we have borrowed `windows` to iterate through them, we can't remove the window from it. + // Add the removal requests to a queue to solve this + removed_windows.push(id); + // No need to run any further commands - this drops the rest of the commands, although the `bevy_window::Window` will be dropped later anyway + break; } - } - bevy_window::WindowCommand::SetAlwaysOnTop { always_on_top } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_always_on_top(always_on_top); - } - bevy_window::WindowCommand::Close => { - // Since we have borrowed `windows` to iterate through them, we can't remove the window from it. - // Add the removal requests to a queue to solve this - removed_windows.push(id); - // No need to run any further commands - this drops the rest of the commands, although the `bevy_window::Window` will be dropped later anyway - break; } } } - } - if !removed_windows.is_empty() { - for id in removed_windows { - // Close the OS window. (The `Drop` impl actually closes the window) - let _ = winit_windows.remove_window(id); - // Clean up our own data structures - windows.remove(id); - window_close_events.send(WindowClosed { id }); + if !removed_windows.is_empty() { + for id in removed_windows { + // Close the OS window. (The `Drop` impl actually closes the window) + let _ = winit_windows.remove_window(id); + // Clean up our own data structures + windows.remove(id); + window_close_events.send(WindowClosed { id }); + } } - } + }); } fn run(event_loop: EventLoop<()>, event_handler: F) -> ! @@ -340,7 +347,9 @@ struct WinitCreateWindowReader(ManualEventReader); pub fn winit_runner_with(mut app: App) { let mut event_loop = app .world - .remove_non_send_resource::>() + .remove_resource::>>() + .unwrap() + .remove() .unwrap(); let mut create_window_event_reader = app .world @@ -351,7 +360,7 @@ pub fn winit_runner_with(mut app: App) { let mut redraw_event_reader = ManualEventReader::::default(); let mut winit_state = WinitPersistentState::default(); app.world - .insert_non_send_resource(event_loop.create_proxy()); + .insert_resource(Tls::new(event_loop.create_proxy())); let return_from_run = app.world.resource::().return_from_run; @@ -390,180 +399,184 @@ pub fn winit_runner_with(mut app: App) { .. } => { let world = app.world.cell(); - let winit_windows = world.non_send_resource_mut::(); + let mut winit_windows = world.resource_mut::>(); let mut windows = world.resource_mut::(); - let window_id = - if let Some(window_id) = winit_windows.get_window_id(winit_window_id) { - window_id - } else { - warn!( - "Skipped event for unknown winit Window Id {:?}", - winit_window_id - ); + winit_windows.get_mut(|winit_windows| { + let window_id = + if let Some(window_id) = winit_windows.get_window_id(winit_window_id) { + window_id + } else { + warn!( + "Skipped event for unknown winit Window Id {:?}", + winit_window_id + ); + return; + }; + + let Some(window) = windows.get_mut(window_id) else { + // If we're here, this window was previously opened + info!("Skipped event for closed window: {:?}", window_id); return; }; + winit_state.low_power_event = true; + match event { + WindowEvent::Resized(size) => { + window.update_actual_size_from_backend(size.width, size.height); + world.send_event(WindowResized { + id: window_id, + width: window.width(), + height: window.height(), + }); + } + WindowEvent::CloseRequested => { + world.send_event(WindowCloseRequested { id: window_id }); + } + WindowEvent::KeyboardInput { ref input, .. } => { + world.send_event(converters::convert_keyboard_input(input)); + } + WindowEvent::CursorMoved { position, .. } => { + let winit_window = winit_windows.get_window(window_id).unwrap(); + let inner_size = winit_window.inner_size(); - let Some(window) = windows.get_mut(window_id) else { - // If we're here, this window was previously opened - info!("Skipped event for closed window: {:?}", window_id); - return; - }; - winit_state.low_power_event = true; - - match event { - WindowEvent::Resized(size) => { - window.update_actual_size_from_backend(size.width, size.height); - world.send_event(WindowResized { - id: window_id, - width: window.width(), - height: window.height(), - }); - } - WindowEvent::CloseRequested => { - world.send_event(WindowCloseRequested { id: window_id }); - } - WindowEvent::KeyboardInput { ref input, .. } => { - world.send_event(converters::convert_keyboard_input(input)); - } - WindowEvent::CursorMoved { position, .. } => { - let winit_window = winit_windows.get_window(window_id).unwrap(); - let inner_size = winit_window.inner_size(); - - // move origin to bottom left - let y_position = inner_size.height as f64 - position.y; + // move origin to bottom left + let y_position = inner_size.height as f64 - position.y; - let physical_position = DVec2::new(position.x, y_position); - window - .update_cursor_physical_position_from_backend(Some(physical_position)); + let physical_position = DVec2::new(position.x, y_position); + window.update_cursor_physical_position_from_backend(Some( + physical_position, + )); - world.send_event(CursorMoved { - id: window_id, - position: (physical_position / window.scale_factor()).as_vec2(), - }); - } - WindowEvent::CursorEntered { .. } => { - world.send_event(CursorEntered { id: window_id }); - } - WindowEvent::CursorLeft { .. } => { - window.update_cursor_physical_position_from_backend(None); - world.send_event(CursorLeft { id: window_id }); - } - WindowEvent::MouseInput { state, button, .. } => { - world.send_event(MouseButtonInput { - button: converters::convert_mouse_button(button), - state: converters::convert_element_state(state), - }); - } - WindowEvent::MouseWheel { delta, .. } => match delta { - event::MouseScrollDelta::LineDelta(x, y) => { - world.send_event(MouseWheel { - unit: MouseScrollUnit::Line, - x, - y, + world.send_event(CursorMoved { + id: window_id, + position: (physical_position / window.scale_factor()).as_vec2(), }); } - event::MouseScrollDelta::PixelDelta(p) => { - world.send_event(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: p.x as f32, - y: p.y as f32, - }); + WindowEvent::CursorEntered { .. } => { + world.send_event(CursorEntered { id: window_id }); } - }, - WindowEvent::Touch(touch) => { - let mut location = touch.location.to_logical(window.scale_factor()); - - // On a mobile window, the start is from the top while on PC/Linux/OSX from - // bottom - if cfg!(target_os = "android") || cfg!(target_os = "ios") { - let window_height = windows.primary().height(); - location.y = window_height - location.y; + WindowEvent::CursorLeft { .. } => { + window.update_cursor_physical_position_from_backend(None); + world.send_event(CursorLeft { id: window_id }); } + WindowEvent::MouseInput { state, button, .. } => { + world.send_event(MouseButtonInput { + button: converters::convert_mouse_button(button), + state: converters::convert_element_state(state), + }); + } + WindowEvent::MouseWheel { delta, .. } => match delta { + event::MouseScrollDelta::LineDelta(x, y) => { + world.send_event(MouseWheel { + unit: MouseScrollUnit::Line, + x, + y, + }); + } + event::MouseScrollDelta::PixelDelta(p) => { + world.send_event(MouseWheel { + unit: MouseScrollUnit::Pixel, + x: p.x as f32, + y: p.y as f32, + }); + } + }, + WindowEvent::Touch(touch) => { + let mut location = touch.location.to_logical(window.scale_factor()); + + // On a mobile window, the start is from the top while on PC/Linux/OSX from + // bottom + if cfg!(target_os = "android") || cfg!(target_os = "ios") { + let window_height = windows.primary().height(); + location.y = window_height - location.y; + } - world.send_event(converters::convert_touch_input(touch, location)); - } - WindowEvent::ReceivedCharacter(c) => { - world.send_event(ReceivedCharacter { - id: window_id, - char: c, - }); - } - WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size, - } => { - world.send_event(WindowBackendScaleFactorChanged { - id: window_id, + world.send_event(converters::convert_touch_input(touch, location)); + } + WindowEvent::ReceivedCharacter(c) => { + world.send_event(ReceivedCharacter { + id: window_id, + char: c, + }); + } + WindowEvent::ScaleFactorChanged { scale_factor, - }); - let prior_factor = window.scale_factor(); - window.update_scale_factor_from_backend(scale_factor); - let new_factor = window.scale_factor(); - if let Some(forced_factor) = window.scale_factor_override() { - // If there is a scale factor override, then force that to be used - // Otherwise, use the OS suggested size - // We have already told the OS about our resize constraints, so - // the new_inner_size should take those into account - *new_inner_size = winit::dpi::LogicalSize::new( - window.requested_width(), - window.requested_height(), - ) - .to_physical::(forced_factor); - } else if approx::relative_ne!(new_factor, prior_factor) { - world.send_event(WindowScaleFactorChanged { + new_inner_size, + } => { + world.send_event(WindowBackendScaleFactorChanged { id: window_id, scale_factor, }); - } + let prior_factor = window.scale_factor(); + window.update_scale_factor_from_backend(scale_factor); + let new_factor = window.scale_factor(); + if let Some(forced_factor) = window.scale_factor_override() { + // If there is a scale factor override, then force that to be used + // Otherwise, use the OS suggested size + // We have already told the OS about our resize constraints, so + // the new_inner_size should take those into account + *new_inner_size = winit::dpi::LogicalSize::new( + window.requested_width(), + window.requested_height(), + ) + .to_physical::(forced_factor); + } else if approx::relative_ne!(new_factor, prior_factor) { + world.send_event(WindowScaleFactorChanged { + id: window_id, + scale_factor, + }); + } - let new_logical_width = new_inner_size.width as f64 / new_factor; - let new_logical_height = new_inner_size.height as f64 / new_factor; - if approx::relative_ne!(window.width() as f64, new_logical_width) - || approx::relative_ne!(window.height() as f64, new_logical_height) - { - world.send_event(WindowResized { + let new_logical_width = new_inner_size.width as f64 / new_factor; + let new_logical_height = new_inner_size.height as f64 / new_factor; + if approx::relative_ne!(window.width() as f64, new_logical_width) + || approx::relative_ne!(window.height() as f64, new_logical_height) + { + world.send_event(WindowResized { + id: window_id, + width: new_logical_width as f32, + height: new_logical_height as f32, + }); + } + window.update_actual_size_from_backend( + new_inner_size.width, + new_inner_size.height, + ); + } + WindowEvent::Focused(focused) => { + window.update_focused_status_from_backend(focused); + world.send_event(WindowFocused { id: window_id, - width: new_logical_width as f32, - height: new_logical_height as f32, + focused, }); } - window.update_actual_size_from_backend( - new_inner_size.width, - new_inner_size.height, - ); - } - WindowEvent::Focused(focused) => { - window.update_focused_status_from_backend(focused); - world.send_event(WindowFocused { - id: window_id, - focused, - }); - } - WindowEvent::DroppedFile(path_buf) => { - world.send_event(FileDragAndDrop::DroppedFile { - id: window_id, - path_buf, - }); - } - WindowEvent::HoveredFile(path_buf) => { - world.send_event(FileDragAndDrop::HoveredFile { - id: window_id, - path_buf, - }); - } - WindowEvent::HoveredFileCancelled => { - world.send_event(FileDragAndDrop::HoveredFileCancelled { id: window_id }); - } - WindowEvent::Moved(position) => { - let position = ivec2(position.x, position.y); - window.update_actual_position_from_backend(position); - world.send_event(WindowMoved { - id: window_id, - position, - }); + WindowEvent::DroppedFile(path_buf) => { + world.send_event(FileDragAndDrop::DroppedFile { + id: window_id, + path_buf, + }); + } + WindowEvent::HoveredFile(path_buf) => { + world.send_event(FileDragAndDrop::HoveredFile { + id: window_id, + path_buf, + }); + } + WindowEvent::HoveredFileCancelled => { + world.send_event(FileDragAndDrop::HoveredFileCancelled { + id: window_id, + }); + } + WindowEvent::Moved(position) => { + let position = ivec2(position.x, position.y); + window.update_actual_position_from_backend(position); + world.send_event(WindowMoved { + id: window_id, + position, + }); + } + _ => {} } - _ => {} - } + }); } event::Event::DeviceEvent { event: DeviceEvent::MouseMotion { delta: (x, y) }, @@ -657,40 +670,42 @@ fn handle_create_window_events( create_window_event_reader: &mut ManualEventReader, ) { let world = world.cell(); - let mut winit_windows = world.non_send_resource_mut::(); - let mut windows = world.resource_mut::(); - let create_window_events = world.resource::>(); - for create_window_event in create_window_event_reader.iter(&create_window_events) { - let window = winit_windows.create_window( - event_loop, - create_window_event.id, - &create_window_event.descriptor, - ); - // This event is already sent on windows, x11, and xwayland. - // TODO: we aren't yet sure about native wayland, so we might be able to exclude it, - // but sending a duplicate event isn't problematic, as windows already does this. - #[cfg(not(any(target_os = "windows", target_feature = "x11")))] - world.send_event(WindowResized { - id: create_window_event.id, - width: window.width(), - height: window.height(), - }); - windows.add(window); - world.send_event(WindowCreated { - id: create_window_event.id, - }); - - #[cfg(target_arch = "wasm32")] - { - let channel = world.resource_mut::(); - if create_window_event.descriptor.fit_canvas_to_parent { - let selector = if let Some(selector) = &create_window_event.descriptor.canvas { - selector - } else { - web_resize::WINIT_CANVAS_SELECTOR - }; - channel.listen_to_selector(create_window_event.id, selector); + let mut winit_windows = world.resource_mut::>(); + winit_windows.get_mut(|winit_windows| { + let mut windows = world.resource_mut::(); + let create_window_events = world.resource::>(); + for create_window_event in create_window_event_reader.iter(&create_window_events) { + let window = winit_windows.create_window( + event_loop, + create_window_event.id, + &create_window_event.descriptor, + ); + // This event is already sent on windows, x11, and xwayland. + // TODO: we aren't yet sure about native wayland, so we might be able to exclude it, + // but sending a duplicate event isn't problematic, as windows already does this. + #[cfg(not(any(target_os = "windows", target_feature = "x11")))] + world.send_event(WindowResized { + id: create_window_event.id, + width: window.width(), + height: window.height(), + }); + windows.add(window); + world.send_event(WindowCreated { + id: create_window_event.id, + }); + + #[cfg(target_arch = "wasm32")] + { + let channel = world.resource_mut::(); + if create_window_event.descriptor.fit_canvas_to_parent { + let selector = if let Some(selector) = &create_window_event.descriptor.canvas { + selector + } else { + web_resize::WINIT_CANVAS_SELECTOR + }; + channel.listen_to_selector(create_window_event.id, selector); + } } } - } + }); } From fc6752763471aac1a12065d53ddc627d5d617f7e Mon Sep 17 00:00:00 2001 From: Michael Hsu Date: Wed, 16 Nov 2022 15:57:26 -0800 Subject: [PATCH 4/6] change MainThread to just a non_send marker --- crates/bevy_audio/src/audio_output.rs | 3 +- .../src/schedule/ambiguity_detection.rs | 26 +---- .../src/schedule/executor_parallel.rs | 2 +- crates/bevy_ecs/src/system/mod.rs | 14 +-- crates/bevy_ecs/src/system/thread_marker.rs | 95 ++++--------------- crates/bevy_gilrs/src/gilrs_system.rs | 6 +- crates/bevy_render/src/view/window.rs | 7 +- crates/bevy_winit/src/lib.rs | 3 +- 8 files changed, 36 insertions(+), 120 deletions(-) diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index b48a4854436f6..740a339978294 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -84,7 +84,8 @@ where /// Plays audio currently queued in the [`Audio`] resource through the [`AudioOutput`] resource pub fn play_queued_audio_system( - audio_output: MainThread>>>, + _marker: MainThread, + audio_output: Res>>, audio_sources: Option>>, mut audio: ResMut>, mut sinks: ResMut>, diff --git a/crates/bevy_ecs/src/schedule/ambiguity_detection.rs b/crates/bevy_ecs/src/schedule/ambiguity_detection.rs index 11949ff63e480..3ecefd41b5798 100644 --- a/crates/bevy_ecs/src/schedule/ambiguity_detection.rs +++ b/crates/bevy_ecs/src/schedule/ambiguity_detection.rs @@ -282,7 +282,6 @@ mod tests { use crate as bevy_ecs; use crate::event::Events; use crate::prelude::*; - use crate::system::MainThread; #[derive(Resource)] struct R; @@ -299,8 +298,6 @@ mod tests { fn empty_system() {} fn res_system(_res: Res) {} fn resmut_system(_res: ResMut) {} - fn nonsend_system(_ns: MainThread>) {} - fn nonsendmut_system(_ns: MainThread>) {} fn read_component_system(_query: Query<&A>) {} fn write_component_system(_query: Query<&mut A>) {} fn with_filtered_component_system(_query: Query<&mut A, With>) {} @@ -345,8 +342,6 @@ mod tests { .add_system(empty_system) .add_system(res_system) .add_system(res_system) - .add_system(nonsend_system) - .add_system(nonsend_system) .add_system(read_component_system) .add_system(read_component_system) .add_system(event_reader_system) @@ -391,21 +386,6 @@ mod tests { assert_eq!(test_stage.ambiguity_count(&world), 1); } - #[test] - fn nonsend() { - let mut world = World::new(); - world.insert_resource(R); - - let mut test_stage = SystemStage::parallel(); - test_stage - .add_system(nonsendmut_system) - .add_system(nonsend_system); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 1); - } - #[test] fn components() { let mut world = World::new(); @@ -502,8 +482,7 @@ mod tests { let mut test_stage = SystemStage::parallel(); test_stage .add_system(resmut_system.ignore_all_ambiguities()) - .add_system(res_system) - .add_system(nonsend_system); + .add_system(res_system); test_stage.run(&mut world); @@ -521,8 +500,7 @@ mod tests { let mut test_stage = SystemStage::parallel(); test_stage .add_system(resmut_system.ambiguous_with(IgnoreMe)) - .add_system(res_system.label(IgnoreMe)) - .add_system(nonsend_system.label(IgnoreMe)); + .add_system(res_system.label(IgnoreMe)); test_stage.run(&mut world); diff --git a/crates/bevy_ecs/src/schedule/executor_parallel.rs b/crates/bevy_ecs/src/schedule/executor_parallel.rs index 12825c42bea8c..47facbec58920 100644 --- a/crates/bevy_ecs/src/schedule/executor_parallel.rs +++ b/crates/bevy_ecs/src/schedule/executor_parallel.rs @@ -537,7 +537,7 @@ mod tests { use std::thread; let mut world = World::new(); world.insert_resource(Tls::new(thread::current().id())); - fn non_send(thread_id: MainThread>>) { + fn non_send(_marker: MainThread, thread_id: Res>) { thread_id.get(|thread_id| { assert_eq!(thread::current().id(), *thread_id); }); diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 9445de77976b9..0f0e237142f79 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -517,13 +517,12 @@ mod tests { world.insert_resource(Tls::new(NotSend1(std::rc::Rc::new(0)))); fn sys( - op: MainThread>>>, - mut _op2: MainThread>>>, + _marker: MainThread, + op: Option>>, + mut _op2: Option>>, mut system_ran: ResMut, ) { - // TODO: need to fix this. Probably should just implement SystemPAram - // for Option> - let MainThread(Some(_op)) = op else {panic!("blah");}; + op.expect("op to exist"); *system_ran = SystemRan::Yes; } @@ -544,8 +543,9 @@ mod tests { world.insert_resource(Tls::new(NotSend2(std::rc::Rc::new(2)))); fn sys( - _op: MainThread>>, - mut _op2: MainThread>>, + _marker: MainThread, + _op: Res>, + mut _op2: ResMut>, mut system_ran: ResMut, ) { *system_ran = SystemRan::Yes; diff --git a/crates/bevy_ecs/src/system/thread_marker.rs b/crates/bevy_ecs/src/system/thread_marker.rs index e590af4a9fa29..2b5fffb761956 100644 --- a/crates/bevy_ecs/src/system/thread_marker.rs +++ b/crates/bevy_ecs/src/system/thread_marker.rs @@ -1,74 +1,38 @@ -use std::{ - ops::{Deref, DerefMut}, - sync::Arc, -}; +use std::sync::Arc; use thread_local_object::ThreadLocal; use crate as bevy_ecs; use crate::prelude::World; -use super::{ - Resource, SystemMeta, SystemParam, SystemParamFetch, SystemParamItem, SystemParamState, -}; +use super::{Resource, SystemMeta, SystemParam, SystemParamFetch, SystemParamState}; -pub struct MainThread<'w, 's, T: SystemParam>(pub SystemParamItem<'w, 's, T>); -impl<'w, 's, T: SystemParam + Send + Sync + 'static> SystemParam for MainThread<'w, 's, T> { - type Fetch = MainThreadState; +pub struct MainThread; +impl SystemParam for MainThread { + type Fetch = MainThreadState; } -impl<'w, 's, T: SystemParam + Send + Sync> Deref for MainThread<'w, 's, T> { - type Target = SystemParamItem<'w, 's, T>; +pub struct MainThreadState; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'w, 's, T: SystemParam + Send + Sync> DerefMut for MainThread<'w, 's, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl<'w, 's, T: SystemParam + Send + Sync> AsRef> - for MainThread<'w, 's, T> -{ - #[inline] - fn as_ref(&self) -> &SystemParamItem<'w, 's, T> { - self.deref() - } -} - -pub struct MainThreadState(T::Fetch); - -// SAFETY: this impl defers to `NonSendMutState`, which initializes +// SAFETY: this impl defers to `MainThreadState`, which initializes // and validates the correct world access -unsafe impl SystemParamState for MainThreadState { - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { +unsafe impl SystemParamState for MainThreadState { + fn init(_world: &mut World, system_meta: &mut SystemMeta) -> Self { system_meta.set_non_send(); - Self(T::Fetch::init(world, system_meta)) + MainThreadState } } -impl<'w, 's, T: SystemParam + Send + Sync + 'static> SystemParamFetch<'w, 's> for MainThreadState -where - T::Fetch: SystemParamState, -{ - type Item = MainThread<'w, 's, T>; +impl<'w, 's> SystemParamFetch<'w, 's> for MainThreadState { + type Item = MainThread; #[inline] unsafe fn get_param( - state: &'s mut Self, - system_meta: &SystemMeta, - world: &'w World, - change_tick: u32, + _state: &'s mut Self, + _system_meta: &SystemMeta, + _world: &'w World, + _change_tick: u32, ) -> Self::Item { - MainThread(T::Fetch::get_param( - &mut state.0, - system_meta, - world, - change_tick, - )) + MainThread } } @@ -138,28 +102,3 @@ impl Drop for Tls { // the usize is only written to on the call to ThreadLocal::new() unsafe impl Send for Tls {} unsafe impl Sync for Tls {} - -#[cfg(test)] -mod tests { - use crate as bevy_ecs; - use crate::prelude::World; - use crate::schedule::{Stage, SystemStage}; - use crate::system::{MainThread, ResMut, Resource}; - - #[derive(Resource)] - struct A(pub usize); - - #[test] - fn test() { - fn system(mut non_send_res: MainThread>) { - (*non_send_res).0 = 1; - } - let mut world = World::new(); - world.insert_resource(A(0)); - let mut stage = SystemStage::parallel(); - stage.add_system(system); - stage.run(&mut world); - let res = world.get_resource::().unwrap(); - assert_eq!(res.0, 1); - } -} diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index c9d977fc9eb50..c81fa5cfb112c 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -6,7 +6,8 @@ use bevy_input::{gamepad::GamepadEventRaw, prelude::*}; use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter, Gilrs}; pub fn gilrs_event_startup_system( - gilrs: MainThread>>, + _marker: MainThread, + gilrs: Res>, mut events: EventWriter, ) { gilrs.get(|gilrs| { @@ -24,7 +25,8 @@ pub fn gilrs_event_startup_system( } pub fn gilrs_event_system( - mut gilrs: MainThread>>, + _marker: MainThread, + mut gilrs: ResMut>, mut events: EventWriter, ) { gilrs.get_mut(|mut gilrs| { diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 5895923a5547a..28b11d03536a0 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -12,10 +12,6 @@ use bevy_window::{ use std::ops::{Deref, DerefMut}; use wgpu::TextureFormat; -/// Token to ensure a system runs on the main thread. -#[derive(Resource, Default)] -pub struct NonSendMarker; - pub struct WindowRenderPlugin; #[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)] @@ -29,7 +25,6 @@ impl Plugin for WindowRenderPlugin { render_app .init_resource::() .init_resource::() - .init_resource::() .add_system_to_stage(RenderStage::Extract, extract_windows) .add_system_to_stage( RenderStage::Prepare, @@ -166,7 +161,7 @@ pub struct WindowSurfaces { pub fn prepare_windows( // By accessing a NonSend resource, we tell the scheduler to put this system on the main thread, // which is necessary for some OS s - _marker: MainThread>, + _marker: MainThread, mut windows: ResMut, mut window_surfaces: ResMut, render_device: Res, diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index d19e53e2a7423..9cbafa7dcd6ab 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -62,7 +62,8 @@ impl Plugin for WinitPlugin { } fn change_window( - mut winit_windows: MainThread>>, + _marker: MainThread, + mut winit_windows: ResMut>, mut windows: ResMut, mut window_dpi_changed_events: EventWriter, mut window_close_events: EventWriter, From 5ab7d8a19b8f18b383c6ce8be2a506a09ff4216c Mon Sep 17 00:00:00 2001 From: Michael Hsu Date: Wed, 16 Nov 2022 18:39:19 -0800 Subject: [PATCH 5/6] derive component for tls --- crates/bevy_ecs/src/system/thread_marker.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/system/thread_marker.rs b/crates/bevy_ecs/src/system/thread_marker.rs index 2b5fffb761956..731d88ee9c615 100644 --- a/crates/bevy_ecs/src/system/thread_marker.rs +++ b/crates/bevy_ecs/src/system/thread_marker.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use thread_local_object::ThreadLocal; -use crate as bevy_ecs; use crate::prelude::World; +use crate::{self as bevy_ecs, prelude::Component}; use super::{Resource, SystemMeta, SystemParam, SystemParamFetch, SystemParamState}; @@ -36,7 +36,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for MainThreadState { } } -#[derive(Resource)] +#[derive(Resource, Component)] pub struct Tls(Arc>); impl Tls { From 03d0187979b4338384a7635fe74fb5464b6e9331 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Wed, 16 Nov 2022 20:25:37 -0800 Subject: [PATCH 6/6] fixes from cargo run -p ci --- crates/bevy_asset/src/assets.rs | 50 ++++++++++++--------- crates/bevy_asset/src/debug_asset_server.rs | 47 ++++++++++--------- crates/bevy_ecs/src/system/mod.rs | 3 +- crates/bevy_ecs/src/system/thread_marker.rs | 6 ++- crates/bevy_gilrs/src/gilrs_system.rs | 7 +-- crates/bevy_log/src/lib.rs | 5 ++- 6 files changed, 66 insertions(+), 52 deletions(-) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 19749df7cf897..e8b9f67f8a75d 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -367,9 +367,11 @@ impl AddAsset for App { self.add_system(crate::debug_asset_server::sync_debug_assets::); let mut app = self .world - .non_send_resource_mut::(); - app.add_asset::() - .init_resource::>(); + .resource_mut::>(); + app.get_mut(|app| { + app.add_asset::() + .init_resource::>(); + }); } self } @@ -390,8 +392,10 @@ impl AddAsset for App { { let mut app = self .world - .non_send_resource_mut::(); - app.init_asset_loader::(); + .resource_mut::>(); + app.get_mut(|app| { + app.init_asset_loader::(); + }); } self } @@ -416,14 +420,16 @@ macro_rules! load_internal_asset { { let mut debug_app = $app .world - .non_send_resource_mut::<$crate::debug_asset_server::DebugAssetApp>(); - $crate::debug_asset_server::register_handle_with_loader( - $loader, - &mut debug_app, - $handle, - file!(), - $path_str, - ); + .resource_mut::>(); + debug_app.get_mut(|debug_app| { + $crate::debug_asset_server::register_handle_with_loader( + $loader, + debug_app, + $handle, + file!(), + $path_str, + ); + }); } let mut assets = $app.world.resource_mut::<$crate::Assets<_>>(); assets.set_untracked($handle, ($loader)(include_str!($path_str))); @@ -454,14 +460,16 @@ macro_rules! load_internal_binary_asset { { let mut debug_app = $app .world - .non_send_resource_mut::<$crate::debug_asset_server::DebugAssetApp>(); - $crate::debug_asset_server::register_handle_with_loader( - $loader, - &mut debug_app, - $handle, - file!(), - $path_str, - ); + .resource_mut::>(); + debug_app.get_mut(|debug_app| { + $crate::debug_asset_server::register_handle_with_loader( + $loader, + debug_app, + $handle, + file!(), + $path_str, + ); + }); } let mut assets = $app.world.resource_mut::<$crate::Assets<_>>(); assets.set_untracked($handle, ($loader)(include_bytes!($path_str).as_ref())); diff --git a/crates/bevy_asset/src/debug_asset_server.rs b/crates/bevy_asset/src/debug_asset_server.rs index 690dd6769dc7c..a1f41cae96fe1 100644 --- a/crates/bevy_asset/src/debug_asset_server.rs +++ b/crates/bevy_asset/src/debug_asset_server.rs @@ -6,7 +6,7 @@ use bevy_app::{App, Plugin}; use bevy_ecs::{ event::Events, schedule::SystemLabel, - system::{NonSendMut, Res, ResMut, Resource, SystemState}, + system::{MainThread, Res, ResMut, Resource, SystemState, Tls}, }; use bevy_tasks::{IoTaskPool, TaskPoolBuilder}; use bevy_utils::HashMap; @@ -78,37 +78,42 @@ impl Plugin for DebugAssetServerPlugin { asset_folder: "crates".to_string(), watch_for_changes: true, }); - app.insert_non_send_resource(DebugAssetApp(debug_asset_app)); + app.insert_resource(Tls::new(DebugAssetApp(debug_asset_app))); app.add_system(run_debug_asset_app); } } -fn run_debug_asset_app(mut debug_asset_app: NonSendMut) { - debug_asset_app.0.update(); +fn run_debug_asset_app(_marker: MainThread, mut debug_asset_app: ResMut>) { + debug_asset_app.get_mut(|debug_asset_app| { + debug_asset_app.0.update(); + }); } pub(crate) fn sync_debug_assets( - mut debug_asset_app: NonSendMut, + _marker: MainThread, + mut debug_asset_app: ResMut>, mut assets: ResMut>, ) { - let world = &mut debug_asset_app.0.world; - let mut state = SystemState::<( - Res>>, - Res>, - Res>, - )>::new(world); - let (changed_shaders, handle_map, debug_assets) = state.get_mut(world); - for changed in changed_shaders.iter_current_update_events() { - let debug_handle = match changed { - AssetEvent::Created { handle } | AssetEvent::Modified { handle } => handle, - AssetEvent::Removed { .. } => continue, - }; - if let Some(handle) = handle_map.handles.get(debug_handle) { - if let Some(debug_asset) = debug_assets.get(debug_handle) { - assets.set_untracked(handle, debug_asset.clone()); + debug_asset_app.get_mut(|debug_asset_app| { + let world = &mut debug_asset_app.0.world; + let mut state = SystemState::<( + Res>>, + Res>, + Res>, + )>::new(world); + let (changed_shaders, handle_map, debug_assets) = state.get_mut(world); + for changed in changed_shaders.iter_current_update_events() { + let debug_handle = match changed { + AssetEvent::Created { handle } | AssetEvent::Modified { handle } => handle, + AssetEvent::Removed { .. } => continue, + }; + if let Some(handle) = handle_map.handles.get(debug_handle) { + if let Some(debug_asset) = debug_assets.get(debug_handle) { + assets.set_untracked(handle, debug_asset.clone()); + } } } - } + }); } /// Uses the return type of the given loader to register the given handle with the appropriate type diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 0f0e237142f79..df8b107c72553 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -55,8 +55,7 @@ //! - [`Local`] //! - [`EventReader`](crate::event::EventReader) //! - [`EventWriter`](crate::event::EventWriter) -//! - [`NonSend`] and `Option` -//! - [`NonSendMut`] and `Option` +//! - [`MainThread`] //! - [`&World`](crate::world::World) //! - [`RemovedComponents`] //! - [`SystemName`] diff --git a/crates/bevy_ecs/src/system/thread_marker.rs b/crates/bevy_ecs/src/system/thread_marker.rs index 731d88ee9c615..35641e0ce5bdf 100644 --- a/crates/bevy_ecs/src/system/thread_marker.rs +++ b/crates/bevy_ecs/src/system/thread_marker.rs @@ -98,7 +98,9 @@ impl Drop for Tls { } } -// pretty sure this is safe as ThreadLocal just wraps a usize and a phatom data -// the usize is only written to on the call to ThreadLocal::new() +// SAFETY: pretty sure this is safe as ThreadLocal just wraps a usize and a phantom data +// and the usize is only written to on the call to ThreadLocal::new() unsafe impl Send for Tls {} +// SAFETY: pretty sure this is safe as ThreadLocal just wraps a usize and a phantom data +// and the usize is only written to on the call to ThreadLocal::new() unsafe impl Sync for Tls {} diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index c81fa5cfb112c..0b622c207eef3 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -29,11 +29,8 @@ pub fn gilrs_event_system( mut gilrs: ResMut>, mut events: EventWriter, ) { - gilrs.get_mut(|mut gilrs| { - while let Some(gilrs_event) = gilrs - .next_event() - .filter_ev(&axis_dpad_to_button, &mut gilrs) - { + gilrs.get_mut(|gilrs| { + while let Some(gilrs_event) = gilrs.next_event().filter_ev(&axis_dpad_to_button, gilrs) { gilrs.update(&gilrs_event); match gilrs_event.event { diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index d8e1d6a2a68bc..430b487fd84a6 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -31,6 +31,8 @@ pub use bevy_utils::tracing::{ }; use bevy_app::{App, Plugin}; +#[cfg(feature = "tracing-chrome")] +use bevy_ecs::system::Tls; use tracing_log::LogTracer; #[cfg(feature = "tracing-chrome")] use tracing_subscriber::fmt::{format::DefaultFields, FormattedFields}; @@ -146,7 +148,8 @@ impl Plugin for LogPlugin { } })) .build(); - app.world.insert_non_send_resource(guard); + // TODO: Test that this still works. shoving it in a tls might break the feature. + app.world.insert_resource(Tls::new(guard)); chrome_layer };