From 71056a3552778cc6fbe87da9579f1959988ccaec Mon Sep 17 00:00:00 2001 From: Pietrek14 Date: Tue, 27 Sep 2022 19:12:57 +0200 Subject: [PATCH 01/40] made resource_scope work for non-send resources --- crates/bevy_ecs/src/lib.rs | 31 +++++++++++++++++++++++++++++++ crates/bevy_ecs/src/world/mod.rs | 15 ++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 102df951f89a0..9374cdd935d06 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1263,6 +1263,37 @@ mod tests { assert_eq!(world.resource::().0, 1); } + #[test] + fn non_send_resource_scope() { + let mut world = World::default(); + world.insert_non_send_resource(A(0)); + 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] + fn non_send_resource_scope_from_different_thread() { + let mut world = World::default(); + world.insert_non_send_resource(A(0)); + + std::thread::scope(|s| { + s.spawn(|| { + // Accessing the non-send resource on a different thread + // Should result in a panic + world.resource_scope(|world: &mut World, mut value: Mut| { + value.0 += 1; + assert!(!world.contains_resource::()); + }); + }); + }); + + assert_eq!(world.get_non_send_resource::().unwrap().0, 1); + } + #[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(); From 83f241771cdd671ae6e13fe1f7879b62886918b3 Mon Sep 17 00:00:00 2001 From: Pietrek14 Date: Tue, 27 Sep 2022 21:31:37 +0200 Subject: [PATCH 02/40] fixed the non-send resource scope tests --- crates/bevy_ecs/src/lib.rs | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index b695ea91049e1..f04b4a5da8aa3 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -69,6 +69,7 @@ mod tests { atomic::{AtomicUsize, Ordering}, Arc, Mutex, }, + marker::PhantomData, }; #[derive(Component, Resource, Debug, PartialEq, Eq, Clone, Copy)] @@ -78,6 +79,9 @@ mod tests { #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] struct C; + #[derive(Default)] + struct NonSend(usize, PhantomData<*mut ()>); + #[derive(Component, Clone, Debug)] struct DropCk(Arc); impl DropCk { @@ -1265,32 +1269,31 @@ mod tests { #[test] fn non_send_resource_scope() { let mut world = World::default(); - world.insert_non_send_resource(A(0)); - world.resource_scope(|world: &mut World, mut value: Mut| { + world.insert_non_send_resource(NonSend::default()); + world.resource_scope(|world: &mut World, mut value: Mut| { value.0 += 1; - assert!(!world.contains_resource::()); + assert!(!world.contains_resource::()); }); - assert_eq!(world.non_send_resource::().0, 1); + assert_eq!(world.non_send_resource::().0, 1); } #[test] - #[should_panic] + #[should_panic(expected = "attempted to access NonSend resource bevy_ecs::tests::NonSend off of the main thread")] fn non_send_resource_scope_from_different_thread() { let mut world = World::default(); - world.insert_non_send_resource(A(0)); - - std::thread::scope(|s| { - s.spawn(|| { - // Accessing the non-send resource on a different thread - // Should result in a panic - world.resource_scope(|world: &mut World, mut value: Mut| { - value.0 += 1; - assert!(!world.contains_resource::()); - }); + world.insert_non_send_resource(NonSend::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; }); }); - assert_eq!(world.get_non_send_resource::().unwrap().0, 1); + if let Err(err) = thread.join() { + panic!("{}", err.downcast::().unwrap()); + } } #[test] From 6da0e41d5eb02e78175f74dcf2287ab355889d2f Mon Sep 17 00:00:00 2001 From: Pietrek14 Date: Tue, 27 Sep 2022 22:56:06 +0200 Subject: [PATCH 03/40] formatting --- crates/bevy_ecs/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index f04b4a5da8aa3..5fae64cfcad60 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -65,11 +65,11 @@ mod tests { use bevy_tasks::{ComputeTaskPool, TaskPool}; use std::{ any::TypeId, + marker::PhantomData, sync::{ atomic::{AtomicUsize, Ordering}, Arc, Mutex, }, - marker::PhantomData, }; #[derive(Component, Resource, Debug, PartialEq, Eq, Clone, Copy)] @@ -1278,7 +1278,9 @@ mod tests { } #[test] - #[should_panic(expected = "attempted to access NonSend resource bevy_ecs::tests::NonSend off of the main thread")] + #[should_panic( + expected = "attempted to access NonSend resource bevy_ecs::tests::NonSend off of the main thread" + )] fn non_send_resource_scope_from_different_thread() { let mut world = World::default(); world.insert_non_send_resource(NonSend::default()); From 3500039d02175084bbbb66e597e6d758d80013d6 Mon Sep 17 00:00:00 2001 From: Dawid Piotrowski <41804418+Pietrek14@users.noreply.github.com> Date: Wed, 28 Sep 2022 11:10:24 +0200 Subject: [PATCH 04/40] simplified panic in a non-send resource scope test Co-authored-by: Mike --- crates/bevy_ecs/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 5fae64cfcad60..3e9ebde63a961 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1294,7 +1294,7 @@ mod tests { }); if let Err(err) = thread.join() { - panic!("{}", err.downcast::().unwrap()); + std::panic::resume_unwind(err); } } From f68eb33f42c0124010cd49a09986a553b0451b4d Mon Sep 17 00:00:00 2001 From: Pietrek14 Date: Wed, 28 Sep 2022 12:16:53 +0200 Subject: [PATCH 05/40] changed the name of non-send struct used for testing --- crates/bevy_ecs/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 3e9ebde63a961..6143036737dbb 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -80,7 +80,7 @@ mod tests { struct C; #[derive(Default)] - struct NonSend(usize, PhantomData<*mut ()>); + struct NonSendA(usize, PhantomData<*mut ()>); #[derive(Component, Clone, Debug)] struct DropCk(Arc); @@ -1269,26 +1269,26 @@ mod tests { #[test] fn non_send_resource_scope() { let mut world = World::default(); - world.insert_non_send_resource(NonSend::default()); - world.resource_scope(|world: &mut World, mut value: Mut| { + world.insert_non_send_resource(NonSendA::default()); + world.resource_scope(|world: &mut World, mut value: Mut| { value.0 += 1; - assert!(!world.contains_resource::()); + assert!(!world.contains_resource::()); }); - assert_eq!(world.non_send_resource::().0, 1); + assert_eq!(world.non_send_resource::().0, 1); } #[test] #[should_panic( - expected = "attempted to access NonSend resource bevy_ecs::tests::NonSend off of the main thread" + 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(NonSend::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| { + world.resource_scope(|_: &mut World, mut value: Mut| { value.0 += 1; }); }); From 6b176d54117fb92937d034f28d49ea7a373a3c85 Mon Sep 17 00:00:00 2001 From: Pietrek14 Date: Wed, 18 Jan 2023 01:37:51 +0100 Subject: [PATCH 06/40] added interaction policy --- crates/bevy_ui/src/focus.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 0c71fe790e770..928dc3189fa50 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -53,6 +53,27 @@ impl Default for Interaction { } } +/// Describes whether [`Interaction`] component should remain in the `Clicked` state after +/// the cursor stopped hovering over the node. +#[derive(Component, Copy, Clone, Eq, PartialEq, Debug, Reflect, Serialize, Deserialize)] +#[reflect(Component, Serialize, Deserialize, PartialEq)] +pub enum InteractionPolicy { + /// Keep the node clicked after it stopped being hovered + Hold, + /// Release the node if the cursor stops hovering + Release, +} + +impl InteractionPolicy { + const DEFAULT: Self = Self::Hold; +} + +impl Default for InteractionPolicy { + fn default() -> Self { + Self::DEFAULT + } +} + /// A component storing the position of the mouse relative to the node, (0., 0.) being the top-left corner and (1., 1.) being the bottom-right /// If the mouse is not over the node, the value will go beyond the range of (0., 0.) to (1., 1.) /// A None value means that the cursor position is unknown. @@ -120,6 +141,7 @@ pub struct NodeQuery { node: &'static Node, global_transform: &'static GlobalTransform, interaction: Option<&'static mut Interaction>, + interaction_policy: Option<&'static InteractionPolicy>, relative_cursor_position: Option<&'static mut RelativeCursorPosition>, focus_policy: Option<&'static FocusPolicy>, calculated_clip: Option<&'static CalculatedClip>, @@ -242,7 +264,12 @@ pub fn ui_focus_system( Some(*entity) } else { if let Some(mut interaction) = node.interaction { - if *interaction == Interaction::Hovered || (cursor_position.is_none()) { + let interaction_policy = node.interaction_policy.unwrap_or(&InteractionPolicy::DEFAULT); + + if *interaction == Interaction::Hovered + || (cursor_position.is_none()) + || *interaction_policy == InteractionPolicy::Release + { interaction.set_if_neq(Interaction::None); } } From cd4695add37c4dc5540e84f264908bb1a74be9c0 Mon Sep 17 00:00:00 2001 From: Pietrek14 Date: Wed, 18 Jan 2023 01:40:07 +0100 Subject: [PATCH 07/40] added interactionpolicy to the buttonbundle --- crates/bevy_ui/src/node_bundles.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 8369b33bfe0b0..402ddb190efb4 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -2,7 +2,7 @@ use crate::{ widget::Button, BackgroundColor, CalculatedSize, FocusPolicy, Interaction, Node, Style, - UiImage, ZIndex, + UiImage, ZIndex, InteractionPolicy, }; use bevy_ecs::bundle::Bundle; use bevy_render::{ @@ -171,6 +171,8 @@ pub struct ButtonBundle { pub style: Style, /// Describes whether and how the button has been interacted with by the input pub interaction: Interaction, + /// Describes whether the button should be pressed after the cursor stops hovering over it + pub interaction_policy: InteractionPolicy, /// Whether this node should block interaction with lower nodes pub focus_policy: FocusPolicy, /// The background color, which serves as a "fill" for this node @@ -205,6 +207,7 @@ impl Default for ButtonBundle { button: Default::default(), style: Default::default(), interaction: Default::default(), + interaction_policy: Default::default(), background_color: Default::default(), image: Default::default(), transform: Default::default(), From b06d6b7cbcf5fbf4e97de02d2d021bcb1826d133 Mon Sep 17 00:00:00 2001 From: Pietrek14 Date: Wed, 18 Jan 2023 02:14:06 +0100 Subject: [PATCH 08/40] added an interactionpolicy example --- Cargo.toml | 10 +++ examples/ui/interaction_policy.rs | 127 ++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 examples/ui/interaction_policy.rs diff --git a/Cargo.toml b/Cargo.toml index 3e29eff47f2d4..46703e8931db2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1465,6 +1465,16 @@ description = "Illustrates how FontAtlases are populated (used to optimize text category = "UI (User Interface)" wasm = true +[[example]] +name = "interaction_policy" +path = "examples/ui/interaction_policy.rs" + +[package.metadata.example.interaction_policy] +name = "Interaction Policy" +description = "Illustrates how the InteractionPolicy component works" +category = "UI (User Interface)" +wasm = true + [[example]] name = "relative_cursor_position" path = "examples/ui/relative_cursor_position.rs" diff --git a/examples/ui/interaction_policy.rs b/examples/ui/interaction_policy.rs new file mode 100644 index 0000000000000..bab4552269f2e --- /dev/null +++ b/examples/ui/interaction_policy.rs @@ -0,0 +1,127 @@ +//! This example illustrates how does the InteractionPolicy component work and its purpose. + +use bevy::{prelude::*, winit::WinitSettings, ui::InteractionPolicy}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + // Only run the app when there is user input. This will significantly reduce CPU/GPU use. + .insert_resource(WinitSettings::desktop_app()) + .add_startup_system(setup) + .add_system(button_system) + .run(); +} + +const NORMAL_BUTTON: Color = Color::rgb(0.15, 0.15, 0.15); +const HOVERED_BUTTON: Color = Color::rgb(0.25, 0.25, 0.25); +const PRESSED_BUTTON: Color = Color::rgb(0.35, 0.75, 0.35); + +fn button_system( + mut interaction_query: Query< + (&Interaction, &mut BackgroundColor, &Children), + (Changed, With