diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index e4fa7f65520d9..a05225da5745d 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -493,6 +493,96 @@ impl<'w, 's> Commands<'w, 's> { self.queue.push(remove_resource::); } + /// Pushes a [`Command`] to the queue for inserting a non-[`Send`] resource in the [`World`] with an inferred value. + /// + /// See [`World::init_non_send_resource`] for more details. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # struct MyNonSend(*const u8); + /// # + /// # impl Default for MyNonSend { + /// # fn default() -> Self { + /// # MyNonSend(std::ptr::null()) + /// # } + /// # } + /// # + /// # fn init_my_non_send(mut commands: Commands) { + /// commands.init_non_send_resource::(); + /// # } + /// # bevy_ecs::system::assert_is_system(init_my_non_send); + /// ``` + pub fn init_non_send_resource(&mut self) { + self.queue.push(init_non_send_resource::); + } + + /// Pushes a [`Command`] to the queue for inserting a non-[`Send`] resource in the [`World`] with an specific value. + /// + /// Note that this command takes a closure, not a value. This closure is executed on the main thread and should return + /// the value of the non-[`Send`] resource. The closure itself must be [`Send`], but its returned value does not need to be. + /// + /// See [`World::insert_non_send_resource`] for more details. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # struct MyNonSend(*const u8); + /// # + /// # fn insert_my_non_send(mut commands: Commands) { + /// commands.insert_non_send_resource(|| { + /// MyNonSend(std::ptr::null()) + /// }); + /// # } + /// # bevy_ecs::system::assert_is_system(insert_my_non_send); + /// ``` + /// + /// Note that the value must be constructed inside of the closure. Moving it will fail to compile: + /// + /// ```compile_fail,E0277 + /// # use bevy_ecs::prelude::*; + /// # + /// # struct MyNonSend(*const u8); + /// # + /// # fn insert_my_non_send(mut commands: Commands) { + /// let my_non_send = MyNonSend(std::ptr::null()); + /// + /// commands.insert_non_send_resource(move || { + /// my_non_send + /// }); + /// # } + /// # bevy_ecs::system::assert_is_system(insert_my_non_send); + /// ``` + pub fn insert_non_send_resource(&mut self, func: F) + where + F: FnOnce() -> R + Send + 'static, + R: 'static, + { + self.queue.push(insert_non_send_resource(func)); + } + + /// Pushes a [`Command`] to the queue for removing a non-[`Send`] resource from the [`World`]. + /// + /// See [`World::remove_non_send_resource`] for more details. + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # struct MyNonSend(*const u8); + /// # + /// # fn remove_my_non_send(mut commands: Commands) { + /// commands.remove_non_send_resource::(); + /// # } + /// # bevy_ecs::system::assert_is_system(remove_my_non_send); + /// ``` + pub fn remove_non_send_resource(&mut self) { + self.queue.push(remove_non_send_resource::); + } + /// Runs the system corresponding to the given [`SystemId`]. /// Systems are ran in an exclusive and single threaded way. /// Running slow systems can become a bottleneck. @@ -1122,15 +1212,39 @@ fn init_resource(world: &mut World) { world.init_resource::(); } +/// A [`Command`] that inserts a [`Resource`] into the world. +fn insert_resource(resource: R) -> impl Command { + move |world: &mut World| { + world.insert_resource(resource); + } +} + /// A [`Command`] that removes the [resource](Resource) `R` from the world. fn remove_resource(world: &mut World) { world.remove_resource::(); } -/// A [`Command`] that inserts a [`Resource`] into the world. -fn insert_resource(resource: R) -> impl Command { +/// A [`Command`] that inserts a non-[`Send`] resource `R` into the world using +/// a value created with the [`FromWorld`] trait. +fn init_non_send_resource(world: &mut World) { + world.init_non_send_resource::(); +} + +/// A [`Command`] that removes a non-[`Send`] resource `R` from the world. +fn remove_non_send_resource(world: &mut World) { + world.remove_non_send_resource::(); +} + +/// A [`Command`] that inserts a non-[`Send`] resource into the world by calling +/// `func` on the main thread and inserting its returned value. +fn insert_non_send_resource(func: F) -> impl Command +where + // `R` is not `Send`, but the function is! + F: FnOnce() -> R + Send + 'static, + R: 'static, +{ move |world: &mut World| { - world.insert_resource(resource); + world.insert_non_send_resource((func)()); } } @@ -1324,4 +1438,64 @@ mod tests { assert!(world.contains_resource::>()); assert!(world.contains_resource::>()); } + + mod non_send { + use super::*; + + #[allow(dead_code)] + struct MyNonSend(*const ()); + + impl Default for MyNonSend { + fn default() -> Self { + MyNonSend(std::ptr::null()) + } + } + + #[test] + fn init() { + let mut world = World::default(); + let mut queue = CommandQueue::default(); + + { + let mut commands = Commands::new(&mut queue, &world); + commands.init_non_send_resource::(); + } + + queue.apply(&mut world); + + assert!(world.contains_non_send::()); + } + + #[test] + fn insert() { + let mut world = World::default(); + let mut queue = CommandQueue::default(); + + { + let mut commands = Commands::new(&mut queue, &world); + commands.insert_non_send_resource(|| MyNonSend(std::ptr::from_ref(&()))); + } + + queue.apply(&mut world); + + assert!(world.contains_non_send::()); + } + + #[test] + fn remove() { + let mut world = World::default(); + let mut queue = CommandQueue::default(); + + world.init_non_send_resource::(); + + { + let mut commands = Commands::new(&mut queue, &world); + commands.remove_non_send_resource::(); + } + + queue.apply(&mut world); + + assert!(!world.contains_non_send::()); + } + } } diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs index 617f96f7f7da8..7b1d59c0ef2c0 100644 --- a/tools/ci/src/main.rs +++ b/tools/ci/src/main.rs @@ -18,6 +18,7 @@ bitflags! { const EXAMPLE_CHECK = 0b10000000; const COMPILE_CHECK = 0b100000000; const CFG_CHECK = 0b1000000000; + const TEST_CHECK = 0b10000000000; } } @@ -56,13 +57,18 @@ fn main() { ("doc", Check::DOC_TEST | Check::DOC_CHECK), ( "compile", - Check::COMPILE_FAIL | Check::BENCH_CHECK | Check::EXAMPLE_CHECK | Check::COMPILE_CHECK, + Check::COMPILE_FAIL + | Check::BENCH_CHECK + | Check::EXAMPLE_CHECK + | Check::COMPILE_CHECK + | Check::TEST_CHECK, ), ("format", Check::FORMAT), ("clippy", Check::CLIPPY), ("compile-fail", Check::COMPILE_FAIL), ("bench-check", Check::BENCH_CHECK), ("example-check", Check::EXAMPLE_CHECK), + ("test-check", Check::TEST_CHECK), ("cfg-check", Check::CFG_CHECK), ("doc-check", Check::DOC_CHECK), ("doc-test", Check::DOC_TEST), @@ -314,6 +320,24 @@ fn main() { ); } + if checks.contains(Check::TEST_CHECK) { + let mut args = vec!["--workspace", "--examples"]; + + if flags.contains(Flag::KEEP_GOING) { + args.push("--keep-going"); + } + + test_suite.insert( + Check::TEST_CHECK, + vec![CITest { + command: cmd!(sh, "cargo check {args...}"), + failure_message: "Please fix compiler examples for tests in output above.", + subdir: None, + env_vars: Vec::new(), + }], + ); + } + // Actually run the tests: let mut failed_checks: Check = Check::empty();