Skip to content

Manipulate non-send resources with Commands #12819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 177 additions & 3 deletions crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,96 @@ impl<'w, 's> Commands<'w, 's> {
self.queue.push(remove_resource::<R>);
}

/// 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::<MyNonSend>();
/// # }
/// # bevy_ecs::system::assert_is_system(init_my_non_send);
/// ```
pub fn init_non_send_resource<R: FromWorld + 'static>(&mut self) {
self.queue.push(init_non_send_resource::<R>);
}

/// 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<F, R>(&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::<MyNonSend>();
/// # }
/// # bevy_ecs::system::assert_is_system(remove_my_non_send);
/// ```
pub fn remove_non_send_resource<R: 'static>(&mut self) {
self.queue.push(remove_non_send_resource::<R>);
}

/// 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.
Expand Down Expand Up @@ -1122,15 +1212,39 @@ fn init_resource<R: Resource + FromWorld>(world: &mut World) {
world.init_resource::<R>();
}

/// A [`Command`] that inserts a [`Resource`] into the world.
fn insert_resource<R: 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<R: Resource>(world: &mut World) {
world.remove_resource::<R>();
}

/// A [`Command`] that inserts a [`Resource`] into the world.
fn insert_resource<R: 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<R: FromWorld + 'static>(world: &mut World) {
world.init_non_send_resource::<R>();
}

/// A [`Command`] that removes a non-[`Send`] resource `R` from the world.
fn remove_non_send_resource<R: 'static>(world: &mut World) {
world.remove_non_send_resource::<R>();
}

/// 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<F, R>(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)());
}
}

Expand Down Expand Up @@ -1324,4 +1438,64 @@ mod tests {
assert!(world.contains_resource::<W<i32>>());
assert!(world.contains_resource::<W<f64>>());
}

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::<MyNonSend>();
}

queue.apply(&mut world);

assert!(world.contains_non_send::<MyNonSend>());
}

#[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::<MyNonSend>());
}

#[test]
fn remove() {
let mut world = World::default();
let mut queue = CommandQueue::default();

world.init_non_send_resource::<MyNonSend>();

{
let mut commands = Commands::new(&mut queue, &world);
commands.remove_non_send_resource::<MyNonSend>();
}

queue.apply(&mut world);

assert!(!world.contains_non_send::<MyNonSend>());
}
}
}
26 changes: 25 additions & 1 deletion tools/ci/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ bitflags! {
const EXAMPLE_CHECK = 0b10000000;
const COMPILE_CHECK = 0b100000000;
const CFG_CHECK = 0b1000000000;
const TEST_CHECK = 0b10000000000;
}
}

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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();
Expand Down