diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 56f9e938e5864..6143036737dbb 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -65,6 +65,7 @@ mod tests { use bevy_tasks::{ComputeTaskPool, TaskPool}; use std::{ any::TypeId, + marker::PhantomData, sync::{ atomic::{AtomicUsize, Ordering}, Arc, Mutex, @@ -78,6 +79,9 @@ mod tests { #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] struct C; + #[derive(Default)] + struct NonSendA(usize, PhantomData<*mut ()>); + #[derive(Component, Clone, Debug)] struct DropCk(Arc); impl DropCk { @@ -1262,6 +1266,38 @@ 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/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 914046526af12..128c5e8dcf094 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1149,7 +1149,13 @@ impl World { /// }); /// assert_eq!(world.get_resource::().unwrap().0, 2); /// ``` - pub fn resource_scope(&mut self, f: impl FnOnce(&mut World, Mut) -> U) -> U { + pub fn resource_scope< + R: 'static, /* The resource doesn't need to be Send nor Sync. */ + U, + >( + &mut self, + f: impl FnOnce(&mut World, Mut) -> U, + ) -> U { let last_change_tick = self.last_change_tick(); let change_tick = self.change_tick(); @@ -1157,6 +1163,13 @@ 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) = { let resource_archetype = self.archetypes.resource_mut(); let unique_components = resource_archetype.unique_components_mut();