From 38a08d9b18c881f0320aff955dc66d4955c63681 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 7 Mar 2023 14:49:14 -0800 Subject: [PATCH 01/68] Update contributing guide to refer to roles instead of people (#7962) --- CONTRIBUTING.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 057280cc60..43e44f537d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -292,11 +292,10 @@ There are three main places you can check for things to review: 2. Pull requests on [bevy](https://github.com/bevyengine/bevy/pulls) and the [bevy-website](https://github.com/bevyengine/bevy-website/pulls) repos. 3. [RFCs](https://github.com/bevyengine/rfcs), which need extensive thoughtful community input on their design. -Official focus areas and work done by @cart go through this review process as well. -Not even our project lead is exempt from reviews and RFCs! +Not even our Project Leads and Maintainers are exempt from reviews and RFCs! By giving feedback on this work (and related supporting work), you can help us make sure our releases are both high-quality and timely. -Finally, if nothing brings you more satisfaction than seeing every last issue labeled and all resolved issues closed, feel free to message @cart for a Bevy org role to help us keep things tidy. +Finally, if nothing brings you more satisfaction than seeing every last issue labeled and all resolved issues closed, feel free to message the Project Lead (currently @cart) for a Bevy org role to help us keep things tidy. As discussed in our [*Bevy Organization doc*](/docs/the_bevy_organization.md), this role only requires good faith and a basic understanding of our development process. ### How to adopt pull requests @@ -340,8 +339,8 @@ To locally lint your files using the same workflow as our CI: 1. Install [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli). 2. Run `markdownlint -f -c .github/linters/.markdown-lint.yml .` in the root directory of the Bevy project. 5. Push your changes to your fork on Github and open a Pull Request. -6. Respond to any CI failures or review feedback. While CI failures must be fixed before we can merge your PR, you do not need to *agree* with all feedback from your reviews, merely acknowledge that it was given. If you cannot come to an agreement, leave the thread open and defer to @cart's final judgement. -7. When your PR is ready to merge, @cart will review it and suggest final changes. If those changes are minimal he may even apply them directly to speed up merging. +6. Respond to any CI failures or review feedback. While CI failures must be fixed before we can merge your PR, you do not need to *agree* with all feedback from your reviews, merely acknowledge that it was given. If you cannot come to an agreement, leave the thread open and defer to a Maintainer or Project Lead's final judgement. +7. When your PR is ready to merge, a Maintainer or Project Lead will review it and suggest final changes. If those changes are minimal they may even apply them directly to speed up merging. If you end up adding a new official Bevy crate to the `bevy` repo: From 6240ba8fa231fb438f0c0d29ee9d76eb92f64e0d Mon Sep 17 00:00:00 2001 From: Ame <104745335+ameknite@users.noreply.github.com> Date: Wed, 8 Mar 2023 13:28:23 -0600 Subject: [PATCH 02/68] Small Fix: Add an escape character to avoid blockquote (#7980) --- crates/bevy_core_pipeline/src/bloom/settings.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/settings.rs b/crates/bevy_core_pipeline/src/bloom/settings.rs index 32cfab0702..eb982d4b0e 100644 --- a/crates/bevy_core_pipeline/src/bloom/settings.rs +++ b/crates/bevy_core_pipeline/src/bloom/settings.rs @@ -49,7 +49,7 @@ pub struct BloomSettings { /// /// In this configuration: /// * 0.0 means no bloom - /// * > 0.0 means a proportionate amount of scattered light is added + /// * \> 0.0 means a proportionate amount of scattered light is added pub intensity: f32, /// Low frequency contribution boost. @@ -69,7 +69,7 @@ pub struct BloomSettings { /// /// In this configuration: /// * 0.0 means no bloom - /// * > 0.0 means a proportionate amount of scattered light is added + /// * \> 0.0 means a proportionate amount of scattered light is added pub low_frequency_boost: f32, /// Low frequency contribution boost curve. From d3df04cb4c8d94cb06780a777cc6712fa4a316dd Mon Sep 17 00:00:00 2001 From: Ame <104745335+ameknite@users.noreply.github.com> Date: Wed, 8 Mar 2023 15:12:44 -0600 Subject: [PATCH 03/68] Small fix: Change ">" to "Greater than" (#7983) --- crates/bevy_core_pipeline/src/bloom/settings.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/settings.rs b/crates/bevy_core_pipeline/src/bloom/settings.rs index eb982d4b0e..edf8ea493f 100644 --- a/crates/bevy_core_pipeline/src/bloom/settings.rs +++ b/crates/bevy_core_pipeline/src/bloom/settings.rs @@ -49,7 +49,7 @@ pub struct BloomSettings { /// /// In this configuration: /// * 0.0 means no bloom - /// * \> 0.0 means a proportionate amount of scattered light is added + /// * Greater than 0.0 means a proportionate amount of scattered light is added pub intensity: f32, /// Low frequency contribution boost. @@ -69,7 +69,7 @@ pub struct BloomSettings { /// /// In this configuration: /// * 0.0 means no bloom - /// * \> 0.0 means a proportionate amount of scattered light is added + /// * Greater than 0.0 means a proportionate amount of scattered light is added pub low_frequency_boost: f32, /// Low frequency contribution boost curve. From 2908bb5e8a3ec00b883c7120a7a69f6e34bb642e Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Wed, 8 Mar 2023 15:27:54 -0700 Subject: [PATCH 04/68] Use derive for default impl of DynamicVariant (#7986) --- crates/bevy_reflect/src/enums/dynamic_enum.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/bevy_reflect/src/enums/dynamic_enum.rs b/crates/bevy_reflect/src/enums/dynamic_enum.rs index fd3a8585b7..db9e050fbe 100644 --- a/crates/bevy_reflect/src/enums/dynamic_enum.rs +++ b/crates/bevy_reflect/src/enums/dynamic_enum.rs @@ -8,8 +8,9 @@ use std::any::Any; use std::fmt::Formatter; /// A dynamic representation of an enum variant. -#[derive(Debug)] +#[derive(Debug, Default)] pub enum DynamicVariant { + #[default] Unit, Tuple(DynamicTuple), Struct(DynamicStruct), @@ -25,12 +26,6 @@ impl Clone for DynamicVariant { } } -impl Default for DynamicVariant { - fn default() -> Self { - DynamicVariant::Unit - } -} - impl From for DynamicVariant { fn from(dyn_tuple: DynamicTuple) -> Self { Self::Tuple(dyn_tuple) From 3cfcc66bdca29c2df818ec988a11d35da9af868c Mon Sep 17 00:00:00 2001 From: Aceeri Date: Wed, 8 Mar 2023 21:39:01 -0800 Subject: [PATCH 05/68] Move cursor position to internal state (#7988) --- crates/bevy_window/src/window.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index e41415a0e8..5b079d5137 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -261,27 +261,28 @@ impl Window { /// The cursor position in this window #[inline] pub fn cursor_position(&self) -> Option { - self.cursor - .physical_position + self.internal + .physical_cursor_position .map(|position| (position / self.scale_factor()).as_vec2()) } /// The physical cursor position in this window #[inline] pub fn physical_cursor_position(&self) -> Option { - self.cursor - .physical_position + self.internal + .physical_cursor_position .map(|position| position.as_vec2()) } /// Set the cursor position in this window pub fn set_cursor_position(&mut self, position: Option) { - self.cursor.physical_position = position.map(|p| p.as_dvec2() * self.scale_factor()); + self.internal.physical_cursor_position = + position.map(|p| p.as_dvec2() * self.scale_factor()); } /// Set the physical cursor position in this window pub fn set_physical_cursor_position(&mut self, position: Option) { - self.cursor.physical_position = position; + self.internal.physical_cursor_position = position; } } @@ -397,9 +398,6 @@ pub struct Cursor { /// /// - iOS / Android / Web / X11: Unsupported. pub hit_test: bool, - - /// The position of this window's cursor. - physical_position: Option, } impl Default for Cursor { @@ -409,7 +407,6 @@ impl Default for Cursor { visible: true, grab_mode: CursorGrabMode::None, hit_test: true, - physical_position: None, } } } @@ -648,7 +645,7 @@ pub enum CursorGrabMode { } /// Stores internal state that isn't directly accessible. -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Reflect, FromReflect)] +#[derive(Default, Debug, Copy, Clone, PartialEq, Reflect, FromReflect)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -660,6 +657,8 @@ pub struct InternalWindowState { minimize_request: Option, /// If this is true then next frame we will ask to maximize/un-maximize the window depending on `maximized`. maximize_request: Option, + /// Unscaled cursor position. + physical_cursor_position: Option, } impl InternalWindowState { From 84711e38b12016d6eb48b1e74ab41e33085f8141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=9E=97=E4=BC=9F?= Date: Thu, 9 Mar 2023 13:39:49 +0800 Subject: [PATCH 06/68] Set cursor hittest during window creation (#7966) --- crates/bevy_winit/src/winit_windows.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index eefb07ec73..33441333b9 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -166,6 +166,12 @@ impl WinitWindows { } winit_window.set_cursor_visible(window.cursor.visible); + if let Err(err) = winit_window.set_cursor_hittest(window.cursor.hit_test) { + warn!( + "Could not set cursor hit test for window {:?}: {:?}", + window.title, err + ); + } self.entity_to_winit.insert(entity, winit_window.id()); self.winit_to_entity.insert(winit_window.id(), entity); From 545965075f5ac00e84baa0c5834fe84083688d26 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Thu, 9 Mar 2023 00:40:06 -0500 Subject: [PATCH 07/68] Make `WorldQuery` meta types unnameable (#7964) --- crates/bevy_ecs/macros/src/fetch.rs | 53 +++++++++++++++++------------ crates/bevy_ecs/src/query/fetch.rs | 6 +--- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/crates/bevy_ecs/macros/src/fetch.rs b/crates/bevy_ecs/macros/src/fetch.rs index 418c5741e7..0308d8dc23 100644 --- a/crates/bevy_ecs/macros/src/fetch.rs +++ b/crates/bevy_ecs/macros/src/fetch.rs @@ -176,7 +176,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { &field_types }; - quote! { + let item_struct = quote! { #derive_macro_call #[doc = "Automatically generated [`WorldQuery`] item type for [`"] #[doc = stringify!(#struct_name)] @@ -186,7 +186,9 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { #(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::WorldQuery>::Item<'__w>,)* #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* } + }; + let query_impl = quote! { #[doc(hidden)] #[doc = "Automatically generated internal [`WorldQuery`] fetch type for [`"] #[doc = stringify!(#struct_name)] @@ -324,13 +326,14 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { true #(&& <#field_types>::matches_component_set(&state.#field_idents, _set_contains_id))* } } - } + }; + (item_struct, query_impl) }; - let mutable_impl = impl_fetch(false); - let readonly_impl = if fetch_struct_attributes.is_mutable { - let world_query_impl = impl_fetch(true); - quote! { + let (mutable_struct, mutable_impl) = impl_fetch(false); + let (read_only_struct, read_only_impl) = if fetch_struct_attributes.is_mutable { + let (readonly_state, read_only_impl) = impl_fetch(true); + let read_only_structs = quote! { #[doc = "Automatically generated [`WorldQuery`] type for a read-only variant of [`"] #[doc = stringify!(#struct_name)] #[doc = "`]."] @@ -340,10 +343,11 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* } - #world_query_impl - } + #readonly_state + }; + (read_only_structs, read_only_impl) } else { - quote! {} + (quote! {}, quote! {}) }; let read_only_asserts = if fetch_struct_attributes.is_mutable { @@ -367,24 +371,30 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { }; TokenStream::from(quote! { - #mutable_impl + #mutable_struct - #readonly_impl - - #[doc(hidden)] - #[doc = "Automatically generated internal [`WorldQuery`] state type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], used for caching."] - #[automatically_derived] - #visibility struct #state_struct_name #user_impl_generics #user_where_clauses { - #(#field_idents: <#field_types as #path::query::WorldQuery>::State,)* - #(#ignored_field_idents: #ignored_field_types,)* - } + #read_only_struct /// SAFETY: we assert fields are readonly below unsafe impl #user_impl_generics #path::query::ReadOnlyWorldQuery for #read_only_struct_name #user_ty_generics #user_where_clauses {} + const _: () = { + #[doc(hidden)] + #[doc = "Automatically generated internal [`WorldQuery`] state type for [`"] + #[doc = stringify!(#struct_name)] + #[doc = "`], used for caching."] + #[automatically_derived] + #visibility struct #state_struct_name #user_impl_generics #user_where_clauses { + #(#field_idents: <#field_types as #path::query::WorldQuery>::State,)* + #(#ignored_field_idents: #ignored_field_types,)* + } + + #mutable_impl + + #read_only_impl + }; + #[allow(dead_code)] const _: () = { fn assert_readonly() @@ -412,7 +422,6 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { #(q.#ignored_field_idents;)* #(q2.#field_idents;)* #(q2.#ignored_field_idents;)* - } }; }) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 7e4c7a3f74..9ff765648f 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -92,16 +92,13 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// /// ## Macro expansion /// -/// Expanding the macro will declare three or six additional structs, depending on whether or not the struct is marked as mutable. +/// Expanding the macro will declare one or three additional structs, depending on whether or not the struct is marked as mutable. /// For a struct named `X`, the additional structs will be: /// /// |Struct name|`mutable` only|Description| /// |:---:|:---:|---| -/// |`XState`|---|Used as the [`State`] type for `X` and `XReadOnly`| /// |`XItem`|---|The type of the query item for `X`| -/// |`XFetch`|---|Used as the [`Fetch`] type for `X`| /// |`XReadOnlyItem`|✓|The type of the query item for `XReadOnly`| -/// |`XReadOnlyFetch`|✓|Used as the [`Fetch`] type for `XReadOnly`| /// |`XReadOnly`|✓|[`ReadOnly`] variant of `X`| /// /// ## Adding mutable references @@ -296,7 +293,6 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// [`Added`]: crate::query::Added /// [`fetch`]: Self::fetch /// [`Changed`]: crate::query::Changed -/// [`Fetch`]: crate::query::WorldQuery::Fetch /// [`matches_component_set`]: Self::matches_component_set /// [`Or`]: crate::query::Or /// [`Query`]: crate::system::Query From 21ddc60372aa38bbd1b237bd4384240cf9d36745 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Thu, 9 Mar 2023 00:40:58 -0500 Subject: [PATCH 08/68] Add a public constructor for `Mut` (#7931) --- crates/bevy_ecs/src/change_detection.rs | 55 +++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index ad55766578..e9d52e8281 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -537,6 +537,41 @@ pub struct Mut<'a, T: ?Sized> { pub(crate) ticks: TicksMut<'a>, } +impl<'a, T: ?Sized> Mut<'a, T> { + /// Creates a new change-detection enabled smart pointer. + /// In almost all cases you do not need to call this method manually, + /// as instances of `Mut` will be created by engine-internal code. + /// + /// Many use-cases of this method would be better served by [`Mut::map_unchanged`] + /// or [`Mut::reborrow`]. + /// + /// - `value` - The value wrapped by this smart pointer. + /// - `added` - A [`Tick`] that stores the tick when the wrapped value was created. + /// - `last_changed` - A [`Tick`] that stores the last time the wrapped value was changed. + /// This will be updated to the value of `change_tick` if the returned smart pointer + /// is modified. + /// - `last_change_tick` - A [`Tick`], occurring before `change_tick`, which is used + /// as a reference to determine whether the wrapped value is newly added or changed. + /// - `change_tick` - A [`Tick`] corresponding to the current point in time -- "now". + pub fn new( + value: &'a mut T, + added: &'a mut Tick, + last_changed: &'a mut Tick, + last_change_tick: u32, + change_tick: u32, + ) -> Self { + Self { + value, + ticks: TicksMut { + added, + changed: last_changed, + last_change_tick, + change_tick, + }, + } + } +} + impl<'a, T: ?Sized> From> for Ref<'a, T> { fn from(mut_ref: Mut<'a, T>) -> Self { Self { @@ -827,6 +862,26 @@ mod tests { assert_eq!(4, into_mut.ticks.change_tick); } + #[test] + fn mut_new() { + let mut component_ticks = ComponentTicks { + added: Tick::new(1), + changed: Tick::new(3), + }; + let mut res = R {}; + + let val = Mut::new( + &mut res, + &mut component_ticks.added, + &mut component_ticks.changed, + 2, // last_change_tick + 4, // current change_tick + ); + + assert!(!val.is_added()); + assert!(val.is_changed()); + } + #[test] fn mut_from_non_send_mut() { let mut component_ticks = ComponentTicks { From 37df316219d419f41e8666b7d1179394c47c4f00 Mon Sep 17 00:00:00 2001 From: James Liu Date: Wed, 8 Mar 2023 23:22:48 -0800 Subject: [PATCH 09/68] Document bevy_ecs::storage (#7770) Co-authored-by: Alice Cecile Co-authored-by: Rob Parrett Co-authored-by: Carter Weinberg Co-authored-by: MinerSebas <66798382+MinerSebas@users.noreply.github.com> Co-authored-by: Cameron <51241057+maniwani@users.noreply.github.com> --- crates/bevy_ecs/src/archetype.rs | 3 + crates/bevy_ecs/src/storage/blob_vec.rs | 48 +++++- crates/bevy_ecs/src/storage/mod.rs | 24 +++ crates/bevy_ecs/src/storage/resource.rs | 15 +- crates/bevy_ecs/src/storage/sparse_set.rs | 66 +++++++ crates/bevy_ecs/src/storage/table.rs | 199 ++++++++++++++++++++-- 6 files changed, 335 insertions(+), 20 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index a2215630a8..d106a83a4f 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -648,6 +648,9 @@ impl Archetypes { self.archetypes.get(id.index()) } + /// # Panics + /// + /// Panics if `a` and `b` are equal. #[inline] pub(crate) fn get_2_mut( &mut self, diff --git a/crates/bevy_ecs/src/storage/blob_vec.rs b/crates/bevy_ecs/src/storage/blob_vec.rs index 49b370b7b8..1f7f636fb6 100644 --- a/crates/bevy_ecs/src/storage/blob_vec.rs +++ b/crates/bevy_ecs/src/storage/blob_vec.rs @@ -10,7 +10,9 @@ use bevy_utils::OnDrop; /// A flat, type-erased data storage type /// -/// Used to densely store homogeneous ECS data. +/// Used to densely store homogeneous ECS data. A blob is usually just an arbitrary block of contiguous memory without any identity, and +/// could be used to represent any arbitrary data (i.e. string, arrays, etc). This type is an extendable and reallcatable blob, which makes +/// it a blobby Vec, a `BlobVec`. pub(super) struct BlobVec { item_layout: Layout, capacity: usize, @@ -35,6 +37,13 @@ impl std::fmt::Debug for BlobVec { } impl BlobVec { + /// Creates a new [`BlobVec`] with the specified `capacity`. + /// + /// `drop` is an optional function pointer that is meant to be invoked when any element in the [`BlobVec`] + /// should be dropped. For all Rust-based types, this should match 1:1 with the implementation of [`Drop`] + /// if present, and should be `None` if `T: !Drop`. For non-Rust based types, this should match any cleanup + /// processes typically associated with the stored element. + /// /// # Safety /// /// `drop` should be safe to call with an [`OwningPtr`] pointing to any item that's been pushed into this [`BlobVec`]. @@ -42,6 +51,7 @@ impl BlobVec { /// If `drop` is `None`, the items will be leaked. This should generally be set as None based on [`needs_drop`]. /// /// [`needs_drop`]: core::mem::needs_drop + /// [`Drop`]: core::ops::Drop pub unsafe fn new( item_layout: Layout, drop: Option)>, @@ -70,26 +80,36 @@ impl BlobVec { } } + /// Returns the number of elements in the vector. #[inline] pub fn len(&self) -> usize { self.len } + /// Returns `true` if the vector contains no elements. #[inline] pub fn is_empty(&self) -> bool { self.len == 0 } + /// Returns the total number of elements the vector can hold without reallocating. #[inline] pub fn capacity(&self) -> usize { self.capacity } + /// Returns the [`Layout`] of the element type stored in the vector. #[inline] pub fn layout(&self) -> Layout { self.item_layout } + /// Reserves the minimum capacity for at least `additional` more elements to be inserted in the given `BlobVec`. + /// After calling `reserve_exact`, capacity will be greater than or equal to `self.len() + additional`. Does nothing if + /// the capacity is already sufficient. + /// + /// Note that the allocator may give the collection more space than it requests. Therefore, capacity can not be relied upon + /// to be precisely minimal. pub fn reserve_exact(&mut self, additional: usize) { let available_space = self.capacity - self.len; if available_space < additional && self.item_layout.size() > 0 { @@ -134,6 +154,8 @@ impl BlobVec { self.capacity = new_capacity; } + /// Initializes the value at `index` to `value`. This function does not do any bounds checking. + /// /// # Safety /// - index must be in bounds /// - the memory in the [`BlobVec`] starting at index `index`, of a size matching this [`BlobVec`]'s @@ -145,6 +167,8 @@ impl BlobVec { std::ptr::copy_nonoverlapping::(value.as_ptr(), ptr.as_ptr(), self.item_layout.size()); } + /// Replaces the value at `index` with `value`. This function does not do any bounds checking. + /// /// # Safety /// - index must be in-bounds /// - the memory in the [`BlobVec`] starting at index `index`, of a size matching this @@ -201,10 +225,10 @@ impl BlobVec { std::ptr::copy_nonoverlapping::(source, destination.as_ptr(), self.item_layout.size()); } - /// Pushes a value to the [`BlobVec`]. + /// Appends an element to the back of the vector. /// /// # Safety - /// `value` must be valid to add to this [`BlobVec`] + /// The `value` must match the [`layout`](`BlobVec::layout`) of the elements in the [`BlobVec`]. #[inline] pub unsafe fn push(&mut self, value: OwningPtr<'_>) { self.reserve_exact(1); @@ -213,6 +237,8 @@ impl BlobVec { self.initialize_unchecked(index, value); } + /// Forces the length of the vector to `len`. + /// /// # Safety /// `len` must be <= `capacity`. if length is decreased, "out of bounds" items must be dropped. /// Newly added items must be immediately populated with valid values and length must be @@ -255,6 +281,7 @@ impl BlobVec { /// Removes the value at `index` and copies the value stored into `ptr`. /// Does not do any bounds checking on `index`. + /// The removed element is replaced by the last element of the `BlobVec`. /// /// # Safety /// It is the caller's responsibility to ensure that `index` is < `self.len()` @@ -274,6 +301,10 @@ impl BlobVec { self.len -= 1; } + /// Removes the value at `index` and drops it. + /// Does not do any bounds checking on `index`. + /// The removed element is replaced by the last element of the `BlobVec`. + /// /// # Safety /// It is the caller's responsibility to ensure that `index` is < self.len() #[inline] @@ -286,8 +317,10 @@ impl BlobVec { } } + /// Returns a reference to the element at `index`, without doing bounds checking. + /// /// # Safety - /// It is the caller's responsibility to ensure that `index` is < self.len() + /// It is the caller's responsibility to ensure that `index < self.len()`. #[inline] pub unsafe fn get_unchecked(&self, index: usize) -> Ptr<'_> { debug_assert!(index < self.len()); @@ -300,8 +333,10 @@ impl BlobVec { self.get_ptr().byte_add(index * size) } + /// Returns a mutable reference to the element at `index`, without doing bounds checking. + /// /// # Safety - /// It is the caller's responsibility to ensure that `index` is < self.len() + /// It is the caller's responsibility to ensure that `index < self.len()`. #[inline] pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> PtrMut<'_> { debug_assert!(index < self.len()); @@ -337,6 +372,9 @@ impl BlobVec { std::slice::from_raw_parts(self.data.as_ptr() as *const UnsafeCell, self.len) } + /// Clears the vector, removing (and dropping) all values. + /// + /// Note that this method has no effect on the allocated capacity of the vector. pub fn clear(&mut self) { let len = self.len; // We set len to 0 _before_ dropping elements for unwind safety. This ensures we don't diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index b2fab3fdcb..2cabf44303 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -1,4 +1,24 @@ //! Storage layouts for ECS data. +//! +//! This module implements the low-level collections that store data in a [`World`]. These all offer minimal and often +//! unsafe APIs, and have been made `pub` primarily for debugging and monitoring purposes. +//! +//! # Fetching Storages +//! Each of the below data stores can be fetched via [`Storages`], which can be fetched from a +//! [`World`] via [`World::storages`]. It exposes a top level container for each class of storage within +//! ECS: +//! +//! - [`Tables`] - columnar contiguous blocks of memory, optimized for fast iteration. +//! - [`SparseSets`] - sparse `HashMap`-like mappings from entities to components, optimized for random +//! lookup and regular insertion/removal of components. +//! - [`Resources`] - singleton storage for the resources in the world +//! +//! # Safety +//! To avoid trivially unsound use of the APIs in this module, it is explicitly impossible to get a mutable +//! reference to [`Storages`] from [`World`], and none of the types publicly expose a mutable interface. +//! +//! [`World`]: crate::world::World +//! [`World::storages`]: crate::world::World::storages mod blob_vec; mod resource; @@ -12,8 +32,12 @@ pub use table::*; /// The raw data stores of a [World](crate::world::World) #[derive(Default)] pub struct Storages { + /// Backing storage for [`SparseSet`] components. pub sparse_sets: SparseSets, + /// Backing storage for [`Table`] components. pub tables: Tables, + /// Backing storage for resources. pub resources: Resources, + /// Backing storage for `!Send` resources. pub non_send_resources: Resources, } diff --git a/crates/bevy_ecs/src/storage/resource.rs b/crates/bevy_ecs/src/storage/resource.rs index f1a0e9fb53..bdebad152e 100644 --- a/crates/bevy_ecs/src/storage/resource.rs +++ b/crates/bevy_ecs/src/storage/resource.rs @@ -42,6 +42,10 @@ impl ResourceData { /// The only row in the underlying column. const ROW: TableRow = TableRow::new(0); + /// Validates the access to `!Send` resources is only done on the thread they were created from. + /// + /// # Panics + /// If `SEND` is false, this will panic if called from a different thread than the one it was inserted from. #[inline] fn validate_access(&self) { if SEND { @@ -70,7 +74,7 @@ impl ResourceData { self.id } - /// Gets a read-only pointer to the underlying resource, if available. + /// Returns a reference to the resource, if it exists. /// /// # Panics /// If `SEND` is false, this will panic if a value is present and is not accessed from the @@ -83,12 +87,14 @@ impl ResourceData { }) } - /// Gets a read-only reference to the change ticks of the underlying resource, if available. + /// Returns a reference to the resource's change ticks, if it exists. #[inline] pub fn get_ticks(&self) -> Option { self.column.get_ticks(Self::ROW) } + /// Returns references to the resource and its change ticks, if it exists. + /// /// # Panics /// If `SEND` is false, this will panic if a value is present and is not accessed from the /// original thread it was inserted in. @@ -100,6 +106,11 @@ impl ResourceData { }) } + /// Returns a mutable reference to the resource, if it exists. + /// + /// # Panics + /// If `SEND` is false, this will panic if a value is present and is not accessed from the + /// original thread it was inserted in. pub(crate) fn get_mut( &mut self, last_change_tick: u32, diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 54412b54c2..217609dc8c 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -41,12 +41,16 @@ impl SparseArray { macro_rules! impl_sparse_array { ($ty:ident) => { impl $ty { + /// Returns `true` if the collection contains a value for the specified `index`. #[inline] pub fn contains(&self, index: I) -> bool { let index = index.sparse_set_index(); self.values.get(index).map(|v| v.is_some()).unwrap_or(false) } + /// Returns a reference to the value at `index`. + /// + /// Returns `None` if `index` does not have a value or if `index` is out of bounds. #[inline] pub fn get(&self, index: I) -> Option<&V> { let index = index.sparse_set_index(); @@ -60,6 +64,9 @@ impl_sparse_array!(SparseArray); impl_sparse_array!(ImmutableSparseArray); impl SparseArray { + /// Inserts `value` at `index` in the array. + /// + /// If `index` is out-of-bounds, this will enlarge the buffer to accommodate it. #[inline] pub fn insert(&mut self, index: I, value: V) { let index = index.sparse_set_index(); @@ -69,6 +76,9 @@ impl SparseArray { self.values[index] = Some(value); } + /// Returns a mutable reference to the value at `index`. + /// + /// Returns `None` if `index` does not have a value or if `index` is out of bounds. #[inline] pub fn get_mut(&mut self, index: I) -> Option<&mut V> { let index = index.sparse_set_index(); @@ -78,16 +88,21 @@ impl SparseArray { .unwrap_or(None) } + /// Removes and returns the value stored at `index`. + /// + /// Returns `None` if `index` did not have a value or if `index` is out of bounds. #[inline] pub fn remove(&mut self, index: I) -> Option { let index = index.sparse_set_index(); self.values.get_mut(index).and_then(|value| value.take()) } + /// Removes all of the values stored within. pub fn clear(&mut self) { self.values.clear(); } + /// Converts the [`SparseArray`] into an immutable variant. pub(crate) fn into_immutable(self) -> ImmutableSparseArray { ImmutableSparseArray { values: self.values.into_boxed_slice(), @@ -113,6 +128,8 @@ pub struct ComponentSparseSet { } impl ComponentSparseSet { + /// Creates a new [`ComponentSparseSet`] with a given component type layout and + /// initial `capacity`. pub(crate) fn new(component_info: &ComponentInfo, capacity: usize) -> Self { Self { dense: Column::with_capacity(component_info, capacity), @@ -121,17 +138,20 @@ impl ComponentSparseSet { } } + /// Removes all of the values stored within. pub(crate) fn clear(&mut self) { self.dense.clear(); self.entities.clear(); self.sparse.clear(); } + /// Returns the number of component values in the sparse set. #[inline] pub fn len(&self) -> usize { self.dense.len() } + /// Returns `true` if the sparse set contains no component values. #[inline] pub fn is_empty(&self) -> bool { self.dense.len() == 0 @@ -162,6 +182,7 @@ impl ComponentSparseSet { } } + /// Returns `true` if the sparse set has a component value for the provided `entity`. #[inline] pub fn contains(&self, entity: Entity) -> bool { #[cfg(debug_assertions)] @@ -178,6 +199,9 @@ impl ComponentSparseSet { self.sparse.contains(entity.index()) } + /// Returns a reference to the entity's component value. + /// + /// Returns `None` if `entity` does not have a component in the sparse set. #[inline] pub fn get(&self, entity: Entity) -> Option> { self.sparse.get(entity.index()).map(|dense_index| { @@ -189,6 +213,9 @@ impl ComponentSparseSet { }) } + /// Returns references to the entity's component value and its added and changed ticks. + /// + /// Returns `None` if `entity` does not have a component in the sparse set. #[inline] pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, TickCells<'_>)> { let dense_index = TableRow::new(*self.sparse.get(entity.index())? as usize); @@ -206,6 +233,9 @@ impl ComponentSparseSet { } } + /// Returns a reference to the "added" tick of the entity's component value. + /// + /// Returns `None` if `entity` does not have a component in the sparse set. #[inline] pub fn get_added_ticks(&self, entity: Entity) -> Option<&UnsafeCell> { let dense_index = *self.sparse.get(entity.index())? as usize; @@ -220,6 +250,9 @@ impl ComponentSparseSet { } } + /// Returns a reference to the "changed" tick of the entity's component value. + /// + /// Returns `None` if `entity` does not have a component in the sparse set. #[inline] pub fn get_changed_ticks(&self, entity: Entity) -> Option<&UnsafeCell> { let dense_index = *self.sparse.get(entity.index())? as usize; @@ -234,6 +267,9 @@ impl ComponentSparseSet { } } + /// Returns a reference to the "added" and "changed" ticks of the entity's component value. + /// + /// Returns `None` if `entity` does not have a component in the sparse set. #[inline] pub fn get_ticks(&self, entity: Entity) -> Option { let dense_index = *self.sparse.get(entity.index())? as usize; @@ -270,6 +306,9 @@ impl ComponentSparseSet { }) } + /// Removes (and drops) the entity's component value from the sparse set. + /// + /// Returns `true` if `entity` had a component value in the sparse set. pub(crate) fn remove(&mut self, entity: Entity) -> bool { if let Some(dense_index) = self.sparse.remove(entity.index()) { let dense_index = dense_index as usize; @@ -320,16 +359,21 @@ pub(crate) struct ImmutableSparseSet { macro_rules! impl_sparse_set { ($ty:ident) => { impl $ty { + /// Returns the number of elements in the sparse set. #[inline] pub fn len(&self) -> usize { self.dense.len() } + /// Returns `true` if the sparse set contains a value for `index`. #[inline] pub fn contains(&self, index: I) -> bool { self.sparse.contains(index) } + /// Returns a reference to the value for `index`. + /// + /// Returns `None` if `index` does not have a value in the sparse set. pub fn get(&self, index: I) -> Option<&V> { self.sparse.get(index).map(|dense_index| { // SAFETY: if the sparse index points to something in the dense vec, it exists @@ -337,6 +381,9 @@ macro_rules! impl_sparse_set { }) } + /// Returns a mutable reference to the value for `index`. + /// + /// Returns `None` if `index` does not have a value in the sparse set. pub fn get_mut(&mut self, index: I) -> Option<&mut V> { let dense = &mut self.dense; self.sparse.get(index).map(move |dense_index| { @@ -345,22 +392,27 @@ macro_rules! impl_sparse_set { }) } + /// Returns an iterator visiting all keys (indices) in arbitrary order. pub fn indices(&self) -> impl Iterator + '_ { self.indices.iter().cloned() } + /// Returns an iterator visiting all values in arbitrary order. pub fn values(&self) -> impl Iterator { self.dense.iter() } + /// Returns an iterator visiting all values mutably in arbitrary order. pub fn values_mut(&mut self) -> impl Iterator { self.dense.iter_mut() } + /// Returns an iterator visiting all key-value pairs in arbitrary order, with references to the values. pub fn iter(&self) -> impl Iterator { self.indices.iter().zip(self.dense.iter()) } + /// Returns an iterator visiting all key-value pairs in arbitrary order, with mutable references to the values. pub fn iter_mut(&mut self) -> impl Iterator { self.indices.iter().zip(self.dense.iter_mut()) } @@ -378,6 +430,7 @@ impl Default for SparseSet { } impl SparseSet { + /// Creates a new [`SparseSet`]. pub const fn new() -> Self { Self { dense: Vec::new(), @@ -388,6 +441,7 @@ impl SparseSet { } impl SparseSet { + /// Creates a new [`SparseSet`] with a specified initial capacity. pub fn with_capacity(capacity: usize) -> Self { Self { dense: Vec::with_capacity(capacity), @@ -396,11 +450,15 @@ impl SparseSet { } } + /// Returns the total number of elements the [`SparseSet`] can hold without needing to reallocate. #[inline] pub fn capacity(&self) -> usize { self.dense.capacity() } + /// Inserts `value` at `index`. + /// + /// If a value was already present at `index`, it will be overwritten. pub fn insert(&mut self, index: I, value: V) { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { // SAFETY: dense indices stored in self.sparse always exist @@ -414,6 +472,8 @@ impl SparseSet { } } + /// Returns a reference to the value for `index`, inserting one computed from `func` + /// if not already present. pub fn get_or_insert_with(&mut self, index: I, func: impl FnOnce() -> V) -> &mut V { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { // SAFETY: dense indices stored in self.sparse always exist @@ -429,11 +489,15 @@ impl SparseSet { } } + /// Returns `true` if the sparse set contains no elements. #[inline] pub fn is_empty(&self) -> bool { self.dense.len() == 0 } + /// Removes and returns the value for `index`. + /// + /// Returns `None` if `index` does not have a value in the sparse set. pub fn remove(&mut self, index: I) -> Option { self.sparse.remove(index).map(|dense_index| { let is_last = dense_index == self.dense.len() - 1; @@ -447,12 +511,14 @@ impl SparseSet { }) } + /// Clears all of the elements from the sparse set. pub fn clear(&mut self) { self.dense.clear(); self.indices.clear(); self.sparse.clear(); } + /// Converts the sparse set into its immutable variant. pub(crate) fn into_immutable(self) -> ImmutableSparseSet { ImmutableSparseSet { dense: self.dense.into_boxed_slice(), diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index a8077be288..d456d1f900 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -33,16 +33,24 @@ pub struct TableId(u32); impl TableId { pub(crate) const INVALID: TableId = TableId(u32::MAX); + /// Creates a new [`TableId`]. + /// + /// `index` *must* be retrieved from calling [`TableId::index`] on a `TableId` you got + /// from a table of a given [`World`] or the created ID may be invalid. + /// + /// [`World`]: crate::world::World #[inline] pub fn new(index: usize) -> Self { TableId(index as u32) } + /// Gets the underlying table index from the ID. #[inline] pub fn index(self) -> usize { self.0 as usize } + /// The [`TableId`] of the [`Table`] without any components. #[inline] pub const fn empty() -> TableId { TableId(0) @@ -71,7 +79,7 @@ impl TableId { pub struct TableRow(u32); impl TableRow { - pub const INVALID: TableRow = TableRow(u32::MAX); + pub(crate) const INVALID: TableRow = TableRow(u32::MAX); /// Creates a `TableRow`. #[inline] @@ -86,6 +94,19 @@ impl TableRow { } } +/// A type-erased contiguous container for data of a homogenous type. +/// +/// Conceptually, a [`Column`] is very similar to a type-erased `Vec`. +/// It also stores the change detection ticks for its components, kept in two separate +/// contiguous buffers internally. An element shares its data across these buffers by using the +/// same index (i.e. the entity at row 3 has it's data at index 3 and its change detection ticks at +/// index 3). A slice to these contiguous blocks of memory can be fetched +/// via [`Column::get_data_slice`], [`Column::get_added_ticks_slice`], and +/// [`Column::get_changed_ticks_slice`]. +/// +/// Like many other low-level storage types, [`Column`] has a limited and highly unsafe +/// interface. It's highly advised to use higher level types and their safe abstractions +/// instead of working directly with [`Column`]. #[derive(Debug)] pub struct Column { data: BlobVec, @@ -94,6 +115,7 @@ pub struct Column { } impl Column { + /// Constructs a new [`Column`], configured with a component's layout and an initial `capacity`. #[inline] pub(crate) fn with_capacity(component_info: &ComponentInfo, capacity: usize) -> Self { Column { @@ -104,6 +126,7 @@ impl Column { } } + /// Fetches the [`Layout`] for the underlying type. #[inline] pub fn item_layout(&self) -> Layout { self.data.layout() @@ -150,18 +173,29 @@ impl Column { self.data.replace_unchecked(row.index(), data); } + /// Gets the current number of elements stored in the column. #[inline] pub fn len(&self) -> usize { self.data.len() } + /// Checks if the column is empty. Returns `true` if there are no elements, `false` otherwise. #[inline] pub fn is_empty(&self) -> bool { self.data.is_empty() } + /// Removes an element from the [`Column`]. + /// + /// - The value will be dropped if it implements [`Drop`]. + /// - This does not preserve ordering, but is O(1). + /// - This does not do any bounds checking. + /// - The element is replaced with the last element in the [`Column`]. + /// /// # Safety - /// index must be in-bounds + /// `row` must be within the range `[0, self.len())`. + /// + /// [`Drop`]: std::ops::Drop #[inline] pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) { self.data.swap_remove_and_drop_unchecked(row.index()); @@ -169,6 +203,16 @@ impl Column { self.changed_ticks.swap_remove(row.index()); } + /// Removes an element from the [`Column`] and returns it and its change detection ticks. + /// This does not preserve ordering, but is O(1). + /// + /// The element is replaced with the last element in the [`Column`]. + /// + /// It is the caller's responsibility to ensure that the removed value is dropped or used. + /// Failure to do so may result in resources not being released (i.e. files handles not being + /// released, memory leaks, etc.) + /// + /// Returns `None` if `row` is out of bounds. #[inline] #[must_use = "The returned pointer should be used to drop the removed component"] pub(crate) fn swap_remove_and_forget( @@ -184,8 +228,18 @@ impl Column { }) } + /// Removes an element from the [`Column`] and returns it and its change detection ticks. + /// This does not preserve ordering, but is O(1). Unlike [`Column::swap_remove_and_forget`] + /// this does not do any bounds checking. + /// + /// The element is replaced with the last element in the [`Column`]. + /// + /// It's the caller's responsibility to ensure that the removed value is dropped or used. + /// Failure to do so may result in resources not being released (i.e. files handles not being + /// released, memory leaks, etc.) + /// /// # Safety - /// index must be in-bounds + /// `row` must be within the range `[0, self.len())`. #[inline] #[must_use = "The returned pointer should be used to dropped the removed component"] pub(crate) unsafe fn swap_remove_and_forget_unchecked( @@ -225,8 +279,10 @@ impl Column { other.changed_ticks.swap_remove(src_row.index()); } - // # Safety - // - ptr must point to valid data of this column's component type + /// Pushes a new value onto the end of the [`Column`]. + /// + /// # Safety + /// `ptr` must point to valid data of this column's component type pub(crate) unsafe fn push(&mut self, ptr: OwningPtr<'_>, ticks: ComponentTicks) { self.data.push(ptr); self.added_ticks.push(UnsafeCell::new(ticks.added)); @@ -240,27 +296,57 @@ impl Column { self.changed_ticks.reserve_exact(additional); } + /// Fetches the data pointer to the first element of the [`Column`]. + /// + /// The pointer is type erased, so using this function to fetch anything + /// other than the first element will require computing the offset using + /// [`Column::item_layout`]. #[inline] pub fn get_data_ptr(&self) -> Ptr<'_> { self.data.get_ptr() } + /// Fetches the slice to the [`Column`]'s data cast to a given type. + /// + /// Note: The values stored within are [`UnsafeCell`]. + /// Users of this API must ensure that accesses to each individual element + /// adhere to the safety invariants of [`UnsafeCell`]. + /// /// # Safety /// The type `T` must be the type of the items in this column. + /// + /// [`UnsafeCell`]: std::cell::UnsafeCell pub unsafe fn get_data_slice(&self) -> &[UnsafeCell] { self.data.get_slice() } + /// Fetches the slice to the [`Column`]'s "added" change detection ticks. + /// + /// Note: The values stored within are [`UnsafeCell`]. + /// Users of this API must ensure that accesses to each individual element + /// adhere to the safety invariants of [`UnsafeCell`]. + /// + /// [`UnsafeCell`]: std::cell::UnsafeCell #[inline] pub fn get_added_ticks_slice(&self) -> &[UnsafeCell] { &self.added_ticks } + /// Fetches the slice to the [`Column`]'s "changed" change detection ticks. + /// + /// Note: The values stored within are [`UnsafeCell`]. + /// Users of this API must ensure that accesses to each individual element + /// adhere to the safety invariants of [`UnsafeCell`]. + /// + /// [`UnsafeCell`]: std::cell::UnsafeCell #[inline] pub fn get_changed_ticks_slice(&self) -> &[UnsafeCell] { &self.changed_ticks } + /// Fetches a reference to the data and change detection ticks at `row`. + /// + /// Returns `None` if `row` is out of bounds. #[inline] pub fn get(&self, row: TableRow) -> Option<(Ptr<'_>, TickCells<'_>)> { (row.index() < self.data.len()) @@ -277,6 +363,9 @@ impl Column { }) } + /// Fetches a read-only reference to the data at `row`. + /// + /// Returns `None` if `row` is out of bounds. #[inline] pub fn get_data(&self, row: TableRow) -> Option> { // SAFETY: The row is length checked before fetching the pointer. This is being @@ -284,15 +373,21 @@ impl Column { (row.index() < self.data.len()).then(|| unsafe { self.data.get_unchecked(row.index()) }) } + /// Fetches a read-only reference to the data at `row`. Unlike [`Column::get`] this does not + /// do any bounds checking. + /// /// # Safety - /// - index must be in-bounds - /// - no other reference to the data of the same row can exist at the same time + /// - `row` must be within the range `[0, self.len())`. + /// - no other mutable reference to the data of the same row can exist at the same time #[inline] pub unsafe fn get_data_unchecked(&self, row: TableRow) -> Ptr<'_> { debug_assert!(row.index() < self.data.len()); self.data.get_unchecked(row.index()) } + /// Fetches a mutable reference to the data at `row`. + /// + /// Returns `None` if `row` is out of bounds. #[inline] pub fn get_data_mut(&mut self, row: TableRow) -> Option> { // SAFETY: The row is length checked before fetching the pointer. This is being @@ -300,6 +395,9 @@ impl Column { (row.index() < self.data.len()).then(|| unsafe { self.data.get_unchecked_mut(row.index()) }) } + /// Fetches a mutable reference to the data at `row`. Unlike [`Column::get_data_mut`] this does not + /// do any bounds checking. + /// /// # Safety /// - index must be in-bounds /// - no other reference to the data of the same row can exist at the same time @@ -309,16 +407,37 @@ impl Column { self.data.get_unchecked_mut(row.index()) } + /// Fetches the "added" change detection ticks for the value at `row`. + /// + /// Returns `None` if `row` is out of bounds. + /// + /// Note: The values stored within are [`UnsafeCell`]. + /// Users of this API must ensure that accesses to each individual element + /// adhere to the safety invariants of [`UnsafeCell`]. + /// + /// [`UnsafeCell`]: std::cell::UnsafeCell #[inline] pub fn get_added_ticks(&self, row: TableRow) -> Option<&UnsafeCell> { self.added_ticks.get(row.index()) } + /// Fetches the "changed" change detection ticks for the value at `row`. + /// + /// Returns `None` if `row` is out of bounds. + /// + /// Note: The values stored within are [`UnsafeCell`]. + /// Users of this API must ensure that accesses to each individual element + /// adhere to the safety invariants of [`UnsafeCell`]. + /// + /// [`UnsafeCell`]: std::cell::UnsafeCell #[inline] pub fn get_changed_ticks(&self, row: TableRow) -> Option<&UnsafeCell> { self.changed_ticks.get(row.index()) } + /// Fetches the change detection ticks for the value at `row`. + /// + /// Returns `None` if `row` is out of bounds. #[inline] pub fn get_ticks(&self, row: TableRow) -> Option { if row.index() < self.data.len() { @@ -329,24 +448,33 @@ impl Column { } } + /// Fetches the "added" change detection ticks for the value at `row`. Unlike [`Column::get_added_ticks`] + /// this function does not do any bounds checking. + /// /// # Safety - /// index must be in-bounds + /// `row` must be within the range `[0, self.len())`. #[inline] pub unsafe fn get_added_ticks_unchecked(&self, row: TableRow) -> &UnsafeCell { debug_assert!(row.index() < self.added_ticks.len()); self.added_ticks.get_unchecked(row.index()) } + /// Fetches the "changed" change detection ticks for the value at `row`. Unlike [`Column::get_changed_ticks`] + /// this function does not do any bounds checking. + /// /// # Safety - /// index must be in-bounds + /// `row` must be within the range `[0, self.len())`. #[inline] pub unsafe fn get_changed_ticks_unchecked(&self, row: TableRow) -> &UnsafeCell { debug_assert!(row.index() < self.changed_ticks.len()); self.changed_ticks.get_unchecked(row.index()) } + /// Fetches the change detection ticks for the value at `row`. Unlike [`Column::get_ticks`] + /// this function does not do any bounds checking. + /// /// # Safety - /// index must be in-bounds + /// `row` must be within the range `[0, self.len())`. #[inline] pub unsafe fn get_ticks_unchecked(&self, row: TableRow) -> ComponentTicks { debug_assert!(row.index() < self.added_ticks.len()); @@ -357,6 +485,9 @@ impl Column { } } + /// Clears the column, removing all values. + /// + /// Note that this function has no effect on the allocated capacity of the [`Column`]> pub fn clear(&mut self) { self.data.clear(); self.added_ticks.clear(); @@ -417,10 +548,10 @@ impl TableBuilder { /// in a [`World`]. /// /// Conceptually, a `Table` can be thought of as an `HashMap`, where -/// each `Column` is a type-erased `Vec`. Each row corresponds to a single entity +/// each [`Column`] is a type-erased `Vec`. Each row corresponds to a single entity /// (i.e. index 3 in Column A and index 3 in Column B point to different components on the same /// entity). Fetching components from a table involves fetching the associated column for a -/// component type (via it's [`ComponentId`]), then fetching the entity's row within that column. +/// component type (via its [`ComponentId`]), then fetching the entity's row within that column. /// /// [structure-of-arrays]: https://en.wikipedia.org/wiki/AoS_and_SoA#Structure_of_arrays /// [`Component`]: crate::component::Component @@ -431,6 +562,7 @@ pub struct Table { } impl Table { + /// Fetches a read-only slice of the entities stored within the [`Table`]. #[inline] pub fn entities(&self) -> &[Entity] { &self.entities @@ -457,7 +589,8 @@ impl Table { /// Moves the `row` column values to `new_table`, for the columns shared between both tables. /// Returns the index of the new row in `new_table` and the entity in this table swapped in /// to replace it (if an entity was swapped in). missing columns will be "forgotten". It is - /// the caller's responsibility to drop them + /// the caller's responsibility to drop them. Failure to do so may result in resources not + /// being released (i.e. files handles not being released, memory leaks, etc.) /// /// # Safety /// Row must be in-bounds @@ -548,21 +681,39 @@ impl Table { } } + /// Fetches a read-only reference to the [`Column`] for a given [`Component`] within the + /// table. + /// + /// Returns `None` if the corresponding component does not belong to the table. + /// + /// [`Component`]: crate::component::Component #[inline] pub fn get_column(&self, component_id: ComponentId) -> Option<&Column> { self.columns.get(component_id) } + /// Fetches a mutable reference to the [`Column`] for a given [`Component`] within the + /// table. + /// + /// Returns `None` if the corresponding component does not belong to the table. + /// + /// [`Component`]: crate::component::Component #[inline] pub(crate) fn get_column_mut(&mut self, component_id: ComponentId) -> Option<&mut Column> { self.columns.get_mut(component_id) } + /// Checks if the table contains a [`Column`] for a given [`Component`]. + /// + /// Returns `true` if the column is present, `false` otherwise. + /// + /// [`Component`]: crate::component::Component #[inline] pub fn has_column(&self, component_id: ComponentId) -> bool { self.columns.contains(component_id) } + /// Reserves `additional` elements worth of capacity within the table. pub(crate) fn reserve(&mut self, additional: usize) { if self.entities.capacity() - self.entities.len() < additional { self.entities.reserve(additional); @@ -592,21 +743,28 @@ impl Table { TableRow::new(index) } + /// Gets the number of entities currently being stored in the table. #[inline] pub fn entity_count(&self) -> usize { self.entities.len() } + /// Gets the number of components being stored in the table. #[inline] pub fn component_count(&self) -> usize { self.columns.len() } + /// Gets the maximum number of entities the table can currently store + /// without reallocating the underlying memory. #[inline] pub fn entity_capacity(&self) -> usize { self.entities.capacity() } + /// Checks if the [`Table`] is empty or not. + /// + /// Returns `true` if the table contains no entities, `false` otherwise. #[inline] pub fn is_empty(&self) -> bool { self.entities.is_empty() @@ -618,10 +776,12 @@ impl Table { } } + /// Iterates over the [`Column`]s of the [`Table`]. pub fn iter(&self) -> impl Iterator { self.columns.values() } + /// Clears all of the stored components in the [`Table`]. pub(crate) fn clear(&mut self) { self.entities.clear(); for column in self.columns.values_mut() { @@ -666,11 +826,19 @@ impl Tables { self.tables.is_empty() } + /// Fetches a [`Table`] by its [`TableId`]. + /// + /// Returns `None` if `id` is invalid. #[inline] pub fn get(&self, id: TableId) -> Option<&Table> { self.tables.get(id.index()) } + /// Fetches mutable references to two different [`Table`]s. + /// + /// # Panics + /// + /// Panics if `a` and `b` are equal. #[inline] pub(crate) fn get_2_mut(&mut self, a: TableId, b: TableId) -> (&mut Table, &mut Table) { if a.index() > b.index() { @@ -682,6 +850,9 @@ impl Tables { } } + /// Attempts to fetch a table based on the provided components, + /// creating and returning a new [`Table`] if one did not already exist. + /// /// # Safety /// `component_ids` must contain components that exist in `components` pub(crate) unsafe fn get_id_or_insert( @@ -706,10 +877,12 @@ impl Tables { *value } + /// Iterates through all of the tables stored within in [`TableId`] order. pub fn iter(&self) -> std::slice::Iter<'_, Table> { self.tables.iter() } + /// Clears all data from all [`Table`]s stored within. pub(crate) fn clear(&mut self) { for table in &mut self.tables { table.clear(); From e22572d9a31b7d6210139f12e92fc995e80e77fb Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Thu, 9 Mar 2023 18:20:45 +0900 Subject: [PATCH 10/68] Fix typo in utility.rs (#7997) --- crates/bevy_reflect/bevy_reflect_derive/src/utility.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs index cb9b11957c..c0675d5363 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs @@ -92,7 +92,7 @@ impl Default for WhereClauseOptions { /// # Arguments /// /// * `where_clause`: existing `where` clause present on the object to be derived -/// * `where_clause_options`: additional paramters defining which trait bounds to add to the `where` clause +/// * `where_clause_options`: additional parameters defining which trait bounds to add to the `where` clause /// /// # Example /// From f554700108983a8581f9939edfd81c9e4fb4bd07 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 9 Mar 2023 14:12:54 +0000 Subject: [PATCH 11/68] Add methods for calculating the size and postion of UI nodes (#7930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: François --- crates/bevy_ui/src/ui_node.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index a457f699dd..52290aa3a5 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -7,6 +7,7 @@ use bevy_render::{ color::Color, texture::{Image, DEFAULT_IMAGE_HANDLE}, }; +use bevy_transform::prelude::GlobalTransform; use serde::{Deserialize, Serialize}; use std::ops::{Div, DivAssign, Mul, MulAssign}; use thiserror::Error; @@ -26,6 +27,22 @@ impl Node { pub fn size(&self) -> Vec2 { self.calculated_size } + + /// Returns the logical pixel coordinates of the UI node, based on its `GlobalTransform`. + #[inline] + pub fn logical_rect(&self, transform: &GlobalTransform) -> Rect { + Rect::from_center_size(transform.translation().truncate(), self.size()) + } + + /// Returns the physical pixel coordinates of the UI node, based on its `GlobalTransform` and the scale factor. + #[inline] + pub fn physical_rect(&self, transform: &GlobalTransform, scale_factor: f32) -> Rect { + let rect = self.logical_rect(transform); + Rect { + min: rect.min / scale_factor, + max: rect.max / scale_factor, + } + } } impl Node { From b7ac5d5121d4a4f682f9714bc259819ac62f26d8 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Thu, 9 Mar 2023 08:46:06 -0700 Subject: [PATCH 12/68] Update trybuild tests for Rust 1.68 (#8002) --- .../tests/ui/world_query_derive.stderr | 4 ++-- .../tests/reflect_derive/generics.fail.stderr | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/world_query_derive.stderr b/crates/bevy_ecs_compile_fail_tests/tests/ui/world_query_derive.stderr index b762ac275f..158be3f9d7 100644 --- a/crates/bevy_ecs_compile_fail_tests/tests/ui/world_query_derive.stderr +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/world_query_derive.stderr @@ -18,7 +18,7 @@ note: required by a bound in `_::assert_readonly` --> tests/ui/world_query_derive.rs:7:10 | 7 | #[derive(WorldQuery)] - | ^^^^^^^^^^ required by this bound in `_::assert_readonly` + | ^^^^^^^^^^ required by this bound in `assert_readonly` = note: this error originates in the derive macro `WorldQuery` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `MutableMarked: ReadOnlyWorldQuery` is not satisfied @@ -41,5 +41,5 @@ note: required by a bound in `_::assert_readonly` --> tests/ui/world_query_derive.rs:18:10 | 18 | #[derive(WorldQuery)] - | ^^^^^^^^^^ required by this bound in `_::assert_readonly` + | ^^^^^^^^^^ required by this bound in `assert_readonly` = note: this error originates in the derive macro `WorldQuery` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/generics.fail.stderr b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/generics.fail.stderr index f59304dbd4..0f568ed9c5 100644 --- a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/generics.fail.stderr +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/generics.fail.stderr @@ -24,7 +24,7 @@ note: required for `Foo` to implement `Reflect` --> tests/reflect_derive/generics.fail.rs:3:10 | 3 | #[derive(Reflect)] - | ^^^^^^^ + | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro 4 | struct Foo { | ^^^^^^ = note: required for the cast from `Foo` to the object type `dyn Reflect` From 2e7b915ba4348d85254eada9798370de8c41086b Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Thu, 9 Mar 2023 12:17:02 -0500 Subject: [PATCH 13/68] Increase type safety and clarity for change detection (#7905) --- crates/bevy_ecs/macros/src/fetch.rs | 8 +- crates/bevy_ecs/macros/src/lib.rs | 4 +- crates/bevy_ecs/src/bundle.rs | 12 +- crates/bevy_ecs/src/change_detection.rs | 131 ++++++++--------- crates/bevy_ecs/src/component.rs | 97 +++++++------ crates/bevy_ecs/src/query/fetch.rs | 119 +++++++-------- crates/bevy_ecs/src/query/filter.rs | 38 ++--- crates/bevy_ecs/src/query/iter.rs | 61 +++----- crates/bevy_ecs/src/query/state.rs | 101 ++++++------- crates/bevy_ecs/src/removal_detection.rs | 4 +- crates/bevy_ecs/src/schedule/schedule.rs | 6 +- crates/bevy_ecs/src/storage/resource.rs | 14 +- crates/bevy_ecs/src/storage/sparse_set.rs | 11 +- crates/bevy_ecs/src/storage/table.rs | 13 +- crates/bevy_ecs/src/system/combinator.rs | 17 ++- .../src/system/exclusive_function_system.rs | 21 ++- crates/bevy_ecs/src/system/function_system.rs | 29 ++-- crates/bevy_ecs/src/system/mod.rs | 4 +- crates/bevy_ecs/src/system/query.rs | 137 +++++++----------- crates/bevy_ecs/src/system/system.rs | 36 ++--- crates/bevy_ecs/src/system/system_param.rs | 117 +++++++-------- crates/bevy_ecs/src/world/identifier.rs | 3 +- crates/bevy_ecs/src/world/mod.rs | 40 ++--- crates/bevy_ecs/src/world/spawn_batch.rs | 4 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 15 +- crates/bevy_render/src/extract_param.rs | 3 +- 26 files changed, 475 insertions(+), 570 deletions(-) diff --git a/crates/bevy_ecs/macros/src/fetch.rs b/crates/bevy_ecs/macros/src/fetch.rs index 0308d8dc23..8bb5597f0d 100644 --- a/crates/bevy_ecs/macros/src/fetch.rs +++ b/crates/bevy_ecs/macros/src/fetch.rs @@ -224,16 +224,16 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { unsafe fn init_fetch<'__w>( _world: &'__w #path::world::World, state: &Self::State, - _last_change_tick: u32, - _change_tick: u32 + _last_run: #path::component::Tick, + _this_run: #path::component::Tick, ) -> ::Fetch<'__w> { #fetch_struct_name { #(#field_idents: <#field_types>::init_fetch( _world, &state.#field_idents, - _last_change_tick, - _change_tick + _last_run, + _this_run, ), )* #(#ignored_field_idents: Default::default(),)* diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index b4dde955dd..dbafe0d9e3 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -227,7 +227,7 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { state: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'w, 's> { ParamSet { param_states: state, @@ -431,7 +431,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { state: &'s2 mut Self::State, system_meta: &#path::system::SystemMeta, world: &'w2 #path::world::World, - change_tick: u32, + change_tick: #path::component::Tick, ) -> Self::Item<'w2, 's2> { let (#(#tuple_patterns,)*) = < (#(#tuple_types,)*) as #path::system::SystemParam diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index ff4ceb03af..66fe6cdbcf 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -283,7 +283,7 @@ impl BundleInfo { components: &mut Components, storages: &'a mut Storages, archetype_id: ArchetypeId, - change_tick: u32, + change_tick: Tick, ) -> BundleInserter<'a, 'b> { let new_archetype_id = self.add_bundle_to_archetype(archetypes, storages, components, archetype_id); @@ -342,7 +342,7 @@ impl BundleInfo { archetypes: &'a mut Archetypes, components: &mut Components, storages: &'a mut Storages, - change_tick: u32, + change_tick: Tick, ) -> BundleSpawner<'a, 'b> { let new_archetype_id = self.add_bundle_to_archetype(archetypes, storages, components, ArchetypeId::EMPTY); @@ -383,7 +383,7 @@ impl BundleInfo { bundle_component_status: &S, entity: Entity, table_row: TableRow, - change_tick: u32, + change_tick: Tick, bundle: T, ) { // NOTE: get_components calls this closure on each component in "bundle order". @@ -397,7 +397,7 @@ impl BundleInfo { // SAFETY: bundle_component is a valid index for this bundle match bundle_component_status.get_status(bundle_component) { ComponentStatus::Added => { - column.initialize(table_row, component_ptr, Tick::new(change_tick)); + column.initialize(table_row, component_ptr, change_tick); } ComponentStatus::Mutated => { column.replace(table_row, component_ptr, change_tick); @@ -508,7 +508,7 @@ pub(crate) struct BundleInserter<'a, 'b> { sparse_sets: &'a mut SparseSets, result: InsertBundleResult<'a>, archetypes_ptr: *mut Archetype, - change_tick: u32, + change_tick: Tick, } pub(crate) enum InsertBundleResult<'a> { @@ -666,7 +666,7 @@ pub(crate) struct BundleSpawner<'a, 'b> { bundle_info: &'b BundleInfo, table: &'a mut Table, sparse_sets: &'a mut SparseSets, - change_tick: u32, + change_tick: Tick, } impl<'a, 'b> BundleSpawner<'a, 'b> { diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index e9d52e8281..273b9b119d 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -56,7 +56,7 @@ pub trait DetectChanges { /// For comparison, the previous change tick of a system can be read using the /// [`SystemChangeTick`](crate::system::SystemChangeTick) /// [`SystemParam`](crate::system::SystemParam). - fn last_changed(&self) -> u32; + fn last_changed(&self) -> Tick; } /// Types that implement reliable change detection. @@ -109,7 +109,7 @@ pub trait DetectChangesMut: DetectChanges { /// This is a complex and error-prone operation, primarily intended for use with rollback networking strategies. /// If you merely want to flag this data as changed, use [`set_changed`](DetectChangesMut::set_changed) instead. /// If you want to avoid triggering change detection, use [`bypass_change_detection`](DetectChangesMut::bypass_change_detection) instead. - fn set_last_changed(&mut self, last_change_tick: u32); + fn set_last_changed(&mut self, last_changed: Tick); /// Manually bypasses change detection, allowing you to mutate the underlying value without updating the change tick. /// @@ -145,19 +145,19 @@ macro_rules! change_detection_impl { fn is_added(&self) -> bool { self.ticks .added - .is_newer_than(self.ticks.last_change_tick, self.ticks.change_tick) + .is_newer_than(self.ticks.last_run, self.ticks.this_run) } #[inline] fn is_changed(&self) -> bool { self.ticks .changed - .is_newer_than(self.ticks.last_change_tick, self.ticks.change_tick) + .is_newer_than(self.ticks.last_run, self.ticks.this_run) } #[inline] - fn last_changed(&self) -> u32 { - self.ticks.changed.tick + fn last_changed(&self) -> Tick { + *self.ticks.changed } } @@ -186,16 +186,12 @@ macro_rules! change_detection_mut_impl { #[inline] fn set_changed(&mut self) { - self.ticks - .changed - .set_changed(self.ticks.change_tick); + *self.ticks.changed = self.ticks.this_run; } #[inline] - fn set_last_changed(&mut self, last_changed: u32) { - self.ticks - .changed - .set_changed(last_changed); + fn set_last_changed(&mut self, last_changed: Tick) { + *self.ticks.changed = last_changed; } #[inline] @@ -242,8 +238,8 @@ macro_rules! impl_methods { ticks: TicksMut { added: self.ticks.added, changed: self.ticks.changed, - last_change_tick: self.ticks.last_change_tick, - change_tick: self.ticks.change_tick, + last_run: self.ticks.last_run, + this_run: self.ticks.this_run, } } } @@ -299,8 +295,8 @@ macro_rules! impl_debug { pub(crate) struct Ticks<'a> { pub(crate) added: &'a Tick, pub(crate) changed: &'a Tick, - pub(crate) last_change_tick: u32, - pub(crate) change_tick: u32, + pub(crate) last_run: Tick, + pub(crate) this_run: Tick, } impl<'a> Ticks<'a> { @@ -309,14 +305,14 @@ impl<'a> Ticks<'a> { #[inline] pub(crate) unsafe fn from_tick_cells( cells: TickCells<'a>, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> Self { Self { added: cells.added.deref(), changed: cells.changed.deref(), - last_change_tick, - change_tick, + last_run, + this_run, } } } @@ -324,8 +320,8 @@ impl<'a> Ticks<'a> { pub(crate) struct TicksMut<'a> { pub(crate) added: &'a mut Tick, pub(crate) changed: &'a mut Tick, - pub(crate) last_change_tick: u32, - pub(crate) change_tick: u32, + pub(crate) last_run: Tick, + pub(crate) this_run: Tick, } impl<'a> TicksMut<'a> { @@ -334,14 +330,14 @@ impl<'a> TicksMut<'a> { #[inline] pub(crate) unsafe fn from_tick_cells( cells: TickCells<'a>, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> Self { Self { added: cells.added.deref_mut(), changed: cells.changed.deref_mut(), - last_change_tick, - change_tick, + last_run, + this_run, } } } @@ -351,8 +347,8 @@ impl<'a> From> for Ticks<'a> { Ticks { added: ticks.added, changed: ticks.changed, - last_change_tick: ticks.last_change_tick, - change_tick: ticks.change_tick, + last_run: ticks.last_run, + this_run: ticks.this_run, } } } @@ -550,23 +546,23 @@ impl<'a, T: ?Sized> Mut<'a, T> { /// - `last_changed` - A [`Tick`] that stores the last time the wrapped value was changed. /// This will be updated to the value of `change_tick` if the returned smart pointer /// is modified. - /// - `last_change_tick` - A [`Tick`], occurring before `change_tick`, which is used + /// - `last_run` - A [`Tick`], occurring before `this_run`, which is used /// as a reference to determine whether the wrapped value is newly added or changed. - /// - `change_tick` - A [`Tick`] corresponding to the current point in time -- "now". + /// - `this_run` - A [`Tick`] corresponding to the current point in time -- "now". pub fn new( value: &'a mut T, added: &'a mut Tick, last_changed: &'a mut Tick, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> Self { Self { value, ticks: TicksMut { added, changed: last_changed, - last_change_tick, - change_tick, + last_run, + this_run, }, } } @@ -643,8 +639,8 @@ impl<'a> MutUntyped<'a> { ticks: TicksMut { added: self.ticks.added, changed: self.ticks.changed, - last_change_tick: self.ticks.last_change_tick, - change_tick: self.ticks.change_tick, + last_run: self.ticks.last_run, + this_run: self.ticks.this_run, }, } } @@ -681,19 +677,19 @@ impl<'a> DetectChanges for MutUntyped<'a> { fn is_added(&self) -> bool { self.ticks .added - .is_newer_than(self.ticks.last_change_tick, self.ticks.change_tick) + .is_newer_than(self.ticks.last_run, self.ticks.this_run) } #[inline] fn is_changed(&self) -> bool { self.ticks .changed - .is_newer_than(self.ticks.last_change_tick, self.ticks.change_tick) + .is_newer_than(self.ticks.last_run, self.ticks.this_run) } #[inline] - fn last_changed(&self) -> u32 { - self.ticks.changed.tick + fn last_changed(&self) -> Tick { + *self.ticks.changed } } @@ -702,12 +698,12 @@ impl<'a> DetectChangesMut for MutUntyped<'a> { #[inline] fn set_changed(&mut self) { - self.ticks.changed.set_changed(self.ticks.change_tick); + *self.ticks.changed = self.ticks.this_run; } #[inline] - fn set_last_changed(&mut self, last_changed: u32) { - self.ticks.changed.set_changed(last_changed); + fn set_last_changed(&mut self, last_changed: Tick) { + *self.ticks.changed = last_changed; } #[inline] @@ -792,7 +788,7 @@ mod tests { } let mut world = World::new(); - world.last_change_tick = u32::MAX; + world.last_change_tick = Tick::new(u32::MAX); *world.change_tick.get_mut() = 0; // component added: 0, changed: 0 @@ -820,8 +816,8 @@ mod tests { let mut query = world.query::>(); for tracker in query.iter(&world) { - let ticks_since_insert = change_tick.wrapping_sub(tracker.ticks.added.tick); - let ticks_since_change = change_tick.wrapping_sub(tracker.ticks.changed.tick); + let ticks_since_insert = change_tick.relative_to(*tracker.ticks.added).get(); + let ticks_since_change = change_tick.relative_to(*tracker.ticks.changed).get(); assert!(ticks_since_insert > MAX_CHANGE_AGE); assert!(ticks_since_change > MAX_CHANGE_AGE); } @@ -830,8 +826,8 @@ mod tests { world.check_change_ticks(); for tracker in query.iter(&world) { - let ticks_since_insert = change_tick.wrapping_sub(tracker.ticks.added.tick); - let ticks_since_change = change_tick.wrapping_sub(tracker.ticks.changed.tick); + let ticks_since_insert = change_tick.relative_to(*tracker.ticks.added).get(); + let ticks_since_change = change_tick.relative_to(*tracker.ticks.changed).get(); assert!(ticks_since_insert == MAX_CHANGE_AGE); assert!(ticks_since_change == MAX_CHANGE_AGE); } @@ -846,8 +842,8 @@ mod tests { let ticks = TicksMut { added: &mut component_ticks.added, changed: &mut component_ticks.changed, - last_change_tick: 3, - change_tick: 4, + last_run: Tick::new(3), + this_run: Tick::new(4), }; let mut res = R {}; let res_mut = ResMut { @@ -856,10 +852,10 @@ mod tests { }; let into_mut: Mut = res_mut.into(); - assert_eq!(1, into_mut.ticks.added.tick); - assert_eq!(2, into_mut.ticks.changed.tick); - assert_eq!(3, into_mut.ticks.last_change_tick); - assert_eq!(4, into_mut.ticks.change_tick); + assert_eq!(1, into_mut.ticks.added.get()); + assert_eq!(2, into_mut.ticks.changed.get()); + assert_eq!(3, into_mut.ticks.last_run.get()); + assert_eq!(4, into_mut.ticks.this_run.get()); } #[test] @@ -874,8 +870,8 @@ mod tests { &mut res, &mut component_ticks.added, &mut component_ticks.changed, - 2, // last_change_tick - 4, // current change_tick + Tick::new(2), // last_run + Tick::new(4), // this_run ); assert!(!val.is_added()); @@ -891,8 +887,8 @@ mod tests { let ticks = TicksMut { added: &mut component_ticks.added, changed: &mut component_ticks.changed, - last_change_tick: 3, - change_tick: 4, + last_run: Tick::new(3), + this_run: Tick::new(4), }; let mut res = R {}; let non_send_mut = NonSendMut { @@ -901,10 +897,10 @@ mod tests { }; let into_mut: Mut = non_send_mut.into(); - assert_eq!(1, into_mut.ticks.added.tick); - assert_eq!(2, into_mut.ticks.changed.tick); - assert_eq!(3, into_mut.ticks.last_change_tick); - assert_eq!(4, into_mut.ticks.change_tick); + assert_eq!(1, into_mut.ticks.added.get()); + assert_eq!(2, into_mut.ticks.changed.get()); + assert_eq!(3, into_mut.ticks.last_run.get()); + assert_eq!(4, into_mut.ticks.this_run.get()); } #[test] @@ -912,7 +908,8 @@ mod tests { use super::*; struct Outer(i64); - let (last_change_tick, change_tick) = (2, 3); + let last_run = Tick::new(2); + let this_run = Tick::new(3); let mut component_ticks = ComponentTicks { added: Tick::new(1), changed: Tick::new(2), @@ -920,8 +917,8 @@ mod tests { let ticks = TicksMut { added: &mut component_ticks.added, changed: &mut component_ticks.changed, - last_change_tick, - change_tick, + last_run, + this_run, }; let mut outer = Outer(0); @@ -939,7 +936,7 @@ mod tests { *inner = 64; assert!(inner.is_changed()); // Modifying one field of a component should flag a change for the entire component. - assert!(component_ticks.is_changed(last_change_tick, change_tick)); + assert!(component_ticks.is_changed(last_run, this_run)); } #[test] diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index ade0eb6b7d..a7c090e0f4 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -586,61 +586,72 @@ impl Components { } } -/// Used to track changes in state between system runs, e.g. components being added or accessed mutably. +/// A value that tracks when a system ran relative to other systems. +/// This is used to power change detection. #[derive(Copy, Clone, Debug)] pub struct Tick { - pub(crate) tick: u32, + tick: u32, } impl Tick { + /// The maximum relative age for a change tick. + /// The value of this is equal to [`crate::change_detection::MAX_CHANGE_AGE`]. + /// + /// Since change detection will not work for any ticks older than this, + /// ticks are periodically scanned to ensure their relative values are below this. + pub const MAX: Self = Self::new(MAX_CHANGE_AGE); + pub const fn new(tick: u32) -> Self { Self { tick } } + /// Gets the value of this change tick. #[inline] - /// Returns `true` if this `Tick` occurred since the system's `last_change_tick`. + pub const fn get(self) -> u32 { + self.tick + } + + /// Sets the value of this change tick. + #[inline] + pub fn set(&mut self, tick: u32) { + self.tick = tick; + } + + #[inline] + /// Returns `true` if this `Tick` occurred since the system's `last_run`. /// - /// `change_tick` is the current tick of the system, used as a reference to help deal with wraparound. - pub fn is_newer_than(&self, last_change_tick: u32, change_tick: u32) -> bool { - // This works even with wraparound because the world tick (`change_tick`) is always "newer" than - // `last_change_tick` and `self.tick`, and we scan periodically to clamp `ComponentTicks` values + /// `this_run` is the current tick of the system, used as a reference to help deal with wraparound. + pub fn is_newer_than(self, last_run: Tick, this_run: Tick) -> bool { + // This works even with wraparound because the world tick (`this_run`) is always "newer" than + // `last_run` and `self.tick`, and we scan periodically to clamp `ComponentTicks` values // so they never get older than `u32::MAX` (the difference would overflow). // // The clamp here ensures determinism (since scans could differ between app runs). - let ticks_since_insert = change_tick.wrapping_sub(self.tick).min(MAX_CHANGE_AGE); - let ticks_since_system = change_tick - .wrapping_sub(last_change_tick) - .min(MAX_CHANGE_AGE); + let ticks_since_insert = this_run.relative_to(self).tick.min(MAX_CHANGE_AGE); + let ticks_since_system = this_run.relative_to(last_run).tick.min(MAX_CHANGE_AGE); ticks_since_system > ticks_since_insert } - pub(crate) fn check_tick(&mut self, change_tick: u32) { - let age = change_tick.wrapping_sub(self.tick); - // This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true - // so long as this check always runs before that can happen. - if age > MAX_CHANGE_AGE { - self.tick = change_tick.wrapping_sub(MAX_CHANGE_AGE); - } + /// Returns a change tick representing the relationship between `self` and `other`. + pub(crate) fn relative_to(self, other: Self) -> Self { + let tick = self.tick.wrapping_sub(other.tick); + Self { tick } } - /// Manually sets the change tick. + /// Wraps this change tick's value if it exceeds [`Tick::MAX`]. /// - /// This is normally done automatically via the [`DerefMut`](std::ops::DerefMut) implementation - /// on [`Mut`](crate::change_detection::Mut), [`ResMut`](crate::change_detection::ResMut), etc. - /// However, components and resources that make use of interior mutability might require manual updates. - /// - /// # Example - /// ```rust,no_run - /// # use bevy_ecs::{world::World, component::ComponentTicks}; - /// let world: World = unimplemented!(); - /// let component_ticks: ComponentTicks = unimplemented!(); - /// - /// component_ticks.set_changed(world.read_change_tick()); - /// ``` - #[inline] - pub fn set_changed(&mut self, change_tick: u32) { - self.tick = change_tick; + /// Returns `true` if wrapping was performed. Otherwise, returns `false`. + pub(crate) fn check_tick(&mut self, tick: Tick) -> bool { + let age = tick.relative_to(*self); + // This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true + // so long as this check always runs before that can happen. + if age.get() > Self::MAX.get() { + *self = tick.relative_to(Self::MAX); + true + } else { + false + } } } @@ -673,20 +684,20 @@ pub struct ComponentTicks { impl ComponentTicks { #[inline] /// Returns `true` if the component was added after the system last ran. - pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool { - self.added.is_newer_than(last_change_tick, change_tick) + pub fn is_added(&self, last_run: Tick, this_run: Tick) -> bool { + self.added.is_newer_than(last_run, this_run) } #[inline] /// Returns `true` if the component was added or mutably dereferenced after the system last ran. - pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool { - self.changed.is_newer_than(last_change_tick, change_tick) + pub fn is_changed(&self, last_run: Tick, this_run: Tick) -> bool { + self.changed.is_newer_than(last_run, this_run) } - pub(crate) fn new(change_tick: u32) -> Self { + pub(crate) fn new(change_tick: Tick) -> Self { Self { - added: Tick::new(change_tick), - changed: Tick::new(change_tick), + added: change_tick, + changed: change_tick, } } @@ -705,8 +716,8 @@ impl ComponentTicks { /// component_ticks.set_changed(world.read_change_tick()); /// ``` #[inline] - pub fn set_changed(&mut self, change_tick: u32) { - self.changed.set_changed(change_tick); + pub fn set_changed(&mut self, change_tick: Tick) { + self.changed = change_tick; } } diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 9ff765648f..23d52c795d 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -329,8 +329,8 @@ pub unsafe trait WorldQuery { unsafe fn init_fetch<'w>( world: &'w World, state: &Self::State, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> Self::Fetch<'w>; /// While this function can be called for any query, it is always safe to call if `Self: ReadOnlyWorldQuery` holds. @@ -460,8 +460,8 @@ unsafe impl WorldQuery for Entity { unsafe fn init_fetch<'w>( _world: &'w World, _state: &Self::State, - _last_change_tick: u32, - _change_tick: u32, + _last_run: Tick, + _this_run: Tick, ) -> Self::Fetch<'w> { } @@ -542,8 +542,8 @@ unsafe impl WorldQuery for &T { unsafe fn init_fetch<'w>( world: &'w World, &component_id: &ComponentId, - _last_change_tick: u32, - _change_tick: u32, + _last_run: Tick, + _this_run: Tick, ) -> ReadFetch<'w, T> { ReadFetch { table_components: None, @@ -660,8 +660,8 @@ pub struct RefFetch<'w, T> { // T::Storage = SparseStorage sparse_set: Option<&'w ComponentSparseSet>, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, } /// SAFETY: `Self` is the same as `Self::ReadOnly` @@ -687,8 +687,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { unsafe fn init_fetch<'w>( world: &'w World, &component_id: &ComponentId, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> RefFetch<'w, T> { RefFetch { table_data: None, @@ -699,8 +699,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { .get(component_id) .debug_checked_unwrap() }), - last_change_tick, - change_tick, + last_run, + this_run, } } @@ -708,8 +708,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { RefFetch { table_data: fetch.table_data, sparse_set: fetch.sparse_set, - last_change_tick: fetch.last_change_tick, - change_tick: fetch.change_tick, + last_run: fetch.last_run, + this_run: fetch.this_run, } } @@ -754,8 +754,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { ticks: Ticks { added: added_ticks.get(table_row.index()).deref(), changed: changed_ticks.get(table_row.index()).deref(), - change_tick: fetch.change_tick, - last_change_tick: fetch.last_change_tick, + this_run: fetch.this_run, + last_run: fetch.last_run, }, } } @@ -767,7 +767,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { .debug_checked_unwrap(); Ref { value: component.deref(), - ticks: Ticks::from_tick_cells(ticks, fetch.last_change_tick, fetch.change_tick), + ticks: Ticks::from_tick_cells(ticks, fetch.last_run, fetch.this_run), } } } @@ -821,8 +821,8 @@ pub struct WriteFetch<'w, T> { // T::Storage = SparseStorage sparse_set: Option<&'w ComponentSparseSet>, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, } /// SAFETY: access of `&T` is a subset of `&mut T` @@ -848,8 +848,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { unsafe fn init_fetch<'w>( world: &'w World, &component_id: &ComponentId, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> WriteFetch<'w, T> { WriteFetch { table_data: None, @@ -860,8 +860,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { .get(component_id) .debug_checked_unwrap() }), - last_change_tick, - change_tick, + last_run, + this_run, } } @@ -869,8 +869,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { WriteFetch { table_data: fetch.table_data, sparse_set: fetch.sparse_set, - last_change_tick: fetch.last_change_tick, - change_tick: fetch.change_tick, + last_run: fetch.last_run, + this_run: fetch.this_run, } } @@ -915,8 +915,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { ticks: TicksMut { added: added_ticks.get(table_row.index()).deref_mut(), changed: changed_ticks.get(table_row.index()).deref_mut(), - change_tick: fetch.change_tick, - last_change_tick: fetch.last_change_tick, + this_run: fetch.this_run, + last_run: fetch.last_run, }, } } @@ -928,11 +928,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { .debug_checked_unwrap(); Mut { value: component.assert_unique().deref_mut(), - ticks: TicksMut::from_tick_cells( - ticks, - fetch.last_change_tick, - fetch.change_tick, - ), + ticks: TicksMut::from_tick_cells(ticks, fetch.last_run, fetch.this_run), } } } @@ -996,11 +992,11 @@ unsafe impl WorldQuery for Option { unsafe fn init_fetch<'w>( world: &'w World, state: &T::State, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> OptionFetch<'w, T> { OptionFetch { - fetch: T::init_fetch(world, state, last_change_tick, change_tick), + fetch: T::init_fetch(world, state, last_run, this_run), matches: false, } } @@ -1114,8 +1110,8 @@ unsafe impl ReadOnlyWorldQuery for Option {} #[deprecated = "`ChangeTrackers` will be removed in bevy 0.11. Use `bevy_ecs::prelude::Ref` instead."] pub struct ChangeTrackers { pub(crate) component_ticks: ComponentTicks, - pub(crate) last_change_tick: u32, - pub(crate) change_tick: u32, + pub(crate) last_run: Tick, + pub(crate) this_run: Tick, marker: PhantomData, } @@ -1134,8 +1130,8 @@ impl std::fmt::Debug for ChangeTrackers { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ChangeTrackers") .field("component_ticks", &self.component_ticks) - .field("last_change_tick", &self.last_change_tick) - .field("change_tick", &self.change_tick) + .field("last_run", &self.last_run) + .field("this_run", &self.this_run) .finish() } } @@ -1144,14 +1140,13 @@ impl std::fmt::Debug for ChangeTrackers { impl ChangeTrackers { /// Returns true if this component has been added since the last execution of this system. pub fn is_added(&self) -> bool { - self.component_ticks - .is_added(self.last_change_tick, self.change_tick) + self.component_ticks.is_added(self.last_run, self.this_run) } /// Returns true if this component has been changed since the last execution of this system. pub fn is_changed(&self) -> bool { self.component_ticks - .is_changed(self.last_change_tick, self.change_tick) + .is_changed(self.last_run, self.this_run) } } @@ -1164,8 +1159,8 @@ pub struct ChangeTrackersFetch<'w, T> { sparse_set: Option<&'w ComponentSparseSet>, marker: PhantomData, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, } #[allow(deprecated)] @@ -1192,8 +1187,8 @@ unsafe impl WorldQuery for ChangeTrackers { unsafe fn init_fetch<'w>( world: &'w World, &component_id: &ComponentId, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> ChangeTrackersFetch<'w, T> { ChangeTrackersFetch { table_added: None, @@ -1206,8 +1201,8 @@ unsafe impl WorldQuery for ChangeTrackers { .debug_checked_unwrap() }), marker: PhantomData, - last_change_tick, - change_tick, + last_run, + this_run, } } @@ -1217,8 +1212,8 @@ unsafe impl WorldQuery for ChangeTrackers { table_changed: fetch.table_changed, sparse_set: fetch.sparse_set, marker: fetch.marker, - last_change_tick: fetch.last_change_tick, - change_tick: fetch.change_tick, + last_run: fetch.last_run, + this_run: fetch.this_run, } } @@ -1268,8 +1263,8 @@ unsafe impl WorldQuery for ChangeTrackers { } }, marker: PhantomData, - last_change_tick: fetch.last_change_tick, - change_tick: fetch.change_tick, + last_run: fetch.last_run, + this_run: fetch.this_run, }, StorageType::SparseSet => ChangeTrackers { component_ticks: fetch @@ -1278,8 +1273,8 @@ unsafe impl WorldQuery for ChangeTrackers { .get_ticks(entity) .debug_checked_unwrap(), marker: PhantomData, - last_change_tick: fetch.last_change_tick, - change_tick: fetch.change_tick, + last_run: fetch.last_run, + this_run: fetch.this_run, }, } } @@ -1338,9 +1333,9 @@ macro_rules! impl_tuple_fetch { } #[allow(clippy::unused_unit)] - unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> { let ($($name,)*) = state; - ($($name::init_fetch(_world, $name, _last_change_tick, _change_tick),)*) + ($($name::init_fetch(_world, $name, _last_run, _this_run),)*) } unsafe fn clone_fetch<'w>( @@ -1447,9 +1442,9 @@ macro_rules! impl_anytuple_fetch { } #[allow(clippy::unused_unit)] - unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> { let ($($name,)*) = state; - ($(($name::init_fetch(_world, $name, _last_change_tick, _change_tick), false),)*) + ($(($name::init_fetch(_world, $name, _last_run, _this_run), false),)*) } unsafe fn clone_fetch<'w>( @@ -1585,13 +1580,7 @@ unsafe impl WorldQuery for NopWorldQuery { const IS_ARCHETYPAL: bool = true; #[inline(always)] - unsafe fn init_fetch( - _world: &World, - _state: &Q::State, - _last_change_tick: u32, - _change_tick: u32, - ) { - } + unsafe fn init_fetch(_world: &World, _state: &Q::State, _last_run: Tick, _this_run: Tick) {} unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {} diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index b2dd9023a6..c0b91b54af 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -50,13 +50,7 @@ unsafe impl WorldQuery for With { fn shrink<'wlong: 'wshort, 'wshort>(_: Self::Item<'wlong>) -> Self::Item<'wshort> {} - unsafe fn init_fetch( - _world: &World, - _state: &ComponentId, - _last_change_tick: u32, - _change_tick: u32, - ) { - } + unsafe fn init_fetch(_world: &World, _state: &ComponentId, _last_run: Tick, _this_run: Tick) {} unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {} @@ -152,13 +146,7 @@ unsafe impl WorldQuery for Without { fn shrink<'wlong: 'wshort, 'wshort>(_: Self::Item<'wlong>) -> Self::Item<'wshort> {} - unsafe fn init_fetch( - _world: &World, - _state: &ComponentId, - _last_change_tick: u32, - _change_tick: u32, - ) { - } + unsafe fn init_fetch(_world: &World, _state: &ComponentId, _last_run: Tick, _this_run: Tick) {} unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {} @@ -277,10 +265,10 @@ macro_rules! impl_query_filter_tuple { const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*; - unsafe fn init_fetch<'w>(world: &'w World, state: &Self::State, last_change_tick: u32, change_tick: u32) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w>(world: &'w World, state: &Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> { let ($($filter,)*) = state; ($(OrFetch { - fetch: $filter::init_fetch(world, $filter, last_change_tick, change_tick), + fetch: $filter::init_fetch(world, $filter, last_run, this_run), matches: false, },)*) } @@ -417,8 +405,8 @@ macro_rules! impl_tick_filter { table_ticks: Option< ThinSlicePtr<'w, UnsafeCell>>, marker: PhantomData, sparse_set: Option<&'w ComponentSparseSet>, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, } // SAFETY: `Self::ReadOnly` is the same as `Self` @@ -432,7 +420,7 @@ macro_rules! impl_tick_filter { item } - unsafe fn init_fetch<'w>(world: &'w World, &id: &ComponentId, last_change_tick: u32, change_tick: u32) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w>(world: &'w World, &id: &ComponentId, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> { Self::Fetch::<'w> { table_ticks: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet) @@ -443,8 +431,8 @@ macro_rules! impl_tick_filter { .debug_checked_unwrap() }), marker: PhantomData, - last_change_tick, - change_tick, + last_run, + this_run, } } @@ -454,8 +442,8 @@ macro_rules! impl_tick_filter { $fetch_name { table_ticks: fetch.table_ticks, sparse_set: fetch.sparse_set, - last_change_tick: fetch.last_change_tick, - change_tick: fetch.change_tick, + last_run: fetch.last_run, + this_run: fetch.this_run, marker: PhantomData, } } @@ -509,7 +497,7 @@ macro_rules! impl_tick_filter { .debug_checked_unwrap() .get(table_row.index()) .deref() - .is_newer_than(fetch.last_change_tick, fetch.change_tick) + .is_newer_than(fetch.last_run, fetch.this_run) } StorageType::SparseSet => { let sparse_set = &fetch @@ -518,7 +506,7 @@ macro_rules! impl_tick_filter { $get_sparse_set(sparse_set, entity) .debug_checked_unwrap() .deref() - .is_newer_than(fetch.last_change_tick, fetch.change_tick) + .is_newer_than(fetch.last_run, fetch.this_run) } } } diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index d456b04410..893df47aac 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -1,5 +1,6 @@ use crate::{ archetype::{ArchetypeEntity, ArchetypeId, Archetypes}, + component::Tick, entity::{Entities, Entity}, prelude::World, query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, WorldQuery}, @@ -29,14 +30,14 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { pub(crate) unsafe fn new( world: &'w World, query_state: &'s QueryState, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> Self { QueryIter { query_state, tables: &world.storages().tables, archetypes: &world.archetypes, - cursor: QueryIterationCursor::init(world, query_state, last_change_tick, change_tick), + cursor: QueryIterationCursor::init(world, query_state, last_run, this_run), } } } @@ -98,21 +99,11 @@ where world: &'w World, query_state: &'s QueryState, entity_list: EntityList, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> QueryManyIter<'w, 's, Q, F, I> { - let fetch = Q::init_fetch( - world, - &query_state.fetch_state, - last_change_tick, - change_tick, - ); - let filter = F::init_fetch( - world, - &query_state.filter_state, - last_change_tick, - change_tick, - ); + let fetch = Q::init_fetch(world, &query_state.fetch_state, last_run, this_run); + let filter = F::init_fetch(world, &query_state.filter_state, last_run, this_run); QueryManyIter { query_state, entities: &world.entities, @@ -298,8 +289,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery, const K: usize> pub(crate) unsafe fn new( world: &'w World, query_state: &'s QueryState, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> Self { // Initialize array with cursors. // There is no FromIterator on arrays, so instead initialize it manually with MaybeUninit @@ -312,16 +303,16 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery, const K: usize> ptr.write(QueryIterationCursor::init( world, query_state, - last_change_tick, - change_tick, + last_run, + this_run, )); } for slot in (1..K).map(|offset| ptr.add(offset)) { slot.write(QueryIterationCursor::init_empty( world, query_state, - last_change_tick, - change_tick, + last_run, + this_run, )); } @@ -496,34 +487,24 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, unsafe fn init_empty( world: &'w World, query_state: &'s QueryState, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> Self { QueryIterationCursor { table_id_iter: [].iter(), archetype_id_iter: [].iter(), - ..Self::init(world, query_state, last_change_tick, change_tick) + ..Self::init(world, query_state, last_run, this_run) } } unsafe fn init( world: &'w World, query_state: &'s QueryState, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> Self { - let fetch = Q::init_fetch( - world, - &query_state.fetch_state, - last_change_tick, - change_tick, - ); - let filter = F::init_fetch( - world, - &query_state.filter_state, - last_change_tick, - change_tick, - ); + let fetch = Q::init_fetch(world, &query_state.fetch_state, last_run, this_run); + let filter = F::init_fetch(world, &query_state.filter_state, last_run, this_run); QueryIterationCursor { fetch, filter, diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index f4ae1a6837..d85c2965de 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1,6 +1,6 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId}, - component::ComponentId, + component::{ComponentId, Tick}, entity::Entity, prelude::FromWorld, query::{ @@ -130,11 +130,11 @@ impl QueryState { /// Checks if the query is empty for the given [`World`], where the last change and current tick are given. #[inline] - pub fn is_empty(&self, world: &World, last_change_tick: u32, change_tick: u32) -> bool { + pub fn is_empty(&self, world: &World, last_run: Tick, this_run: Tick) -> bool { // SAFETY: NopFetch does not access any members while &self ensures no one has exclusive access unsafe { self.as_nop() - .iter_unchecked_manual(world, last_change_tick, change_tick) + .iter_unchecked_manual(world, last_run, this_run) .next() .is_none() } @@ -390,8 +390,8 @@ impl QueryState { &self, world: &'w World, entity: Entity, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> Result, QueryEntityError> { let location = world .entities @@ -407,8 +407,8 @@ impl QueryState { .archetypes .get(location.archetype_id) .debug_checked_unwrap(); - let mut fetch = Q::init_fetch(world, &self.fetch_state, last_change_tick, change_tick); - let mut filter = F::init_fetch(world, &self.filter_state, last_change_tick, change_tick); + let mut fetch = Q::init_fetch(world, &self.fetch_state, last_run, this_run); + let mut filter = F::init_fetch(world, &self.filter_state, last_run, this_run); let table = world .storages() @@ -436,20 +436,17 @@ impl QueryState { &self, world: &'w World, entities: [Entity; N], - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> Result<[ROQueryItem<'w, Q>; N], QueryEntityError> { let mut values = [(); N].map(|_| MaybeUninit::uninit()); for (value, entity) in std::iter::zip(&mut values, entities) { // SAFETY: fetch is read-only // and world must be validated - let item = self.as_readonly().get_unchecked_manual( - world, - entity, - last_change_tick, - change_tick, - )?; + let item = self + .as_readonly() + .get_unchecked_manual(world, entity, last_run, this_run)?; *value = MaybeUninit::new(item); } @@ -471,8 +468,8 @@ impl QueryState { &self, world: &'w World, entities: [Entity; N], - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> Result<[Q::Item<'w>; N], QueryEntityError> { // Verify that all entities are unique for i in 0..N { @@ -486,7 +483,7 @@ impl QueryState { let mut values = [(); N].map(|_| MaybeUninit::uninit()); for (value, entity) in std::iter::zip(&mut values, entities) { - let item = self.get_unchecked_manual(world, entity, last_change_tick, change_tick)?; + let item = self.get_unchecked_manual(world, entity, last_run, this_run)?; *value = MaybeUninit::new(item); } @@ -708,10 +705,10 @@ impl QueryState { pub(crate) unsafe fn iter_unchecked_manual<'w, 's>( &'s self, world: &'w World, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> QueryIter<'w, 's, Q, F> { - QueryIter::new(world, self, last_change_tick, change_tick) + QueryIter::new(world, self, last_run, this_run) } /// Returns an [`Iterator`] for the given [`World`] and list of [`Entity`]'s, where the last change and @@ -729,13 +726,13 @@ impl QueryState { &'s self, entities: EntityList, world: &'w World, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> QueryManyIter<'w, 's, Q, F, EntityList::IntoIter> where EntityList::Item: Borrow, { - QueryManyIter::new(world, self, entities, last_change_tick, change_tick) + QueryManyIter::new(world, self, entities, last_run, this_run) } /// Returns an [`Iterator`] over all possible combinations of `K` query results for the @@ -752,10 +749,10 @@ impl QueryState { pub(crate) unsafe fn iter_combinations_unchecked_manual<'w, 's, const K: usize>( &'s self, world: &'w World, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> QueryCombinationIter<'w, 's, Q, F, K> { - QueryCombinationIter::new(world, self, last_change_tick, change_tick) + QueryCombinationIter::new(world, self, last_run, this_run) } /// Runs `func` on each query result for the given [`World`]. This is faster than the equivalent @@ -856,13 +853,13 @@ impl QueryState { &self, world: &'w World, mut func: FN, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) { // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual - let mut fetch = Q::init_fetch(world, &self.fetch_state, last_change_tick, change_tick); - let mut filter = F::init_fetch(world, &self.filter_state, last_change_tick, change_tick); + let mut fetch = Q::init_fetch(world, &self.fetch_state, last_run, this_run); + let mut filter = F::init_fetch(world, &self.filter_state, last_run, this_run); let tables = &world.storages().tables; if Q::IS_DENSE && F::IS_DENSE { @@ -931,8 +928,8 @@ impl QueryState { world: &'w World, batch_size: usize, func: FN, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) { // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual @@ -950,18 +947,10 @@ impl QueryState { let func = func.clone(); let len = batch_size.min(table.entity_count() - offset); let task = async move { - let mut fetch = Q::init_fetch( - world, - &self.fetch_state, - last_change_tick, - change_tick, - ); - let mut filter = F::init_fetch( - world, - &self.filter_state, - last_change_tick, - change_tick, - ); + let mut fetch = + Q::init_fetch(world, &self.fetch_state, last_run, this_run); + let mut filter = + F::init_fetch(world, &self.filter_state, last_run, this_run); let tables = &world.storages().tables; let table = tables.get(*table_id).debug_checked_unwrap(); let entities = table.entities(); @@ -1002,18 +991,10 @@ impl QueryState { let func = func.clone(); let len = batch_size.min(archetype.len() - offset); let task = async move { - let mut fetch = Q::init_fetch( - world, - &self.fetch_state, - last_change_tick, - change_tick, - ); - let mut filter = F::init_fetch( - world, - &self.filter_state, - last_change_tick, - change_tick, - ); + let mut fetch = + Q::init_fetch(world, &self.fetch_state, last_run, this_run); + let mut filter = + F::init_fetch(world, &self.filter_state, last_run, this_run); let tables = &world.storages().tables; let archetype = world.archetypes.get(*archetype_id).debug_checked_unwrap(); @@ -1161,10 +1142,10 @@ impl QueryState { pub unsafe fn get_single_unchecked_manual<'w>( &self, world: &'w World, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, ) -> Result, QuerySingleError> { - let mut query = self.iter_unchecked_manual(world, last_change_tick, change_tick); + let mut query = self.iter_unchecked_manual(world, last_run, this_run); let first = query.next(); let extra = query.next().is_some(); diff --git a/crates/bevy_ecs/src/removal_detection.rs b/crates/bevy_ecs/src/removal_detection.rs index 004f08ca34..139c092bb8 100644 --- a/crates/bevy_ecs/src/removal_detection.rs +++ b/crates/bevy_ecs/src/removal_detection.rs @@ -2,7 +2,7 @@ use crate::{ self as bevy_ecs, - component::{Component, ComponentId, ComponentIdFor}, + component::{Component, ComponentId, ComponentIdFor, Tick}, entity::Entity, event::{EventId, Events, ManualEventIterator, ManualEventIteratorWithId, ManualEventReader}, prelude::Local, @@ -265,7 +265,7 @@ unsafe impl<'a> SystemParam for &'a RemovedComponentEvents { _state: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, - _change_tick: u32, + _change_tick: Tick, ) -> Self::Item<'w, 's> { world.removed_components() } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index df4b26c6cb..d466e8aef1 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -17,7 +17,7 @@ use fixedbitset::FixedBitSet; use crate::{ self as bevy_ecs, - component::{ComponentId, Components}, + component::{ComponentId, Components, Tick}, schedule::*, system::{BoxedSystem, Resource, System}, world::World, @@ -99,7 +99,7 @@ impl Schedules { /// Iterates the change ticks of all systems in all stored schedules and clamps any older than /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). /// This prevents overflow and thus prevents false positives. - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { #[cfg(feature = "trace")] let _all_span = info_span!("check stored schedule ticks").entered(); // label used when trace feature is enabled @@ -283,7 +283,7 @@ impl Schedule { /// Iterates the change ticks of all systems in the schedule and clamps any older than /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). /// This prevents overflow and thus prevents false positives. - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { for system in &mut self.executable.systems { system.check_change_tick(change_tick); } diff --git a/crates/bevy_ecs/src/storage/resource.rs b/crates/bevy_ecs/src/storage/resource.rs index bdebad152e..f856bf9bb7 100644 --- a/crates/bevy_ecs/src/storage/resource.rs +++ b/crates/bevy_ecs/src/storage/resource.rs @@ -1,6 +1,6 @@ use crate::archetype::ArchetypeComponentId; use crate::change_detection::{MutUntyped, TicksMut}; -use crate::component::{ComponentId, ComponentTicks, Components, TickCells}; +use crate::component::{ComponentId, ComponentTicks, Components, Tick, TickCells}; use crate::storage::{Column, SparseSet, TableRow}; use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; use std::{mem::ManuallyDrop, thread::ThreadId}; @@ -111,17 +111,13 @@ impl ResourceData { /// # Panics /// If `SEND` is false, this will panic if a value is present and is not accessed from the /// original thread it was inserted in. - pub(crate) fn get_mut( - &mut self, - last_change_tick: u32, - change_tick: u32, - ) -> Option> { + pub(crate) fn get_mut(&mut self, last_run: Tick, this_run: Tick) -> Option> { let (ptr, ticks) = self.get_with_ticks()?; Some(MutUntyped { // SAFETY: We have exclusive access to the underlying storage. value: unsafe { ptr.assert_unique() }, // SAFETY: We have exclusive access to the underlying storage. - ticks: unsafe { TicksMut::from_tick_cells(ticks, last_change_tick, change_tick) }, + ticks: unsafe { TicksMut::from_tick_cells(ticks, last_run, this_run) }, }) } @@ -135,7 +131,7 @@ impl ResourceData { /// # Safety /// - `value` must be valid for the underlying type for the resource. #[inline] - pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: u32) { + pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: Tick) { if self.is_present() { self.validate_access(); self.column.replace(Self::ROW, value, change_tick); @@ -284,7 +280,7 @@ impl Resources { }) } - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { for info in self.resources.values_mut() { info.column.check_change_ticks(change_tick); } diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 217609dc8c..d15aca7c4a 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -163,7 +163,12 @@ impl ComponentSparseSet { /// # Safety /// The `value` pointer must point to a valid address that matches the [`Layout`](std::alloc::Layout) /// inside the [`ComponentInfo`] given when constructing this sparse set. - pub(crate) unsafe fn insert(&mut self, entity: Entity, value: OwningPtr<'_>, change_tick: u32) { + pub(crate) unsafe fn insert( + &mut self, + entity: Entity, + value: OwningPtr<'_>, + change_tick: Tick, + ) { if let Some(&dense_index) = self.sparse.get(entity.index()) { #[cfg(debug_assertions)] assert_eq!(entity, self.entities[dense_index as usize]); @@ -332,7 +337,7 @@ impl ComponentSparseSet { } } - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { self.dense.check_change_ticks(change_tick); } } @@ -614,7 +619,7 @@ impl SparseSets { } } - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { for set in self.sets.values_mut() { set.check_change_ticks(change_tick); } diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index d456d1f900..bae50e86c2 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -152,13 +152,10 @@ impl Column { /// # Safety /// Assumes data has already been allocated for the given row. #[inline] - pub(crate) unsafe fn replace(&mut self, row: TableRow, data: OwningPtr<'_>, change_tick: u32) { + pub(crate) unsafe fn replace(&mut self, row: TableRow, data: OwningPtr<'_>, change_tick: Tick) { debug_assert!(row.index() < self.len()); self.data.replace_unchecked(row.index(), data); - self.changed_ticks - .get_unchecked_mut(row.index()) - .get_mut() - .set_changed(change_tick); + *self.changed_ticks.get_unchecked_mut(row.index()).get_mut() = change_tick; } /// Writes component data to the column at given row. @@ -495,7 +492,7 @@ impl Column { } #[inline] - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { for component_ticks in &mut self.added_ticks { component_ticks.get_mut().check_tick(change_tick); } @@ -770,7 +767,7 @@ impl Table { self.entities.is_empty() } - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { for column in self.columns.values_mut() { column.check_change_ticks(change_tick); } @@ -889,7 +886,7 @@ impl Tables { } } - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { for table in &mut self.tables { table.check_change_ticks(change_tick); } diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 51304831d2..ec782e715b 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -3,7 +3,10 @@ use std::{borrow::Cow, cell::UnsafeCell, marker::PhantomData}; use bevy_ptr::UnsafeCellDeref; use crate::{ - archetype::ArchetypeComponentId, component::ComponentId, prelude::World, query::Access, + archetype::ArchetypeComponentId, + component::{ComponentId, Tick}, + prelude::World, + query::Access, }; use super::{ReadOnlySystem, System}; @@ -203,18 +206,18 @@ where .extend(self.b.archetype_component_access()); } - fn check_change_tick(&mut self, change_tick: u32) { + fn check_change_tick(&mut self, change_tick: Tick) { self.a.check_change_tick(change_tick); self.b.check_change_tick(change_tick); } - fn get_last_change_tick(&self) -> u32 { - self.a.get_last_change_tick() + fn get_last_run(&self) -> Tick { + self.a.get_last_run() } - fn set_last_change_tick(&mut self, last_change_tick: u32) { - self.a.set_last_change_tick(last_change_tick); - self.b.set_last_change_tick(last_change_tick); + fn set_last_run(&mut self, last_run: Tick) { + self.a.set_last_run(last_run); + self.b.set_last_run(last_run); } fn default_system_sets(&self) -> Vec> { diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 1fde9619e2..d82c6ff153 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -1,7 +1,6 @@ use crate::{ archetype::ArchetypeComponentId, - change_detection::MAX_CHANGE_AGE, - component::ComponentId, + component::{ComponentId, Tick}, query::Access, system::{ check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, In, IntoSystem, @@ -95,7 +94,7 @@ where fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out { let saved_last_tick = world.last_change_tick; - world.last_change_tick = self.system_meta.last_change_tick; + world.last_change_tick = self.system_meta.last_run; let params = F::Param::get_param( self.param_state.as_mut().expect(PARAM_MESSAGE), @@ -104,7 +103,7 @@ where let out = self.func.run(world, input, params); let change_tick = world.change_tick.get_mut(); - self.system_meta.last_change_tick = *change_tick; + self.system_meta.last_run.set(*change_tick); *change_tick = change_tick.wrapping_add(1); world.last_change_tick = saved_last_tick; @@ -116,12 +115,12 @@ where true } - fn get_last_change_tick(&self) -> u32 { - self.system_meta.last_change_tick + fn get_last_run(&self) -> Tick { + self.system_meta.last_run } - fn set_last_change_tick(&mut self, last_change_tick: u32) { - self.system_meta.last_change_tick = last_change_tick; + fn set_last_run(&mut self, last_run: Tick) { + self.system_meta.last_run = last_run; } #[inline] @@ -134,16 +133,16 @@ where #[inline] fn initialize(&mut self, world: &mut World) { self.world_id = Some(world.id()); - self.system_meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); + self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX); self.param_state = Some(F::Param::init(world, &mut self.system_meta)); } fn update_archetype_component_access(&mut self, _world: &World) {} #[inline] - fn check_change_tick(&mut self, change_tick: u32) { + fn check_change_tick(&mut self, change_tick: Tick) { check_system_change_tick( - &mut self.system_meta.last_change_tick, + &mut self.system_meta.last_run, change_tick, self.system_meta.name.as_ref(), ); diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index fe6701df60..0e88ad0187 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -1,7 +1,6 @@ use crate::{ archetype::{ArchetypeComponentId, ArchetypeGeneration, ArchetypeId}, - change_detection::MAX_CHANGE_AGE, - component::ComponentId, + component::{ComponentId, Tick}, prelude::FromWorld, query::{Access, FilteredAccessSet}, system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem}, @@ -22,7 +21,7 @@ pub struct SystemMeta { // NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent // SystemParams from overriding each other is_send: bool, - pub(crate) last_change_tick: u32, + pub(crate) last_run: Tick, } impl SystemMeta { @@ -32,7 +31,7 @@ impl SystemMeta { archetype_component_access: Access::default(), component_access_set: FilteredAccessSet::default(), is_send: true, - last_change_tick: 0, + last_run: Tick::new(0), } } @@ -151,7 +150,7 @@ pub struct SystemState { impl SystemState { pub fn new(world: &mut World) -> Self { let mut meta = SystemMeta::new::(); - meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); + meta.last_run = world.change_tick().relative_to(Tick::MAX); let param_state = Param::init_state(world, &mut meta); Self { meta, @@ -288,10 +287,10 @@ impl SystemState { unsafe fn fetch<'w, 's>( &'s mut self, world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> SystemParamItem<'w, 's, Param> { let param = Param::get_param(&mut self.param_state, &self.meta, world, change_tick); - self.meta.last_change_tick = change_tick; + self.meta.last_run = change_tick; param } } @@ -464,16 +463,16 @@ where change_tick, ); let out = self.func.run(input, params); - self.system_meta.last_change_tick = change_tick; + self.system_meta.last_run = change_tick; out } - fn get_last_change_tick(&self) -> u32 { - self.system_meta.last_change_tick + fn get_last_run(&self) -> Tick { + self.system_meta.last_run } - fn set_last_change_tick(&mut self, last_change_tick: u32) { - self.system_meta.last_change_tick = last_change_tick; + fn set_last_run(&mut self, last_run: Tick) { + self.system_meta.last_run = last_run; } #[inline] @@ -485,7 +484,7 @@ where #[inline] fn initialize(&mut self, world: &mut World) { self.world_id = Some(world.id()); - self.system_meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); + self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX); self.param_state = Some(F::Param::init_state(world, &mut self.system_meta)); } @@ -507,9 +506,9 @@ where } #[inline] - fn check_change_tick(&mut self, change_tick: u32) { + fn check_change_tick(&mut self, change_tick: Tick) { check_system_change_tick( - &mut self.system_meta.last_change_tick, + &mut self.system_meta.last_run, change_tick, self.system_meta.name.as_ref(), ); diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 0e88adf24e..61351523bc 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -157,7 +157,7 @@ mod tests { archetype::{ArchetypeComponentId, Archetypes}, bundle::Bundles, change_detection::DetectChanges, - component::{Component, Components}, + component::{Component, Components, Tick}, entity::{Entities, Entity}, prelude::AnyOf, query::{Added, Changed, Or, With, Without}, @@ -1227,7 +1227,7 @@ mod tests { let world2 = World::new(); let qstate = world1.query::<()>(); // SAFETY: doesnt access anything - let query = unsafe { Query::new(&world2, &qstate, 0, 0, false) }; + let query = unsafe { Query::new(&world2, &qstate, Tick::new(0), Tick::new(0), false) }; query.iter(); } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 0c093b2587..e8c54472da 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1,5 +1,5 @@ use crate::{ - component::Component, + component::{Component, Tick}, entity::Entity, query::{ BatchingStrategy, QueryCombinationIter, QueryEntityError, QueryIter, QueryManyIter, @@ -276,8 +276,8 @@ use std::{any::TypeId, borrow::Borrow, fmt::Debug}; pub struct Query<'world, 'state, Q: WorldQuery, F: ReadOnlyWorldQuery = ()> { world: &'world World, state: &'state QueryState, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, // SAFETY: This is used to ensure that `get_component_mut::` properly fails when a Query writes C // and gets converted to a read-only query using `to_readonly`. Without checking this, `get_component_mut` relies on // QueryState's archetype_component_access, which will continue allowing write access to C after being cast to @@ -288,7 +288,7 @@ pub struct Query<'world, 'state, Q: WorldQuery, F: ReadOnlyWorldQuery = ()> { impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> std::fmt::Debug for Query<'w, 's, Q, F> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Query {{ matched entities: {}, world: {:?}, state: {:?}, last_change_tick: {}, change_tick: {} }}", self.iter().count(), self.world, self.state, self.last_change_tick, self.change_tick) + write!(f, "Query {{ matched entities: {}, world: {:?}, state: {:?}, last_run: {:?}, this_run: {:?} }}", self.iter().count(), self.world, self.state, self.last_run, self.this_run) } } @@ -307,8 +307,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { pub(crate) unsafe fn new( world: &'w World, state: &'s QueryState, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, force_read_only_component_access: bool, ) -> Self { state.validate_world(world); @@ -317,8 +317,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { force_read_only_component_access, world, state, - last_change_tick, - change_tick, + last_run, + this_run, } } @@ -334,8 +334,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { Query::new( self.world, new_state, - self.last_change_tick, - self.change_tick, + self.last_run, + self.this_run, // SAFETY: this must be set to true or `get_component_mut` will be unsound. See the comments // on this field for more details true, @@ -372,11 +372,9 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict unsafe { - self.state.as_readonly().iter_unchecked_manual( - self.world, - self.last_change_tick, - self.change_tick, - ) + self.state + .as_readonly() + .iter_unchecked_manual(self.world, self.last_run, self.this_run) } } @@ -410,7 +408,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // same-system queries have runtime borrow checks when they conflict unsafe { self.state - .iter_unchecked_manual(self.world, self.last_change_tick, self.change_tick) + .iter_unchecked_manual(self.world, self.last_run, self.this_run) } } @@ -442,8 +440,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { unsafe { self.state.as_readonly().iter_combinations_unchecked_manual( self.world, - self.last_change_tick, - self.change_tick, + self.last_run, + self.this_run, ) } } @@ -474,11 +472,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict unsafe { - self.state.iter_combinations_unchecked_manual( - self.world, - self.last_change_tick, - self.change_tick, - ) + self.state + .iter_combinations_unchecked_manual(self.world, self.last_run, self.this_run) } } @@ -532,8 +527,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { self.state.as_readonly().iter_many_unchecked_manual( entities, self.world, - self.last_change_tick, - self.change_tick, + self.last_run, + self.this_run, ) } } @@ -585,8 +580,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { self.state.iter_many_unchecked_manual( entities, self.world, - self.last_change_tick, - self.change_tick, + self.last_run, + self.this_run, ) } } @@ -606,7 +601,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // SEMI-SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict self.state - .iter_unchecked_manual(self.world, self.last_change_tick, self.change_tick) + .iter_unchecked_manual(self.world, self.last_run, self.this_run) } /// Iterates over all possible combinations of `K` query items without repetition. @@ -625,11 +620,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { ) -> QueryCombinationIter<'_, 's, Q, F, K> { // SEMI-SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict - self.state.iter_combinations_unchecked_manual( - self.world, - self.last_change_tick, - self.change_tick, - ) + self.state + .iter_combinations_unchecked_manual(self.world, self.last_run, self.this_run) } /// Returns an [`Iterator`] over the query items generated from an [`Entity`] list. @@ -650,12 +642,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { where EntityList::Item: Borrow, { - self.state.iter_many_unchecked_manual( - entities, - self.world, - self.last_change_tick, - self.change_tick, - ) + self.state + .iter_many_unchecked_manual(entities, self.world, self.last_run, self.this_run) } /// Runs `f` on each read-only query item. @@ -690,8 +678,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { self.state.as_readonly().for_each_unchecked_manual( self.world, f, - self.last_change_tick, - self.change_tick, + self.last_run, + self.this_run, ); }; } @@ -725,12 +713,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // SAFETY: system runs without conflicts with other systems. same-system queries have runtime // borrow checks when they conflict unsafe { - self.state.for_each_unchecked_manual( - self.world, - f, - self.last_change_tick, - self.change_tick, - ); + self.state + .for_each_unchecked_manual(self.world, f, self.last_run, self.this_run); }; } @@ -801,8 +785,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { self.state.as_readonly().get_unchecked_manual( self.world, entity, - self.last_change_tick, - self.change_tick, + self.last_run, + self.this_run, ) } } @@ -823,12 +807,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { ) -> Result<[ROQueryItem<'_, Q>; N], QueryEntityError> { // SAFETY: it is the scheduler's responsibility to ensure that `Query` is never handed out on the wrong `World`. unsafe { - self.state.get_many_read_only_manual( - self.world, - entities, - self.last_change_tick, - self.change_tick, - ) + self.state + .get_many_read_only_manual(self.world, entities, self.last_run, self.this_run) } } @@ -910,12 +890,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict unsafe { - self.state.get_unchecked_manual( - self.world, - entity, - self.last_change_tick, - self.change_tick, - ) + self.state + .get_unchecked_manual(self.world, entity, self.last_run, self.this_run) } } @@ -934,12 +910,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { ) -> Result<[Q::Item<'_>; N], QueryEntityError> { // SAFETY: scheduler ensures safe Query world access unsafe { - self.state.get_many_unchecked_manual( - self.world, - entities, - self.last_change_tick, - self.change_tick, - ) + self.state + .get_many_unchecked_manual(self.world, entities, self.last_run, self.this_run) } } @@ -1013,7 +985,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // SEMI-SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict self.state - .get_unchecked_manual(self.world, entity, self.last_change_tick, self.change_tick) + .get_unchecked_manual(self.world, entity, self.last_run, self.this_run) } /// Returns a shared reference to the component `T` of the given [`Entity`]. @@ -1151,7 +1123,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { .has_write(archetype_component) { entity_ref - .get_mut_using_ticks::(self.last_change_tick, self.change_tick) + .get_mut_using_ticks::(self.last_run, self.this_run) .ok_or(QueryComponentError::MissingComponent) } else { Err(QueryComponentError::MissingWriteAccess) @@ -1227,8 +1199,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { unsafe { self.state.as_readonly().get_single_unchecked_manual( self.world, - self.last_change_tick, - self.change_tick, + self.last_run, + self.this_run, ) } } @@ -1296,11 +1268,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // the query ensures mutable access to the components it accesses, and the query // is uniquely borrowed unsafe { - self.state.get_single_unchecked_manual( - self.world, - self.last_change_tick, - self.change_tick, - ) + self.state + .get_single_unchecked_manual(self.world, self.last_run, self.this_run) } } @@ -1327,7 +1296,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { #[inline] pub fn is_empty(&self) -> bool { self.state - .is_empty(self.world, self.last_change_tick, self.change_tick) + .is_empty(self.world, self.last_run, self.this_run) } /// Returns `true` if the given [`Entity`] matches the query. @@ -1358,7 +1327,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { unsafe { self.state .as_nop() - .get_unchecked_manual(self.world, entity, self.last_change_tick, self.change_tick) + .get_unchecked_manual(self.world, entity, self.last_run, self.this_run) .is_ok() } } @@ -1459,8 +1428,8 @@ impl<'w, 's, Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { self.state.as_readonly().get_unchecked_manual( self.world, entity, - self.last_change_tick, - self.change_tick, + self.last_run, + self.this_run, ) } } @@ -1493,11 +1462,9 @@ impl<'w, 's, Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict unsafe { - self.state.as_readonly().iter_unchecked_manual( - self.world, - self.last_change_tick, - self.change_tick, - ) + self.state + .as_readonly() + .iter_unchecked_manual(self.world, self.last_run, self.this_run) } } } diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 78980e9063..307dd2bc7d 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -1,10 +1,8 @@ use bevy_utils::tracing::warn; use core::fmt::Debug; -use crate::{ - archetype::ArchetypeComponentId, change_detection::MAX_CHANGE_AGE, component::ComponentId, - query::Access, world::World, -}; +use crate::component::Tick; +use crate::{archetype::ArchetypeComponentId, component::ComponentId, query::Access, world::World}; use std::any::TypeId; use std::borrow::Cow; @@ -63,19 +61,20 @@ pub trait System: Send + Sync + 'static { fn initialize(&mut self, _world: &mut World); /// Update the system's archetype component [`Access`]. fn update_archetype_component_access(&mut self, world: &World); - fn check_change_tick(&mut self, change_tick: u32); + fn check_change_tick(&mut self, change_tick: Tick); /// Returns the system's default [system sets](crate::schedule::SystemSet). fn default_system_sets(&self) -> Vec> { Vec::new() } - /// Gets the system's last change tick - fn get_last_change_tick(&self) -> u32; - /// Sets the system's last change tick + /// Gets the tick indicating the last time this system ran. + fn get_last_run(&self) -> Tick; + /// Overwrites the tick indicating the last time this system ran. + /// /// # Warning /// This is a complex and error-prone operation, that can have unexpected consequences on any system relying on this code. /// However, it can be an essential escape hatch when, for example, /// you are trying to synchronize representations using change detection and need to avoid infinite recursion. - fn set_last_change_tick(&mut self, last_change_tick: u32); + fn set_last_run(&mut self, last_run: Tick); } /// [`System`] types that do not modify the [`World`] when run. @@ -91,23 +90,14 @@ pub unsafe trait ReadOnlySystem: System {} /// A convenience type alias for a boxed [`System`] trait object. pub type BoxedSystem = Box>; -pub(crate) fn check_system_change_tick( - last_change_tick: &mut u32, - change_tick: u32, - system_name: &str, -) { - let age = change_tick.wrapping_sub(*last_change_tick); - // This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true - // so long as this check always runs before that can happen. - if age > MAX_CHANGE_AGE { +pub(crate) fn check_system_change_tick(last_run: &mut Tick, this_run: Tick, system_name: &str) { + if last_run.check_tick(this_run) { + let age = this_run.relative_to(*last_run).get(); warn!( - "System '{}' has not run for {} ticks. \ + "System '{system_name}' has not run for {age} ticks. \ Changes older than {} ticks will not be detected.", - system_name, - age, - MAX_CHANGE_AGE - 1, + Tick::MAX.get() - 1, ); - *last_change_tick = change_tick.wrapping_sub(MAX_CHANGE_AGE); } } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 437a726f44..018c79630f 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -3,7 +3,7 @@ use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundles, change_detection::{Ticks, TicksMut}, - component::{ComponentId, ComponentTicks, Components}, + component::{ComponentId, ComponentTicks, Components, Tick}, entity::Entities, query::{ Access, FilteredAccess, FilteredAccessSet, QueryState, ReadOnlyWorldQuery, WorldQuery, @@ -169,7 +169,7 @@ pub unsafe trait SystemParam: Sized { state: &'state mut Self::State, system_meta: &SystemMeta, world: &'world World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'world, 'state>; } @@ -227,15 +227,9 @@ unsafe impl SystemPara state: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'w, 's> { - Query::new( - world, - state, - system_meta.last_change_tick, - change_tick, - false, - ) + Query::new(world, state, system_meta.last_run, change_tick, false) } } @@ -371,7 +365,7 @@ pub struct ParamSet<'w, 's, T: SystemParam> { param_states: &'s mut T::State, world: &'w World, system_meta: SystemMeta, - change_tick: u32, + change_tick: Tick, } impl_param_set!(); @@ -445,7 +439,7 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'w, 's> { let (ptr, ticks) = world .as_unsafe_world_cell_migration_internal() @@ -462,8 +456,8 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { ticks: Ticks { added: ticks.added.deref(), changed: ticks.changed.deref(), - last_change_tick: system_meta.last_change_tick, - change_tick, + last_run: system_meta.last_run, + this_run: change_tick, }, } } @@ -486,7 +480,7 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'w, 's> { world .as_unsafe_world_cell_migration_internal() @@ -496,8 +490,8 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { ticks: Ticks { added: ticks.added.deref(), changed: ticks.changed.deref(), - last_change_tick: system_meta.last_change_tick, - change_tick, + last_run: system_meta.last_run, + this_run: change_tick, }, }) } @@ -540,7 +534,7 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'w, 's> { let value = world .as_unsafe_world_cell_migration_internal() @@ -557,8 +551,8 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { ticks: TicksMut { added: value.ticks.added, changed: value.ticks.changed, - last_change_tick: system_meta.last_change_tick, - change_tick, + last_run: system_meta.last_run, + this_run: change_tick, }, } } @@ -578,7 +572,7 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'w, 's> { world .as_unsafe_world_cell_migration_internal() @@ -588,8 +582,8 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { ticks: TicksMut { added: value.ticks.added, changed: value.ticks.changed, - last_change_tick: system_meta.last_change_tick, - change_tick, + last_run: system_meta.last_run, + this_run: change_tick, }, }) } @@ -631,7 +625,7 @@ unsafe impl SystemParam for &'_ World { _state: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, - _change_tick: u32, + _change_tick: Tick, ) -> Self::Item<'w, 's> { world } @@ -752,7 +746,7 @@ unsafe impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> { state: &'s mut Self::State, _system_meta: &SystemMeta, _world: &'w World, - _change_tick: u32, + _change_tick: Tick, ) -> Self::Item<'w, 's> { Local(state.get()) } @@ -927,7 +921,7 @@ unsafe impl SystemParam for Deferred<'_, T> { state: &'s mut Self::State, _system_meta: &SystemMeta, _world: &'w World, - _change_tick: u32, + _change_tick: Tick, ) -> Self::Item<'w, 's> { Deferred(state.get()) } @@ -948,8 +942,8 @@ unsafe impl SystemParam for Deferred<'_, T> { pub struct NonSend<'w, T: 'static> { pub(crate) value: &'w T, ticks: ComponentTicks, - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, } // SAFETY: Only reads a single World non-send resource @@ -967,13 +961,12 @@ where 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) + self.ticks.is_added(self.last_run, self.this_run) } /// 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) + self.ticks.is_changed(self.last_run, self.this_run) } } @@ -992,8 +985,8 @@ impl<'a, T> From> for NonSend<'a, T> { added: nsm.ticks.added.to_owned(), changed: nsm.ticks.changed.to_owned(), }, - change_tick: nsm.ticks.change_tick, - last_change_tick: nsm.ticks.last_change_tick, + this_run: nsm.ticks.this_run, + last_run: nsm.ticks.last_run, } } } @@ -1034,7 +1027,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'w, 's> { let (ptr, ticks) = world .as_unsafe_world_cell_migration_internal() @@ -1050,8 +1043,8 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { NonSend { value: ptr.deref(), ticks: ticks.read(), - last_change_tick: system_meta.last_change_tick, - change_tick, + last_run: system_meta.last_run, + this_run: change_tick, } } } @@ -1073,7 +1066,7 @@ unsafe impl SystemParam for Option> { &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'w, 's> { world .as_unsafe_world_cell_migration_internal() @@ -1081,8 +1074,8 @@ unsafe impl SystemParam for Option> { .map(|(ptr, ticks)| NonSend { value: ptr.deref(), ticks: ticks.read(), - last_change_tick: system_meta.last_change_tick, - change_tick, + last_run: system_meta.last_run, + this_run: change_tick, }) } } @@ -1126,7 +1119,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'w, 's> { let (ptr, ticks) = world .as_unsafe_world_cell_migration_internal() @@ -1140,7 +1133,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { }); NonSendMut { value: ptr.assert_unique().deref_mut(), - ticks: TicksMut::from_tick_cells(ticks, system_meta.last_change_tick, change_tick), + ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick), } } } @@ -1159,14 +1152,14 @@ unsafe impl<'a, T: 'static> SystemParam for Option> { &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'w, 's> { world .as_unsafe_world_cell_migration_internal() .get_non_send_with_ticks(component_id) .map(|(ptr, ticks)| NonSendMut { value: ptr.assert_unique().deref_mut(), - ticks: TicksMut::from_tick_cells(ticks, system_meta.last_change_tick, change_tick), + ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick), }) } } @@ -1186,7 +1179,7 @@ unsafe impl<'a> SystemParam for &'a Archetypes { _state: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, - _change_tick: u32, + _change_tick: Tick, ) -> Self::Item<'w, 's> { world.archetypes() } @@ -1207,7 +1200,7 @@ unsafe impl<'a> SystemParam for &'a Components { _state: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, - _change_tick: u32, + _change_tick: Tick, ) -> Self::Item<'w, 's> { world.components() } @@ -1228,7 +1221,7 @@ unsafe impl<'a> SystemParam for &'a Entities { _state: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, - _change_tick: u32, + _change_tick: Tick, ) -> Self::Item<'w, 's> { world.entities() } @@ -1249,7 +1242,7 @@ unsafe impl<'a> SystemParam for &'a Bundles { _state: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, - _change_tick: u32, + _change_tick: Tick, ) -> Self::Item<'w, 's> { world.bundles() } @@ -1258,29 +1251,29 @@ unsafe impl<'a> SystemParam for &'a Bundles { /// A [`SystemParam`] that reads the previous and current change ticks of the system. /// /// A system's change ticks are updated each time it runs: -/// - `last_change_tick` copies the previous value of `change_tick` -/// - `change_tick` copies the current value of [`World::read_change_tick`] +/// - `last_run` copies the previous value of `change_tick` +/// - `this_run` copies the current value of [`World::read_change_tick`] /// -/// Component change ticks that are more recent than `last_change_tick` will be detected by the system. +/// Component change ticks that are more recent than `last_run` will be detected by the system. /// Those can be read by calling [`last_changed`](crate::change_detection::DetectChanges::last_changed) /// on a [`Mut`](crate::change_detection::Mut) or [`ResMut`](crate::change_detection::ResMut). #[derive(Debug)] pub struct SystemChangeTick { - last_change_tick: u32, - change_tick: u32, + last_run: Tick, + this_run: Tick, } impl SystemChangeTick { /// Returns the current [`World`] change tick seen by the system. #[inline] - pub fn change_tick(&self) -> u32 { - self.change_tick + pub fn this_run(&self) -> Tick { + self.this_run } /// Returns the [`World`] change tick seen by the system the previous time it ran. #[inline] - pub fn last_change_tick(&self) -> u32 { - self.last_change_tick + pub fn last_run(&self) -> Tick { + self.last_run } } @@ -1298,11 +1291,11 @@ unsafe impl SystemParam for SystemChangeTick { _state: &'s mut Self::State, system_meta: &SystemMeta, _world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'w, 's> { SystemChangeTick { - last_change_tick: system_meta.last_change_tick, - change_tick, + last_run: system_meta.last_run, + this_run: change_tick, } } } @@ -1368,7 +1361,7 @@ unsafe impl SystemParam for SystemName<'_> { name: &'s mut Self::State, _system_meta: &SystemMeta, _world: &'w World, - _change_tick: u32, + _change_tick: Tick, ) -> Self::Item<'w, 's> { SystemName { name } } @@ -1410,7 +1403,7 @@ macro_rules! impl_system_param_tuple { state: &'s mut Self::State, _system_meta: &SystemMeta, _world: &'w World, - _change_tick: u32, + _change_tick: Tick, ) -> Self::Item<'w, 's> { let ($($param,)*) = state; @@ -1534,7 +1527,7 @@ unsafe impl SystemParam for StaticSystemParam<'_, '_, state: &'state mut Self::State, system_meta: &SystemMeta, world: &'world World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'world, 'state> { // SAFETY: Defer to the safety of P::SystemParam StaticSystemParam(P::get_param(state, system_meta, world, change_tick)) diff --git a/crates/bevy_ecs/src/world/identifier.rs b/crates/bevy_ecs/src/world/identifier.rs index e6a78be5ca..55e2b6a5fa 100644 --- a/crates/bevy_ecs/src/world/identifier.rs +++ b/crates/bevy_ecs/src/world/identifier.rs @@ -1,4 +1,5 @@ use crate::{ + component::Tick, storage::SparseSetIndex, system::{ReadOnlySystemParam, SystemParam}, world::{FromWorld, World}, @@ -56,7 +57,7 @@ unsafe impl SystemParam for WorldId { _: &'state mut Self::State, _: &crate::system::SystemMeta, world: &'world super::World, - _: u32, + _: Tick, ) -> Self::Item<'world, 'state> { world.id } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 0f78884e16..0832141f4b 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -12,7 +12,7 @@ use crate::{ archetype::{ArchetypeComponentId, ArchetypeId, ArchetypeRow, Archetypes}, bundle::{Bundle, BundleInserter, BundleSpawner, Bundles}, change_detection::{MutUntyped, TicksMut}, - component::{Component, ComponentDescriptor, ComponentId, ComponentInfo, Components}, + component::{Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, Tick}, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, event::{Event, Events}, query::{DebugCheckedUnwrap, QueryState, ReadOnlyWorldQuery, WorldQuery}, @@ -64,8 +64,8 @@ pub struct World { /// Access cache used by [WorldCell]. Is only accessed in the `Drop` impl of `WorldCell`. pub(crate) archetype_component_access: ArchetypeComponentAccess, pub(crate) change_tick: AtomicU32, - pub(crate) last_change_tick: u32, - pub(crate) last_check_tick: u32, + pub(crate) last_change_tick: Tick, + pub(crate) last_check_tick: Tick, } impl Default for World { @@ -82,8 +82,8 @@ impl Default for World { // 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), - last_change_tick: 0, - last_check_tick: 0, + last_change_tick: Tick::new(0), + last_check_tick: Tick::new(0), } } } @@ -493,6 +493,7 @@ impl World { /// ``` pub fn spawn(&mut self, bundle: B) -> EntityMut { self.flush(); + let change_tick = self.change_tick(); let entity = self.entities.alloc(); let entity_location = { let bundle_info = self @@ -503,7 +504,7 @@ impl World { &mut self.archetypes, &mut self.components, &mut self.storages, - *self.change_tick.get_mut(), + change_tick, ); // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent @@ -1174,8 +1175,7 @@ impl World { { self.flush(); - let iter = iter.into_iter(); - let change_tick = *self.change_tick.get_mut(); + let change_tick = self.change_tick(); let bundle_info = self .bundles @@ -1306,8 +1306,8 @@ impl World { ticks: TicksMut { added: &mut ticks.added, changed: &mut ticks.changed, - last_change_tick, - change_tick, + last_run: last_change_tick, + this_run: change_tick, }, }; let result = f(self, value_mut); @@ -1467,9 +1467,11 @@ impl World { } } + /// Increments the world's current change tick, and returns the old value. #[inline] - pub fn increment_change_tick(&self) -> u32 { - self.change_tick.fetch_add(1, Ordering::AcqRel) + pub fn increment_change_tick(&self) -> Tick { + let prev_tick = self.change_tick.fetch_add(1, Ordering::AcqRel); + Tick::new(prev_tick) } /// Reads the current change tick of this world. @@ -1477,8 +1479,9 @@ impl World { /// If you have exclusive (`&mut`) access to the world, consider using [`change_tick()`](Self::change_tick), /// which is more efficient since it does not require atomic synchronization. #[inline] - pub fn read_change_tick(&self) -> u32 { - self.change_tick.load(Ordering::Acquire) + pub fn read_change_tick(&self) -> Tick { + let tick = self.change_tick.load(Ordering::Acquire); + Tick::new(tick) } /// Reads the current change tick of this world. @@ -1486,12 +1489,13 @@ impl World { /// This does the same thing as [`read_change_tick()`](Self::read_change_tick), only this method /// is more efficient since it does not require atomic synchronization. #[inline] - pub fn change_tick(&mut self) -> u32 { - *self.change_tick.get_mut() + pub fn change_tick(&mut self) -> Tick { + let tick = *self.change_tick.get_mut(); + Tick::new(tick) } #[inline] - pub fn last_change_tick(&self) -> u32 { + pub fn last_change_tick(&self) -> Tick { self.last_change_tick } @@ -1503,7 +1507,7 @@ impl World { // TODO: benchmark and optimize pub fn check_change_ticks(&mut self) { let change_tick = self.change_tick(); - if change_tick.wrapping_sub(self.last_check_tick) < CHECK_TICK_THRESHOLD { + if change_tick.relative_to(self.last_check_tick).get() < CHECK_TICK_THRESHOLD { return; } diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index f5e1bd2792..965ff97540 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -25,6 +25,8 @@ where // necessary world.flush(); + let change_tick = world.change_tick(); + let (lower, upper) = iter.size_hint(); let length = upper.unwrap_or(lower); @@ -37,7 +39,7 @@ where &mut world.archetypes, &mut world.components, &mut world.storages, - *world.change_tick.get_mut(), + change_tick, ); spawner.reserve_storage(length); diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 364cef1bc3..99231fe3f3 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -6,7 +6,7 @@ use crate::{ bundle::Bundles, change_detection::{MutUntyped, TicksMut}, component::{ - ComponentId, ComponentStorage, ComponentTicks, Components, StorageType, TickCells, + ComponentId, ComponentStorage, ComponentTicks, Components, StorageType, Tick, TickCells, }, entity::{Entities, Entity, EntityLocation}, prelude::Component, @@ -185,16 +185,17 @@ impl<'w> UnsafeWorldCell<'w> { /// Reads the current change tick of this world. #[inline] - pub fn read_change_tick(self) -> u32 { + pub fn read_change_tick(self) -> Tick { // SAFETY: // - we only access world metadata - unsafe { self.world_metadata() } + let tick = unsafe { self.world_metadata() } .change_tick - .load(Ordering::Acquire) + .load(Ordering::Acquire); + Tick::new(tick) } #[inline] - pub fn last_change_tick(self) -> u32 { + pub fn last_change_tick(self) -> Tick { // SAFETY: // - we only access world metadata unsafe { self.world_metadata() }.last_change_tick @@ -655,8 +656,8 @@ impl<'w> UnsafeEntityCell<'w> { #[inline] pub(crate) unsafe fn get_mut_using_ticks( &self, - last_change_tick: u32, - change_tick: u32, + last_change_tick: Tick, + change_tick: Tick, ) -> Option> { let component_id = self.world.components().get_id(TypeId::of::())?; diff --git a/crates/bevy_render/src/extract_param.rs b/crates/bevy_render/src/extract_param.rs index 92290407f5..898dfe0e28 100644 --- a/crates/bevy_render/src/extract_param.rs +++ b/crates/bevy_render/src/extract_param.rs @@ -1,5 +1,6 @@ use crate::MainWorld; use bevy_ecs::{ + component::Tick, prelude::*, system::{ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemState}, }; @@ -76,7 +77,7 @@ where state: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: Tick, ) -> Self::Item<'w, 's> { // SAFETY: // - The caller ensures that `world` is the same one that `init_state` was called with. From 3ec764ea244e45296f718226492ca5fc8521ea5c Mon Sep 17 00:00:00 2001 From: Konstantin Kostiuk Date: Thu, 9 Mar 2023 19:24:24 +0200 Subject: [PATCH 14/68] Initialize empty schedules when calling `.in_schedule` if they do not already exist (#7911) --- crates/bevy_app/src/app.rs | 75 ++++++++++++++++++++++++++++++++--- crates/bevy_app/src/config.rs | 5 ++- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index d70669be5a..9ffc7a7c23 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -313,7 +313,7 @@ impl App { } /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules - /// for each state variant, an instance of [`apply_state_transition::`] in + /// for each state variant (if they don't already exist), an instance of [`apply_state_transition::`] in /// [`CoreSet::StateTransitions`] so that transitions happen before [`CoreSet::Update`] and /// a instance of [`run_enter_schedule::`] in [`CoreSet::StateTransitions`] with a /// [`run_once`](`run_once_condition`) condition to run the on enter schedule of the @@ -355,11 +355,14 @@ impl App { .run_if(in_state(variant)), ); } - // These are different for loops to avoid conflicting access to self for variant in S::variants() { - self.add_schedule(OnEnter(variant.clone()), Schedule::new()); - self.add_schedule(OnExit(variant), Schedule::new()); + if self.get_schedule(OnEnter(variant.clone())).is_none() { + self.add_schedule(OnEnter(variant.clone()), Schedule::new()); + } + if self.get_schedule(OnExit(variant.clone())).is_none() { + self.add_schedule(OnExit(variant), Schedule::new()); + } } self @@ -390,7 +393,9 @@ impl App { if let Some(schedule) = schedules.get_mut(&*schedule_label) { schedule.add_system(system); } else { - panic!("Schedule {schedule_label:?} does not exist.") + let mut schedule = Schedule::new(); + schedule.add_system(system); + schedules.insert(schedule_label, schedule); } } else if let Some(default_schedule) = schedules.get_mut(&*self.default_schedule_label) { default_schedule.add_system(system); @@ -1014,7 +1019,12 @@ pub struct AppExit; #[cfg(test)] mod tests { - use crate::{App, Plugin}; + use bevy_ecs::{ + schedule::{OnEnter, States}, + system::Commands, + }; + + use crate::{App, IntoSystemAppConfig, IntoSystemAppConfigs, Plugin}; struct PluginA; impl Plugin for PluginA { @@ -1068,4 +1078,57 @@ mod tests { } App::new().add_plugin(PluginRun); } + + #[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)] + enum AppState { + #[default] + MainMenu, + } + fn bar(mut commands: Commands) { + commands.spawn_empty(); + } + + fn foo(mut commands: Commands) { + commands.spawn_empty(); + } + + #[test] + fn add_system_should_create_schedule_if_it_does_not_exist() { + let mut app = App::new(); + app.add_system(foo.in_schedule(OnEnter(AppState::MainMenu))) + .add_state::(); + + app.world.run_schedule(OnEnter(AppState::MainMenu)); + assert_eq!(app.world.entities().len(), 1); + } + + #[test] + fn add_system_should_create_schedule_if_it_does_not_exist2() { + let mut app = App::new(); + app.add_state::() + .add_system(foo.in_schedule(OnEnter(AppState::MainMenu))); + + app.world.run_schedule(OnEnter(AppState::MainMenu)); + assert_eq!(app.world.entities().len(), 1); + } + + #[test] + fn add_systems_should_create_schedule_if_it_does_not_exist() { + let mut app = App::new(); + app.add_state::() + .add_systems((foo, bar).in_schedule(OnEnter(AppState::MainMenu))); + + app.world.run_schedule(OnEnter(AppState::MainMenu)); + assert_eq!(app.world.entities().len(), 2); + } + + #[test] + fn add_systems_should_create_schedule_if_it_does_not_exist2() { + let mut app = App::new(); + app.add_systems((foo, bar).in_schedule(OnEnter(AppState::MainMenu))) + .add_state::(); + + app.world.run_schedule(OnEnter(AppState::MainMenu)); + assert_eq!(app.world.entities().len(), 2); + } } diff --git a/crates/bevy_app/src/config.rs b/crates/bevy_app/src/config.rs index 3a5f30a504..fb3a3fde1c 100644 --- a/crates/bevy_app/src/config.rs +++ b/crates/bevy_app/src/config.rs @@ -190,7 +190,10 @@ pub trait IntoSystemAppConfigs: Sized { /// Adds the systems to the provided `schedule`. /// - /// If a schedule is not specified, they will be added to the [`App`]'s default schedule. + /// If a schedule with specified label does not exist, it will be created. + /// + /// If a schedule with the specified label does not exist, an empty one will be created. + /// /// /// [`App`]: crate::App /// From 7d9cb1c4ab210595c5228af0ed4ec7d095241db5 Mon Sep 17 00:00:00 2001 From: James Liu Date: Thu, 9 Mar 2023 12:02:56 -0800 Subject: [PATCH 15/68] Remove ChangeTrackers (#7902) --- crates/bevy_ecs/src/lib.rs | 30 ---- crates/bevy_ecs/src/query/fetch.rs | 243 +--------------------------- crates/bevy_ecs/src/query/filter.rs | 2 +- 3 files changed, 3 insertions(+), 272 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index d8d8e5948a..fce9800306 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -25,9 +25,6 @@ pub use bevy_ptr as ptr; /// Most commonly used re-exported types. pub mod prelude { - #[doc(hidden)] - #[allow(deprecated)] - pub use crate::query::ChangeTrackers; #[doc(hidden)] #[cfg(feature = "bevy_reflect")] pub use crate::reflect::{ReflectComponent, ReflectResource}; @@ -1299,33 +1296,6 @@ mod tests { .unwrap(); } - #[test] - #[allow(deprecated)] - fn trackers_query() { - use crate::prelude::ChangeTrackers; - - let mut world = World::default(); - let e1 = world.spawn((A(0), B(0))).id(); - world.spawn(B(0)); - - let mut trackers_query = world.query::>>(); - let trackers = trackers_query.iter(&world).collect::>(); - let a_trackers = trackers[0].as_ref().unwrap(); - assert!(trackers[1].is_none()); - assert!(a_trackers.is_added()); - assert!(a_trackers.is_changed()); - world.clear_trackers(); - let trackers = trackers_query.iter(&world).collect::>(); - let a_trackers = trackers[0].as_ref().unwrap(); - assert!(!a_trackers.is_added()); - assert!(!a_trackers.is_changed()); - *world.get_mut(e1).unwrap() = A(1); - let trackers = trackers_query.iter(&world).collect::>(); - let a_trackers = trackers[0].as_ref().unwrap(); - assert!(!a_trackers.is_added()); - assert!(a_trackers.is_changed()); - } - #[test] fn exact_size_query() { let mut world = World::default(); diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 23d52c795d..3b4a6a076f 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1,7 +1,7 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, change_detection::{Ticks, TicksMut}, - component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType, Tick}, + component::{Component, ComponentId, ComponentStorage, StorageType, Tick}, entity::Entity, query::{Access, DebugCheckedUnwrap, FilteredAccess}, storage::{ComponentSparseSet, Table, TableRow}, @@ -37,7 +37,7 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// Wrapping it into an `Option` will increase the query search space, and it will return `None` if an entity doesn't satisfy the `WorldQuery`. /// - **[`AnyOf`].** /// Equivalent to wrapping each world query inside it into an `Option`. -/// - **[`ChangeTrackers`].** +/// - **[`Ref`].** /// Similar to change detection filters but it is used as a query fetch parameter. /// It exposes methods to check for changes to the wrapped component. /// @@ -1075,245 +1075,6 @@ unsafe impl WorldQuery for Option { /// SAFETY: [`OptionFetch`] is read only because `T` is read only unsafe impl ReadOnlyWorldQuery for Option {} -/// [`WorldQuery`] that tracks changes and additions for component `T`. -/// -/// Wraps a [`Component`] to track whether the component changed for the corresponding entities in -/// a query since the last time the system that includes these queries ran. -/// -/// If you only care about entities that changed or that got added use the -/// [`Changed`](crate::query::Changed) and [`Added`](crate::query::Added) filters instead. -/// -/// # Examples -/// -/// ``` -/// # use bevy_ecs::component::Component; -/// # use bevy_ecs::query::ChangeTrackers; -/// # use bevy_ecs::system::IntoSystem; -/// # use bevy_ecs::system::Query; -/// # -/// # #[derive(Component, Debug)] -/// # struct Name {}; -/// # #[derive(Component)] -/// # struct Transform {}; -/// # -/// fn print_moving_objects_system(query: Query<(&Name, ChangeTrackers)>) { -/// for (name, tracker) in &query { -/// if tracker.is_changed() { -/// println!("Entity moved: {:?}", name); -/// } else { -/// println!("Entity stood still: {:?}", name); -/// } -/// } -/// } -/// # bevy_ecs::system::assert_is_system(print_moving_objects_system); -/// ``` -#[deprecated = "`ChangeTrackers` will be removed in bevy 0.11. Use `bevy_ecs::prelude::Ref` instead."] -pub struct ChangeTrackers { - pub(crate) component_ticks: ComponentTicks, - pub(crate) last_run: Tick, - pub(crate) this_run: Tick, - marker: PhantomData, -} - -#[allow(deprecated)] -impl Clone for ChangeTrackers { - fn clone(&self) -> Self { - *self - } -} - -#[allow(deprecated)] -impl Copy for ChangeTrackers {} - -#[allow(deprecated)] -impl std::fmt::Debug for ChangeTrackers { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ChangeTrackers") - .field("component_ticks", &self.component_ticks) - .field("last_run", &self.last_run) - .field("this_run", &self.this_run) - .finish() - } -} - -#[allow(deprecated)] -impl ChangeTrackers { - /// Returns true if this component has been added since the last execution of this system. - pub fn is_added(&self) -> bool { - self.component_ticks.is_added(self.last_run, self.this_run) - } - - /// Returns true if this component has been changed since the last execution of this system. - pub fn is_changed(&self) -> bool { - self.component_ticks - .is_changed(self.last_run, self.this_run) - } -} - -#[doc(hidden)] -pub struct ChangeTrackersFetch<'w, T> { - // T::Storage = TableStorage - table_added: Option>>, - table_changed: Option>>, - // T::Storage = SparseStorage - sparse_set: Option<&'w ComponentSparseSet>, - - marker: PhantomData, - last_run: Tick, - this_run: Tick, -} - -#[allow(deprecated)] -// SAFETY: `ROQueryFetch` is the same as `QueryFetch` -unsafe impl WorldQuery for ChangeTrackers { - type Fetch<'w> = ChangeTrackersFetch<'w, T>; - type Item<'w> = ChangeTrackers; - type ReadOnly = Self; - type State = ComponentId; - - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - - const IS_DENSE: bool = { - match T::Storage::STORAGE_TYPE { - StorageType::Table => true, - StorageType::SparseSet => false, - } - }; - - const IS_ARCHETYPAL: bool = true; - - unsafe fn init_fetch<'w>( - world: &'w World, - &component_id: &ComponentId, - last_run: Tick, - this_run: Tick, - ) -> ChangeTrackersFetch<'w, T> { - ChangeTrackersFetch { - table_added: None, - table_changed: None, - sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { - world - .storages() - .sparse_sets - .get(component_id) - .debug_checked_unwrap() - }), - marker: PhantomData, - last_run, - this_run, - } - } - - unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { - ChangeTrackersFetch { - table_added: fetch.table_added, - table_changed: fetch.table_changed, - sparse_set: fetch.sparse_set, - marker: fetch.marker, - last_run: fetch.last_run, - this_run: fetch.this_run, - } - } - - #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut ChangeTrackersFetch<'w, T>, - component_id: &ComponentId, - _archetype: &'w Archetype, - table: &'w Table, - ) { - if Self::IS_DENSE { - Self::set_table(fetch, component_id, table); - } - } - - #[inline] - unsafe fn set_table<'w>( - fetch: &mut ChangeTrackersFetch<'w, T>, - &id: &ComponentId, - table: &'w Table, - ) { - let column = table.get_column(id).debug_checked_unwrap(); - fetch.table_added = Some(column.get_added_ticks_slice().into()); - fetch.table_changed = Some(column.get_changed_ticks_slice().into()); - } - - #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w> { - match T::Storage::STORAGE_TYPE { - StorageType::Table => ChangeTrackers { - component_ticks: { - ComponentTicks { - added: fetch - .table_added - .debug_checked_unwrap() - .get(table_row.index()) - .read(), - changed: fetch - .table_changed - .debug_checked_unwrap() - .get(table_row.index()) - .read(), - } - }, - marker: PhantomData, - last_run: fetch.last_run, - this_run: fetch.this_run, - }, - StorageType::SparseSet => ChangeTrackers { - component_ticks: fetch - .sparse_set - .debug_checked_unwrap() - .get_ticks(entity) - .debug_checked_unwrap(), - marker: PhantomData, - last_run: fetch.last_run, - this_run: fetch.this_run, - }, - } - } - - fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { - assert!( - !access.access().has_write(id), - "ChangeTrackers<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", - std::any::type_name::() - ); - access.add_read(id); - } - - fn update_archetype_component_access( - &id: &ComponentId, - archetype: &Archetype, - access: &mut Access, - ) { - if let Some(archetype_component_id) = archetype.get_archetype_component_id(id) { - access.add_read(archetype_component_id); - } - } - - fn init_state(world: &mut World) -> ComponentId { - world.init_component::() - } - - fn matches_component_set( - &id: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - set_contains_id(id) - } -} - -#[allow(deprecated)] -/// SAFETY: access is read only -unsafe impl ReadOnlyWorldQuery for ChangeTrackers {} - macro_rules! impl_tuple_fetch { ($(($name: ident, $state: ident)),*) => { #[allow(non_snake_case)] diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index c0b91b54af..41f2a7941b 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -596,7 +596,7 @@ impl_tick_filter!( /// Bevy does not compare components to their previous values. /// /// To retain all results without filtering but still check whether they were changed after the - /// system last ran, use [`ChangeTrackers`](crate::query::ChangeTrackers). + /// system last ran, use [`Ref`](crate::change_detection::Ref). /// /// # Examples /// From 8aa217cc8bf5bdfbb2a72108bbb6b10ef36eb120 Mon Sep 17 00:00:00 2001 From: Christian Hughes <9044780+ItsDoot@users.noreply.github.com> Date: Fri, 10 Mar 2023 03:06:23 -0600 Subject: [PATCH 16/68] Add OnTransition schedule that is ran between OnExit and OnEnter (#7936) --- crates/bevy_ecs/src/lib.rs | 4 ++-- crates/bevy_ecs/src/schedule/state.rs | 25 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index fce9800306..1c574befe7 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -40,8 +40,8 @@ pub mod prelude { schedule::{ apply_state_transition, apply_system_buffers, common_conditions::*, Condition, IntoSystemConfig, IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig, - IntoSystemSetConfigs, NextState, OnEnter, OnExit, OnUpdate, Schedule, Schedules, State, - States, SystemSet, + IntoSystemSetConfigs, NextState, OnEnter, OnExit, OnTransition, OnUpdate, Schedule, + Schedules, State, States, SystemSet, }, system::{ adapter as system_adapter, diff --git a/crates/bevy_ecs/src/schedule/state.rs b/crates/bevy_ecs/src/schedule/state.rs index 92f7c3864c..abb001e90a 100644 --- a/crates/bevy_ecs/src/schedule/state.rs +++ b/crates/bevy_ecs/src/schedule/state.rs @@ -4,7 +4,7 @@ use std::mem; use crate as bevy_ecs; use crate::change_detection::DetectChangesMut; -use crate::schedule::{ScheduleLabel, SystemSet}; +use crate::schedule::{ScheduleLabel, Schedules, SystemSet}; use crate::system::Resource; use crate::world::World; @@ -54,6 +54,18 @@ pub struct OnEnter(pub S); #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] pub struct OnExit(pub S); +/// The label of a [`Schedule`](super::Schedule) that **only** runs whenever [`State`] +/// exits the `from` state, AND enters the `to` state. +/// +/// Systems added to this schedule are always ran *after* [`OnExit`], and *before* [`OnEnter`]. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct OnTransition { + /// The state being exited. + pub from: S, + /// The state being entered. + pub to: S, +} + /// A [`SystemSet`] that will run within `CoreSet::Update` when this state is active. /// /// This set, when created via `App::add_state`, is configured with both a base set and a run condition. @@ -105,7 +117,16 @@ pub fn apply_state_transition(world: &mut World) { next_state_resource.set_changed(); let exited = mem::replace(&mut world.resource_mut::>().0, entered.clone()); - world.run_schedule(OnExit(exited)); + world.run_schedule(OnExit(exited.clone())); + + let transition_schedule = OnTransition { + from: exited, + to: entered.clone(), + }; + if world.resource::().contains(&transition_schedule) { + world.run_schedule(transition_schedule); + } + world.run_schedule(OnEnter(entered)); } } From 729458815c5840e4a50f52e9e5d8cb76459d8027 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 10 Mar 2023 14:57:41 +0000 Subject: [PATCH 17/68] Fix the `Text2d` text anchor's incorrect horizontal alignment (#8019) --- crates/bevy_text/src/text2d.rs | 2 +- examples/2d/text2d.rs | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 13f9d13405..aac6c400fe 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -103,7 +103,7 @@ pub fn extract_text2d_sprite( } let text_glyphs = &text_layout_info.glyphs; - let text_anchor = anchor.as_vec() * Vec2::new(1., -1.) - 0.5; + let text_anchor = -(anchor.as_vec() + 0.5); let alignment_offset = text_layout_info.size * text_anchor; let mut color = Color::WHITE; let mut current_section = usize::MAX; diff --git a/examples/2d/text2d.rs b/examples/2d/text2d.rs index e920ab5062..100379e5aa 100644 --- a/examples/2d/text2d.rs +++ b/examples/2d/text2d.rs @@ -7,6 +7,7 @@ use bevy::{ prelude::*, + sprite::Anchor, text::{BreakLineOn, Text2dBounds}, }; @@ -133,6 +134,29 @@ fn setup(mut commands: Commands, asset_server: Res) { ..default() }); }); + + for (text_anchor, color) in [ + (Anchor::TopLeft, Color::RED), + (Anchor::TopRight, Color::GREEN), + (Anchor::BottomRight, Color::BLUE), + (Anchor::BottomLeft, Color::YELLOW), + ] { + commands.spawn(Text2dBundle { + text: Text { + sections: vec![TextSection::new( + format!(" Anchor::{text_anchor:?} "), + TextStyle { + color, + ..slightly_smaller_text_style.clone() + }, + )], + ..Default::default() + }, + transform: Transform::from_translation(250. * Vec3::Y), + text_anchor, + ..default() + }); + } } fn animate_translation( From fd1af7c8b8a737b4da79615741f8844069bc6a5c Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Mar 2023 13:15:22 -0500 Subject: [PATCH 18/68] Replace multiple calls to `add_system` with `add_systems` (#8001) --- .../bevy_ecs/scheduling/run_condition.rs | 32 ++++------------ .../bevy_ecs/scheduling/running_systems.rs | 15 +++----- .../benches/bevy_ecs/scheduling/schedule.rs | 4 +- crates/bevy_app/src/lib.rs | 38 ++++++++++++------- crates/bevy_asset/src/asset_server.rs | 6 ++- crates/bevy_asset/src/assets.rs | 6 ++- crates/bevy_core_pipeline/src/bloom/mod.rs | 10 +++-- crates/bevy_core_pipeline/src/core_2d/mod.rs | 8 ++-- crates/bevy_core_pipeline/src/core_3d/mod.rs | 12 +++--- crates/bevy_ecs/examples/change_detection.rs | 14 ++++--- crates/bevy_ecs/examples/events.rs | 6 ++- crates/bevy_ecs/examples/resources.rs | 3 +- crates/bevy_ecs/src/schedule/mod.rs | 33 ++++++++-------- crates/bevy_ecs/src/system/mod.rs | 24 ++++++------ crates/bevy_ecs/src/system/system_param.rs | 9 ++--- crates/bevy_pbr/src/lib.rs | 32 +++++----------- crates/bevy_pbr/src/material.rs | 10 ++--- crates/bevy_pbr/src/prepass/mod.rs | 12 +++--- crates/bevy_pbr/src/render/mesh.rs | 8 ++-- crates/bevy_render/src/camera/projection.rs | 21 +++++----- crates/bevy_render/src/lib.rs | 34 +++++++++++------ crates/bevy_render/src/render_asset.rs | 6 ++- crates/bevy_render/src/view/mod.rs | 6 +-- crates/bevy_render/src/view/visibility/mod.rs | 14 ++----- crates/bevy_sprite/src/mesh2d/material.rs | 8 ++-- crates/bevy_sprite/src/mesh2d/mesh.rs | 8 ++-- crates/bevy_transform/src/lib.rs | 24 ++++++------ crates/bevy_transform/src/systems.rs | 15 +++----- crates/bevy_ui/src/accessibility.rs | 5 +-- crates/bevy_ui/src/lib.rs | 8 ++-- crates/bevy_ui/src/render/mod.rs | 8 ++-- crates/bevy_winit/src/accessibility.rs | 10 +++-- examples/2d/bloom_2d.rs | 3 +- examples/2d/mesh2d_manual.rs | 6 ++- examples/2d/move_sprite.rs | 3 +- examples/2d/pixel_perfect.rs | 3 +- examples/2d/sprite_sheet.rs | 3 +- examples/2d/text2d.rs | 10 +++-- examples/2d/texture_atlas.rs | 8 ++-- examples/3d/3d_shapes.rs | 3 +- examples/3d/atmospheric_fog.rs | 4 +- examples/3d/blend_modes.rs | 3 +- examples/3d/bloom_3d.rs | 8 ++-- examples/3d/fog.rs | 4 +- examples/3d/fxaa.rs | 3 +- examples/3d/lighting.rs | 4 +- examples/3d/load_gltf.rs | 3 +- examples/3d/msaa.rs | 3 +- examples/3d/parenting.rs | 3 +- examples/3d/pbr.rs | 3 +- examples/3d/render_to_texture.rs | 4 +- examples/3d/shadow_biases.rs | 12 +++--- examples/3d/shadow_caster_receiver.rs | 4 +- examples/3d/skybox.rs | 12 +++--- examples/3d/split_screen.rs | 3 +- examples/3d/spotlight.rs | 4 +- examples/3d/tonemapping.rs | 22 ++++++----- examples/3d/update_gltf_scene.rs | 3 +- examples/animation/animated_fox.rs | 8 ++-- examples/animation/custom_skinned_mesh.rs | 3 +- examples/animation/gltf_skinned_mesh.rs | 3 +- examples/asset/custom_asset.rs | 3 +- examples/async_tasks/async_compute.rs | 4 +- .../external_source_external_thread.rs | 5 +-- examples/audio/audio_control.rs | 5 +-- examples/audio/spatial_audio_2d.rs | 3 +- examples/audio/spatial_audio_3d.rs | 3 +- examples/diagnostics/custom_diagnostic.rs | 3 +- examples/ecs/apply_system_buffers.rs | 14 ++++--- examples/ecs/component_change_detection.rs | 10 +++-- examples/ecs/custom_query_param.rs | 13 +++++-- examples/ecs/event.rs | 4 +- examples/ecs/fixed_timestep.rs | 10 +++-- examples/ecs/generic_system.rs | 8 ++-- examples/ecs/hierarchy.rs | 3 +- examples/ecs/nondeterministic_system_order.rs | 38 ++++++++++--------- examples/ecs/parallel_query.rs | 4 +- examples/ecs/removal_detection.rs | 8 ++-- examples/ecs/run_conditions.rs | 8 +--- examples/ecs/state.rs | 8 ++-- examples/ecs/system_param.rs | 3 +- examples/ecs/system_piping.rs | 14 ++++--- examples/ecs/timers.rs | 4 +- examples/games/alien_cake_addict.rs | 2 +- examples/games/contributors.rs | 13 ++++--- examples/games/game_menu.rs | 11 +++--- examples/input/gamepad_input_events.rs | 3 +- examples/input/text_input.rs | 14 ++++--- examples/mobile/src/lib.rs | 6 +-- examples/scene/scene.rs | 4 +- examples/shader/array_texture.rs | 3 +- examples/shader/post_processing.rs | 3 +- examples/shader/shader_instancing.rs | 6 ++- .../shader_material_screenspace_texture.rs | 3 +- examples/shader/shader_prepass.rs | 4 +- examples/stress_tests/bevymark.rs | 14 ++++--- .../stress_tests/many_animated_sprites.rs | 10 +++-- examples/stress_tests/many_buttons.rs | 3 +- examples/stress_tests/many_cubes.rs | 4 +- examples/stress_tests/many_foxes.rs | 10 +++-- examples/stress_tests/many_lights.rs | 4 +- examples/stress_tests/many_sprites.rs | 8 ++-- examples/stress_tests/text_pipeline.rs | 3 +- examples/tools/gamepad_viewer.rs | 15 ++++---- .../tools/scene_viewer/scene_viewer_plugin.rs | 12 +++--- examples/transforms/3d_rotation.rs | 3 +- examples/transforms/scale.rs | 4 +- examples/transforms/transform.rs | 10 +++-- examples/transforms/translation.rs | 3 +- examples/ui/button.rs | 3 +- examples/ui/font_atlas_debug.rs | 4 +- examples/ui/relative_cursor_position.rs | 3 +- examples/ui/text.rs | 4 +- examples/ui/text_debug.rs | 3 +- examples/ui/ui.rs | 3 +- examples/ui/ui_scaling.rs | 8 ++-- examples/window/clear_color.rs | 3 +- examples/window/low_power.rs | 12 +++--- examples/window/scale_factor_override.rs | 10 +++-- examples/window/window_resizing.rs | 6 +-- examples/window/window_settings.rs | 12 +++--- tests/how_to_test_systems.rs | 6 +-- tests/window/minimising.rs | 3 +- tests/window/resizing.rs | 11 +++--- 124 files changed, 505 insertions(+), 540 deletions(-) diff --git a/benches/benches/bevy_ecs/scheduling/run_condition.rs b/benches/benches/bevy_ecs/scheduling/run_condition.rs index be6f25a4c7..367cd7839e 100644 --- a/benches/benches/bevy_ecs/scheduling/run_condition.rs +++ b/benches/benches/bevy_ecs/scheduling/run_condition.rs @@ -21,12 +21,7 @@ pub fn run_condition_yes(criterion: &mut Criterion) { let mut schedule = Schedule::new(); schedule.add_system(empty.run_if(yes)); for _ in 0..amount { - schedule - .add_system(empty.run_if(yes)) - .add_system(empty.run_if(yes)) - .add_system(empty.run_if(yes)) - .add_system(empty.run_if(yes)) - .add_system(empty.run_if(yes)); + schedule.add_systems((empty, empty, empty, empty, empty).distributive_run_if(yes)); } // run once to initialize systems schedule.run(&mut world); @@ -49,12 +44,7 @@ pub fn run_condition_no(criterion: &mut Criterion) { let mut schedule = Schedule::new(); schedule.add_system(empty.run_if(no)); for _ in 0..amount { - schedule - .add_system(empty.run_if(no)) - .add_system(empty.run_if(no)) - .add_system(empty.run_if(no)) - .add_system(empty.run_if(no)) - .add_system(empty.run_if(no)); + schedule.add_systems((empty, empty, empty, empty, empty).distributive_run_if(no)); } // run once to initialize systems schedule.run(&mut world); @@ -84,12 +74,9 @@ pub fn run_condition_yes_with_query(criterion: &mut Criterion) { let mut schedule = Schedule::new(); schedule.add_system(empty.run_if(yes_with_query)); for _ in 0..amount { - schedule - .add_system(empty.run_if(yes_with_query)) - .add_system(empty.run_if(yes_with_query)) - .add_system(empty.run_if(yes_with_query)) - .add_system(empty.run_if(yes_with_query)) - .add_system(empty.run_if(yes_with_query)); + schedule.add_systems( + (empty, empty, empty, empty, empty).distributive_run_if(yes_with_query), + ); } // run once to initialize systems schedule.run(&mut world); @@ -116,12 +103,9 @@ pub fn run_condition_yes_with_resource(criterion: &mut Criterion) { let mut schedule = Schedule::new(); schedule.add_system(empty.run_if(yes_with_resource)); for _ in 0..amount { - schedule - .add_system(empty.run_if(yes_with_resource)) - .add_system(empty.run_if(yes_with_resource)) - .add_system(empty.run_if(yes_with_resource)) - .add_system(empty.run_if(yes_with_resource)) - .add_system(empty.run_if(yes_with_resource)); + schedule.add_systems( + (empty, empty, empty, empty, empty).distributive_run_if(yes_with_resource), + ); } // run once to initialize systems schedule.run(&mut world); diff --git a/benches/benches/bevy_ecs/scheduling/running_systems.rs b/benches/benches/bevy_ecs/scheduling/running_systems.rs index 9206bd285f..99a0827c2a 100644 --- a/benches/benches/bevy_ecs/scheduling/running_systems.rs +++ b/benches/benches/bevy_ecs/scheduling/running_systems.rs @@ -35,12 +35,7 @@ pub fn empty_systems(criterion: &mut Criterion) { for amount in 1..21 { let mut schedule = Schedule::new(); for _ in 0..amount { - schedule - .add_system(empty) - .add_system(empty) - .add_system(empty) - .add_system(empty) - .add_system(empty); + schedule.add_systems((empty, empty, empty, empty, empty)); } schedule.run(&mut world); group.bench_function(&format!("{:03}_systems", 5 * amount), |bencher| { @@ -79,9 +74,9 @@ pub fn busy_systems(criterion: &mut Criterion) { world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0)))); for system_amount in 0..5 { let mut schedule = Schedule::new(); - schedule.add_system(ab).add_system(cd).add_system(ce); + schedule.add_systems((ab, cd, ce)); for _ in 0..system_amount { - schedule.add_system(ab).add_system(cd).add_system(ce); + schedule.add_systems((ab, cd, ce)); } schedule.run(&mut world); group.bench_function( @@ -130,9 +125,9 @@ pub fn contrived(criterion: &mut Criterion) { world.spawn_batch((0..ENTITY_BUNCH).map(|_| (C(0.0), D(0.0)))); for system_amount in 0..5 { let mut schedule = Schedule::new(); - schedule.add_system(s_0).add_system(s_1).add_system(s_2); + schedule.add_systems((s_0, s_1, s_2)); for _ in 0..system_amount { - schedule.add_system(s_0).add_system(s_1).add_system(s_2); + schedule.add_systems((s_0, s_1, s_2)); } schedule.run(&mut world); group.bench_function( diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index 3117c0fff2..5d2ab87677 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -47,9 +47,7 @@ pub fn schedule(c: &mut Criterion) { world.spawn_batch((0..10000).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0)))); let mut schedule = Schedule::new(); - schedule.add_system(ab); - schedule.add_system(cd); - schedule.add_system(ce); + schedule.add_systems((ab, cd, ce)); schedule.run(&mut world); b.iter(move || schedule.run(&mut world)); diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 8348a7c16e..165e9ebd70 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -33,8 +33,8 @@ pub mod prelude { use bevy_ecs::{ schedule::{ - apply_system_buffers, IntoSystemConfig, IntoSystemSetConfig, IntoSystemSetConfigs, - Schedule, ScheduleLabel, SystemSet, + apply_system_buffers, IntoSystemConfig, IntoSystemSetConfigs, Schedule, ScheduleLabel, + SystemSet, }, system::Local, world::World, @@ -136,11 +136,13 @@ impl CoreSet { // Create "stage-like" structure using buffer flushes + ordering schedule .set_default_base_set(Update) - .add_system(apply_system_buffers.in_base_set(FirstFlush)) - .add_system(apply_system_buffers.in_base_set(PreUpdateFlush)) - .add_system(apply_system_buffers.in_base_set(UpdateFlush)) - .add_system(apply_system_buffers.in_base_set(PostUpdateFlush)) - .add_system(apply_system_buffers.in_base_set(LastFlush)) + .add_systems(( + apply_system_buffers.in_base_set(FirstFlush), + apply_system_buffers.in_base_set(PreUpdateFlush), + apply_system_buffers.in_base_set(UpdateFlush), + apply_system_buffers.in_base_set(PostUpdateFlush), + apply_system_buffers.in_base_set(LastFlush), + )) .configure_sets( ( First, @@ -197,13 +199,23 @@ impl StartupSet { schedule.set_default_base_set(Startup); // Create "stage-like" structure using buffer flushes + ordering - schedule.add_system(apply_system_buffers.in_base_set(PreStartupFlush)); - schedule.add_system(apply_system_buffers.in_base_set(StartupFlush)); - schedule.add_system(apply_system_buffers.in_base_set(PostStartupFlush)); + schedule.add_systems(( + apply_system_buffers.in_base_set(PreStartupFlush), + apply_system_buffers.in_base_set(StartupFlush), + apply_system_buffers.in_base_set(PostStartupFlush), + )); - schedule.configure_set(PreStartup.before(PreStartupFlush)); - schedule.configure_set(Startup.after(PreStartupFlush).before(StartupFlush)); - schedule.configure_set(PostStartup.after(StartupFlush).before(PostStartupFlush)); + schedule.configure_sets( + ( + PreStartup, + PreStartupFlush, + Startup, + StartupFlush, + PostStartup, + PostStartupFlush, + ) + .chain(), + ); schedule } diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index c7eea2916c..dd6a8ae307 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -852,8 +852,10 @@ mod test { let mut app = App::new(); app.insert_resource(assets); app.insert_resource(asset_server); - app.add_system(free_unused_assets_system.in_set(FreeUnusedAssets)); - app.add_system(update_asset_storage_system::.after(FreeUnusedAssets)); + app.add_systems(( + free_unused_assets_system.in_set(FreeUnusedAssets), + update_asset_storage_system::.after(FreeUnusedAssets), + )); fn load_asset(path: AssetPath, world: &World) -> HandleUntyped { let asset_server = world.resource::(); diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index f0f1a035f6..40d390187f 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -331,8 +331,10 @@ impl AddAsset for App { }; self.insert_resource(assets) - .add_system(Assets::::asset_event_system.in_base_set(AssetSet::AssetEvents)) - .add_system(update_asset_storage_system::.in_base_set(AssetSet::LoadAssets)) + .add_systems(( + Assets::::asset_event_system.in_base_set(AssetSet::AssetEvents), + update_asset_storage_system::.in_base_set(AssetSet::LoadAssets), + )) .register_type::>() .add_event::>() } diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 4653d10765..5ff8cfba4e 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -71,10 +71,12 @@ impl Plugin for BloomPlugin { .init_resource::() .init_resource::>() .init_resource::>() - .add_system(prepare_bloom_textures.in_set(RenderSet::Prepare)) - .add_system(prepare_downsampling_pipeline.in_set(RenderSet::Prepare)) - .add_system(prepare_upsampling_pipeline.in_set(RenderSet::Prepare)) - .add_system(queue_bloom_bind_groups.in_set(RenderSet::Queue)); + .add_systems(( + prepare_bloom_textures.in_set(RenderSet::Prepare), + prepare_downsampling_pipeline.in_set(RenderSet::Prepare), + prepare_upsampling_pipeline.in_set(RenderSet::Prepare), + queue_bloom_bind_groups.in_set(RenderSet::Queue), + )); // Add bloom to the 3d render graph { diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 573bf0d5a0..a94da4d0b3 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -52,13 +52,13 @@ impl Plugin for Core2dPlugin { render_app .init_resource::>() - .add_system(extract_core_2d_camera_phases.in_schedule(ExtractSchedule)) - .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) - .add_system( + .add_systems(( + extract_core_2d_camera_phases.in_schedule(ExtractSchedule), + sort_phase_system::.in_set(RenderSet::PhaseSort), batch_phase_system:: .after(sort_phase_system::) .in_set(RenderSet::PhaseSort), - ); + )); let pass_node_2d = MainPass2dNode::new(&mut render_app.world); let tonemapping = TonemappingNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 71cfaf6d40..166f6765e0 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -68,15 +68,15 @@ impl Plugin for Core3dPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .add_system(extract_core_3d_camera_phases.in_schedule(ExtractSchedule)) - .add_system( + .add_systems(( + extract_core_3d_camera_phases.in_schedule(ExtractSchedule), prepare_core_3d_depth_textures .in_set(RenderSet::Prepare) .after(bevy_render::view::prepare_windows), - ) - .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) - .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) - .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)); + sort_phase_system::.in_set(RenderSet::PhaseSort), + sort_phase_system::.in_set(RenderSet::PhaseSort), + sort_phase_system::.in_set(RenderSet::PhaseSort), + )); let prepass_node = PrepassNode::new(&mut render_app.world); let pass_node_3d = MainPass3dNode::new(&mut render_app.world); diff --git a/crates/bevy_ecs/examples/change_detection.rs b/crates/bevy_ecs/examples/change_detection.rs index 03a9793318..0abbf487a0 100644 --- a/crates/bevy_ecs/examples/change_detection.rs +++ b/crates/bevy_ecs/examples/change_detection.rs @@ -21,11 +21,13 @@ fn main() { // Add systems to the Schedule to execute our app logic // We can label our systems to force a specific run-order between some of them - schedule.add_system(spawn_entities.in_set(SimulationSystem::Spawn)); - schedule.add_system(print_counter_when_changed.after(SimulationSystem::Spawn)); - schedule.add_system(age_all_entities.in_set(SimulationSystem::Age)); - schedule.add_system(remove_old_entities.after(SimulationSystem::Age)); - schedule.add_system(print_changed_entities.after(SimulationSystem::Age)); + schedule.add_systems(( + spawn_entities.in_set(SimulationSet::Spawn), + print_counter_when_changed.after(SimulationSet::Spawn), + age_all_entities.in_set(SimulationSet::Age), + remove_old_entities.after(SimulationSet::Age), + print_changed_entities.after(SimulationSet::Age), + )); // Simulate 10 frames in our world for iteration in 1..=10 { @@ -48,7 +50,7 @@ struct Age { // System sets can be used to group systems and configured to control relative ordering #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] -enum SimulationSystem { +enum SimulationSet { Spawn, Age, } diff --git a/crates/bevy_ecs/examples/events.rs b/crates/bevy_ecs/examples/events.rs index b0f96e39a1..2dae9475cf 100644 --- a/crates/bevy_ecs/examples/events.rs +++ b/crates/bevy_ecs/examples/events.rs @@ -19,8 +19,10 @@ fn main() { schedule.add_system(Events::::update_system.in_set(FlushEvents)); // Add systems sending and receiving events after the events are flushed. - schedule.add_system(sending_system.after(FlushEvents)); - schedule.add_system(receiving_system.after(sending_system)); + schedule.add_systems(( + sending_system.after(FlushEvents), + receiving_system.after(sending_system), + )); // Simulate 10 frames of our world for iteration in 1..=10 { diff --git a/crates/bevy_ecs/examples/resources.rs b/crates/bevy_ecs/examples/resources.rs index a3fd26fc29..4030b5fee6 100644 --- a/crates/bevy_ecs/examples/resources.rs +++ b/crates/bevy_ecs/examples/resources.rs @@ -15,8 +15,7 @@ fn main() { let mut schedule = Schedule::default(); // Add systems to increase the counter and to print out the current value - schedule.add_system(increase_counter); - schedule.add_system(print_counter.after(increase_counter)); + schedule.add_systems((increase_counter, print_counter).chain()); for iteration in 1..=10 { println!("Simulating frame {iteration}/10"); diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index f886d815d1..0a5f92a4dd 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -127,13 +127,13 @@ mod tests { world.init_resource::(); - schedule.add_system(named_system); - schedule.add_system(make_function_system(1).before(named_system)); - schedule.add_system( + schedule.add_systems(( + named_system, + make_function_system(1).before(named_system), make_function_system(0) .after(named_system) .in_set(TestSet::A), - ); + )); schedule.run(&mut world); assert_eq!(world.resource::().0, vec![1, u32::MAX, 0]); @@ -144,12 +144,12 @@ mod tests { // modify the schedule after it's been initialized and test ordering with sets schedule.configure_set(TestSet::A.after(named_system)); - schedule.add_system( + schedule.add_systems(( make_function_system(3) .before(TestSet::A) .after(named_system), - ); - schedule.add_system(make_function_system(4).after(TestSet::A)); + make_function_system(4).after(TestSet::A), + )); schedule.run(&mut world); assert_eq!( @@ -275,10 +275,12 @@ mod tests { world.init_resource::(); - schedule.add_system(counting_system.run_if(|| false).run_if(|| false)); - schedule.add_system(counting_system.run_if(|| true).run_if(|| false)); - schedule.add_system(counting_system.run_if(|| false).run_if(|| true)); - schedule.add_system(counting_system.run_if(|| true).run_if(|| true)); + schedule.add_systems(( + counting_system.run_if(|| false).run_if(|| false), + counting_system.run_if(|| true).run_if(|| false), + counting_system.run_if(|| false).run_if(|| true), + counting_system.run_if(|| true).run_if(|| true), + )); schedule.run(&mut world); assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); @@ -535,8 +537,7 @@ mod tests { let mut schedule = Schedule::new(); // Schedule `bar` to run after `foo`. - schedule.add_system(foo); - schedule.add_system(bar.after(foo)); + schedule.add_systems((foo, bar.after(foo))); // There's only one `foo`, so it's fine. let result = schedule.initialize(&mut world); @@ -790,8 +791,10 @@ mod tests { schedule .set_default_base_set(Base::A) .configure_set(Base::A.before(Base::B)) - .add_system(make_function_system(0).in_base_set(Base::B)) - .add_system(make_function_system(1)); + .add_systems(( + make_function_system(0).in_base_set(Base::B), + make_function_system(1), + )); schedule.run(&mut world); assert_eq!(world.resource::().0, vec![1, 0]); diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 61351523bc..2b24eaadc7 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -55,14 +55,18 @@ //! //! ``` //! # use bevy_ecs::prelude::*; -//! # let mut app = Schedule::new(); -//! // Prints "Hello, World!" each frame. -//! app -//! .add_system(print_first.before(print_mid)) -//! .add_system(print_mid) -//! .add_system(print_last.after(print_mid)); +//! # let mut schedule = Schedule::new(); //! # let mut world = World::new(); -//! # app.run(&mut world); +//! // Configure these systems to run in order using `chain()`. +//! schedule.add_systems((print_first, print_last).chain()); +//! // Prints "HelloWorld!" +//! schedule.run(&mut world); +//! +//! // Configure this system to run in between the other two systems +//! // using explicit dependencies. +//! schedule.add_system(print_mid.after(print_first).before(print_last)); +//! // Prints "Hello, World!" +//! schedule.run(&mut world); //! //! fn print_first() { //! print!("Hello"); @@ -162,7 +166,7 @@ mod tests { prelude::AnyOf, query::{Added, Changed, Or, With, Without}, removal_detection::RemovedComponents, - schedule::{apply_system_buffers, IntoSystemConfig, Schedule}, + schedule::{apply_system_buffers, IntoSystemConfigs, Schedule}, system::{ Commands, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, QueryComponentError, Res, ResMut, Resource, System, SystemState, @@ -334,9 +338,7 @@ mod tests { let mut schedule = Schedule::default(); - schedule.add_system(incr_e_on_flip); - schedule.add_system(apply_system_buffers.after(incr_e_on_flip)); - schedule.add_system(World::clear_trackers.after(apply_system_buffers)); + schedule.add_systems((incr_e_on_flip, apply_system_buffers, World::clear_trackers).chain()); schedule.run(&mut world); assert_eq!(world.resource::().0, 1); diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 018c79630f..60fc47c56e 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -396,8 +396,7 @@ impl_param_set!(); /// resource.value = 0; /// assert_eq!(resource.value, 0); /// } -/// # schedule.add_system(read_resource_system); -/// # schedule.add_system(write_resource_system.after(read_resource_system)); +/// # schedule.add_systems((read_resource_system, write_resource_system).chain()); /// # schedule.run(&mut world); /// ``` pub trait Resource: Send + Sync + 'static {} @@ -861,10 +860,8 @@ pub trait SystemBuffer: FromWorld + Send + 'static { /// }); /// /// let mut schedule = Schedule::new(); -/// schedule -/// // These two systems have no conflicts and will run in parallel. -/// .add_system(alert_criminal) -/// .add_system(alert_monster); +/// // These two systems have no conflicts and will run in parallel. +/// schedule.add_systems((alert_criminal, alert_monster)); /// /// // There are no criminals or monsters, so the alarm is not sounded. /// schedule.run(&mut world); diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 530a2fa71e..a55458d802 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -188,22 +188,18 @@ impl Plugin for PbrPlugin { .in_base_set(CoreSet::PostUpdate), ) .add_plugin(FogPlugin) - .add_system(add_clusters.in_set(SimulationLightSystems::AddClusters)) - .add_system(apply_system_buffers.in_set(SimulationLightSystems::AddClustersFlush)) - .add_system( + .add_systems(( + add_clusters.in_set(SimulationLightSystems::AddClusters), + apply_system_buffers.in_set(SimulationLightSystems::AddClustersFlush), assign_lights_to_clusters .in_set(SimulationLightSystems::AssignLightsToClusters) .after(TransformSystem::TransformPropagate) .after(VisibilitySystems::CheckVisibility) .after(CameraUpdateSystem), - ) - .add_system( update_directional_light_cascades .in_set(SimulationLightSystems::UpdateDirectionalLightCascades) .after(TransformSystem::TransformPropagate) .after(CameraUpdateSystem), - ) - .add_system( update_directional_light_frusta .in_set(SimulationLightSystems::UpdateLightFrusta) // This must run after CheckVisibility because it relies on ComputedVisibility::is_visible() @@ -214,20 +210,14 @@ impl Plugin for PbrPlugin { // so these systems will run independently of one another. // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. .ambiguous_with(update_spot_light_frusta), - ) - .add_system( update_point_light_frusta .in_set(SimulationLightSystems::UpdateLightFrusta) .after(TransformSystem::TransformPropagate) .after(SimulationLightSystems::AssignLightsToClusters), - ) - .add_system( update_spot_light_frusta .in_set(SimulationLightSystems::UpdateLightFrusta) .after(TransformSystem::TransformPropagate) .after(SimulationLightSystems::AssignLightsToClusters), - ) - .add_system( check_light_mesh_visibility .in_set(SimulationLightSystems::CheckLightVisibility) .after(VisibilitySystems::CalculateBoundsFlush) @@ -237,7 +227,7 @@ impl Plugin for PbrPlugin { // because that resets entity ComputedVisibility for the first view // which would override any results from this otherwise .after(VisibilitySystems::CheckVisibility), - ); + )); app.world .resource_mut::>() @@ -267,25 +257,21 @@ impl Plugin for PbrPlugin { ) .in_schedule(ExtractSchedule), ) - .add_system( + .add_systems(( render::prepare_lights .before(ViewSet::PrepareUniforms) .in_set(RenderLightSystems::PrepareLights), - ) - // A sync is needed after prepare_lights, before prepare_view_uniforms, - // because prepare_lights creates new views for shadow mapping - .add_system( + // A sync is needed after prepare_lights, before prepare_view_uniforms, + // because prepare_lights creates new views for shadow mapping apply_system_buffers .in_set(RenderSet::Prepare) .after(RenderLightSystems::PrepareLights) .before(ViewSet::PrepareUniforms), - ) - .add_system( render::prepare_clusters .after(render::prepare_lights) .in_set(RenderLightSystems::PrepareClusters), - ) - .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) + sort_phase_system::.in_set(RenderSet::PhaseSort), + )) .init_resource::() .init_resource::() .init_resource::(); diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index cb57e68f4d..0072474083 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -199,14 +199,14 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_system(extract_materials::.in_schedule(ExtractSchedule)) - .add_system( + .add_systems(( + extract_materials::.in_schedule(ExtractSchedule), prepare_materials:: .in_set(RenderSet::Prepare) .after(PrepareAssetSet::PreAssetPrepare), - ) - .add_system(render::queue_shadows::.in_set(RenderLightSystems::QueueShadows)) - .add_system(queue_material_meshes::.in_set(RenderSet::Queue)); + render::queue_shadows::.in_set(RenderLightSystems::QueueShadows), + queue_material_meshes::.in_set(RenderSet::Queue), + )); } // PrepassPipelinePlugin is required for shadow mapping and the optional PrepassPlugin diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 855d1e4ae8..b890de737d 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -129,15 +129,15 @@ where }; render_app - .add_system(extract_camera_prepass_phase.in_schedule(ExtractSchedule)) - .add_system( + .add_systems(( + extract_camera_prepass_phase.in_schedule(ExtractSchedule), prepare_prepass_textures .in_set(RenderSet::Prepare) .after(bevy_render::view::prepare_windows), - ) - .add_system(queue_prepass_material_meshes::.in_set(RenderSet::Queue)) - .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) - .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) + queue_prepass_material_meshes::.in_set(RenderSet::Queue), + sort_phase_system::.in_set(RenderSet::PhaseSort), + sort_phase_system::.in_set(RenderSet::PhaseSort), + )) .init_resource::>() .init_resource::>() .add_render_command::>() diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 9b1239099b..1faf66bc4c 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -108,9 +108,11 @@ impl Plugin for MeshRenderPlugin { .init_resource::() .init_resource::() .add_systems((extract_meshes, extract_skinned_meshes).in_schedule(ExtractSchedule)) - .add_system(prepare_skinned_meshes.in_set(RenderSet::Prepare)) - .add_system(queue_mesh_bind_group.in_set(RenderSet::Queue)) - .add_system(queue_mesh_view_bind_groups.in_set(RenderSet::Queue)); + .add_systems(( + prepare_skinned_meshes.in_set(RenderSet::Prepare), + queue_mesh_bind_group.in_set(RenderSet::Queue), + queue_mesh_view_bind_groups.in_set(RenderSet::Queue), + )); } } } diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index 34757708f3..fd82cd72da 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use bevy_app::{App, CoreSchedule, CoreSet, Plugin, StartupSet}; +use bevy_app::{App, CoreSchedule, CoreSet, IntoSystemAppConfig, Plugin, StartupSet}; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_math::{Mat4, Rect, Vec2}; use bevy_reflect::{ @@ -31,22 +31,21 @@ impl Plugin for CameraPro schedule.configure_set(CameraUpdateSystem.in_base_set(StartupSet::PostStartup)); }) .configure_set(CameraUpdateSystem.in_base_set(CoreSet::PostUpdate)) - .add_startup_system( + .add_systems(( + crate::camera::camera_system:: + .on_startup() + .in_set(CameraUpdateSystem) + // We assume that each camera will only have one projection, + // so we can ignore ambiguities with all other monomorphizations. + // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. + .ambiguous_with(CameraUpdateSystem), crate::camera::camera_system:: .in_set(CameraUpdateSystem) // We assume that each camera will only have one projection, // so we can ignore ambiguities with all other monomorphizations. // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. .ambiguous_with(CameraUpdateSystem), - ) - .add_system( - crate::camera::camera_system:: - .in_set(CameraUpdateSystem) - // We assume that each camera will only have one projection, - // so we can ignore ambiguities with all other monomorphizations. - // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. - .ambiguous_with(CameraUpdateSystem), - ); + )); } } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 783ee7cdfe..1f4c1a2a90 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -112,18 +112,30 @@ impl RenderSet { let mut schedule = Schedule::new(); // Create "stage-like" structure using buffer flushes + ordering - schedule.add_system(apply_system_buffers.in_set(PrepareFlush)); - schedule.add_system(apply_system_buffers.in_set(QueueFlush)); - schedule.add_system(apply_system_buffers.in_set(PhaseSortFlush)); - schedule.add_system(apply_system_buffers.in_set(RenderFlush)); - schedule.add_system(apply_system_buffers.in_set(CleanupFlush)); + schedule.add_systems(( + apply_system_buffers.in_set(PrepareFlush), + apply_system_buffers.in_set(QueueFlush), + apply_system_buffers.in_set(PhaseSortFlush), + apply_system_buffers.in_set(RenderFlush), + apply_system_buffers.in_set(CleanupFlush), + )); - schedule.configure_set(ExtractCommands.before(Prepare)); - schedule.configure_set(Prepare.after(ExtractCommands).before(PrepareFlush)); - schedule.configure_set(Queue.after(PrepareFlush).before(QueueFlush)); - schedule.configure_set(PhaseSort.after(QueueFlush).before(PhaseSortFlush)); - schedule.configure_set(Render.after(PhaseSortFlush).before(RenderFlush)); - schedule.configure_set(Cleanup.after(RenderFlush).before(CleanupFlush)); + schedule.configure_sets( + ( + ExtractCommands, + Prepare, + PrepareFlush, + Queue, + QueueFlush, + PhaseSort, + PhaseSortFlush, + Render, + RenderFlush, + Cleanup, + CleanupFlush, + ) + .chain(), + ); schedule } diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index 65542e6ea1..d49471c122 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -92,8 +92,10 @@ impl Plugin for RenderAssetPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .add_system(extract_render_asset::.in_schedule(ExtractSchedule)) - .add_system(prepare_assets::.in_set(self.prepare_asset_set.clone())); + .add_systems(( + extract_render_asset::.in_schedule(ExtractSchedule), + prepare_assets::.in_set(self.prepare_asset_set.clone()), + )); } } } diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 2a15363fda..fa8bf29603 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -56,13 +56,13 @@ impl Plugin for ViewPlugin { render_app .init_resource::() .configure_set(ViewSet::PrepareUniforms.in_set(RenderSet::Prepare)) - .add_system(prepare_view_uniforms.in_set(ViewSet::PrepareUniforms)) - .add_system( + .add_systems(( + prepare_view_uniforms.in_set(ViewSet::PrepareUniforms), prepare_view_targets .after(WindowSystem::Prepare) .in_set(RenderSet::Prepare) .after(crate::render_asset::prepare_assets::), - ); + )); } } } diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index ca5e8a9df2..3163f7a831 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -223,8 +223,8 @@ impl Plugin for VisibilityPlugin { .configure_set(UpdateProjectionFrusta.in_base_set(CoreSet::PostUpdate)) .configure_set(CheckVisibility.in_base_set(CoreSet::PostUpdate)) .configure_set(VisibilityPropagate.in_base_set(CoreSet::PostUpdate)) - .add_system(calculate_bounds.in_set(CalculateBounds)) - .add_system( + .add_systems(( + calculate_bounds.in_set(CalculateBounds), update_frusta:: .in_set(UpdateOrthographicFrusta) .after(camera_system::) @@ -234,8 +234,6 @@ impl Plugin for VisibilityPlugin { // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. .ambiguous_with(update_frusta::) .ambiguous_with(update_frusta::), - ) - .add_system( update_frusta:: .in_set(UpdatePerspectiveFrusta) .after(camera_system::) @@ -244,15 +242,11 @@ impl Plugin for VisibilityPlugin { // so these systems will run independently of one another. // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. .ambiguous_with(update_frusta::), - ) - .add_system( update_frusta:: .in_set(UpdateProjectionFrusta) .after(camera_system::) .after(TransformSystem::TransformPropagate), - ) - .add_system(visibility_propagate_system.in_set(VisibilityPropagate)) - .add_system( + visibility_propagate_system.in_set(VisibilityPropagate), check_visibility .in_set(CheckVisibility) .after(CalculateBoundsFlush) @@ -261,7 +255,7 @@ impl Plugin for VisibilityPlugin { .after(UpdateProjectionFrusta) .after(VisibilityPropagate) .after(TransformSystem::TransformPropagate), - ); + )); } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 4982efe381..4ee87cff02 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -161,13 +161,13 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_system(extract_materials_2d::.in_schedule(ExtractSchedule)) - .add_system( + .add_systems(( + extract_materials_2d::.in_schedule(ExtractSchedule), prepare_materials_2d:: .in_set(RenderSet::Prepare) .after(PrepareAssetSet::PreAssetPrepare), - ) - .add_system(queue_material2d_meshes::.in_set(RenderSet::Queue)); + queue_material2d_meshes::.in_set(RenderSet::Queue), + )); } } } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 42030fb52b..a001b00dc3 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -103,9 +103,11 @@ impl Plugin for Mesh2dRenderPlugin { render_app .init_resource::() .init_resource::>() - .add_system(extract_mesh2d.in_schedule(ExtractSchedule)) - .add_system(queue_mesh2d_bind_group.in_set(RenderSet::Queue)) - .add_system(queue_mesh2d_view_bind_groups.in_set(RenderSet::Queue)); + .add_systems(( + extract_mesh2d.in_schedule(ExtractSchedule), + queue_mesh2d_bind_group.in_set(RenderSet::Queue), + queue_mesh2d_view_bind_groups.in_set(RenderSet::Queue), + )); } } } diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index e45c648f72..d377d08970 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -106,20 +106,20 @@ impl Plugin for TransformPlugin { TransformSystem::TransformPropagate.in_base_set(StartupSet::PostStartup), ); }) - // FIXME: https://github.com/bevyengine/bevy/issues/4381 - // These systems cannot access the same entities, - // due to subtle query filtering that is not yet correctly computed in the ambiguity detector - .add_startup_system( + .add_startup_systems(( + sync_simple_transforms + .in_set(TransformSystem::TransformPropagate) + // FIXME: https://github.com/bevyengine/bevy/issues/4381 + // These systems cannot access the same entities, + // due to subtle query filtering that is not yet correctly computed in the ambiguity detector + .ambiguous_with(PropagateTransformsSet), + propagate_transforms.in_set(PropagateTransformsSet), + )) + .add_systems(( sync_simple_transforms .in_set(TransformSystem::TransformPropagate) .ambiguous_with(PropagateTransformsSet), - ) - .add_startup_system(propagate_transforms.in_set(PropagateTransformsSet)) - .add_system( - sync_simple_transforms - .in_set(TransformSystem::TransformPropagate) - .ambiguous_with(PropagateTransformsSet), - ) - .add_system(propagate_transforms.in_set(PropagateTransformsSet)); + propagate_transforms.in_set(PropagateTransformsSet), + )); } } diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index be0613213f..fcc30c5644 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -171,8 +171,7 @@ mod test { let mut world = World::default(); let mut schedule = Schedule::new(); - schedule.add_system(sync_simple_transforms); - schedule.add_system(propagate_transforms); + schedule.add_systems((sync_simple_transforms, propagate_transforms)); // Root entity world.spawn(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0))); @@ -210,8 +209,7 @@ mod test { let mut world = World::default(); let mut schedule = Schedule::new(); - schedule.add_system(sync_simple_transforms); - schedule.add_system(propagate_transforms); + schedule.add_systems((sync_simple_transforms, propagate_transforms)); // Root entity let mut queue = CommandQueue::default(); @@ -251,8 +249,7 @@ mod test { let mut world = World::default(); let mut schedule = Schedule::new(); - schedule.add_system(sync_simple_transforms); - schedule.add_system(propagate_transforms); + schedule.add_systems((sync_simple_transforms, propagate_transforms)); // Add parent entities let mut children = Vec::new(); @@ -328,8 +325,7 @@ mod test { let mut app = App::new(); ComputeTaskPool::init(TaskPool::default); - app.add_system(sync_simple_transforms); - app.add_system(propagate_transforms); + app.add_systems((sync_simple_transforms, propagate_transforms)); let translation = vec3(1.0, 0.0, 0.0); @@ -375,8 +371,7 @@ mod test { let mut temp = World::new(); let mut app = App::new(); - app.add_system(propagate_transforms) - .add_system(sync_simple_transforms); + app.add_systems((propagate_transforms, sync_simple_transforms)); fn setup_world(world: &mut World) -> (Entity, Entity) { let mut grandchild = Entity::from_raw(0); diff --git a/crates/bevy_ui/src/accessibility.rs b/crates/bevy_ui/src/accessibility.rs index ee79773189..af576ef2ea 100644 --- a/crates/bevy_ui/src/accessibility.rs +++ b/crates/bevy_ui/src/accessibility.rs @@ -149,9 +149,6 @@ pub(crate) struct AccessibilityPlugin; impl Plugin for AccessibilityPlugin { fn build(&self, app: &mut App) { - app.add_system(calc_bounds) - .add_system(button_changed) - .add_system(image_changed) - .add_system(label_changed); + app.add_systems((calc_bounds, button_changed, image_changed, label_changed)); } } diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index b668c2b951..c8456a7d6b 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -141,17 +141,15 @@ impl Plugin for UiPlugin { system }) - .add_system( + .add_systems(( flex_node_system .in_set(UiSystem::Flex) .before(TransformSystem::TransformPropagate), - ) - .add_system(ui_stack_system.in_set(UiSystem::Stack)) - .add_system( + ui_stack_system.in_set(UiSystem::Stack), update_clipping_system .after(TransformSystem::TransformPropagate) .in_base_set(CoreSet::PostUpdate), - ); + )); crate::render::build_ui_render(app); } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 2cfa098d28..853a3776a8 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -86,9 +86,11 @@ pub fn build_ui_render(app: &mut App) { ) .in_schedule(ExtractSchedule), ) - .add_system(prepare_uinodes.in_set(RenderSet::Prepare)) - .add_system(queue_uinodes.in_set(RenderSet::Queue)) - .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)); + .add_systems(( + prepare_uinodes.in_set(RenderSet::Prepare), + queue_uinodes.in_set(RenderSet::Queue), + sort_phase_system::.in_set(RenderSet::PhaseSort), + )); // Render graph let ui_graph_2d = get_ui_graph(render_app); diff --git a/crates/bevy_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index fd0f4dc1e4..68eff9b214 100644 --- a/crates/bevy_winit/src/accessibility.rs +++ b/crates/bevy_winit/src/accessibility.rs @@ -163,9 +163,11 @@ impl Plugin for AccessibilityPlugin { app.init_non_send_resource::() .init_resource::() .add_event::() - .add_system(handle_window_focus) - .add_system(window_closed) - .add_system(poll_receivers) - .add_system(update_accessibility_nodes); + .add_systems(( + handle_window_focus, + window_closed, + poll_receivers, + update_accessibility_nodes, + )); } } diff --git a/examples/2d/bloom_2d.rs b/examples/2d/bloom_2d.rs index e46f24c751..825f4803bf 100644 --- a/examples/2d/bloom_2d.rs +++ b/examples/2d/bloom_2d.rs @@ -13,8 +13,7 @@ fn main() { App::new() .insert_resource(ClearColor(Color::DARK_GRAY)) .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(update_bloom_settings) + .add_systems((setup.on_startup(), update_bloom_settings)) .run(); } diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 635a19c0ce..3425837fe2 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -283,8 +283,10 @@ impl Plugin for ColoredMesh2dPlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_system(extract_colored_mesh2d.in_schedule(ExtractSchedule)) - .add_system(queue_colored_mesh2d.in_set(RenderSet::Queue)); + .add_systems(( + extract_colored_mesh2d.in_schedule(ExtractSchedule), + queue_colored_mesh2d.in_set(RenderSet::Queue), + )); } } diff --git a/examples/2d/move_sprite.rs b/examples/2d/move_sprite.rs index f7e353c266..cab129ff9f 100644 --- a/examples/2d/move_sprite.rs +++ b/examples/2d/move_sprite.rs @@ -5,8 +5,7 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(sprite_movement) + .add_systems((setup.on_startup(), sprite_movement)) .run(); } diff --git a/examples/2d/pixel_perfect.rs b/examples/2d/pixel_perfect.rs index cce3d6135f..0496bd9bcc 100644 --- a/examples/2d/pixel_perfect.rs +++ b/examples/2d/pixel_perfect.rs @@ -5,8 +5,7 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) - .add_startup_system(setup) - .add_system(sprite_movement) + .add_systems((setup.on_startup(), sprite_movement)) .run(); } diff --git a/examples/2d/sprite_sheet.rs b/examples/2d/sprite_sheet.rs index 597737bdb3..94d7d0ecd6 100644 --- a/examples/2d/sprite_sheet.rs +++ b/examples/2d/sprite_sheet.rs @@ -6,8 +6,7 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // prevents blurry sprites - .add_startup_system(setup) - .add_system(animate_sprite) + .add_systems((setup.on_startup(), animate_sprite)) .run(); } diff --git a/examples/2d/text2d.rs b/examples/2d/text2d.rs index 100379e5aa..ef39859ef1 100644 --- a/examples/2d/text2d.rs +++ b/examples/2d/text2d.rs @@ -14,10 +14,12 @@ use bevy::{ fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(animate_translation) - .add_system(animate_rotation) - .add_system(animate_scale) + .add_systems(( + setup.on_startup(), + animate_translation, + animate_rotation, + animate_scale, + )) .run(); } diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index 678fb7d97b..8ffabc8141 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -8,9 +8,11 @@ fn main() { .init_resource::() .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // prevents blurry sprites .add_state::() - .add_system(load_textures.in_schedule(OnEnter(AppState::Setup))) - .add_system(check_textures.in_set(OnUpdate(AppState::Setup))) - .add_system(setup.in_schedule(OnEnter(AppState::Finished))) + .add_systems(( + load_textures.in_schedule(OnEnter(AppState::Setup)), + check_textures.in_set(OnUpdate(AppState::Setup)), + setup.in_schedule(OnEnter(AppState::Finished)), + )) .run(); } diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 5358ca4ed3..22da05a37c 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -11,8 +11,7 @@ use bevy::{ fn main() { App::new() .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) - .add_startup_system(setup) - .add_system(rotate) + .add_systems((setup.on_startup(), rotate)) .run(); } diff --git a/examples/3d/atmospheric_fog.rs b/examples/3d/atmospheric_fog.rs index 5766bc7201..1969303ff7 100644 --- a/examples/3d/atmospheric_fog.rs +++ b/examples/3d/atmospheric_fog.rs @@ -15,9 +15,7 @@ use bevy::{ fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup_camera_fog) - .add_startup_system(setup_terrain_scene) - .add_startup_system(setup_instructions) + .add_startup_systems((setup_camera_fog, setup_terrain_scene, setup_instructions)) .add_system(toggle_system) .run(); } diff --git a/examples/3d/blend_modes.rs b/examples/3d/blend_modes.rs index 78db84aff9..5fe22dfc4a 100644 --- a/examples/3d/blend_modes.rs +++ b/examples/3d/blend_modes.rs @@ -17,8 +17,7 @@ fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(example_control_system); + .add_systems((setup.on_startup(), example_control_system)); // Unfortunately, MSAA and HDR are not supported simultaneously under WebGL. // Since this example uses HDR, we must disable MSAA for WASM builds, at least diff --git a/examples/3d/bloom_3d.rs b/examples/3d/bloom_3d.rs index da922c4b37..94fcdf90fd 100644 --- a/examples/3d/bloom_3d.rs +++ b/examples/3d/bloom_3d.rs @@ -16,9 +16,11 @@ fn main() { App::new() .insert_resource(ClearColor(Color::DARK_GRAY)) .add_plugins(DefaultPlugins) - .add_startup_system(setup_scene) - .add_system(update_bloom_settings) - .add_system(bounce_spheres) + .add_systems(( + setup_scene.on_startup(), + update_bloom_settings, + bounce_spheres, + )) .run(); } diff --git a/examples/3d/fog.rs b/examples/3d/fog.rs index ba9f844c12..745b828982 100644 --- a/examples/3d/fog.rs +++ b/examples/3d/fog.rs @@ -22,9 +22,7 @@ use bevy::{ fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup_camera_fog) - .add_startup_system(setup_pyramid_scene) - .add_startup_system(setup_instructions) + .add_startup_systems((setup_camera_fog, setup_pyramid_scene, setup_instructions)) .add_system(update_system) .run(); } diff --git a/examples/3d/fxaa.rs b/examples/3d/fxaa.rs index 8873987385..06dad37a41 100644 --- a/examples/3d/fxaa.rs +++ b/examples/3d/fxaa.rs @@ -17,8 +17,7 @@ fn main() { // Disable MSAA by default .insert_resource(Msaa::Off) .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(toggle_fxaa) + .add_systems((setup.on_startup(), toggle_fxaa)) .run(); } diff --git a/examples/3d/lighting.rs b/examples/3d/lighting.rs index 3f2dae782d..c12ab5d377 100644 --- a/examples/3d/lighting.rs +++ b/examples/3d/lighting.rs @@ -8,9 +8,7 @@ use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(movement) - .add_system(animate_light_direction) + .add_systems((setup.on_startup(), movement, animate_light_direction)) .run(); } diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs index 216842a830..756bf7653b 100644 --- a/examples/3d/load_gltf.rs +++ b/examples/3d/load_gltf.rs @@ -15,8 +15,7 @@ fn main() { }) .insert_resource(DirectionalLightShadowMap { size: 4096 }) .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(animate_light_direction) + .add_systems((setup.on_startup(), animate_light_direction)) .run(); } diff --git a/examples/3d/msaa.rs b/examples/3d/msaa.rs index b3ee49c272..a82b352dcd 100644 --- a/examples/3d/msaa.rs +++ b/examples/3d/msaa.rs @@ -10,8 +10,7 @@ fn main() { App::new() .insert_resource(Msaa::default()) .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(cycle_msaa) + .add_systems((setup.on_startup(), cycle_msaa)) .run(); } diff --git a/examples/3d/parenting.rs b/examples/3d/parenting.rs index 29dc1a4b28..5e1cb133dc 100644 --- a/examples/3d/parenting.rs +++ b/examples/3d/parenting.rs @@ -6,8 +6,7 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(rotator_system) + .add_systems((setup.on_startup(), rotator_system)) .run(); } diff --git a/examples/3d/pbr.rs b/examples/3d/pbr.rs index 89532aee75..ccf34966d9 100644 --- a/examples/3d/pbr.rs +++ b/examples/3d/pbr.rs @@ -5,8 +5,7 @@ use bevy::{asset::LoadState, prelude::*}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(environment_map_load_finish) + .add_systems((setup.on_startup(), environment_map_load_finish)) .run(); } diff --git a/examples/3d/render_to_texture.rs b/examples/3d/render_to_texture.rs index 7732146097..28588867af 100644 --- a/examples/3d/render_to_texture.rs +++ b/examples/3d/render_to_texture.rs @@ -17,9 +17,7 @@ use bevy::{ fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(cube_rotator_system) - .add_system(rotator_system) + .add_systems((setup.on_startup(), cube_rotator_system, rotator_system)) .run(); } diff --git a/examples/3d/shadow_biases.rs b/examples/3d/shadow_biases.rs index 81b9fbe024..f33a1985f0 100644 --- a/examples/3d/shadow_biases.rs +++ b/examples/3d/shadow_biases.rs @@ -19,11 +19,13 @@ fn main() { ); App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(adjust_point_light_biases) - .add_system(toggle_light) - .add_system(adjust_directional_light_biases) - .add_system(camera_controller) + .add_systems(( + setup.on_startup(), + adjust_point_light_biases, + toggle_light, + adjust_directional_light_biases, + camera_controller, + )) .run(); } diff --git a/examples/3d/shadow_caster_receiver.rs b/examples/3d/shadow_caster_receiver.rs index f8eed512d7..2efacd0f4e 100644 --- a/examples/3d/shadow_caster_receiver.rs +++ b/examples/3d/shadow_caster_receiver.rs @@ -16,9 +16,7 @@ fn main() { ); App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(toggle_light) - .add_system(toggle_shadows) + .add_systems((setup.on_startup(), toggle_light, toggle_shadows)) .run(); } diff --git a/examples/3d/skybox.rs b/examples/3d/skybox.rs index c9dadafd5b..74f149b536 100644 --- a/examples/3d/skybox.rs +++ b/examples/3d/skybox.rs @@ -46,11 +46,13 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(MaterialPlugin::::default()) - .add_startup_system(setup) - .add_system(cycle_cubemap_asset) - .add_system(asset_loaded.after(cycle_cubemap_asset)) - .add_system(camera_controller) - .add_system(animate_light_direction) + .add_systems(( + setup.on_startup(), + cycle_cubemap_asset, + asset_loaded.after(cycle_cubemap_asset), + camera_controller, + animate_light_direction, + )) .run(); } diff --git a/examples/3d/split_screen.rs b/examples/3d/split_screen.rs index bb1e67d5a8..2e8f01acf6 100644 --- a/examples/3d/split_screen.rs +++ b/examples/3d/split_screen.rs @@ -10,8 +10,7 @@ use bevy::{ fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(set_camera_viewports) + .add_systems((setup.on_startup(), set_camera_viewports)) .run(); } diff --git a/examples/3d/spotlight.rs b/examples/3d/spotlight.rs index a066041745..19c5e44317 100644 --- a/examples/3d/spotlight.rs +++ b/examples/3d/spotlight.rs @@ -12,9 +12,7 @@ fn main() { .add_plugins(DefaultPlugins) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) - .add_startup_system(setup) - .add_system(light_sway) - .add_system(movement) + .add_systems((setup.on_startup(), light_sway, movement)) .run(); } diff --git a/examples/3d/tonemapping.rs b/examples/3d/tonemapping.rs index cc4f2cc3f7..996f6cbe14 100644 --- a/examples/3d/tonemapping.rs +++ b/examples/3d/tonemapping.rs @@ -27,15 +27,19 @@ fn main() { .init_resource::() .insert_resource(CurrentScene(1)) .insert_resource(SelectedParameter { value: 0, max: 4 }) - .add_startup_system(setup) - .add_startup_system(setup_basic_scene) - .add_startup_system(setup_color_gradient_scene) - .add_startup_system(setup_image_viewer_scene) - .add_system(update_image_viewer) - .add_system(toggle_scene) - .add_system(toggle_tonemapping_method) - .add_system(update_color_grading_settings) - .add_system(update_ui) + .add_startup_systems(( + setup, + setup_basic_scene, + setup_color_gradient_scene, + setup_image_viewer_scene, + )) + .add_systems(( + update_image_viewer, + toggle_scene, + toggle_tonemapping_method, + update_color_grading_settings, + update_ui, + )) .run(); } diff --git a/examples/3d/update_gltf_scene.rs b/examples/3d/update_gltf_scene.rs index b33a8a24b1..07c095bb60 100644 --- a/examples/3d/update_gltf_scene.rs +++ b/examples/3d/update_gltf_scene.rs @@ -6,8 +6,7 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(move_scene_entities) + .add_systems((setup.on_startup(), move_scene_entities)) .run(); } diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index 1cc50849e6..c00d502dcd 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -13,9 +13,11 @@ fn main() { color: Color::WHITE, brightness: 1.0, }) - .add_startup_system(setup) - .add_system(setup_scene_once_loaded) - .add_system(keyboard_animation_control) + .add_systems(( + setup.on_startup(), + setup_scene_once_loaded, + keyboard_animation_control, + )) .run(); } diff --git a/examples/animation/custom_skinned_mesh.rs b/examples/animation/custom_skinned_mesh.rs index 8b6b6a68b8..9bec25dca1 100644 --- a/examples/animation/custom_skinned_mesh.rs +++ b/examples/animation/custom_skinned_mesh.rs @@ -20,8 +20,7 @@ fn main() { brightness: 1.0, ..default() }) - .add_startup_system(setup) - .add_system(joint_animation) + .add_systems((setup.on_startup(), joint_animation)) .run(); } diff --git a/examples/animation/gltf_skinned_mesh.rs b/examples/animation/gltf_skinned_mesh.rs index 80c0df7aa6..d0f2f9e765 100644 --- a/examples/animation/gltf_skinned_mesh.rs +++ b/examples/animation/gltf_skinned_mesh.rs @@ -12,8 +12,7 @@ fn main() { brightness: 1.0, ..default() }) - .add_startup_system(setup) - .add_system(joint_animation) + .add_systems((setup.on_startup(), joint_animation)) .run(); } diff --git a/examples/asset/custom_asset.rs b/examples/asset/custom_asset.rs index 50089d5dfb..0087cf2d86 100644 --- a/examples/asset/custom_asset.rs +++ b/examples/asset/custom_asset.rs @@ -41,8 +41,7 @@ fn main() { .init_resource::() .add_asset::() .init_asset_loader::() - .add_startup_system(setup) - .add_system(print_on_load) + .add_systems((setup.on_startup(), print_on_load)) .run(); } diff --git a/examples/async_tasks/async_compute.rs b/examples/async_tasks/async_compute.rs index d0f4466d76..4b5461c81f 100644 --- a/examples/async_tasks/async_compute.rs +++ b/examples/async_tasks/async_compute.rs @@ -12,9 +12,7 @@ use std::time::{Duration, Instant}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup_env) - .add_startup_system(add_assets) - .add_startup_system(spawn_tasks) + .add_startup_systems((setup_env, add_assets, spawn_tasks)) .add_system(handle_tasks) .run(); } diff --git a/examples/async_tasks/external_source_external_thread.rs b/examples/async_tasks/external_source_external_thread.rs index 3a17471390..0325d96255 100644 --- a/examples/async_tasks/external_source_external_thread.rs +++ b/examples/async_tasks/external_source_external_thread.rs @@ -10,10 +10,7 @@ fn main() { App::new() .add_event::() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(read_stream) - .add_system(spawn_text) - .add_system(move_text) + .add_systems((setup.on_startup(), read_stream, spawn_text, move_text)) .run(); } diff --git a/examples/audio/audio_control.rs b/examples/audio/audio_control.rs index 33c72147a0..c111e030f6 100644 --- a/examples/audio/audio_control.rs +++ b/examples/audio/audio_control.rs @@ -5,10 +5,7 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(update_speed) - .add_system(pause) - .add_system(volume) + .add_systems((setup.on_startup(), update_speed, pause, volume)) .run(); } diff --git a/examples/audio/spatial_audio_2d.rs b/examples/audio/spatial_audio_2d.rs index 44601d0e63..e076a04959 100644 --- a/examples/audio/spatial_audio_2d.rs +++ b/examples/audio/spatial_audio_2d.rs @@ -4,8 +4,7 @@ use bevy::{prelude::*, sprite::MaterialMesh2dBundle}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(update_positions) + .add_systems((setup.on_startup(), update_positions)) .run(); } diff --git a/examples/audio/spatial_audio_3d.rs b/examples/audio/spatial_audio_3d.rs index c9213a30cf..397540a344 100644 --- a/examples/audio/spatial_audio_3d.rs +++ b/examples/audio/spatial_audio_3d.rs @@ -4,8 +4,7 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(update_positions) + .add_systems((setup.on_startup(), update_positions)) .run(); } diff --git a/examples/diagnostics/custom_diagnostic.rs b/examples/diagnostics/custom_diagnostic.rs index 41cd5aefef..d8fac4aeb2 100644 --- a/examples/diagnostics/custom_diagnostic.rs +++ b/examples/diagnostics/custom_diagnostic.rs @@ -11,8 +11,7 @@ fn main() { // The "print diagnostics" plugin is optional. // It just visualizes our diagnostics in the console. .add_plugin(LogDiagnosticsPlugin::default()) - .add_startup_system(setup_diagnostic_system) - .add_system(my_system) + .add_systems((setup_diagnostic_system.on_startup(), my_system)) .run(); } diff --git a/examples/ecs/apply_system_buffers.rs b/examples/ecs/apply_system_buffers.rs index 67a25274dd..283645ec6d 100644 --- a/examples/ecs/apply_system_buffers.rs +++ b/examples/ecs/apply_system_buffers.rs @@ -15,12 +15,14 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .init_resource::() - .add_startup_system(setup) - .add_system(despawn_old_and_spawn_new_fruits.before(CustomFlush)) - .add_system(apply_system_buffers.in_set(CustomFlush)) - .add_system(count_apple.after(CustomFlush)) - .add_system(count_orange) - .add_system(bevy::window::close_on_esc) + .add_systems(( + setup.on_startup(), + despawn_old_and_spawn_new_fruits.before(CustomFlush), + apply_system_buffers.in_set(CustomFlush), + count_apple.after(CustomFlush), + count_orange, + bevy::window::close_on_esc, + )) .run(); } diff --git a/examples/ecs/component_change_detection.rs b/examples/ecs/component_change_detection.rs index 8758e03c25..704e3ab194 100644 --- a/examples/ecs/component_change_detection.rs +++ b/examples/ecs/component_change_detection.rs @@ -6,10 +6,12 @@ use rand::Rng; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(change_component) - .add_system(change_detection) - .add_system(tracker_monitoring) + .add_systems(( + setup.on_startup(), + change_component, + change_detection, + tracker_monitoring, + )) .run(); } diff --git a/examples/ecs/custom_query_param.rs b/examples/ecs/custom_query_param.rs index e13e4695e7..04b0ff04b5 100644 --- a/examples/ecs/custom_query_param.rs +++ b/examples/ecs/custom_query_param.rs @@ -18,10 +18,15 @@ use std::fmt::Debug; fn main() { App::new() .add_startup_system(spawn) - .add_system(print_components_read_only) - .add_system(print_components_iter_mut.after(print_components_read_only)) - .add_system(print_components_iter.after(print_components_iter_mut)) - .add_system(print_components_tuple.after(print_components_iter)) + .add_systems( + ( + print_components_read_only, + print_components_iter_mut, + print_components_iter, + print_components_tuple, + ) + .chain(), + ) .run(); } diff --git a/examples/ecs/event.rs b/examples/ecs/event.rs index cc4c2a7762..c2ad8feaac 100644 --- a/examples/ecs/event.rs +++ b/examples/ecs/event.rs @@ -9,9 +9,7 @@ fn main() { .add_event::() .add_event::() .init_resource::() - .add_system(event_trigger) - .add_system(event_listener) - .add_system(sound_player) + .add_systems((event_trigger, event_listener, sound_player)) .run(); } diff --git a/examples/ecs/fixed_timestep.rs b/examples/ecs/fixed_timestep.rs index ba776f92b1..98e0d466b9 100644 --- a/examples/ecs/fixed_timestep.rs +++ b/examples/ecs/fixed_timestep.rs @@ -6,10 +6,12 @@ const FIXED_TIMESTEP: f32 = 0.5; fn main() { App::new() .add_plugins(DefaultPlugins) - // this system will run once every update (it should match your screen's refresh rate) - .add_system(frame_update) - // add our system to the fixed timestep schedule - .add_system(fixed_update.in_schedule(CoreSchedule::FixedUpdate)) + .add_systems(( + // this system will run once every update (it should match your screen's refresh rate) + frame_update, + // add our system to the fixed timestep schedule + fixed_update.in_schedule(CoreSchedule::FixedUpdate), + )) // configure our fixed timestep schedule to run twice a second .insert_resource(FixedTime::new_from_secs(FIXED_TIMESTEP)) .run(); diff --git a/examples/ecs/generic_system.rs b/examples/ecs/generic_system.rs index aa63428f66..dec66ba07b 100644 --- a/examples/ecs/generic_system.rs +++ b/examples/ecs/generic_system.rs @@ -42,11 +42,11 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_state::() - .add_system(setup_system.on_startup()) - .add_system(print_text_system) - .add_system(transition_to_in_game_system.in_set(OnUpdate(AppState::MainMenu))) - // add the cleanup systems .add_systems(( + setup_system.on_startup(), + print_text_system, + transition_to_in_game_system.in_set(OnUpdate(AppState::MainMenu)), + // Cleanup systems. // Pass in the types your system should operate on using the :: (turbofish) syntax cleanup_system::.in_schedule(OnExit(AppState::MainMenu)), cleanup_system::.in_schedule(OnExit(AppState::InGame)), diff --git a/examples/ecs/hierarchy.rs b/examples/ecs/hierarchy.rs index fc1aa35324..34ef08c3ea 100644 --- a/examples/ecs/hierarchy.rs +++ b/examples/ecs/hierarchy.rs @@ -7,8 +7,7 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(rotate) + .add_systems((setup.on_startup(), rotate)) .run(); } diff --git a/examples/ecs/nondeterministic_system_order.rs b/examples/ecs/nondeterministic_system_order.rs index e28cc103b4..c467e05c1b 100644 --- a/examples/ecs/nondeterministic_system_order.rs +++ b/examples/ecs/nondeterministic_system_order.rs @@ -28,24 +28,26 @@ fn main() { }) .init_resource::() .init_resource::() - // This pair of systems has an ambiguous order, - // as their data access conflicts, and there's no order between them. - .add_system(reads_a) - .add_system(writes_a) - // This pair of systems has conflicting data access, - // but it's resolved with an explicit ordering: - // the .after relationship here means that we will always double after adding. - .add_system(adds_one_to_b) - .add_system(doubles_b.after(adds_one_to_b)) - // This system isn't ambiguous with adds_one_to_b, - // due to the transitive ordering created by our constraints: - // if A is before B is before C, then A must be before C as well. - .add_system(reads_b.after(doubles_b)) - // This system will conflict with all of our writing systems - // but we've silenced its ambiguity with adds_one_to_b. - // This should only be done in the case of clear false positives: - // leave a comment in your code justifying the decision! - .add_system(reads_a_and_b.ambiguous_with(adds_one_to_b)) + .add_systems(( + // This pair of systems has an ambiguous order, + // as their data access conflicts, and there's no order between them. + reads_a, + writes_a, + // This pair of systems has conflicting data access, + // but it's resolved with an explicit ordering: + // the .after relationship here means that we will always double after adding. + adds_one_to_b, + doubles_b.after(adds_one_to_b), + // This system isn't ambiguous with adds_one_to_b, + // due to the transitive ordering created by our constraints: + // if A is before B is before C, then A must be before C as well. + reads_b.after(doubles_b), + // This system will conflict with all of our writing systems + // but we've silenced its ambiguity with adds_one_to_b. + // This should only be done in the case of clear false positives: + // leave a comment in your code justifying the decision! + reads_a_and_b.ambiguous_with(adds_one_to_b), + )) // Be mindful, internal ambiguities are reported too! // If there are any ambiguities due solely to DefaultPlugins, // or between DefaultPlugins and any of your third party plugins, diff --git a/examples/ecs/parallel_query.rs b/examples/ecs/parallel_query.rs index ca0557320e..987962a164 100644 --- a/examples/ecs/parallel_query.rs +++ b/examples/ecs/parallel_query.rs @@ -69,8 +69,6 @@ fn bounce_system(windows: Query<&Window>, mut sprites: Query<(&Transform, &mut V fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(spawn_system) - .add_system(move_system) - .add_system(bounce_system) + .add_systems((spawn_system.on_startup(), move_system, bounce_system)) .run(); } diff --git a/examples/ecs/removal_detection.rs b/examples/ecs/removal_detection.rs index 87c4f2c7c4..96f0c6e1ca 100644 --- a/examples/ecs/removal_detection.rs +++ b/examples/ecs/removal_detection.rs @@ -14,9 +14,11 @@ fn main() { // `CoreSet::Update', and the system that reacts on the removal in `CoreSet::PostUpdate`. App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(remove_component) - .add_system(react_on_removal.in_base_set(CoreSet::PostUpdate)) + .add_systems(( + setup.on_startup(), + remove_component, + react_on_removal.in_base_set(CoreSet::PostUpdate), + )) .run(); } diff --git a/examples/ecs/run_conditions.rs b/examples/ecs/run_conditions.rs index 76a13319b8..557aa4b50b 100644 --- a/examples/ecs/run_conditions.rs +++ b/examples/ecs/run_conditions.rs @@ -11,7 +11,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .init_resource::() - .add_system( + .add_systems(( increment_input_counter // The common_conditions module has a few useful run conditions // for checking resources and states. These are included in the prelude. @@ -21,8 +21,6 @@ fn main() { // Both run conditions must return `true` in order for the system to run. // Note that this second run condition will be evaluated even if the first returns `false`. .run_if(has_user_input), - ) - .add_system( print_input_counter // `.and_then()` is a run condition combinator that only evaluates the second condition // if the first condition returns `true`. This behavior is known as "short-circuiting", @@ -35,8 +33,6 @@ fn main() { // All the normal rules still apply: all parameters must be read only except for local parameters. |counter: Res| counter.is_changed() && !counter.is_added(), )), - ) - .add_system( print_time_message // This function returns a custom run condition, much like the common conditions module. // It will only return true once 2 seconds have passed. @@ -45,7 +41,7 @@ fn main() { // to inverse a run condition. In this case it will return true if // less than 2.5 seconds have elapsed since the app started. .run_if(not(time_passed(2.5))), - ) + )) .run(); } diff --git a/examples/ecs/state.rs b/examples/ecs/state.rs index 438410a9e1..ae7f9d9950 100644 --- a/examples/ecs/state.rs +++ b/examples/ecs/state.rs @@ -18,9 +18,11 @@ fn main() { .add_system(setup_menu.in_schedule(OnEnter(AppState::Menu))) // By contrast, on_update systems are stored in the main schedule, during CoreSet::Update, // and simply check the value of the `State` resource to see if they should run each frame. - .add_system(menu.in_set(OnUpdate(AppState::Menu))) - .add_system(cleanup_menu.in_schedule(OnExit(AppState::Menu))) - .add_system(setup_game.in_schedule(OnEnter(AppState::InGame))) + .add_systems(( + menu.in_set(OnUpdate(AppState::Menu)), + cleanup_menu.in_schedule(OnExit(AppState::Menu)), + setup_game.in_schedule(OnEnter(AppState::InGame)), + )) .add_systems((movement, change_color).in_set(OnUpdate(AppState::InGame))) .run(); } diff --git a/examples/ecs/system_param.rs b/examples/ecs/system_param.rs index 09e3d4245f..4304ec1699 100644 --- a/examples/ecs/system_param.rs +++ b/examples/ecs/system_param.rs @@ -5,8 +5,7 @@ use bevy::{ecs::system::SystemParam, prelude::*}; fn main() { App::new() .insert_resource(PlayerCount(0)) - .add_startup_system(spawn) - .add_system(count_players) + .add_systems((spawn.on_startup(), count_players)) .run(); } diff --git a/examples/ecs/system_piping.rs b/examples/ecs/system_piping.rs index cff592ff16..c0a772d6a4 100644 --- a/examples/ecs/system_piping.rs +++ b/examples/ecs/system_piping.rs @@ -15,12 +15,14 @@ fn main() { level: Level::TRACE, filter: "".to_string(), }) - .add_system(parse_message_system.pipe(handler_system)) - .add_system(data_pipe_system.pipe(info)) - .add_system(parse_message_system.pipe(dbg)) - .add_system(warning_pipe_system.pipe(warn)) - .add_system(parse_error_message_system.pipe(error)) - .add_system(parse_message_system.pipe(ignore)) + .add_systems(( + parse_message_system.pipe(handler_system), + data_pipe_system.pipe(info), + parse_message_system.pipe(dbg), + warning_pipe_system.pipe(warn), + parse_error_message_system.pipe(error), + parse_message_system.pipe(ignore), + )) .run(); } diff --git a/examples/ecs/timers.rs b/examples/ecs/timers.rs index b2a3374ba9..8376f39a0b 100644 --- a/examples/ecs/timers.rs +++ b/examples/ecs/timers.rs @@ -6,9 +6,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .init_resource::() - .add_startup_system(setup) - .add_system(countdown) - .add_system(print_when_completed) + .add_systems((setup.on_startup(), countdown, print_when_completed)) .run(); } diff --git a/examples/games/alien_cake_addict.rs b/examples/games/alien_cake_addict.rs index e9c27a6e92..138186acaa 100644 --- a/examples/games/alien_cake_addict.rs +++ b/examples/games/alien_cake_addict.rs @@ -43,8 +43,8 @@ fn main() { display_score.in_schedule(OnEnter(GameState::GameOver)), gameover_keyboard.in_set(OnUpdate(GameState::GameOver)), teardown.in_schedule(OnExit(GameState::GameOver)), + bevy::window::close_on_esc, )) - .add_system(bevy::window::close_on_esc) .run(); } diff --git a/examples/games/contributors.rs b/examples/games/contributors.rs index 86afff1822..f18ae839f1 100644 --- a/examples/games/contributors.rs +++ b/examples/games/contributors.rs @@ -11,12 +11,13 @@ use std::{ fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup_contributor_selection) - .add_startup_system(setup) - .add_system(velocity_system) - .add_system(move_system) - .add_system(collision_system) - .add_system(select_system) + .add_startup_systems((setup_contributor_selection, setup)) + .add_systems(( + velocity_system, + move_system, + collision_system, + select_system, + )) .init_resource::() .run(); } diff --git a/examples/games/game_menu.rs b/examples/games/game_menu.rs index 972aa93ffb..64710c5e2a 100644 --- a/examples/games/game_menu.rs +++ b/examples/games/game_menu.rs @@ -58,15 +58,14 @@ mod splash { impl Plugin for SplashPlugin { fn build(&self, app: &mut App) { // As this plugin is managing the splash screen, it will focus on the state `GameState::Splash` - app + app.add_systems(( // When entering the state, spawn everything needed for this screen - .add_system(splash_setup.in_schedule(OnEnter(GameState::Splash))) + splash_setup.in_schedule(OnEnter(GameState::Splash)), // While in this state, run the `countdown` system - .add_system(countdown.in_set(OnUpdate(GameState::Splash))) + countdown.in_set(OnUpdate(GameState::Splash)), // When exiting the state, despawn everything that was spawned for this screen - .add_system( - despawn_screen::.in_schedule(OnExit(GameState::Splash)), - ); + despawn_screen::.in_schedule(OnExit(GameState::Splash)), + )); } } diff --git a/examples/input/gamepad_input_events.rs b/examples/input/gamepad_input_events.rs index d4d9725d0c..a53976b196 100644 --- a/examples/input/gamepad_input_events.rs +++ b/examples/input/gamepad_input_events.rs @@ -10,8 +10,7 @@ use bevy::{ fn main() { App::new() .add_plugins(DefaultPlugins) - .add_system(gamepad_events) - .add_system(gamepad_ordered_events) + .add_systems((gamepad_events, gamepad_ordered_events)) .run(); } diff --git a/examples/input/text_input.rs b/examples/input/text_input.rs index 0861ddb8e0..4263bfc42e 100644 --- a/examples/input/text_input.rs +++ b/examples/input/text_input.rs @@ -9,12 +9,14 @@ use bevy::{input::keyboard::KeyboardInput, prelude::*}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup_scene) - .add_system(toggle_ime) - .add_system(listen_ime_events) - .add_system(listen_received_character_events) - .add_system(listen_keyboard_input_events) - .add_system(bubbling_text) + .add_systems(( + setup_scene.on_startup(), + toggle_ime, + listen_ime_events, + listen_received_character_events, + listen_keyboard_input_events, + bubbling_text, + )) .run(); } diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index 398ea2969f..366c933162 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -12,10 +12,8 @@ fn main() { }), ..default() })) - .add_startup_system(setup_scene) - .add_startup_system(setup_music) - .add_system(touch_camera) - .add_system(button_handler) + .add_startup_systems((setup_scene, setup_music)) + .add_systems((touch_camera, button_handler)) .run(); } diff --git a/examples/scene/scene.rs b/examples/scene/scene.rs index a3210214ad..33a6c426ef 100644 --- a/examples/scene/scene.rs +++ b/examples/scene/scene.rs @@ -14,9 +14,7 @@ fn main() { })) .register_type::() .register_type::() - .add_startup_system(save_scene_system) - .add_startup_system(load_scene_system) - .add_startup_system(infotext_system) + .add_startup_systems((save_scene_system, load_scene_system, infotext_system)) .add_system(log_system) .run(); } diff --git a/examples/shader/array_texture.rs b/examples/shader/array_texture.rs index 4f935369a0..5066779818 100644 --- a/examples/shader/array_texture.rs +++ b/examples/shader/array_texture.rs @@ -11,8 +11,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(MaterialPlugin::::default()) - .add_startup_system(setup) - .add_system(create_array_texture) + .add_systems((setup.on_startup(), create_array_texture)) .run(); } diff --git a/examples/shader/post_processing.rs b/examples/shader/post_processing.rs index 71eb37851c..ce62816fca 100644 --- a/examples/shader/post_processing.rs +++ b/examples/shader/post_processing.rs @@ -23,8 +23,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(Material2dPlugin::::default()) - .add_startup_system(setup) - .add_system(main_camera_cube_rotator_system) + .add_systems((setup.on_startup(), main_camera_cube_rotator_system)) .run(); } diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index b6d0f29d11..902860aaa7 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -85,8 +85,10 @@ impl Plugin for CustomMaterialPlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_system(queue_custom.in_set(RenderSet::Queue)) - .add_system(prepare_instance_buffers.in_set(RenderSet::Prepare)); + .add_systems(( + queue_custom.in_set(RenderSet::Queue), + prepare_instance_buffers.in_set(RenderSet::Prepare), + )); } } diff --git a/examples/shader/shader_material_screenspace_texture.rs b/examples/shader/shader_material_screenspace_texture.rs index f0fec91b19..71b73ae998 100644 --- a/examples/shader/shader_material_screenspace_texture.rs +++ b/examples/shader/shader_material_screenspace_texture.rs @@ -10,8 +10,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(MaterialPlugin::::default()) - .add_startup_system(setup) - .add_system(rotate_camera) + .add_systems((setup.on_startup(), rotate_camera)) .run(); } diff --git a/examples/shader/shader_prepass.rs b/examples/shader/shader_prepass.rs index 688152c5a9..9770d9f46a 100644 --- a/examples/shader/shader_prepass.rs +++ b/examples/shader/shader_prepass.rs @@ -28,9 +28,7 @@ fn main() { prepass_enabled: false, ..default() }) - .add_startup_system(setup) - .add_system(rotate) - .add_system(toggle_prepass_view) + .add_systems((setup.on_startup(), rotate, toggle_prepass_view)) .run(); } diff --git a/examples/stress_tests/bevymark.rs b/examples/stress_tests/bevymark.rs index 4c446ff231..876ef68fa5 100644 --- a/examples/stress_tests/bevymark.rs +++ b/examples/stress_tests/bevymark.rs @@ -43,12 +43,14 @@ fn main() { count: 0, color: Color::WHITE, }) - .add_startup_system(setup) - .add_system(mouse_handler) - .add_system(movement_system) - .add_system(collision_system) - .add_system(counter_system) - .add_system(scheduled_spawner.in_schedule(CoreSchedule::FixedUpdate)) + .add_systems(( + setup.on_startup(), + mouse_handler, + movement_system, + collision_system, + counter_system, + scheduled_spawner.in_schedule(CoreSchedule::FixedUpdate), + )) .insert_resource(FixedTime::new_from_secs(0.2)) .run(); } diff --git a/examples/stress_tests/many_animated_sprites.rs b/examples/stress_tests/many_animated_sprites.rs index d1911b9723..a5d6b4e938 100644 --- a/examples/stress_tests/many_animated_sprites.rs +++ b/examples/stress_tests/many_animated_sprites.rs @@ -32,10 +32,12 @@ fn main() { }), ..default() })) - .add_startup_system(setup) - .add_system(animate_sprite) - .add_system(print_sprite_count) - .add_system(move_camera.after(print_sprite_count)) + .add_systems(( + setup.on_startup(), + animate_sprite, + print_sprite_count, + move_camera.after(print_sprite_count), + )) .run(); } diff --git a/examples/stress_tests/many_buttons.rs b/examples/stress_tests/many_buttons.rs index 79c761277e..d39cfb3881 100644 --- a/examples/stress_tests/many_buttons.rs +++ b/examples/stress_tests/many_buttons.rs @@ -21,8 +21,7 @@ fn main() { .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) .init_resource::() - .add_startup_system(setup) - .add_system(button_system) + .add_systems((setup.on_startup(), button_system)) .run(); } diff --git a/examples/stress_tests/many_cubes.rs b/examples/stress_tests/many_cubes.rs index ab9336c63f..4a87d6f4dc 100644 --- a/examples/stress_tests/many_cubes.rs +++ b/examples/stress_tests/many_cubes.rs @@ -30,9 +30,7 @@ fn main() { })) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) - .add_startup_system(setup) - .add_system(move_camera) - .add_system(print_mesh_count) + .add_systems((setup.on_startup(), move_camera, print_mesh_count)) .run(); } diff --git a/examples/stress_tests/many_foxes.rs b/examples/stress_tests/many_foxes.rs index 5980bb3587..2b42d8602f 100644 --- a/examples/stress_tests/many_foxes.rs +++ b/examples/stress_tests/many_foxes.rs @@ -41,10 +41,12 @@ fn main() { color: Color::WHITE, brightness: 1.0, }) - .add_startup_system(setup) - .add_system(setup_scene_once_loaded) - .add_system(keyboard_animation_control) - .add_system(update_fox_rings.after(keyboard_animation_control)) + .add_systems(( + setup.on_startup(), + setup_scene_once_loaded, + keyboard_animation_control, + update_fox_rings.after(keyboard_animation_control), + )) .run(); } diff --git a/examples/stress_tests/many_lights.rs b/examples/stress_tests/many_lights.rs index b722100e7c..0060ab354d 100644 --- a/examples/stress_tests/many_lights.rs +++ b/examples/stress_tests/many_lights.rs @@ -26,9 +26,7 @@ fn main() { })) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) - .add_startup_system(setup) - .add_system(move_camera) - .add_system(print_light_count) + .add_systems((setup.on_startup(), move_camera, print_light_count)) .add_plugin(LogVisibleLights) .run(); } diff --git a/examples/stress_tests/many_sprites.rs b/examples/stress_tests/many_sprites.rs index 4f2a466f07..f22359d6d9 100644 --- a/examples/stress_tests/many_sprites.rs +++ b/examples/stress_tests/many_sprites.rs @@ -40,9 +40,11 @@ fn main() { }), ..default() })) - .add_startup_system(setup) - .add_system(print_sprite_count) - .add_system(move_camera.after(print_sprite_count)) + .add_systems(( + setup.on_startup(), + print_sprite_count, + move_camera.after(print_sprite_count), + )) .run(); } diff --git a/examples/stress_tests/text_pipeline.rs b/examples/stress_tests/text_pipeline.rs index a2782a97a9..f5396ca90c 100644 --- a/examples/stress_tests/text_pipeline.rs +++ b/examples/stress_tests/text_pipeline.rs @@ -20,8 +20,7 @@ fn main() { })) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) - .add_startup_system(spawn) - .add_system(update_text_bounds) + .add_systems((spawn.on_startup(), update_text_bounds)) .run(); } diff --git a/examples/tools/gamepad_viewer.rs b/examples/tools/gamepad_viewer.rs index ae912192cb..7d549dca49 100644 --- a/examples/tools/gamepad_viewer.rs +++ b/examples/tools/gamepad_viewer.rs @@ -127,14 +127,13 @@ fn main() { .init_resource::() .init_resource::() .init_resource::() - .add_startup_system(setup) - .add_startup_system(setup_sticks) - .add_startup_system(setup_triggers) - .add_startup_system(setup_connected) - .add_system(update_buttons) - .add_system(update_button_values) - .add_system(update_axes) - .add_system(update_connected) + .add_startup_systems((setup, setup_sticks, setup_triggers, setup_connected)) + .add_systems(( + update_buttons, + update_button_values, + update_axes, + update_connected, + )) .run(); } diff --git a/examples/tools/scene_viewer/scene_viewer_plugin.rs b/examples/tools/scene_viewer/scene_viewer_plugin.rs index 33277e6e55..f4df150a18 100644 --- a/examples/tools/scene_viewer/scene_viewer_plugin.rs +++ b/examples/tools/scene_viewer/scene_viewer_plugin.rs @@ -56,14 +56,14 @@ pub struct SceneViewerPlugin; impl Plugin for SceneViewerPlugin { fn build(&self, app: &mut App) { - app.init_resource::() - .add_system(scene_load_check.in_base_set(CoreSet::PreUpdate)) - .add_system(update_lights) - .add_system(camera_tracker); + app.init_resource::().add_systems(( + scene_load_check.in_base_set(CoreSet::PreUpdate), + update_lights, + camera_tracker, + )); #[cfg(feature = "animation")] - app.add_system(start_animation) - .add_system(keyboard_animation_control); + app.add_systems((start_animation, keyboard_animation_control)); } } diff --git a/examples/transforms/3d_rotation.rs b/examples/transforms/3d_rotation.rs index 97e217b215..d30addb24c 100644 --- a/examples/transforms/3d_rotation.rs +++ b/examples/transforms/3d_rotation.rs @@ -13,8 +13,7 @@ struct Rotatable { fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(rotate_cube) + .add_systems((setup.on_startup(), rotate_cube)) .run(); } diff --git a/examples/transforms/scale.rs b/examples/transforms/scale.rs index b716cd33cc..8c24759895 100644 --- a/examples/transforms/scale.rs +++ b/examples/transforms/scale.rs @@ -29,9 +29,7 @@ impl Scaling { fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(change_scale_direction) - .add_system(scale_cube) + .add_systems((setup.on_startup(), change_scale_direction, scale_cube)) .run(); } diff --git a/examples/transforms/transform.rs b/examples/transforms/transform.rs index 429f154e90..4154a20771 100644 --- a/examples/transforms/transform.rs +++ b/examples/transforms/transform.rs @@ -24,10 +24,12 @@ struct Center { fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(move_cube) - .add_system(rotate_cube) - .add_system(scale_down_sphere_proportional_to_cube_travel_distance) + .add_systems(( + setup.on_startup(), + move_cube, + rotate_cube, + scale_down_sphere_proportional_to_cube_travel_distance, + )) .run(); } diff --git a/examples/transforms/translation.rs b/examples/transforms/translation.rs index ce1c29fe73..8c6f408c1d 100644 --- a/examples/transforms/translation.rs +++ b/examples/transforms/translation.rs @@ -25,8 +25,7 @@ impl Movable { fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(move_cube) + .add_systems((setup.on_startup(), move_cube)) .run(); } diff --git a/examples/ui/button.rs b/examples/ui/button.rs index aa364baf8b..ba5f569e79 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -8,8 +8,7 @@ fn main() { .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) + .add_systems((setup.on_startup(), button_system)) .run(); } diff --git a/examples/ui/font_atlas_debug.rs b/examples/ui/font_atlas_debug.rs index 8dd72311c4..a732baca7c 100644 --- a/examples/ui/font_atlas_debug.rs +++ b/examples/ui/font_atlas_debug.rs @@ -8,9 +8,7 @@ fn main() { .init_resource::() .insert_resource(ClearColor(Color::BLACK)) .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(text_update_system) - .add_system(atlas_render_system) + .add_systems((setup.on_startup(), text_update_system, atlas_render_system)) .run(); } diff --git a/examples/ui/relative_cursor_position.rs b/examples/ui/relative_cursor_position.rs index 54e9c527c6..25a8abbbf8 100644 --- a/examples/ui/relative_cursor_position.rs +++ b/examples/ui/relative_cursor_position.rs @@ -7,8 +7,7 @@ fn main() { .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(relative_cursor_position_system) + .add_systems((setup.on_startup(), relative_cursor_position_system)) .run(); } diff --git a/examples/ui/text.rs b/examples/ui/text.rs index a25da20132..3e73c847af 100644 --- a/examples/ui/text.rs +++ b/examples/ui/text.rs @@ -12,9 +12,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(FrameTimeDiagnosticsPlugin::default()) - .add_startup_system(setup) - .add_system(text_update_system) - .add_system(text_color_system) + .add_systems((setup.on_startup(), text_update_system, text_color_system)) .run(); } diff --git a/examples/ui/text_debug.rs b/examples/ui/text_debug.rs index 012affe952..286558bcbb 100644 --- a/examples/ui/text_debug.rs +++ b/examples/ui/text_debug.rs @@ -16,8 +16,7 @@ fn main() { ..default() })) .add_plugin(FrameTimeDiagnosticsPlugin) - .add_startup_system(infotext_system) - .add_system(change_text_system) + .add_systems((infotext_system.on_startup(), change_text_system)) .run(); } diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index f06b5c161b..954708c633 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -15,8 +15,7 @@ fn main() { .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(mouse_scroll) + .add_systems((setup.on_startup(), mouse_scroll)) .run(); } diff --git a/examples/ui/ui_scaling.rs b/examples/ui/ui_scaling.rs index 5452f70abf..877857a352 100644 --- a/examples/ui/ui_scaling.rs +++ b/examples/ui/ui_scaling.rs @@ -16,9 +16,11 @@ fn main() { target_scale: 1.0, target_time: Timer::new(Duration::from_millis(SCALE_TIME), TimerMode::Once), }) - .add_startup_system(setup) - .add_system(change_scaling) - .add_system(apply_scaling.after(change_scaling)) + .add_systems(( + setup.on_startup(), + change_scaling, + apply_scaling.after(change_scaling), + )) .run(); } diff --git a/examples/window/clear_color.rs b/examples/window/clear_color.rs index ecd4c1558d..4e2c8f2fb7 100644 --- a/examples/window/clear_color.rs +++ b/examples/window/clear_color.rs @@ -8,8 +8,7 @@ fn main() { App::new() .insert_resource(ClearColor(Color::rgb(0.5, 0.5, 0.9))) .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(change_clear_color) + .add_systems((setup.on_startup(), change_clear_color)) .run(); } diff --git a/examples/window/low_power.rs b/examples/window/low_power.rs index 0e8ba1e8e3..f57183cc8b 100644 --- a/examples/window/low_power.rs +++ b/examples/window/low_power.rs @@ -33,11 +33,13 @@ fn main() { }), ..default() })) - .add_startup_system(test_setup::setup) - .add_system(test_setup::cycle_modes) - .add_system(test_setup::rotate_cube) - .add_system(test_setup::update_text) - .add_system(update_winit) + .add_systems(( + test_setup::setup.on_startup(), + test_setup::cycle_modes, + test_setup::rotate_cube, + test_setup::update_text, + update_winit, + )) .run(); } diff --git a/examples/window/scale_factor_override.rs b/examples/window/scale_factor_override.rs index 5af87dd416..ae983ad462 100644 --- a/examples/window/scale_factor_override.rs +++ b/examples/window/scale_factor_override.rs @@ -12,10 +12,12 @@ fn main() { }), ..default() })) - .add_startup_system(setup) - .add_system(display_override) - .add_system(toggle_override) - .add_system(change_scale_factor) + .add_systems(( + setup.on_startup(), + display_override, + toggle_override, + change_scale_factor, + )) .run(); } diff --git a/examples/window/window_resizing.rs b/examples/window/window_resizing.rs index 277397a010..436d6b2eba 100644 --- a/examples/window/window_resizing.rs +++ b/examples/window/window_resizing.rs @@ -9,10 +9,8 @@ fn main() { small: Vec2::new(640.0, 360.0), }) .add_plugins(DefaultPlugins) - .add_startup_system(setup_camera) - .add_startup_system(setup_ui) - .add_system(on_resize_system) - .add_system(toggle_resolution) + .add_startup_systems((setup_camera, setup_ui)) + .add_systems((on_resize_system, toggle_resolution)) .run(); } diff --git a/examples/window/window_settings.rs b/examples/window/window_settings.rs index 47867e4f7d..8cb298cd32 100644 --- a/examples/window/window_settings.rs +++ b/examples/window/window_settings.rs @@ -24,11 +24,13 @@ fn main() { })) .add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin) - .add_system(change_title) - .add_system(toggle_cursor) - .add_system(toggle_vsync) - .add_system(cycle_cursor_icon) - .add_system(switch_level) + .add_systems(( + change_title, + toggle_cursor, + toggle_vsync, + cycle_cursor_icon, + switch_level, + )) .run(); } diff --git a/tests/how_to_test_systems.rs b/tests/how_to_test_systems.rs index 5e43c8ac2f..4103269668 100644 --- a/tests/how_to_test_systems.rs +++ b/tests/how_to_test_systems.rs @@ -57,8 +57,7 @@ fn did_hurt_enemy() { app.add_event::(); // Add our two systems - app.add_system(hurt_enemies.before(despawn_dead_enemies)); - app.add_system(despawn_dead_enemies); + app.add_systems((hurt_enemies, despawn_dead_enemies).chain()); // Setup test entities let enemy_id = app @@ -89,8 +88,7 @@ fn did_despawn_enemy() { app.add_event::(); // Add our two systems - app.add_system(hurt_enemies.before(despawn_dead_enemies)); - app.add_system(despawn_dead_enemies); + app.add_systems((hurt_enemies, despawn_dead_enemies).chain()); // Setup test entities let enemy_id = app diff --git a/tests/window/minimising.rs b/tests/window/minimising.rs index 217228dbd8..eb7ee33b09 100644 --- a/tests/window/minimising.rs +++ b/tests/window/minimising.rs @@ -14,8 +14,7 @@ fn main() { ..default() })) .add_system(minimise_automatically) - .add_startup_system(setup_3d) - .add_startup_system(setup_2d) + .add_startup_systems((setup_3d, setup_2d)) .run(); } diff --git a/tests/window/resizing.rs b/tests/window/resizing.rs index 20ba42133a..5e6bdcb4b5 100644 --- a/tests/window/resizing.rs +++ b/tests/window/resizing.rs @@ -35,11 +35,12 @@ fn main() { }), ) .insert_resource(Phase::ContractingY) - .add_system(change_window_size) - .add_system(sync_dimensions) - .add_system(bevy::window::close_on_esc) - .add_startup_system(setup_3d) - .add_startup_system(setup_2d) + .add_systems(( + change_window_size, + sync_dimensions, + bevy::window::close_on_esc, + )) + .add_startup_systems((setup_3d, setup_2d)) .run(); } From ee0e6f485575952fb51d38f807f60907272e1d44 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 10 Mar 2023 22:04:41 -0500 Subject: [PATCH 19/68] Process 1 in Chrome tracing starts expanded (#8024) --- docs/profiling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/profiling.md b/docs/profiling.md index b1dd8ba20e..90d722819c 100644 --- a/docs/profiling.md +++ b/docs/profiling.md @@ -12,7 +12,7 @@ You also need to select a `tracing` backend using the following cargo features: `cargo run --release --features bevy/trace_chrome` -After running your app a `json` file in the "chrome tracing format" will be produced. You can open this file in your browser using . It will look something like this (make sure you expand `Process 1`): +After running your app a `json` file in the "chrome tracing format" will be produced. You can open this file in your browser using . It will look something like this: ![image](https://user-images.githubusercontent.com/2694663/141657409-6f4a3ad3-59b6-4378-95ba-66c0dafecd8e.png) From 9e9ae5b8302091e404bf1b4ba270a55332ea9ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gilbert=20R=C3=B6hrbein?= Date: Sat, 11 Mar 2023 13:12:31 +0100 Subject: [PATCH 20/68] Fix Color::as_rgba_linear for Color::Lcha (#8040) Co-authored-by: James Liu --- crates/bevy_render/src/color/mod.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/src/color/mod.rs b/crates/bevy_render/src/color/mod.rs index 770a7bee9c..a5fa2c2b08 100644 --- a/crates/bevy_render/src/color/mod.rs +++ b/crates/bevy_render/src/color/mod.rs @@ -525,7 +525,7 @@ impl Color { let [red, green, blue] = LchRepresentation::lch_to_nonlinear_srgb(*lightness, *chroma, *hue); - Color::Rgba { + Color::RgbaLinear { red: red.nonlinear_to_linear_srgb(), green: green.nonlinear_to_linear_srgb(), blue: blue.nonlinear_to_linear_srgb(), @@ -1858,4 +1858,22 @@ mod tests { assert_eq!(starting_color * transformation, mutated_color,); } + + // regression test for https://github.com/bevyengine/bevy/pull/8040 + #[test] + fn convert_to_rgba_linear() { + let rgba = Color::rgba(0., 0., 0., 0.); + let rgba_l = Color::rgba_linear(0., 0., 0., 0.); + let hsla = Color::hsla(0., 0., 0., 0.); + let lcha = Color::Lcha { + lightness: 0.0, + chroma: 0.0, + hue: 0.0, + alpha: 0.0, + }; + assert_eq!(rgba_l, rgba_l.as_rgba_linear()); + let Color::RgbaLinear { .. } = rgba.as_rgba_linear() else { panic!("from Rgba") }; + let Color::RgbaLinear { .. } = hsla.as_rgba_linear() else { panic!("from Hsla") }; + let Color::RgbaLinear { .. } = lcha.as_rgba_linear() else { panic!("from Lcha") }; + } } From 7d5f89cca16dcaea145a20d50e39ec4260d1852c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gilbert=20R=C3=B6hrbein?= Date: Sat, 11 Mar 2023 19:50:16 +0100 Subject: [PATCH 21/68] Color::Lcha constructors (#8041) Co-authored-by: James Liu --- crates/bevy_render/src/color/mod.rs | 44 +++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/crates/bevy_render/src/color/mod.rs b/crates/bevy_render/src/color/mod.rs index a5fa2c2b08..d66a455cba 100644 --- a/crates/bevy_render/src/color/mod.rs +++ b/crates/bevy_render/src/color/mod.rs @@ -253,6 +253,43 @@ impl Color { } } + /// New `Color` with LCH representation in sRGB colorspace. + /// + /// # Arguments + /// + /// * `lightness` - Lightness channel. [0.0, 1.5] + /// * `chroma` - Chroma channel. [0.0, 1.5] + /// * `hue` - Hue channel. [0.0, 360.0] + /// + /// See also [`Color::lcha`]. + pub const fn lch(lightness: f32, chroma: f32, hue: f32) -> Color { + Color::Lcha { + lightness, + chroma, + hue, + alpha: 1.0, + } + } + + /// New `Color` with LCH representation in sRGB colorspace. + /// + /// # Arguments + /// + /// * `lightness` - Lightness channel. [0.0, 1.5] + /// * `chroma` - Chroma channel. [0.0, 1.5] + /// * `hue` - Hue channel. [0.0, 360.0] + /// * `alpha` - Alpha channel. [0.0, 1.0] + /// + /// See also [`Color::lch`]. + pub const fn lcha(lightness: f32, chroma: f32, hue: f32, alpha: f32) -> Color { + Color::Lcha { + lightness, + chroma, + hue, + alpha, + } + } + /// New `Color` from sRGB colorspace. /// /// # Examples @@ -1865,12 +1902,7 @@ mod tests { let rgba = Color::rgba(0., 0., 0., 0.); let rgba_l = Color::rgba_linear(0., 0., 0., 0.); let hsla = Color::hsla(0., 0., 0., 0.); - let lcha = Color::Lcha { - lightness: 0.0, - chroma: 0.0, - hue: 0.0, - alpha: 0.0, - }; + let lcha = Color::lcha(0., 0., 0., 0.); assert_eq!(rgba_l, rgba_l.as_rgba_linear()); let Color::RgbaLinear { .. } = rgba.as_rgba_linear() else { panic!("from Rgba") }; let Color::RgbaLinear { .. } = hsla.as_rgba_linear() else { panic!("from Hsla") }; From d8b7fed4fed88a1e1cb32682ef00f223125d0f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sun, 12 Mar 2023 16:24:52 +0100 Subject: [PATCH 22/68] don't panic on unknown ambiguity (#7950) --- crates/bevy_ecs/src/schedule/schedule.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index d466e8aef1..2f75213510 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -1138,18 +1138,18 @@ impl ScheduleGraph { ambiguous_with_flattened.add_edge(lhs, rhs, ()); } (NodeId::Set(_), NodeId::System(_)) => { - for &lhs_ in set_systems.get(&lhs).unwrap() { + for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) { ambiguous_with_flattened.add_edge(lhs_, rhs, ()); } } (NodeId::System(_), NodeId::Set(_)) => { - for &rhs_ in set_systems.get(&rhs).unwrap() { + for &rhs_ in set_systems.get(&rhs).unwrap_or(&Vec::new()) { ambiguous_with_flattened.add_edge(lhs, rhs_, ()); } } (NodeId::Set(_), NodeId::Set(_)) => { - for &lhs_ in set_systems.get(&lhs).unwrap() { - for &rhs_ in set_systems.get(&rhs).unwrap() { + for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) { + for &rhs_ in set_systems.get(&rhs).unwrap_or(&vec![]) { ambiguous_with_flattened.add_edge(lhs_, rhs_, ()); } } From 87dda354dde0b89ac118e8ec438af3b4cef87274 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 13 Mar 2023 15:17:00 +0000 Subject: [PATCH 23/68] Remove `Val::Undefined` (#7485) --- crates/bevy_ui/src/flex/convert.rs | 203 ++++++++++++++++++++++++-- crates/bevy_ui/src/geometry.rs | 153 ++++++------------- crates/bevy_ui/src/ui_node.rs | 60 +++----- crates/bevy_ui/src/widget/text.rs | 4 +- examples/2d/bloom_2d.rs | 7 +- examples/3d/atmospheric_fog.rs | 7 +- examples/3d/blend_modes.rs | 19 +-- examples/3d/bloom_3d.rs | 7 +- examples/3d/fog.rs | 7 +- examples/3d/pbr.rs | 21 +-- examples/3d/tonemapping.rs | 7 +- examples/games/alien_cake_addict.rs | 7 +- examples/games/breakout.rs | 7 +- examples/games/game_menu.rs | 8 +- examples/input/text_input.rs | 7 +- examples/mobile/src/lib.rs | 10 +- examples/shader/shader_prepass.rs | 7 +- examples/stress_tests/bevymark.rs | 7 +- examples/stress_tests/many_buttons.rs | 8 +- examples/ui/font_atlas_debug.rs | 12 +- examples/ui/text.rs | 7 +- examples/ui/text_debug.rs | 33 ++--- examples/ui/ui.rs | 77 ++++------ examples/ui/ui_scaling.rs | 7 +- examples/ui/window_fallthrough.rs | 7 +- examples/ui/z_index.rs | 35 ++--- examples/window/low_power.rs | 7 +- 27 files changed, 361 insertions(+), 380 deletions(-) diff --git a/crates/bevy_ui/src/flex/convert.rs b/crates/bevy_ui/src/flex/convert.rs index 7ebc0fd0cd..60e22f0e0b 100644 --- a/crates/bevy_ui/src/flex/convert.rs +++ b/crates/bevy_ui/src/flex/convert.rs @@ -11,13 +11,12 @@ impl Val { Val::Auto => Val::Auto, Val::Percent(value) => Val::Percent(value), Val::Px(value) => Val::Px((scale_factor * value as f64) as f32), - Val::Undefined => Val::Undefined, } } fn to_inset(self) -> LengthPercentageAuto { match self { - Val::Auto | Val::Undefined => taffy::style::LengthPercentageAuto::Auto, + Val::Auto => taffy::style::LengthPercentageAuto::Auto, Val::Percent(value) => taffy::style::LengthPercentageAuto::Percent(value / 100.0), Val::Px(value) => taffy::style::LengthPercentageAuto::Points(value), } @@ -67,7 +66,7 @@ impl> From for taffy::prelude::Size { impl From for taffy::style::Dimension { fn from(value: Val) -> Self { match value { - Val::Auto | Val::Undefined => taffy::style::Dimension::Auto, + Val::Auto => taffy::style::Dimension::Auto, Val::Percent(value) => taffy::style::Dimension::Percent(value / 100.0), Val::Px(value) => taffy::style::Dimension::Points(value), } @@ -77,7 +76,7 @@ impl From for taffy::style::Dimension { impl From for taffy::style::LengthPercentage { fn from(value: Val) -> Self { match value { - Val::Auto | Val::Undefined => taffy::style::LengthPercentage::Points(0.0), + Val::Auto => taffy::style::LengthPercentage::Points(0.0), Val::Percent(value) => taffy::style::LengthPercentage::Percent(value / 100.0), Val::Px(value) => taffy::style::LengthPercentage::Points(value), } @@ -90,7 +89,6 @@ impl From for taffy::style::LengthPercentageAuto { Val::Auto => taffy::style::LengthPercentageAuto::Auto, Val::Percent(value) => taffy::style::LengthPercentageAuto::Percent(value / 100.0), Val::Px(value) => taffy::style::LengthPercentageAuto::Points(value), - Val::Undefined => taffy::style::LengthPercentageAuto::Points(0.), } } } @@ -106,10 +104,10 @@ pub fn from_style(scale_factor: f64, style: &Style) -> taffy::style::Style { align_content: Some(style.align_content.into()), justify_content: Some(style.justify_content.into()), inset: taffy::prelude::Rect { - left: style.position.left.scaled(scale_factor).to_inset(), - right: style.position.right.scaled(scale_factor).to_inset(), - top: style.position.top.scaled(scale_factor).to_inset(), - bottom: style.position.bottom.scaled(scale_factor).to_inset(), + left: style.left.scaled(scale_factor).to_inset(), + right: style.right.scaled(scale_factor).to_inset(), + top: style.top.scaled(scale_factor).to_inset(), + bottom: style.bottom.scaled(scale_factor).to_inset(), }, margin: style.margin.scaled(scale_factor).into(), padding: style.padding.scaled(scale_factor).into(), @@ -224,3 +222,190 @@ impl From for taffy::style::FlexWrap { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_convert_from() { + let bevy_style = crate::Style { + display: Display::Flex, + position_type: PositionType::Absolute, + left: Val::Px(0.), + right: Val::Percent(0.), + top: Val::Auto, + bottom: Val::Auto, + direction: crate::Direction::Inherit, + flex_direction: FlexDirection::ColumnReverse, + flex_wrap: FlexWrap::WrapReverse, + align_items: AlignItems::Baseline, + align_self: AlignSelf::Start, + align_content: AlignContent::SpaceAround, + justify_content: JustifyContent::SpaceEvenly, + margin: UiRect { + left: Val::Percent(0.), + right: Val::Px(0.), + top: Val::Auto, + bottom: Val::Auto, + }, + padding: UiRect { + left: Val::Percent(0.), + right: Val::Px(0.), + top: Val::Percent(0.), + bottom: Val::Percent(0.), + }, + border: UiRect { + left: Val::Px(0.), + right: Val::Px(0.), + top: Val::Auto, + bottom: Val::Px(0.), + }, + flex_grow: 1., + flex_shrink: 0., + flex_basis: Val::Px(0.), + size: Size { + width: Val::Px(0.), + height: Val::Auto, + }, + min_size: Size { + width: Val::Px(0.), + height: Val::Percent(0.), + }, + max_size: Size { + width: Val::Auto, + height: Val::Px(0.), + }, + aspect_ratio: None, + overflow: crate::Overflow::Hidden, + gap: Size { + width: Val::Px(0.), + height: Val::Percent(0.), + }, + }; + let taffy_style = from_style(1.0, &bevy_style); + assert_eq!(taffy_style.display, taffy::style::Display::Flex); + assert_eq!(taffy_style.position, taffy::style::Position::Absolute); + assert!(matches!( + taffy_style.inset.left, + taffy::style::LengthPercentageAuto::Points(_) + )); + assert!(matches!( + taffy_style.inset.right, + taffy::style::LengthPercentageAuto::Percent(_) + )); + assert!(matches!( + taffy_style.inset.top, + taffy::style::LengthPercentageAuto::Auto + )); + assert!(matches!( + taffy_style.inset.bottom, + taffy::style::LengthPercentageAuto::Auto + )); + assert_eq!( + taffy_style.flex_direction, + taffy::style::FlexDirection::ColumnReverse + ); + assert_eq!(taffy_style.flex_wrap, taffy::style::FlexWrap::WrapReverse); + assert_eq!( + taffy_style.align_items, + Some(taffy::style::AlignItems::Baseline) + ); + assert_eq!(taffy_style.align_self, Some(taffy::style::AlignSelf::Start)); + assert_eq!( + taffy_style.align_content, + Some(taffy::style::AlignContent::SpaceAround) + ); + assert_eq!( + taffy_style.justify_content, + Some(taffy::style::JustifyContent::SpaceEvenly) + ); + assert!(matches!( + taffy_style.margin.left, + taffy::style::LengthPercentageAuto::Percent(_) + )); + assert!(matches!( + taffy_style.margin.right, + taffy::style::LengthPercentageAuto::Points(_) + )); + assert!(matches!( + taffy_style.margin.top, + taffy::style::LengthPercentageAuto::Auto + )); + assert!(matches!( + taffy_style.margin.bottom, + taffy::style::LengthPercentageAuto::Auto + )); + assert!(matches!( + taffy_style.padding.left, + taffy::style::LengthPercentage::Percent(_) + )); + assert!(matches!( + taffy_style.padding.right, + taffy::style::LengthPercentage::Points(_) + )); + assert!(matches!( + taffy_style.padding.top, + taffy::style::LengthPercentage::Percent(_) + )); + assert!(matches!( + taffy_style.padding.bottom, + taffy::style::LengthPercentage::Percent(_) + )); + assert!(matches!( + taffy_style.border.left, + taffy::style::LengthPercentage::Points(_) + )); + assert!(matches!( + taffy_style.border.right, + taffy::style::LengthPercentage::Points(_) + )); + assert!(matches!( + taffy_style.border.top, + taffy::style::LengthPercentage::Points(_) + )); + assert!(matches!( + taffy_style.border.bottom, + taffy::style::LengthPercentage::Points(_) + )); + assert_eq!(taffy_style.flex_grow, 1.); + assert_eq!(taffy_style.flex_shrink, 0.); + assert!(matches!( + taffy_style.flex_basis, + taffy::style::Dimension::Points(_) + )); + assert!(matches!( + taffy_style.size.width, + taffy::style::Dimension::Points(_) + )); + assert!(matches!( + taffy_style.size.height, + taffy::style::Dimension::Auto + )); + assert!(matches!( + taffy_style.min_size.width, + taffy::style::Dimension::Points(_) + )); + assert!(matches!( + taffy_style.min_size.height, + taffy::style::Dimension::Percent(_) + )); + assert!(matches!( + taffy_style.max_size.width, + taffy::style::Dimension::Auto + )); + assert!(matches!( + taffy_style.max_size.height, + taffy::style::Dimension::Points(_) + )); + assert_eq!(taffy_style.aspect_ratio, None); + assert_eq!( + taffy_style.gap.width, + taffy::style::LengthPercentage::Points(0.) + ); + assert_eq!( + taffy_style.gap.height, + taffy::style::LengthPercentage::Percent(0.) + ); + } +} diff --git a/crates/bevy_ui/src/geometry.rs b/crates/bevy_ui/src/geometry.rs index fd90c711c9..ac70c93834 100644 --- a/crates/bevy_ui/src/geometry.rs +++ b/crates/bevy_ui/src/geometry.rs @@ -2,83 +2,10 @@ use crate::Val; use bevy_reflect::Reflect; use std::ops::{Div, DivAssign, Mul, MulAssign}; -/// A type which is commonly used to define positions, margins, paddings and borders. +/// A type which is commonly used to define margins, paddings and borders. /// /// # Examples -/// -/// ## Position -/// -/// A position is used to determine where to place a UI element. -/// -/// ``` -/// # use bevy_ui::{UiRect, Val}; -/// # use bevy_utils::default; -/// # -/// let position = UiRect { -/// left: Val::Px(100.0), -/// top: Val::Px(50.0), -/// ..default() -/// }; -/// ``` -/// -/// If you define opposite sides of the position, the size of the UI element will automatically be calculated -/// if not explicitly specified. This means that if you have a [`Size`] that uses [`Val::Undefined`](crate::Val::Undefined) -/// as a width and height, the size would be determined by the window size and the values specified in the position. -/// -/// ``` -/// # use bevy_ui::{UiRect, Val}; -/// # -/// let position = UiRect { -/// left: Val::Px(100.0), -/// right: Val::Px(200.0), -/// top: Val::Px(300.0), -/// bottom: Val::Px(400.0), -/// }; -/// ``` -/// -/// To determine the width of the UI element you have to take the width of the window and subtract it by the -/// left and right values of the position. To determine the height of the UI element you have to take the height -/// of the window and subtract it by the top and bottom values of the position. If we had a window with a width -/// and height of 1000px, the UI element declared above would have a width of 700px and a height of 300px. -/// -/// ``` -/// // Size of the window -/// let window_width = 1000.0; -/// let window_height = 1000.0; -/// -/// // Values of the position -/// let left = 100.0; -/// let right = 200.0; -/// let top = 300.0; -/// let bottom = 400.0; -/// -/// // Calculation to get the size of the UI element -/// let ui_element_width = window_width - left - right; -/// let ui_element_height = window_height - top - bottom; -/// -/// assert_eq!(ui_element_width, 700.0); -/// assert_eq!(ui_element_height, 300.0); -/// ``` -/// -/// If you define a [`Size`] and also all four sides of the position, the top and left values of the position -/// are used to determine where to place the UI element. The size will not be calculated using the bottom and -/// right values of the position because the size of the UI element is already explicitly specified. -/// -/// ``` -/// # use bevy_ui::{UiRect, Size, Val, Style}; -/// # use bevy_utils::default; -/// # -/// let style = Style { -/// position: UiRect { // Defining all four sides -/// left: Val::Px(100.0), -/// right: Val::Px(200.0), -/// top: Val::Px(300.0), -/// bottom: Val::Px(400.0), -/// }, -/// size: Size::new(Val::Percent(100.0), Val::Percent(50.0)), // but also explicitly specifying a size -/// ..default() -/// }; -/// ``` + /// /// ## Margin /// @@ -134,10 +61,10 @@ pub struct UiRect { impl UiRect { pub const DEFAULT: Self = Self { - left: Val::DEFAULT, - right: Val::DEFAULT, - top: Val::DEFAULT, - bottom: Val::DEFAULT, + left: Val::Px(0.), + right: Val::Px(0.), + top: Val::Px(0.), + bottom: Val::Px(0.), }; /// Creates a new [`UiRect`] from the values specified. @@ -191,7 +118,8 @@ impl UiRect { } } - /// Creates a new [`UiRect`] where `left` and `right` take the given value. + /// Creates a new [`UiRect`] where `left` and `right` take the given value, + /// and `top` and `bottom` set to zero `Val::Px(0.)`. /// /// # Example /// @@ -202,8 +130,8 @@ impl UiRect { /// /// assert_eq!(ui_rect.left, Val::Px(10.0)); /// assert_eq!(ui_rect.right, Val::Px(10.0)); - /// assert_eq!(ui_rect.top, Val::Undefined); - /// assert_eq!(ui_rect.bottom, Val::Undefined); + /// assert_eq!(ui_rect.top, Val::Px(0.)); + /// assert_eq!(ui_rect.bottom, Val::Px(0.)); /// ``` pub fn horizontal(value: Val) -> Self { UiRect { @@ -213,7 +141,8 @@ impl UiRect { } } - /// Creates a new [`UiRect`] where `top` and `bottom` take the given value. + /// Creates a new [`UiRect`] where `top` and `bottom` take the given value, + /// and `left` and `right` are set to `Val::Px(0.)`. /// /// # Example /// @@ -222,8 +151,8 @@ impl UiRect { /// # /// let ui_rect = UiRect::vertical(Val::Px(10.0)); /// - /// assert_eq!(ui_rect.left, Val::Undefined); - /// assert_eq!(ui_rect.right, Val::Undefined); + /// assert_eq!(ui_rect.left, Val::Px(0.)); + /// assert_eq!(ui_rect.right, Val::Px(0.)); /// assert_eq!(ui_rect.top, Val::Px(10.0)); /// assert_eq!(ui_rect.bottom, Val::Px(10.0)); /// ``` @@ -235,7 +164,8 @@ impl UiRect { } } - /// Creates a new [`UiRect`] where `left` takes the given value. + /// Creates a new [`UiRect`] where `left` takes the given value, and + /// the other fields are set to `Val::Px(0.)`. /// /// # Example /// @@ -245,9 +175,9 @@ impl UiRect { /// let ui_rect = UiRect::left(Val::Px(10.0)); /// /// assert_eq!(ui_rect.left, Val::Px(10.0)); - /// assert_eq!(ui_rect.right, Val::Undefined); - /// assert_eq!(ui_rect.top, Val::Undefined); - /// assert_eq!(ui_rect.bottom, Val::Undefined); + /// assert_eq!(ui_rect.right, Val::Px(0.)); + /// assert_eq!(ui_rect.top, Val::Px(0.)); + /// assert_eq!(ui_rect.bottom, Val::Px(0.)); /// ``` pub fn left(value: Val) -> Self { UiRect { @@ -256,7 +186,8 @@ impl UiRect { } } - /// Creates a new [`UiRect`] where `right` takes the given value. + /// Creates a new [`UiRect`] where `right` takes the given value, + /// and the other fields are set to `Val::Px(0.)`. /// /// # Example /// @@ -265,10 +196,10 @@ impl UiRect { /// # /// let ui_rect = UiRect::right(Val::Px(10.0)); /// - /// assert_eq!(ui_rect.left, Val::Undefined); + /// assert_eq!(ui_rect.left, Val::Px(0.)); /// assert_eq!(ui_rect.right, Val::Px(10.0)); - /// assert_eq!(ui_rect.top, Val::Undefined); - /// assert_eq!(ui_rect.bottom, Val::Undefined); + /// assert_eq!(ui_rect.top, Val::Px(0.)); + /// assert_eq!(ui_rect.bottom, Val::Px(0.)); /// ``` pub fn right(value: Val) -> Self { UiRect { @@ -277,7 +208,8 @@ impl UiRect { } } - /// Creates a new [`UiRect`] where `top` takes the given value. + /// Creates a new [`UiRect`] where `top` takes the given value, + /// and the other fields are set to `Val::Px(0.)`. /// /// # Example /// @@ -286,10 +218,10 @@ impl UiRect { /// # /// let ui_rect = UiRect::top(Val::Px(10.0)); /// - /// assert_eq!(ui_rect.left, Val::Undefined); - /// assert_eq!(ui_rect.right, Val::Undefined); + /// assert_eq!(ui_rect.left, Val::Px(0.)); + /// assert_eq!(ui_rect.right, Val::Px(0.)); /// assert_eq!(ui_rect.top, Val::Px(10.0)); - /// assert_eq!(ui_rect.bottom, Val::Undefined); + /// assert_eq!(ui_rect.bottom, Val::Px(0.)); /// ``` pub fn top(value: Val) -> Self { UiRect { @@ -298,7 +230,8 @@ impl UiRect { } } - /// Creates a new [`UiRect`] where `bottom` takes the given value. + /// Creates a new [`UiRect`] where `bottom` takes the given value, + /// and the other fields are set to `Val::Px(0.)`. /// /// # Example /// @@ -307,9 +240,9 @@ impl UiRect { /// # /// let ui_rect = UiRect::bottom(Val::Px(10.0)); /// - /// assert_eq!(ui_rect.left, Val::Undefined); - /// assert_eq!(ui_rect.right, Val::Undefined); - /// assert_eq!(ui_rect.top, Val::Undefined); + /// assert_eq!(ui_rect.left, Val::Px(0.)); + /// assert_eq!(ui_rect.right, Val::Px(0.)); + /// assert_eq!(ui_rect.top, Val::Px(0.)); /// assert_eq!(ui_rect.bottom, Val::Px(10.0)); /// ``` pub fn bottom(value: Val) -> Self { @@ -376,7 +309,9 @@ impl Size { } } - /// Creates a new [`Size`] where `width` takes the given value and its `height` is `Val::Auto`. + /// Creates a new [`Size`] where `width` takes the given value, + /// and `height` is set to [`Val::Auto`]. + /// /// # Example /// @@ -395,7 +330,8 @@ impl Size { } } - /// Creates a new [`Size`] where `height` takes the given value and its `width` is `Val::Auto`. + /// Creates a new [`Size`] where `height` takes the given value, + /// and `width` is set to [`Val::Auto`]. /// /// # Example /// @@ -416,9 +352,6 @@ impl Size { /// Creates a Size where both values are [`Val::Auto`]. pub const AUTO: Self = Self::all(Val::Auto); - - /// Creates a Size where both values are [`Val::Undefined`]. - pub const UNDEFINED: Self = Self::all(Val::Undefined); } impl Default for Size { @@ -481,10 +414,10 @@ mod tests { assert_eq!( UiRect::default(), UiRect { - left: Val::Undefined, - right: Val::Undefined, - top: Val::Undefined, - bottom: Val::Undefined + left: Val::Px(0.), + right: Val::Px(0.), + top: Val::Px(0.), + bottom: Val::Px(0.) } ); assert_eq!(UiRect::default(), UiRect::DEFAULT); diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 52290aa3a5..2a28c5a1e5 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -61,8 +61,6 @@ impl Default for Node { #[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] #[reflect(PartialEq, Serialize, Deserialize)] pub enum Val { - /// No value defined - Undefined, /// Automatically determine this value Auto, /// Set this value in pixels @@ -72,7 +70,7 @@ pub enum Val { } impl Val { - pub const DEFAULT: Self = Self::Undefined; + pub const DEFAULT: Self = Self::Auto; } impl Default for Val { @@ -86,7 +84,6 @@ impl Mul for Val { fn mul(self, rhs: f32) -> Self::Output { match self { - Val::Undefined => Val::Undefined, Val::Auto => Val::Auto, Val::Px(value) => Val::Px(value * rhs), Val::Percent(value) => Val::Percent(value * rhs), @@ -97,7 +94,7 @@ impl Mul for Val { impl MulAssign for Val { fn mul_assign(&mut self, rhs: f32) { match self { - Val::Undefined | Val::Auto => {} + Val::Auto => {} Val::Px(value) | Val::Percent(value) => *value *= rhs, } } @@ -108,7 +105,6 @@ impl Div for Val { fn div(self, rhs: f32) -> Self::Output { match self { - Val::Undefined => Val::Undefined, Val::Auto => Val::Auto, Val::Px(value) => Val::Px(value / rhs), Val::Percent(value) => Val::Percent(value / rhs), @@ -119,7 +115,7 @@ impl Div for Val { impl DivAssign for Val { fn div_assign(&mut self, rhs: f32) { match self { - Val::Undefined | Val::Auto => {} + Val::Auto => {} Val::Px(value) | Val::Percent(value) => *value /= rhs, } } @@ -139,7 +135,7 @@ impl Val { /// When adding non-numeric [`Val`]s, it returns the value unchanged. pub fn try_add(&self, rhs: Val) -> Result { match (self, rhs) { - (Val::Undefined, Val::Undefined) | (Val::Auto, Val::Auto) => Ok(*self), + (Val::Auto, Val::Auto) => Ok(*self), (Val::Px(value), Val::Px(rhs_value)) => Ok(Val::Px(value + rhs_value)), (Val::Percent(value), Val::Percent(rhs_value)) => Ok(Val::Percent(value + rhs_value)), _ => Err(ValArithmeticError::NonIdenticalVariants), @@ -157,7 +153,7 @@ impl Val { /// When adding non-numeric [`Val`]s, it returns the value unchanged. pub fn try_sub(&self, rhs: Val) -> Result { match (self, rhs) { - (Val::Undefined, Val::Undefined) | (Val::Auto, Val::Auto) => Ok(*self), + (Val::Auto, Val::Auto) => Ok(*self), (Val::Px(value), Val::Px(rhs_value)) => Ok(Val::Px(value - rhs_value)), (Val::Percent(value), Val::Percent(rhs_value)) => Ok(Val::Percent(value - rhs_value)), _ => Err(ValArithmeticError::NonIdenticalVariants), @@ -236,6 +232,10 @@ pub struct Style { pub display: Display, /// Whether to arrange this node relative to other nodes, or positioned absolutely pub position_type: PositionType, + pub left: Val, + pub right: Val, + pub top: Val, + pub bottom: Val, /// Which direction the content of this node should go pub direction: Direction, /// Whether to use column or row layout @@ -252,8 +252,6 @@ pub struct Style { pub align_content: AlignContent, /// How items align according to the main axis pub justify_content: JustifyContent, - /// The position of the node as described by its Rect - pub position: UiRect, /// The amount of space around a node outside its border. /// /// If a percentage value is used, the percentage is calculated based on the width of the parent node. @@ -329,7 +327,7 @@ pub struct Style { pub overflow: Overflow, /// The size of the gutters between the rows and columns of the flexbox layout /// - /// Values of `Size::UNDEFINED` and `Size::AUTO` are treated as zero. + /// A value of `Size::AUTO` is treated as zero. pub gap: Size, } @@ -337,6 +335,10 @@ impl Style { pub const DEFAULT: Self = Self { display: Display::DEFAULT, position_type: PositionType::DEFAULT, + left: Val::Auto, + right: Val::Auto, + top: Val::Auto, + bottom: Val::Auto, direction: Direction::DEFAULT, flex_direction: FlexDirection::DEFAULT, flex_wrap: FlexWrap::DEFAULT, @@ -344,7 +346,6 @@ impl Style { align_self: AlignSelf::DEFAULT, align_content: AlignContent::DEFAULT, justify_content: JustifyContent::DEFAULT, - position: UiRect::DEFAULT, margin: UiRect::DEFAULT, padding: UiRect::DEFAULT, border: UiRect::DEFAULT, @@ -356,7 +357,7 @@ impl Style { max_size: Size::AUTO, aspect_ratio: None, overflow: Overflow::DEFAULT, - gap: Size::UNDEFINED, + gap: Size::AUTO, }; } @@ -769,12 +770,10 @@ mod tests { #[test] fn val_try_add() { - let undefined_sum = Val::Undefined.try_add(Val::Undefined).unwrap(); let auto_sum = Val::Auto.try_add(Val::Auto).unwrap(); let px_sum = Val::Px(20.).try_add(Val::Px(22.)).unwrap(); let percent_sum = Val::Percent(50.).try_add(Val::Percent(50.)).unwrap(); - assert_eq!(undefined_sum, Val::Undefined); assert_eq!(auto_sum, Val::Auto); assert_eq!(px_sum, Val::Px(42.)); assert_eq!(percent_sum, Val::Percent(100.)); @@ -791,12 +790,10 @@ mod tests { #[test] fn val_try_sub() { - let undefined_sum = Val::Undefined.try_sub(Val::Undefined).unwrap(); let auto_sum = Val::Auto.try_sub(Val::Auto).unwrap(); let px_sum = Val::Px(72.).try_sub(Val::Px(30.)).unwrap(); let percent_sum = Val::Percent(100.).try_sub(Val::Percent(50.)).unwrap(); - assert_eq!(undefined_sum, Val::Undefined); assert_eq!(auto_sum, Val::Auto); assert_eq!(px_sum, Val::Px(42.)); assert_eq!(percent_sum, Val::Percent(50.)); @@ -804,9 +801,8 @@ mod tests { #[test] fn different_variant_val_try_add() { - let different_variant_sum_1 = Val::Undefined.try_add(Val::Auto); - let different_variant_sum_2 = Val::Px(50.).try_add(Val::Percent(50.)); - let different_variant_sum_3 = Val::Percent(50.).try_add(Val::Undefined); + let different_variant_sum_1 = Val::Px(50.).try_add(Val::Percent(50.)); + let different_variant_sum_2 = Val::Percent(50.).try_add(Val::Auto); assert_eq!( different_variant_sum_1, @@ -816,17 +812,12 @@ mod tests { different_variant_sum_2, Err(ValArithmeticError::NonIdenticalVariants) ); - assert_eq!( - different_variant_sum_3, - Err(ValArithmeticError::NonIdenticalVariants) - ); } #[test] fn different_variant_val_try_sub() { - let different_variant_diff_1 = Val::Undefined.try_sub(Val::Auto); - let different_variant_diff_2 = Val::Px(50.).try_sub(Val::Percent(50.)); - let different_variant_diff_3 = Val::Percent(50.).try_sub(Val::Undefined); + let different_variant_diff_1 = Val::Px(50.).try_sub(Val::Percent(50.)); + let different_variant_diff_2 = Val::Percent(50.).try_sub(Val::Auto); assert_eq!( different_variant_diff_1, @@ -836,10 +827,6 @@ mod tests { different_variant_diff_2, Err(ValArithmeticError::NonIdenticalVariants) ); - assert_eq!( - different_variant_diff_3, - Err(ValArithmeticError::NonIdenticalVariants) - ); } #[test] @@ -861,10 +848,8 @@ mod tests { #[test] fn val_invalid_evaluation() { let size = 250.; - let evaluate_undefined = Val::Undefined.evaluate(size); let evaluate_auto = Val::Auto.evaluate(size); - assert_eq!(evaluate_undefined, Err(ValArithmeticError::NonEvaluateable)); assert_eq!(evaluate_auto, Err(ValArithmeticError::NonEvaluateable)); } @@ -906,10 +891,8 @@ mod tests { fn val_try_add_non_numeric_with_size() { let size = 250.; - let undefined_sum = Val::Undefined.try_add_with_size(Val::Undefined, size); let percent_sum = Val::Auto.try_add_with_size(Val::Auto, size); - assert_eq!(undefined_sum, Err(ValArithmeticError::NonEvaluateable)); assert_eq!(percent_sum, Err(ValArithmeticError::NonEvaluateable)); } @@ -924,4 +907,9 @@ mod tests { "the given variant of Val is not evaluateable (non-numeric)" ); } + + #[test] + fn default_val_equals_const_default_val() { + assert_eq!(Val::default(), Val::DEFAULT); + } } diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index a4464701fd..e3404575e8 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -25,9 +25,7 @@ pub fn text_constraint(min_size: Val, size: Val, max_size: Val, scale_factor: f6 match (min_size, size, max_size) { (_, _, Val::Px(max)) => scale_value(max, scale_factor), (Val::Px(min), _, _) => scale_value(min, scale_factor), - (Val::Undefined, Val::Px(size), Val::Undefined) | (Val::Auto, Val::Px(size), Val::Auto) => { - scale_value(size, scale_factor) - } + (Val::Auto, Val::Px(size), Val::Auto) => scale_value(size, scale_factor), _ => f32::MAX, } } diff --git a/examples/2d/bloom_2d.rs b/examples/2d/bloom_2d.rs index 825f4803bf..3a7ae32a4d 100644 --- a/examples/2d/bloom_2d.rs +++ b/examples/2d/bloom_2d.rs @@ -78,11 +78,8 @@ fn setup( ) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - bottom: Val::Px(10.0), - left: Val::Px(10.0), - ..default() - }, + bottom: Val::Px(10.0), + left: Val::Px(10.0), ..default() }), ); diff --git a/examples/3d/atmospheric_fog.rs b/examples/3d/atmospheric_fog.rs index 1969303ff7..0bf6d52aba 100644 --- a/examples/3d/atmospheric_fog.rs +++ b/examples/3d/atmospheric_fog.rs @@ -101,11 +101,8 @@ fn setup_instructions(mut commands: Commands, asset_server: Res) { ) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - bottom: Val::Px(10.0), - left: Val::Px(10.0), - ..default() - }, + bottom: Val::Px(10.0), + left: Val::Px(10.0), ..default() }),)); } diff --git a/examples/3d/blend_modes.rs b/examples/3d/blend_modes.rs index 5fe22dfc4a..458b5a6582 100644 --- a/examples/3d/blend_modes.rs +++ b/examples/3d/blend_modes.rs @@ -203,23 +203,16 @@ fn setup( ) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(10.0), - left: Val::Px(10.0), - ..default() - }, + top: Val::Px(10.0), + left: Val::Px(10.0), ..default() }), ); commands.spawn(( TextBundle::from_section("", text_style).with_style(Style { - position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(10.0), - right: Val::Px(10.0), - ..default() - }, + top: Val::Px(10.0), + right: Val::Px(10.0), ..default() }), ExampleDisplay, @@ -365,8 +358,8 @@ fn example_control_system( .world_to_viewport(camera_global_transform, world_position) .unwrap(); - style.position.bottom = Val::Px(viewport_position.y); - style.position.left = Val::Px(viewport_position.x); + style.bottom = Val::Px(viewport_position.y); + style.left = Val::Px(viewport_position.x); } let mut display = display.single_mut(); diff --git a/examples/3d/bloom_3d.rs b/examples/3d/bloom_3d.rs index 94fcdf90fd..92688846d7 100644 --- a/examples/3d/bloom_3d.rs +++ b/examples/3d/bloom_3d.rs @@ -106,11 +106,8 @@ fn setup_scene( ) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - bottom: Val::Px(10.0), - left: Val::Px(10.0), - ..default() - }, + bottom: Val::Px(10.0), + left: Val::Px(10.0), ..default() }), ); diff --git a/examples/3d/fog.rs b/examples/3d/fog.rs index 745b828982..3e0c748619 100644 --- a/examples/3d/fog.rs +++ b/examples/3d/fog.rs @@ -145,11 +145,8 @@ fn setup_instructions(mut commands: Commands, asset_server: Res) { ) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(10.0), - left: Val::Px(10.0), - ..default() - }, + top: Val::Px(10.0), + left: Val::Px(10.0), ..default() }),)); } diff --git a/examples/3d/pbr.rs b/examples/3d/pbr.rs index ccf34966d9..a6deda7270 100644 --- a/examples/3d/pbr.rs +++ b/examples/3d/pbr.rs @@ -84,11 +84,8 @@ fn setup( ) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(20.0), - left: Val::Px(100.0), - ..default() - }, + top: Val::Px(20.0), + left: Val::Px(100.0), ..default() }), ); @@ -104,11 +101,8 @@ fn setup( ), style: Style { position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(130.0), - right: Val::Px(0.0), - ..default() - }, + top: Val::Px(130.0), + right: Val::Px(0.0), ..default() }, transform: Transform { @@ -129,11 +123,8 @@ fn setup( ) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - bottom: Val::Px(20.0), - right: Val::Px(20.0), - ..default() - }, + bottom: Val::Px(20.0), + right: Val::Px(20.0), ..default() }), EnvironmentMapLabel, diff --git a/examples/3d/tonemapping.rs b/examples/3d/tonemapping.rs index 996f6cbe14..81cd602b04 100644 --- a/examples/3d/tonemapping.rs +++ b/examples/3d/tonemapping.rs @@ -76,11 +76,8 @@ fn setup( ) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(10.0), - left: Val::Px(10.0), - ..default() - }, + top: Val::Px(10.0), + left: Val::Px(10.0), ..default() }), ); diff --git a/examples/games/alien_cake_addict.rs b/examples/games/alien_cake_addict.rs index 138186acaa..7fbc2961c8 100644 --- a/examples/games/alien_cake_addict.rs +++ b/examples/games/alien_cake_addict.rs @@ -173,11 +173,8 @@ fn setup(mut commands: Commands, asset_server: Res, mut game: ResMu ) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(5.0), - left: Val::Px(5.0), - ..default() - }, + top: Val::Px(5.0), + left: Val::Px(5.0), ..default() }), ); diff --git a/examples/games/breakout.rs b/examples/games/breakout.rs index bc308797fb..d74822872f 100644 --- a/examples/games/breakout.rs +++ b/examples/games/breakout.rs @@ -240,11 +240,8 @@ fn setup( ]) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - top: SCOREBOARD_TEXT_PADDING, - left: SCOREBOARD_TEXT_PADDING, - ..default() - }, + top: SCOREBOARD_TEXT_PADDING, + left: SCOREBOARD_TEXT_PADDING, ..default() }), ); diff --git a/examples/games/game_menu.rs b/examples/games/game_menu.rs index 64710c5e2a..8dc39e701d 100644 --- a/examples/games/game_menu.rs +++ b/examples/games/game_menu.rs @@ -401,12 +401,8 @@ mod menu { // This takes the icons out of the flexbox flow, to be positioned exactly position_type: PositionType::Absolute, // The icon will be close to the left border of the button - position: UiRect { - left: Val::Px(10.0), - right: Val::Auto, - top: Val::Auto, - bottom: Val::Auto, - }, + left: Val::Px(10.0), + right: Val::Auto, ..default() }; let button_text_style = TextStyle { diff --git a/examples/input/text_input.rs b/examples/input/text_input.rs index 4263bfc42e..e083f76d25 100644 --- a/examples/input/text_input.rs +++ b/examples/input/text_input.rs @@ -78,11 +78,8 @@ fn setup_scene(mut commands: Commands, asset_server: Res) { ]) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(10.0), - left: Val::Px(10.0), - ..default() - }, + top: Val::Px(10.0), + left: Val::Px(10.0), ..default() }), ); diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index 366c933162..66ae669676 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -100,12 +100,10 @@ fn setup_scene( justify_content: JustifyContent::Center, align_items: AlignItems::Center, position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(50.0), - right: Val::Px(50.0), - top: Val::Auto, - bottom: Val::Px(50.0), - }, + left: Val::Px(50.0), + right: Val::Px(50.0), + top: Val::Auto, + bottom: Val::Px(50.0), ..default() }, ..default() diff --git a/examples/shader/shader_prepass.rs b/examples/shader/shader_prepass.rs index 9770d9f46a..0271977128 100644 --- a/examples/shader/shader_prepass.rs +++ b/examples/shader/shader_prepass.rs @@ -140,11 +140,8 @@ fn setup( ]) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(10.0), - left: Val::Px(10.0), - ..default() - }, + top: Val::Px(10.0), + left: Val::Px(10.0), ..default() }), ); diff --git a/examples/stress_tests/bevymark.rs b/examples/stress_tests/bevymark.rs index 876ef68fa5..e4131e029f 100644 --- a/examples/stress_tests/bevymark.rs +++ b/examples/stress_tests/bevymark.rs @@ -121,11 +121,8 @@ fn setup(mut commands: Commands, asset_server: Res) { ]) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(5.0), - left: Val::Px(5.0), - ..default() - }, + top: Val::Px(5.0), + left: Val::Px(5.0), ..default() }), StatsText, diff --git a/examples/stress_tests/many_buttons.rs b/examples/stress_tests/many_buttons.rs index d39cfb3881..826aa5395c 100644 --- a/examples/stress_tests/many_buttons.rs +++ b/examples/stress_tests/many_buttons.rs @@ -89,12 +89,8 @@ fn spawn_button( ButtonBundle { style: Style { size: Size::new(Val::Percent(width), Val::Percent(width)), - - position: UiRect { - bottom: Val::Percent(100.0 / total * i as f32), - left: Val::Percent(100.0 / total * j as f32), - ..default() - }, + bottom: Val::Percent(100.0 / total * i as f32), + left: Val::Percent(100.0 / total * j as f32), align_items: AlignItems::Center, position_type: PositionType::Absolute, ..default() diff --git a/examples/ui/font_atlas_debug.rs b/examples/ui/font_atlas_debug.rs index a732baca7c..0ca5596d24 100644 --- a/examples/ui/font_atlas_debug.rs +++ b/examples/ui/font_atlas_debug.rs @@ -49,11 +49,8 @@ fn atlas_render_system( image: texture_atlas.texture.clone().into(), style: Style { position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(0.0), - left: Val::Px(512.0 * x_offset), - ..default() - }, + top: Val::Px(0.0), + left: Val::Px(512.0 * x_offset), ..default() }, ..default() @@ -85,10 +82,7 @@ fn setup(mut commands: Commands, asset_server: Res, mut state: ResM background_color: Color::NONE.into(), style: Style { position_type: PositionType::Absolute, - position: UiRect { - bottom: Val::Px(0.0), - ..default() - }, + bottom: Val::Px(0.0), ..default() }, ..default() diff --git a/examples/ui/text.rs b/examples/ui/text.rs index 3e73c847af..607a204ec6 100644 --- a/examples/ui/text.rs +++ b/examples/ui/text.rs @@ -43,11 +43,8 @@ fn setup(mut commands: Commands, asset_server: Res) { // Set the style of the TextBundle itself. .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - bottom: Val::Px(5.0), - right: Val::Px(15.0), - ..default() - }, + bottom: Val::Px(5.0), + right: Val::Px(15.0), ..default() }), ColorText, diff --git a/examples/ui/text_debug.rs b/examples/ui/text_debug.rs index 286558bcbb..759ee88a66 100644 --- a/examples/ui/text_debug.rs +++ b/examples/ui/text_debug.rs @@ -37,11 +37,8 @@ fn infotext_system(mut commands: Commands, asset_server: Res) { ) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(5.0), - left: Val::Px(15.0), - ..default() - }, + top: Val::Px(5.0), + left: Val::Px(15.0), ..default() }), ); @@ -56,15 +53,9 @@ fn infotext_system(mut commands: Commands, asset_server: Res) { .with_text_alignment(TextAlignment::Center) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(5.0), - right: Val::Px(15.0), - ..default() - }, - max_size: Size { - width: Val::Px(400.), - height: Val::Undefined, - }, + top: Val::Px(5.0), + right: Val::Px(15.0), + max_size: Size::width(Val::Px(400.)), ..default() }) ); @@ -115,11 +106,8 @@ fn infotext_system(mut commands: Commands, asset_server: Res) { ]) .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - bottom: Val::Px(5.0), - right: Val::Px(15.0), - ..default() - }, + bottom: Val::Px(5.0), + right: Val::Px(15.0), ..default() }), TextChanges, @@ -136,11 +124,8 @@ fn infotext_system(mut commands: Commands, asset_server: Res) { .with_style(Style { align_self: AlignSelf::FlexEnd, position_type: PositionType::Absolute, - position: UiRect { - bottom: Val::Px(5.0), - left: Val::Px(15.0), - ..default() - }, + bottom: Val::Px(5.0), + left: Val::Px(15.0), size: Size { width: Val::Px(200.0), ..default() diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index 954708c633..827a75aa19 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -27,7 +27,7 @@ fn setup(mut commands: Commands, asset_server: Res) { commands .spawn(NodeBundle { style: Style { - size: Size::width(Val::Percent(100.0)), + size: Size::all(Val::Percent(100.)), justify_content: JustifyContent::SpaceBetween, ..default() }, @@ -38,8 +38,8 @@ fn setup(mut commands: Commands, asset_server: Res) { parent .spawn(NodeBundle { style: Style { - size: Size::width(Val::Px(200.0)), - border: UiRect::all(Val::Px(2.0)), + size: Size::width(Val::Px(200.)), + border: UiRect::all(Val::Px(2.)), ..default() }, background_color: Color::rgb(0.65, 0.65, 0.65).into(), @@ -50,7 +50,7 @@ fn setup(mut commands: Commands, asset_server: Res) { parent .spawn(NodeBundle { style: Style { - size: Size::width(Val::Percent(100.0)), + size: Size::width(Val::Percent(100.)), ..default() }, background_color: Color::rgb(0.15, 0.15, 0.15).into(), @@ -68,7 +68,7 @@ fn setup(mut commands: Commands, asset_server: Res) { }, ) .with_style(Style { - margin: UiRect::all(Val::Px(5.0)), + margin: UiRect::all(Val::Px(5.)), ..default() }), // Because this is a distinct label widget and @@ -85,7 +85,7 @@ fn setup(mut commands: Commands, asset_server: Res) { flex_direction: FlexDirection::Column, justify_content: JustifyContent::Center, align_items: AlignItems::Center, - size: Size::width(Val::Px(200.0)), + size: Size::width(Val::Px(200.)), ..default() }, background_color: Color::rgb(0.15, 0.15, 0.15).into(), @@ -101,11 +101,7 @@ fn setup(mut commands: Commands, asset_server: Res) { font_size: 25., color: Color::WHITE, }, - ) - .with_style(Style { - size: Size::height(Val::Px(25.)), - ..default() - }), + ), Label, )); // List with hidden overflow @@ -114,7 +110,7 @@ fn setup(mut commands: Commands, asset_server: Res) { style: Style { flex_direction: FlexDirection::Column, align_self: AlignSelf::Stretch, - size: Size::height(Val::Percent(50.0)), + size: Size::height(Val::Percent(50.)), overflow: Overflow::Hidden, ..default() }, @@ -129,7 +125,6 @@ fn setup(mut commands: Commands, asset_server: Res) { style: Style { flex_direction: FlexDirection::Column, flex_grow: 1.0, - max_size: Size::UNDEFINED, align_items: AlignItems::Center, ..default() }, @@ -153,7 +148,7 @@ fn setup(mut commands: Commands, asset_server: Res) { ) .with_style(Style { flex_shrink: 0., - size: Size::new(Val::Undefined, Val::Px(20.)), + size: Size::height(Val::Px(20.)), ..default() }), Label, @@ -166,26 +161,23 @@ fn setup(mut commands: Commands, asset_server: Res) { parent .spawn(NodeBundle { style: Style { - size: Size::new(Val::Px(200.0), Val::Px(200.0)), + size: Size::all(Val::Px(200.)), position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(210.0), - bottom: Val::Px(10.0), - ..default() - }, - border: UiRect::all(Val::Px(20.0)), + left: Val::Px(210.), + bottom: Val::Px(10.), + border: UiRect::all(Val::Px(20.)), ..default() }, - background_color: Color::rgb(0.4, 0.4, 1.0).into(), + background_color: Color::rgb(0.4, 0.4, 1.).into(), ..default() }) .with_children(|parent| { parent.spawn(NodeBundle { style: Style { - size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), + size: Size::all(Val::Percent(100.)), ..default() }, - background_color: Color::rgb(0.8, 0.8, 1.0).into(), + background_color: Color::rgb(0.8, 0.8, 1.).into(), ..default() }); }); @@ -193,7 +185,7 @@ fn setup(mut commands: Commands, asset_server: Res) { parent .spawn(NodeBundle { style: Style { - size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), + size: Size::all(Val::Percent(100.)), position_type: PositionType::Absolute, align_items: AlignItems::Center, justify_content: JustifyContent::Center, @@ -205,10 +197,10 @@ fn setup(mut commands: Commands, asset_server: Res) { parent .spawn(NodeBundle { style: Style { - size: Size::new(Val::Px(100.0), Val::Px(100.0)), + size: Size::all(Val::Px(100.)), ..default() }, - background_color: Color::rgb(1.0, 0.0, 0.0).into(), + background_color: Color::rgb(1.0, 0.0, 0.).into(), ..default() }) .with_children(|parent| { @@ -217,11 +209,8 @@ fn setup(mut commands: Commands, asset_server: Res) { // Take the size of the parent node. size: Size::all(Val::Percent(100.)), position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(20.0), - bottom: Val::Px(20.0), - ..default() - }, + left: Val::Px(20.), + bottom: Val::Px(20.), ..default() }, background_color: Color::rgb(1.0, 0.3, 0.3).into(), @@ -231,11 +220,8 @@ fn setup(mut commands: Commands, asset_server: Res) { style: Style { size: Size::all(Val::Percent(100.)), position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(40.0), - bottom: Val::Px(40.0), - ..default() - }, + left: Val::Px(40.), + bottom: Val::Px(40.), ..default() }, background_color: Color::rgb(1.0, 0.5, 0.5).into(), @@ -245,11 +231,8 @@ fn setup(mut commands: Commands, asset_server: Res) { style: Style { size: Size::all(Val::Percent(100.)), position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(60.0), - bottom: Val::Px(60.0), - ..default() - }, + left: Val::Px(60.), + bottom: Val::Px(60.), ..default() }, background_color: Color::rgb(1.0, 0.7, 0.7).into(), @@ -260,11 +243,8 @@ fn setup(mut commands: Commands, asset_server: Res) { style: Style { size: Size::all(Val::Percent(100.)), position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(80.0), - bottom: Val::Px(80.0), - ..default() - }, + left: Val::Px(80.), + bottom: Val::Px(80.), ..default() }, background_color: Color::rgba(1.0, 0.9, 0.9, 0.4).into(), @@ -286,6 +266,7 @@ fn setup(mut commands: Commands, asset_server: Res) { }) .with_children(|parent| { // bevy logo (image) + parent .spawn(ImageBundle { style: Style { @@ -327,7 +308,7 @@ fn mouse_scroll( }; scrolling_list.position += dy; scrolling_list.position = scrolling_list.position.clamp(-max_scroll, 0.); - style.position.top = Val::Px(scrolling_list.position); + style.top = Val::Px(scrolling_list.position); } } } diff --git a/examples/ui/ui_scaling.rs b/examples/ui/ui_scaling.rs index 877857a352..d9a0adbbb1 100644 --- a/examples/ui/ui_scaling.rs +++ b/examples/ui/ui_scaling.rs @@ -38,11 +38,8 @@ fn setup(mut commands: Commands, asset_server: ResMut) { style: Style { size: Size::new(Val::Percent(50.0), Val::Percent(50.0)), position_type: PositionType::Absolute, - position: UiRect { - left: Val::Percent(25.), - top: Val::Percent(25.), - ..default() - }, + left: Val::Percent(25.), + top: Val::Percent(25.), justify_content: JustifyContent::SpaceAround, align_items: AlignItems::Center, ..default() diff --git a/examples/ui/window_fallthrough.rs b/examples/ui/window_fallthrough.rs index 123b40b41d..21c00cfd33 100644 --- a/examples/ui/window_fallthrough.rs +++ b/examples/ui/window_fallthrough.rs @@ -40,11 +40,8 @@ fn setup(mut commands: Commands, asset_server: Res) { // Set the style of the TextBundle itself. .with_style(Style { position_type: PositionType::Absolute, - position: UiRect { - bottom: Val::Px(5.), - right: Val::Px(10.), - ..default() - }, + bottom: Val::Px(5.), + right: Val::Px(10.), ..default() }), )); diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index 08a51b0a68..05d6e9c77b 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -45,11 +45,8 @@ fn setup(mut commands: Commands) { background_color: Color::RED.into(), style: Style { position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(10.0), - bottom: Val::Px(40.0), - ..default() - }, + left: Val::Px(10.0), + bottom: Val::Px(40.0), size: Size::new(Val::Px(100.0), Val::Px(50.0)), ..default() }, @@ -63,11 +60,8 @@ fn setup(mut commands: Commands) { background_color: Color::BLUE.into(), style: Style { position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(45.0), - bottom: Val::Px(30.0), - ..default() - }, + left: Val::Px(45.0), + bottom: Val::Px(30.0), size: Size::new(Val::Px(100.0), Val::Px(50.0)), ..default() }, @@ -81,11 +75,8 @@ fn setup(mut commands: Commands) { background_color: Color::GREEN.into(), style: Style { position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(70.0), - bottom: Val::Px(20.0), - ..default() - }, + left: Val::Px(70.0), + bottom: Val::Px(20.0), size: Size::new(Val::Px(100.0), Val::Px(75.0)), ..default() }, @@ -100,11 +91,8 @@ fn setup(mut commands: Commands) { background_color: Color::PURPLE.into(), style: Style { position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(15.0), - bottom: Val::Px(10.0), - ..default() - }, + left: Val::Px(15.0), + bottom: Val::Px(10.0), size: Size::new(Val::Px(100.0), Val::Px(60.0)), ..default() }, @@ -119,11 +107,8 @@ fn setup(mut commands: Commands) { background_color: Color::YELLOW.into(), style: Style { position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(-15.0), - bottom: Val::Px(-15.0), - ..default() - }, + left: Val::Px(-15.0), + bottom: Val::Px(-15.0), size: Size::new(Val::Px(100.0), Val::Px(125.0)), ..default() }, diff --git a/examples/window/low_power.rs b/examples/window/low_power.rs index f57183cc8b..8a457cbd0b 100644 --- a/examples/window/low_power.rs +++ b/examples/window/low_power.rs @@ -201,11 +201,8 @@ pub(crate) mod test_setup { .with_style(Style { align_self: AlignSelf::FlexStart, position_type: PositionType::Absolute, - position: UiRect { - top: Val::Px(5.0), - left: Val::Px(5.0), - ..default() - }, + top: Val::Px(5.0), + left: Val::Px(5.0), ..default() }), ModeText, From 6bfc09f53e1ecedbc74c5558d9cc542954596946 Mon Sep 17 00:00:00 2001 From: SneakyBerry Date: Mon, 13 Mar 2023 19:26:47 +0400 Subject: [PATCH 24/68] Construct Box from world for ReflectComponent (#7407) Co-authored-by: SneakyBerry Co-authored-by: Alice Cecile --- crates/bevy_ecs/src/reflect.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/bevy_ecs/src/reflect.rs b/crates/bevy_ecs/src/reflect.rs index bb4dbb1cf3..927973dea2 100644 --- a/crates/bevy_ecs/src/reflect.rs +++ b/crates/bevy_ecs/src/reflect.rs @@ -44,6 +44,8 @@ pub struct ReflectComponent(ReflectComponentFns); /// world. #[derive(Clone)] pub struct ReflectComponentFns { + /// Function pointer implementing [`ReflectComponent::from_world()`]. + pub from_world: fn(&mut World) -> Box, /// Function pointer implementing [`ReflectComponent::insert()`]. pub insert: fn(&mut EntityMut, &dyn Reflect), /// Function pointer implementing [`ReflectComponent::apply()`]. @@ -79,6 +81,11 @@ impl ReflectComponentFns { } impl ReflectComponent { + /// Constructs default reflected [`Component`] from world using [`from_world()`](FromWorld::from_world). + pub fn from_world(&self, world: &mut World) -> Box { + (self.0.from_world)(world) + } + /// Insert a reflected [`Component`] into the entity like [`insert()`](crate::world::EntityMut::insert). pub fn insert(&self, entity: &mut EntityMut, component: &dyn Reflect) { (self.0.insert)(entity, component); @@ -170,6 +177,7 @@ impl ReflectComponent { impl FromType for ReflectComponent { fn from_type() -> Self { ReflectComponent(ReflectComponentFns { + from_world: |world| Box::new(C::from_world(world)), insert: |entity, reflected_component| { let mut component = entity.world_scope(|world| C::from_world(world)); component.apply(reflected_component); From 71b1b35757a01dbf59a122e2e8a7425a75c09a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Mon, 13 Mar 2023 16:31:13 +0100 Subject: [PATCH 25/68] do not set hit test unconditionally on window creation (#7996) --- crates/bevy_winit/src/winit_windows.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 33441333b9..b87678acd3 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -166,11 +166,16 @@ impl WinitWindows { } winit_window.set_cursor_visible(window.cursor.visible); - if let Err(err) = winit_window.set_cursor_hittest(window.cursor.hit_test) { - warn!( - "Could not set cursor hit test for window {:?}: {:?}", - window.title, err - ); + + // Do not set the cursor hittest on window creation if it's false, as it will always fail on some + // platforms and log an unfixable warning. + if !window.cursor.hit_test { + if let Err(err) = winit_window.set_cursor_hittest(window.cursor.hit_test) { + warn!( + "Could not set cursor hit test for window {:?}: {:?}", + window.title, err + ); + } } self.entity_to_winit.insert(entity, winit_window.id()); From 0918b30e2925b2ce628d3e89f6e120e68c61a06c Mon Sep 17 00:00:00 2001 From: Liam Gallagher Date: Tue, 14 Mar 2023 04:39:25 +1300 Subject: [PATCH 26/68] Tests for Run Conditions (#8035) --- crates/bevy_ecs/src/schedule/condition.rs | 679 +++++++++++++++++++++- 1 file changed, 663 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index ba39981b63..3628eef5bd 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -142,6 +142,33 @@ pub mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the first time the condition is run and false every time after + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// app.add_system( + /// // `run_once` will only return true the first time it's evaluated + /// my_system.run_if(run_once()), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // This is the first time the condition will be evaluated so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // This is the seconds time the condition will be evaluated so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` pub fn run_once() -> impl FnMut() -> bool { let mut has_run = false; move || { @@ -156,6 +183,32 @@ pub mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the resource exists. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// app.add_system( + /// // `resource_exsists` will only return true if the given resource exsists in the world + /// my_system.run_if(resource_exists::()), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `Counter` hasn't been added so `my_system` won't run + /// app.run(&mut world); + /// world.init_resource::(); + /// + /// // `Counter` has now been added so `my_system` can run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` pub fn resource_exists() -> impl FnMut(Option>) -> bool where T: Resource, @@ -169,6 +222,33 @@ pub mod common_conditions { /// # Panics /// /// The condition will panic if the resource does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default, PartialEq)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// app.add_system( + /// // `resource_equals` will only return true if the given resource equals the given value + /// my_system.run_if(resource_equals(Counter(0))), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `Counter` is `0` so `my_system` can run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // `Counter` is no longer `0` so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` pub fn resource_equals(value: T) -> impl FnMut(Res) -> bool where T: Resource + PartialEq, @@ -180,6 +260,37 @@ pub mod common_conditions { /// if the resource exists and is equal to `value`. /// /// The condition will return `false` if the resource does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default, PartialEq)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// app.add_system( + /// // `resource_exists_and_equals` will only return true + /// // if the given resource exsists and equals the given value + /// my_system.run_if(resource_exists_and_equals(Counter(0))), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `Counter` hasn't been added so `my_system` can't run + /// app.run(&mut world); + /// world.init_resource::(); + /// + /// // `Counter` is `0` so `my_system` can run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // `Counter` is no longer `0` so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` pub fn resource_exists_and_equals(value: T) -> impl FnMut(Option>) -> bool where T: Resource + PartialEq, @@ -192,6 +303,35 @@ pub mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the resource of the given type has been added since the condition was last checked. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// app.add_system( + /// // `resource_added` will only return true if the + /// // given resource was just added + /// my_system.run_if(resource_added::()), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// world.init_resource::(); + /// + /// // `Counter` was just added so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // `Counter` was not just added so `my_system` will not run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` pub fn resource_added() -> impl FnMut(Option>) -> bool where T: Resource, @@ -213,6 +353,42 @@ pub mod common_conditions { /// # Panics /// /// The condition will panic if the resource does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// app.add_system( + /// // `resource_changed` will only return true if the + /// // given resource was just changed (or added) + /// my_system.run_if( + /// resource_changed::() + /// // By default detecting changes will also trigger if the resource was + /// // just added, this won't work with my example so I will addd a second + /// // condition to make sure the resource wasn't just added + /// .and_then(not(resource_added::())) + /// ), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `Counter` hasn't been changed so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.resource_mut::().0 = 50; + /// + /// // `Counter` was just changed so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 51); + /// ``` pub fn resource_changed() -> impl FnMut(Res) -> bool where T: Resource, @@ -231,6 +407,45 @@ pub mod common_conditions { /// This run condition does not detect when the resource is removed. /// /// The condition will return `false` if the resource does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// app.add_system( + /// // `resource_exists_and_changed` will only return true if the + /// // given resource exsists and was just changed (or added) + /// my_system.run_if( + /// resource_exists_and_changed::() + /// // By default detecting changes will also trigger if the resource was + /// // just added, this won't work with my example so I will addd a second + /// // condition to make sure the resource wasn't just added + /// .and_then(not(resource_added::())) + /// ), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `Counter` doesn't exist so `my_system` won't run + /// app.run(&mut world); + /// world.init_resource::(); + /// + /// // `Counter` hasn't been changed so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.resource_mut::().0 = 50; + /// + /// // `Counter` was just changed so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 51); + /// ``` pub fn resource_exists_and_changed() -> impl FnMut(Option>) -> bool where T: Resource, @@ -253,6 +468,56 @@ pub mod common_conditions { /// has been removed since the run condition was last checked. /// /// The condition will return `false` if the resource does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// app.add_system( + /// // `resource_changed_or_removed` will only return true if the + /// // given resource was just changed or removed (or added) + /// my_system.run_if( + /// resource_changed_or_removed::() + /// // By default detecting changes will also trigger if the resource was + /// // just added, this won't work with my example so I will addd a second + /// // condition to make sure the resource wasn't just added + /// .and_then(not(resource_added::())) + /// ), + /// ); + /// + /// #[derive(Resource, Default)] + /// struct MyResource; + /// + /// // If `Counter` exists, increment it, otherwise insert `MyResource` + /// fn my_system(mut commands: Commands, mut counter: Option>) { + /// if let Some(mut counter) = counter { + /// counter.0 += 1; + /// } else { + /// commands.init_resource::(); + /// } + /// } + /// + /// // `Counter` hasn't been changed so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.resource_mut::().0 = 50; + /// + /// // `Counter` was just changed so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 51); + /// + /// world.remove_resource::(); + /// + /// // `Counter` was just removed so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.contains_resource::(), true); + /// ``` pub fn resource_changed_or_removed() -> impl FnMut(Option>) -> bool where T: Resource, @@ -273,6 +538,41 @@ pub mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the resource of the given type has been removed since the condition was last checked. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// app.add_system( + /// // `resource_removed` will only return true if the + /// // given resource was just removed + /// my_system.run_if(resource_removed::()), + /// ); + /// + /// #[derive(Resource, Default)] + /// struct MyResource; + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// world.init_resource::(); + /// + /// // `MyResource` hasn't just been removed so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.remove_resource::(); + /// + /// // `MyResource` was just removed so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` pub fn resource_removed() -> impl FnMut(Option>) -> bool where T: Resource, @@ -293,6 +593,43 @@ pub mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the state machine exists. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)] + /// enum GameState { + /// #[default] + /// Playing, + /// Paused, + /// } + /// + /// app.add_system( + /// // `state_exists` will only return true if the + /// // given state exsists + /// my_system.run_if(state_exists::()), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `GameState` does not yet exist `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.init_resource::>(); + /// + /// // `GameState` now exists so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` pub fn state_exists() -> impl FnMut(Option>>) -> bool { move |current_state: Option>>| current_state.is_some() } @@ -303,6 +640,50 @@ pub mod common_conditions { /// # Panics /// /// The condition will panic if the resource does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)] + /// enum GameState { + /// #[default] + /// Playing, + /// Paused, + /// } + /// + /// world.init_resource::>(); + /// + /// app.add_systems(( + /// // `in_state` will only return true if the + /// // given state equals the given value + /// play_system.run_if(in_state(GameState::Playing)), + /// pause_system.run_if(in_state(GameState::Paused)), + /// )); + /// + /// fn play_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// fn pause_system(mut counter: ResMut) { + /// counter.0 -= 1; + /// } + /// + /// // We default to `GameState::Playing` so `play_system` runs + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// *world.resource_mut::>() = State(GameState::Paused); + /// + /// // Now that we are in `GameState::Pause`, `pause_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// ``` pub fn in_state(state: S) -> impl FnMut(Res>) -> bool { move |current_state: Res>| current_state.0 == state } @@ -311,6 +692,54 @@ pub mod common_conditions { /// if the state machine exists and is currently in `state`. /// /// The condition will return `false` if the state does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)] + /// enum GameState { + /// #[default] + /// Playing, + /// Paused, + /// } + /// + /// app.add_systems(( + /// // `state_exists_and_equals` will only return true if the + /// // given state exsists and equals the given value + /// play_system.run_if(state_exists_and_equals(GameState::Playing)), + /// pause_system.run_if(state_exists_and_equals(GameState::Paused)), + /// )); + /// + /// fn play_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// fn pause_system(mut counter: ResMut) { + /// counter.0 -= 1; + /// } + /// + /// // `GameState` does not yet exists so neither system will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.init_resource::>(); + /// + /// // We default to `GameState::Playing` so `play_system` runs + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// *world.resource_mut::>() = State(GameState::Paused); + /// + /// // Now that we are in `GameState::Pause`, `pause_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// ``` pub fn state_exists_and_equals( state: S, ) -> impl FnMut(Option>>) -> bool { @@ -329,12 +758,89 @@ pub mod common_conditions { /// # Panics /// /// The condition will panic if the resource does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)] + /// enum GameState { + /// #[default] + /// Playing, + /// Paused, + /// } + /// + /// world.init_resource::>(); + /// + /// app.add_system( + /// // `state_changed` will only return true if the + /// // given states value has just been updated or + /// // the state has just been added + /// my_system.run_if(state_changed::()), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `GameState` has just been added so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // `GameState` has not been updated so `my_system` will not run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// *world.resource_mut::>() = State(GameState::Paused); + /// + /// // Now that `GameState` has been updated `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 2); + /// ``` pub fn state_changed() -> impl FnMut(Res>) -> bool { move |current_state: Res>| current_state.is_changed() } /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if there are any new events of the given type since it was last called. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// # world.init_resource::>(); + /// # app.add_system(Events::::update_system.before(my_system)); + /// + /// app.add_system( + /// my_system.run_if(on_event::()), + /// ); + /// + /// struct MyEvent; + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // No new `MyEvent` events have been push so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.resource_mut::>().send(MyEvent); + /// + /// // A `MyEvent` event has been push so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` pub fn on_event() -> impl FnMut(EventReader) -> bool { // The events need to be consumed, so that there are no false positives on subsequent // calls of the run condition. Simply checking `is_empty` would not be enough. @@ -345,33 +851,69 @@ pub mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if there are any entities with the given component type. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// app.add_system( + /// my_system.run_if(any_with_component::()), + /// ); + /// + /// #[derive(Component)] + /// struct MyComponent; + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // No entities exist yet with a `MyComponent` component so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.spawn(MyComponent); + /// + /// // An entities with `MyComponent` now exists so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` pub fn any_with_component() -> impl FnMut(Query<(), With>) -> bool { move |query: Query<(), With>| !query.is_empty() } /// Generates a [`Condition`](super::Condition) that inverses the result of passed one. /// - /// # Examples + /// # Example /// /// ``` - /// use bevy_ecs::prelude::*; - /// // Building a new schedule/app... - /// let mut sched = Schedule::default(); - /// sched.add_system( - /// // This system will never run. - /// my_system.run_if(not(always_true)) - /// ) - /// // ... - /// # ; + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); /// # let mut world = World::new(); - /// # sched.run(&mut world); + /// # world.init_resource::(); + /// app.add_system( + /// // `not` will inverse any condition you pass in. + /// // Since the condition we choose always returns true + /// // this system will never run + /// my_system.run_if(not(always)), + /// ); /// - /// // A condition that always returns true. - /// fn always_true() -> bool { - /// true + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; /// } - /// # - /// # fn my_system() { unreachable!() } + /// + /// fn always() -> bool { + /// true + /// } + /// + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); /// ``` pub fn not(condition: impl Condition) -> impl Condition<()> { condition.pipe(|In(val): In| !val) @@ -425,3 +967,108 @@ where a(input) || b(input) } } + +#[cfg(test)] +mod tests { + use super::Condition; + use crate as bevy_ecs; + use crate::schedule::common_conditions::not; + use crate::schedule::IntoSystemConfig; + use crate::system::Local; + use crate::{change_detection::ResMut, schedule::Schedule, world::World}; + use bevy_ecs_macros::Resource; + + #[derive(Resource, Default)] + struct Counter(usize); + + fn increment_counter(mut counter: ResMut) { + counter.0 += 1; + } + + fn every_other_time(mut has_ran: Local) -> bool { + *has_ran = !*has_ran; + *has_ran + } + + #[test] + fn run_condition() { + let mut world = World::new(); + world.init_resource::(); + let mut schedule = Schedule::new(); + + // Run every other cycle + schedule.add_system(increment_counter.run_if(every_other_time)); + + schedule.run(&mut world); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 1); + schedule.run(&mut world); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 2); + + // Run every other cycle oppsite to the last one + schedule.add_system(increment_counter.run_if(not(every_other_time))); + + schedule.run(&mut world); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 4); + schedule.run(&mut world); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 6); + } + + #[test] + fn run_condition_combinators() { + let mut world = World::new(); + world.init_resource::(); + let mut schedule = Schedule::new(); + + // Always run + schedule.add_system(increment_counter.run_if(every_other_time.or_else(|| true))); + // Run every other cycle + schedule.add_system(increment_counter.run_if(every_other_time.and_then(|| true))); + + schedule.run(&mut world); + assert_eq!(world.resource::().0, 2); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 3); + } + + #[test] + fn multiple_run_conditions() { + let mut world = World::new(); + world.init_resource::(); + let mut schedule = Schedule::new(); + + // Run every other cycle + schedule.add_system(increment_counter.run_if(every_other_time).run_if(|| true)); + // Never run + schedule.add_system(increment_counter.run_if(every_other_time).run_if(|| false)); + + schedule.run(&mut world); + assert_eq!(world.resource::().0, 1); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 1); + } + + #[test] + fn multiple_run_conditions_is_and_operation() { + let mut world = World::new(); + world.init_resource::(); + + let mut schedule = Schedule::new(); + + // This should never run, if multiple run conditions worked + // like an OR condition then it would always run + schedule.add_system( + increment_counter + .run_if(every_other_time) + .run_if(not(every_other_time)), + ); + + schedule.run(&mut world); + assert_eq!(world.resource::().0, 0); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 0); + } +} From ed97c621b82ccf52f1f16c0df34e5d3dd5f8cd4b Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Mon, 13 Mar 2023 12:16:14 -0400 Subject: [PATCH 27/68] Move docs for `!Sync` resources onto the correct trait (#8066) --- crates/bevy_ecs/src/system/system_param.rs | 64 +++++++++++----------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 60fc47c56e..8f54f0b28c 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -89,38 +89,6 @@ use std::{ /// This will most commonly occur when working with `SystemParam`s generically, as the requirement /// has not been proven to the compiler. /// -/// # `!Sync` Resources -/// A `!Sync` type cannot implement `Resource`. However, it is possible to wrap a `Send` but not `Sync` -/// type in [`SyncCell`] or the currently unstable [`Exclusive`] to make it `Sync`. This forces only -/// having mutable access (`&mut T` only, never `&T`), but makes it safe to reference across multiple -/// threads. -/// -/// This will fail to compile since `RefCell` is `!Sync`. -/// ```compile_fail -/// # use std::cell::RefCell; -/// # use bevy_ecs::system::Resource; -/// -/// #[derive(Resource)] -/// struct NotSync { -/// counter: RefCell, -/// } -/// ``` -/// -/// This will compile since the `RefCell` is wrapped with `SyncCell`. -/// ``` -/// # use std::cell::RefCell; -/// # use bevy_ecs::system::Resource; -/// use bevy_utils::synccell::SyncCell; -/// -/// #[derive(Resource)] -/// struct ActuallySync { -/// counter: SyncCell>, -/// } -/// ``` -/// -/// [`SyncCell`]: bevy_utils::synccell::SyncCell -/// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html -/// /// # Safety /// /// The implementor must ensure the following is true. @@ -399,6 +367,38 @@ impl_param_set!(); /// # schedule.add_systems((read_resource_system, write_resource_system).chain()); /// # schedule.run(&mut world); /// ``` +/// +/// # `!Sync` Resources +/// A `!Sync` type cannot implement `Resource`. However, it is possible to wrap a `Send` but not `Sync` +/// type in [`SyncCell`] or the currently unstable [`Exclusive`] to make it `Sync`. This forces only +/// having mutable access (`&mut T` only, never `&T`), but makes it safe to reference across multiple +/// threads. +/// +/// This will fail to compile since `RefCell` is `!Sync`. +/// ```compile_fail +/// # use std::cell::RefCell; +/// # use bevy_ecs::system::Resource; +/// +/// #[derive(Resource)] +/// struct NotSync { +/// counter: RefCell, +/// } +/// ``` +/// +/// This will compile since the `RefCell` is wrapped with `SyncCell`. +/// ``` +/// # use std::cell::RefCell; +/// # use bevy_ecs::system::Resource; +/// use bevy_utils::synccell::SyncCell; +/// +/// #[derive(Resource)] +/// struct ActuallySync { +/// counter: SyncCell>, +/// } +/// ``` +/// +/// [`SyncCell`]: bevy_utils::synccell::SyncCell +/// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html pub trait Resource: Send + Sync + 'static {} // SAFETY: Res only reads a single World resource From dcc0edf8a78ab8805b8f414f1a0e57286bc04093 Mon Sep 17 00:00:00 2001 From: James Liu Date: Mon, 13 Mar 2023 09:18:49 -0700 Subject: [PATCH 28/68] Make BundleInfo's fields not pub(crate) (#8068) --- crates/bevy_ecs/src/bundle.rs | 6 +++--- crates/bevy_ecs/src/world/entity_ref.rs | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 66fe6cdbcf..ae37fdcdcc 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -261,13 +261,13 @@ impl SparseSetIndex for BundleId { } pub struct BundleInfo { - pub(crate) id: BundleId, - pub(crate) component_ids: Vec, + id: BundleId, + component_ids: Vec, } impl BundleInfo { #[inline] - pub fn id(&self) -> BundleId { + pub const fn id(&self) -> BundleId { self.id } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 1511b7fe88..5a60d0a7b5 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -311,7 +311,7 @@ impl<'w> EntityMut<'w> { return None; } - let mut bundle_components = bundle_info.component_ids.iter().cloned(); + let mut bundle_components = bundle_info.components().iter().cloned(); let entity = self.entity; // SAFETY: bundle components are iterated in order, which guarantees that the component type // matches @@ -463,7 +463,7 @@ impl<'w> EntityMut<'w> { let old_archetype = &mut archetypes[old_location.archetype_id]; let entity = self.entity; - for component_id in bundle_info.component_ids.iter().cloned() { + for component_id in bundle_info.components().iter().cloned() { if old_archetype.contains(component_id) { removed_components.send(component_id, entity); @@ -694,9 +694,11 @@ unsafe fn remove_bundle_from_archetype( let remove_bundle_result = { let current_archetype = &mut archetypes[archetype_id]; if intersection { - current_archetype.edges().get_remove_bundle(bundle_info.id) + current_archetype + .edges() + .get_remove_bundle(bundle_info.id()) } else { - current_archetype.edges().get_take_bundle(bundle_info.id) + current_archetype.edges().get_take_bundle(bundle_info.id()) } }; let result = if let Some(result) = remove_bundle_result { @@ -710,7 +712,7 @@ unsafe fn remove_bundle_from_archetype( let current_archetype = &mut archetypes[archetype_id]; let mut removed_table_components = Vec::new(); let mut removed_sparse_set_components = Vec::new(); - for component_id in bundle_info.component_ids.iter().cloned() { + for component_id in bundle_info.components().iter().cloned() { if current_archetype.contains(component_id) { // SAFETY: bundle components were already initialized by bundles.get_info let component_info = components.get_info_unchecked(component_id); @@ -724,7 +726,7 @@ unsafe fn remove_bundle_from_archetype( // graph current_archetype .edges_mut() - .insert_take_bundle(bundle_info.id, None); + .insert_take_bundle(bundle_info.id(), None); return None; } } @@ -763,11 +765,11 @@ unsafe fn remove_bundle_from_archetype( if intersection { current_archetype .edges_mut() - .insert_remove_bundle(bundle_info.id, result); + .insert_remove_bundle(bundle_info.id(), result); } else { current_archetype .edges_mut() - .insert_take_bundle(bundle_info.id, result); + .insert_take_bundle(bundle_info.id(), result); } result } From 884b9b62af58ab72acb27b060a782e4b95e6e90f Mon Sep 17 00:00:00 2001 From: Anti-Alias Date: Mon, 13 Mar 2023 14:55:47 -0400 Subject: [PATCH 29/68] Added Globals struct to prepass shader (#8070) --- crates/bevy_pbr/src/prepass/mod.rs | 39 ++++++++++++++----- .../src/prepass/prepass_bindings.wgsl | 4 ++ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index b890de737d..2766f985d8 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -17,6 +17,7 @@ use bevy_ecs::{ use bevy_reflect::TypeUuid; use bevy_render::{ camera::ExtractedCamera, + globals::{GlobalsBuffer, GlobalsUniform}, mesh::MeshVertexBufferLayout, prelude::{Camera, Mesh}, render_asset::RenderAssets, @@ -167,7 +168,7 @@ impl FromWorld for PrepassPipeline { // View BindGroupLayoutEntry { binding: 0, - visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + visibility: ShaderStages::VERTEX_FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, @@ -175,6 +176,17 @@ impl FromWorld for PrepassPipeline { }, count: None, }, + // Globals + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: Some(GlobalsUniform::min_size()), + }, + count: None, + }, ], label: Some("prepass_view_layout"), }); @@ -573,19 +585,26 @@ pub fn queue_prepass_view_bind_group( render_device: Res, prepass_pipeline: Res>, view_uniforms: Res, + globals_buffer: Res, mut prepass_view_bind_group: ResMut, ) { - if let Some(view_binding) = view_uniforms.uniforms.binding() { - prepass_view_bind_group.bind_group = - Some(render_device.create_bind_group(&BindGroupDescriptor { - entries: &[BindGroupEntry { + let Some(view_binding) = view_uniforms.uniforms.binding() else { return }; + let Some(globals_binding) = globals_buffer.buffer.binding() else { return }; + prepass_view_bind_group.bind_group = + Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { binding: 0, resource: view_binding, - }], - label: Some("prepass_view_bind_group"), - layout: &prepass_pipeline.view_layout, - })); - } + }, + BindGroupEntry { + binding: 1, + resource: globals_binding, + }, + ], + label: Some("prepass_view_bind_group"), + layout: &prepass_pipeline.view_layout, + })); } #[allow(clippy::too_many_arguments)] diff --git a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl index cd338af0ed..528f2e596d 100644 --- a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl @@ -6,6 +6,9 @@ @group(0) @binding(0) var view: View; +@group(0) @binding(1) +var globals: Globals; + // Material bindings will be in @group(1) @group(2) @binding(0) @@ -16,3 +19,4 @@ var mesh: Mesh; var joint_matrices: SkinnedMesh; #import bevy_pbr::skinning #endif + From 7c4a0eb76828c421b29d99dff638f6e44ded657c Mon Sep 17 00:00:00 2001 From: "Bruce Reif (Buswolley)" Date: Mon, 13 Mar 2023 12:38:04 -0700 Subject: [PATCH 30/68] add Clone to common conditions (#8060) --- crates/bevy_ecs/src/schedule/condition.rs | 31 ++++++++++++---------- crates/bevy_input/src/common_conditions.rs | 28 ++++++++++++++++--- crates/bevy_time/src/common_conditions.rs | 22 +++++++++++++-- 3 files changed, 61 insertions(+), 20 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 3628eef5bd..939d04b191 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -169,7 +169,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 1); /// ``` - pub fn run_once() -> impl FnMut() -> bool { + pub fn run_once() -> impl FnMut() -> bool + Clone { let mut has_run = false; move || { if !has_run { @@ -209,7 +209,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 1); /// ``` - pub fn resource_exists() -> impl FnMut(Option>) -> bool + pub fn resource_exists() -> impl FnMut(Option>) -> bool + Clone where T: Resource, { @@ -332,7 +332,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 1); /// ``` - pub fn resource_added() -> impl FnMut(Option>) -> bool + pub fn resource_added() -> impl FnMut(Option>) -> bool + Clone where T: Resource, { @@ -389,7 +389,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 51); /// ``` - pub fn resource_changed() -> impl FnMut(Res) -> bool + pub fn resource_changed() -> impl FnMut(Res) -> bool + Clone where T: Resource, { @@ -446,7 +446,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 51); /// ``` - pub fn resource_exists_and_changed() -> impl FnMut(Option>) -> bool + pub fn resource_exists_and_changed() -> impl FnMut(Option>) -> bool + Clone where T: Resource, { @@ -518,7 +518,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.contains_resource::(), true); /// ``` - pub fn resource_changed_or_removed() -> impl FnMut(Option>) -> bool + pub fn resource_changed_or_removed() -> impl FnMut(Option>) -> bool + Clone where T: Resource, { @@ -573,7 +573,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 1); /// ``` - pub fn resource_removed() -> impl FnMut(Option>) -> bool + pub fn resource_removed() -> impl FnMut(Option>) -> bool + Clone where T: Resource, { @@ -630,7 +630,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 1); /// ``` - pub fn state_exists() -> impl FnMut(Option>>) -> bool { + pub fn state_exists() -> impl FnMut(Option>>) -> bool + Clone { move |current_state: Option>>| current_state.is_some() } @@ -684,7 +684,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 0); /// ``` - pub fn in_state(state: S) -> impl FnMut(Res>) -> bool { + pub fn in_state(state: S) -> impl FnMut(Res>) -> bool + Clone { move |current_state: Res>| current_state.0 == state } @@ -742,7 +742,7 @@ pub mod common_conditions { /// ``` pub fn state_exists_and_equals( state: S, - ) -> impl FnMut(Option>>) -> bool { + ) -> impl FnMut(Option>>) -> bool + Clone { move |current_state: Option>>| match current_state { Some(current_state) => current_state.0 == state, None => false, @@ -802,7 +802,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 2); /// ``` - pub fn state_changed() -> impl FnMut(Res>) -> bool { + pub fn state_changed() -> impl FnMut(Res>) -> bool + Clone { move |current_state: Res>| current_state.is_changed() } @@ -841,7 +841,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 1); /// ``` - pub fn on_event() -> impl FnMut(EventReader) -> bool { + pub fn on_event() -> impl FnMut(EventReader) -> bool + Clone { // The events need to be consumed, so that there are no false positives on subsequent // calls of the run condition. Simply checking `is_empty` would not be enough. // PERF: note that `count` is efficient (not actually looping/iterating), @@ -882,7 +882,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 1); /// ``` - pub fn any_with_component() -> impl FnMut(Query<(), With>) -> bool { + pub fn any_with_component() -> impl FnMut(Query<(), With>) -> bool + Clone { move |query: Query<(), With>| !query.is_empty() } @@ -915,7 +915,10 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 0); /// ``` - pub fn not(condition: impl Condition) -> impl Condition<()> { + pub fn not(condition: T) -> impl Condition<()> + where + T: Condition, + { condition.pipe(|In(val): In| !val) } } diff --git a/crates/bevy_input/src/common_conditions.rs b/crates/bevy_input/src/common_conditions.rs index 265b1c0c4d..dd93d2bdf1 100644 --- a/crates/bevy_input/src/common_conditions.rs +++ b/crates/bevy_input/src/common_conditions.rs @@ -48,7 +48,7 @@ use std::hash::Hash; /// } /// /// ``` -pub fn input_toggle_active(default: bool, input: T) -> impl FnMut(Res>) -> bool +pub fn input_toggle_active(default: bool, input: T) -> impl FnMut(Res>) -> bool + Clone where T: Copy + Eq + Hash + Send + Sync + 'static, { @@ -60,7 +60,7 @@ where } /// Run condition that is active if [`Input::pressed`] is true for the given input. -pub fn input_pressed(input: T) -> impl FnMut(Res>) -> bool +pub fn input_pressed(input: T) -> impl FnMut(Res>) -> bool + Clone where T: Copy + Eq + Hash + Send + Sync + 'static, { @@ -81,7 +81,7 @@ where /// /// # fn jump() {} /// ``` -pub fn input_just_pressed(input: T) -> impl FnMut(Res>) -> bool +pub fn input_just_pressed(input: T) -> impl FnMut(Res>) -> bool + Clone where T: Copy + Eq + Hash + Send + Sync + 'static, { @@ -89,9 +89,29 @@ where } /// Run condition that is active if [`Input::just_released`] is true for the given input. -pub fn input_just_released(input: T) -> impl FnMut(Res>) -> bool +pub fn input_just_released(input: T) -> impl FnMut(Res>) -> bool + Clone where T: Copy + Eq + Hash + Send + Sync + 'static, { move |inputs: Res>| inputs.just_released(input) } + +#[cfg(test)] +mod tests { + use super::*; + use bevy::prelude::{IntoSystemConfigs, KeyCode, Schedule}; + + fn test_system() {} + + // Ensure distributive_run_if compiles with the common conditions. + #[test] + fn distributive_run_if_compiles() { + Schedule::default().add_systems( + (test_system, test_system) + .distributive_run_if(input_toggle_active(false, KeyCode::Escape)) + .distributive_run_if(input_pressed(KeyCode::Escape)) + .distributive_run_if(input_just_pressed(KeyCode::Escape)) + .distributive_run_if(input_just_released(KeyCode::Escape)), + ); + } +} diff --git a/crates/bevy_time/src/common_conditions.rs b/crates/bevy_time/src/common_conditions.rs index 357f83cbe2..1f5625515e 100644 --- a/crates/bevy_time/src/common_conditions.rs +++ b/crates/bevy_time/src/common_conditions.rs @@ -32,7 +32,7 @@ use bevy_utils::Duration; /// For more accurate timers, use the [`Timer`] class directly (see /// [`Timer::times_finished_this_tick`] to address the problem mentioned above), or /// use fixed timesteps that allow systems to run multiple times per frame. -pub fn on_timer(duration: Duration) -> impl FnMut(Res>) { + query.par_iter_mut().for_each_mut(|(a, mut b)| { + b.0 = a.0; + }); + } + + fn modify_system(mut query: Query<&mut A>) { + for mut a in &mut query { + a.0 = 2; + } + } + + let mut schedule = Schedule::new(); + schedule.add_systems((propagate_system, modify_system).chain()); + schedule.run(&mut world); + world.clear_trackers(); + schedule.run(&mut world); + world.clear_trackers(); + + let values = world.query::<&B>().iter(&world).collect::>(); + assert_eq!(values, vec![&B(2)]); + } } diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index 72744a8efa..4d3393c8f0 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -1,4 +1,4 @@ -use crate::world::World; +use crate::{component::Tick, world::World}; use bevy_tasks::ComputeTaskPool; use std::ops::Range; @@ -81,6 +81,8 @@ impl BatchingStrategy { pub struct QueryParIter<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> { pub(crate) world: &'w World, pub(crate) state: &'s QueryState, + pub(crate) last_run: Tick, + pub(crate) this_run: Tick, pub(crate) batching_strategy: BatchingStrategy, } @@ -148,12 +150,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> { ) { let thread_count = ComputeTaskPool::get().thread_num(); if thread_count <= 1 { - self.state.for_each_unchecked_manual( - self.world, - func, - self.world.last_change_tick(), - self.world.read_change_tick(), - ); + self.state + .for_each_unchecked_manual(self.world, func, self.last_run, self.this_run); } else { // Need a batch size of at least 1. let batch_size = self.get_batch_size(thread_count).max(1); @@ -161,8 +159,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> { self.world, batch_size, func, - self.world.last_change_tick(), - self.world.read_change_tick(), + self.last_run, + self.this_run, ); } } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index d85c2965de..65774d40a9 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -820,6 +820,8 @@ impl QueryState { QueryParIter { world, state: self, + last_run: world.last_change_tick(), + this_run: world.read_change_tick(), batching_strategy: BatchingStrategy::new(), } } @@ -832,9 +834,12 @@ impl QueryState { #[inline] pub fn par_iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryParIter<'w, 's, Q, F> { self.update_archetypes(world); + let this_run = world.change_tick(); QueryParIter { world, state: self, + last_run: world.last_change_tick(), + this_run, batching_strategy: BatchingStrategy::new(), } } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index e8c54472da..bda0401a57 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -728,6 +728,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { QueryParIter { world: self.world, state: self.state.as_readonly(), + last_run: self.last_run, + this_run: self.this_run, batching_strategy: BatchingStrategy::new(), } } @@ -742,6 +744,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { QueryParIter { world: self.world, state: self.state, + last_run: self.last_run, + this_run: self.this_run, batching_strategy: BatchingStrategy::new(), } } From e77eb003ec6e374b224b890c4e6cf72f59b40adc Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 14 Mar 2023 00:01:27 +0000 Subject: [PATCH 33/68] Perform text scaling calculations per text, not per glyph (#7819) --- Cargo.toml | 10 ++++ crates/bevy_text/src/text2d.rs | 50 +++++++++---------- crates/bevy_ui/src/render/mod.rs | 57 +++++++++------------- examples/README.md | 1 + examples/stress_tests/many_glyphs.rs | 73 ++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 61 deletions(-) create mode 100644 examples/stress_tests/many_glyphs.rs diff --git a/Cargo.toml b/Cargo.toml index ac0fd63b8c..d0c6d99820 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1553,6 +1553,16 @@ description = "Loads an animated fox model and spawns lots of them. Good for tes category = "Stress Tests" wasm = true +[[example]] +name = "many_glyphs" +path = "examples/stress_tests/many_glyphs.rs" + +[package.metadata.example.many_glyphs] +name = "Many Glyphs" +description = "Simple benchmark to test text rendering." +category = "Stress Tests" +wasm = true + [[example]] name = "many_lights" path = "examples/stress_tests/many_lights.rs" diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index aac6c400fe..f252406823 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -23,8 +23,8 @@ use bevy_utils::HashSet; use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged}; use crate::{ - Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline, - TextSettings, YAxisOrientation, + Font, FontAtlasSet, FontAtlasWarning, PositionedGlyph, Text, TextError, TextLayoutInfo, + TextPipeline, TextSettings, YAxisOrientation, }; /// The maximum width and height of text. The text will wrap according to the specified size. @@ -94,48 +94,42 @@ pub fn extract_text2d_sprite( .get_single() .map(|window| window.resolution.scale_factor() as f32) .unwrap_or(1.0); + let scaling = GlobalTransform::from_scale(Vec3::splat(scale_factor.recip())); - for (entity, computed_visibility, text, text_layout_info, anchor, text_transform) in + for (entity, computed_visibility, text, text_layout_info, anchor, global_transform) in text2d_query.iter() { if !computed_visibility.is_visible() { continue; } - let text_glyphs = &text_layout_info.glyphs; let text_anchor = -(anchor.as_vec() + 0.5); - let alignment_offset = text_layout_info.size * text_anchor; + let alignment_translation = text_layout_info.size * text_anchor; + let transform = *global_transform + * scaling + * GlobalTransform::from_translation(alignment_translation.extend(0.)); let mut color = Color::WHITE; let mut current_section = usize::MAX; - for text_glyph in text_glyphs { - if text_glyph.section_index != current_section { - color = text.sections[text_glyph.section_index] - .style - .color - .as_rgba_linear(); - current_section = text_glyph.section_index; + for PositionedGlyph { + position, + atlas_info, + section_index, + .. + } in &text_layout_info.glyphs + { + if *section_index != current_section { + color = text.sections[*section_index].style.color.as_rgba_linear(); + current_section = *section_index; } - let atlas = texture_atlases - .get(&text_glyph.atlas_info.texture_atlas) - .unwrap(); - let handle = atlas.texture.clone_weak(); - let index = text_glyph.atlas_info.glyph_index; - let rect = Some(atlas.textures[index]); - - let glyph_transform = - Transform::from_translation((alignment_offset + text_glyph.position).extend(0.)); - - let transform = *text_transform - * GlobalTransform::from_scale(Vec3::splat(scale_factor.recip())) - * glyph_transform; + let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap(); extracted_sprites.sprites.push(ExtractedSprite { entity, - transform, + transform: transform * GlobalTransform::from_translation(position.extend(0.)), color, - rect, + rect: Some(atlas.textures[atlas_info.glyph_index]), custom_size: None, - image_handle_id: handle.id(), + image_handle_id: atlas.texture.id(), flip_x: false, flip_y: false, anchor: Anchor::Center.as_vec(), diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 853a3776a8..029fd5d8a1 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -31,7 +31,7 @@ use bevy_sprite::SpriteAssetEvents; #[cfg(feature = "bevy_text")] use bevy_sprite::TextureAtlas; #[cfg(feature = "bevy_text")] -use bevy_text::{Text, TextLayoutInfo}; +use bevy_text::{PositionedGlyph, Text, TextLayoutInfo}; use bevy_transform::components::GlobalTransform; use bevy_utils::FloatOrd; use bevy_utils::HashMap; @@ -318,52 +318,43 @@ pub fn extract_text_uinodes( .map(|window| window.resolution.scale_factor() as f32) .unwrap_or(1.0); + let scaling = Mat4::from_scale(Vec3::splat(scale_factor.recip())); + for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { if let Ok((uinode, global_transform, text, text_layout_info, visibility, clip)) = uinode_query.get(*entity) { - if !visibility.is_visible() { + // Skip if not visible or if size is set to zero (e.g. when a parent is set to `Display::None`) + if !visibility.is_visible() || uinode.size().x == 0. || uinode.size().y == 0. { continue; } - // Skip if size is set to zero (e.g. when a parent is set to `Display::None`) - if uinode.size() == Vec2::ZERO { - continue; - } - let text_glyphs = &text_layout_info.glyphs; - let alignment_offset = (uinode.size() / -2.0).extend(0.0); + + let transform = global_transform.compute_matrix() + * Mat4::from_translation(-0.5 * uinode.size().extend(0.)) + * scaling; let mut color = Color::WHITE; let mut current_section = usize::MAX; - for text_glyph in text_glyphs { - if text_glyph.section_index != current_section { - color = text.sections[text_glyph.section_index] - .style - .color - .as_rgba_linear(); - current_section = text_glyph.section_index; + for PositionedGlyph { + position, + atlas_info, + section_index, + .. + } in &text_layout_info.glyphs + { + if *section_index != current_section { + color = text.sections[*section_index].style.color.as_rgba_linear(); + current_section = *section_index; } - let atlas = texture_atlases - .get(&text_glyph.atlas_info.texture_atlas) - .unwrap(); - let texture = atlas.texture.clone_weak(); - let index = text_glyph.atlas_info.glyph_index; - let rect = atlas.textures[index]; - let atlas_size = Some(atlas.size); - - // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` - let extracted_transform = global_transform.compute_matrix() - * Mat4::from_scale(Vec3::splat(scale_factor.recip())) - * Mat4::from_translation( - alignment_offset * scale_factor + text_glyph.position.extend(0.), - ); + let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap(); extracted_uinodes.uinodes.push(ExtractedUiNode { stack_index, - transform: extracted_transform, + transform: transform * Mat4::from_translation(position.extend(0.)), color, - rect, - image: texture, - atlas_size, + rect: atlas.textures[atlas_info.glyph_index], + image: atlas.texture.clone_weak(), + atlas_size: Some(atlas.size), clip: clip.map(|clip| clip.clip), flip_x: false, flip_y: false, diff --git a/examples/README.md b/examples/README.md index bc03d2382c..8fd8f7c29b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -299,6 +299,7 @@ Example | Description [Many Buttons](../examples/stress_tests/many_buttons.rs) | Test rendering of many UI elements [Many Cubes](../examples/stress_tests/many_cubes.rs) | Simple benchmark to test per-entity draw overhead. Run with the `sphere` argument to test frustum culling [Many Foxes](../examples/stress_tests/many_foxes.rs) | Loads an animated fox model and spawns lots of them. Good for testing skinned mesh performance. Takes an unsigned integer argument for the number of foxes to spawn. Defaults to 1000 +[Many Glyphs](../examples/stress_tests/many_glyphs.rs) | Simple benchmark to test text rendering. [Many Lights](../examples/stress_tests/many_lights.rs) | Simple benchmark to test rendering many point lights. Run with `WGPU_SETTINGS_PRIO=webgl2` to restrict to uniform buffers and max 256 lights [Many Sprites](../examples/stress_tests/many_sprites.rs) | Displays many sprites in a grid arrangement! Used for performance testing. Use `--colored` to enable color tinted sprites. [Text Pipeline](../examples/stress_tests/text_pipeline.rs) | Text Pipeline benchmark diff --git a/examples/stress_tests/many_glyphs.rs b/examples/stress_tests/many_glyphs.rs new file mode 100644 index 0000000000..b5c7fc0f37 --- /dev/null +++ b/examples/stress_tests/many_glyphs.rs @@ -0,0 +1,73 @@ +//! Simple text rendering benchmark. +//! +//! Creates a `Text` with a single `TextSection` containing `100_000` glyphs, +//! and renders it with the UI in a white color and with Text2d in a red color. +use bevy::{ + diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, + prelude::*, + text::{BreakLineOn, Text2dBounds}, + window::{PresentMode, WindowPlugin}, +}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + present_mode: PresentMode::Immediate, + ..default() + }), + ..default() + })) + .add_plugin(FrameTimeDiagnosticsPlugin::default()) + .add_plugin(LogDiagnosticsPlugin::default()) + .add_startup_system(setup) + .run(); +} + +fn setup(mut commands: Commands, asset_server: Res) { + commands.spawn(Camera2dBundle::default()); + let mut text = Text { + sections: vec![TextSection { + value: "0123456789".repeat(10_000), + style: TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 4., + color: Color::WHITE, + }, + }], + alignment: TextAlignment::Left, + linebreak_behaviour: BreakLineOn::AnyCharacter, + }; + + commands + .spawn(NodeBundle { + style: Style { + flex_basis: Val::Percent(100.), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + ..default() + }, + ..default() + }) + .with_children(|commands| { + commands.spawn(TextBundle { + text: text.clone(), + style: Style { + size: Size::width(Val::Px(1000.)), + ..Default::default() + }, + ..Default::default() + }); + }); + + text.sections[0].style.color = Color::RED; + + commands.spawn(Text2dBundle { + text, + text_anchor: bevy::sprite::Anchor::Center, + text_2d_bounds: Text2dBounds { + size: Vec2::new(1000., f32::INFINITY), + }, + ..Default::default() + }); +} From 520e413c219494b3158ecbdf2b69a786a8469819 Mon Sep 17 00:00:00 2001 From: sark Date: Tue, 14 Mar 2023 02:58:55 -0600 Subject: [PATCH 34/68] `unused_variables` warning when building with `filesystem_watcher` feature disabled (#7938) --- crates/bevy_asset/src/io/file_asset_io.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_asset/src/io/file_asset_io.rs b/crates/bevy_asset/src/io/file_asset_io.rs index 93c6878c31..61003e2249 100644 --- a/crates/bevy_asset/src/io/file_asset_io.rs +++ b/crates/bevy_asset/src/io/file_asset_io.rs @@ -127,6 +127,7 @@ impl AssetIo for FileAssetIo { to_watch: &Path, to_reload: Option, ) -> Result<(), AssetIoError> { + #![allow(unused_variables)] #[cfg(feature = "filesystem_watcher")] { let to_reload = to_reload.unwrap_or_else(|| to_watch.to_owned()); From 13196613ee3a1add2e525dc8d6a52d8534ebef81 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Tue, 14 Mar 2023 15:10:50 +0100 Subject: [PATCH 35/68] Add depending bevy features for higher level one (#7855) --- Cargo.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d0c6d99820..47d8ea3c9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ bevy_asset = ["bevy_internal/bevy_asset"] bevy_audio = ["bevy_internal/bevy_audio"] # Provides cameras and other basic render pipeline features -bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline"] +bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline", "bevy_asset", "bevy_render"] # Plugin for dynamic loading (using [libloading](https://crates.io/crates/libloading)) bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"] @@ -75,25 +75,25 @@ bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"] bevy_gilrs = ["bevy_internal/bevy_gilrs"] # [glTF](https://www.khronos.org/gltf/) support -bevy_gltf = ["bevy_internal/bevy_gltf"] +bevy_gltf = ["bevy_internal/bevy_gltf", "bevy_asset", "bevy_scene", "bevy_pbr"] # Adds PBR rendering -bevy_pbr = ["bevy_internal/bevy_pbr"] +bevy_pbr = ["bevy_internal/bevy_pbr", "bevy_asset", "bevy_render", "bevy_core_pipeline"] # Provides rendering functionality bevy_render = ["bevy_internal/bevy_render"] # Provides scene functionality -bevy_scene = ["bevy_internal/bevy_scene"] +bevy_scene = ["bevy_internal/bevy_scene", "bevy_asset"] # Provides sprite functionality -bevy_sprite = ["bevy_internal/bevy_sprite"] +bevy_sprite = ["bevy_internal/bevy_sprite", "bevy_render", "bevy_core_pipeline"] # Provides text functionality bevy_text = ["bevy_internal/bevy_text"] # A custom ECS-driven UI framework -bevy_ui = ["bevy_internal/bevy_ui"] +bevy_ui = ["bevy_internal/bevy_ui", "bevy_core_pipeline", "bevy_text", "bevy_sprite"] # winit window and input backend bevy_winit = ["bevy_internal/bevy_winit"] From 602f3baf3f8bbd6ae97d039732682666bc5a2648 Mon Sep 17 00:00:00 2001 From: BlondeBurrito <15195945+BlondeBurrito@users.noreply.github.com> Date: Wed, 15 Mar 2023 00:11:55 +0000 Subject: [PATCH 36/68] fix: register Cascade in the TypeRegistry (#8088) --- crates/bevy_pbr/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index a55458d802..544984d5c1 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -151,6 +151,7 @@ impl Plugin for PbrPlugin { app.register_asset_reflect::() .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() From f6c72a7442c044f77d5f43069a4096880f2553da Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Tue, 14 Mar 2023 18:03:53 -0700 Subject: [PATCH 37/68] Fix blend_modes example UI positioning (#8093) --- examples/3d/blend_modes.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/3d/blend_modes.rs b/examples/3d/blend_modes.rs index 458b5a6582..a3e68747a6 100644 --- a/examples/3d/blend_modes.rs +++ b/examples/3d/blend_modes.rs @@ -211,6 +211,7 @@ fn setup( commands.spawn(( TextBundle::from_section("", text_style).with_style(Style { + position_type: PositionType::Absolute, top: Val::Px(10.0), right: Val::Px(10.0), ..default() From 1d4910a1e3280ea4cc175c2ec2b4a8a6108a4d74 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Tue, 14 Mar 2023 23:50:37 -0700 Subject: [PATCH 38/68] Fix scrolling in UI example (#8069) Co-authored-by: Carter Anderson --- examples/ui/ui.rs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index 827a75aa19..647299048e 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -124,7 +124,6 @@ fn setup(mut commands: Commands, asset_server: Res) { NodeBundle { style: Style { flex_direction: FlexDirection::Column, - flex_grow: 1.0, align_items: AlignItems::Center, ..default() }, @@ -145,12 +144,7 @@ fn setup(mut commands: Commands, asset_server: Res) { font_size: 20., color: Color::WHITE, }, - ) - .with_style(Style { - flex_shrink: 0., - size: Size::height(Val::Px(20.)), - ..default() - }), + ), Label, AccessibilityNode(NodeBuilder::new(Role::ListItem)), )); @@ -291,21 +285,21 @@ struct ScrollingList { fn mouse_scroll( mut mouse_wheel_events: EventReader, - mut query_list: Query<(&mut ScrollingList, &mut Style, &Children, &Node)>, - query_item: Query<&Node>, + mut query_list: Query<(&mut ScrollingList, &mut Style, &Parent, &Node)>, + query_node: Query<&Node>, ) { for mouse_wheel_event in mouse_wheel_events.iter() { - for (mut scrolling_list, mut style, children, uinode) in &mut query_list { - let items_height: f32 = children - .iter() - .map(|entity| query_item.get(*entity).unwrap().size().y) - .sum(); - let panel_height = uinode.size().y; - let max_scroll = (items_height - panel_height).max(0.); + for (mut scrolling_list, mut style, parent, list_node) in &mut query_list { + let items_height = list_node.size().y; + let container_height = query_node.get(parent.get()).unwrap().size().y; + + let max_scroll = (items_height - container_height).max(0.); + let dy = match mouse_wheel_event.unit { MouseScrollUnit::Line => mouse_wheel_event.y * 20., MouseScrollUnit::Pixel => mouse_wheel_event.y, }; + scrolling_list.position += dy; scrolling_list.position = scrolling_list.position.clamp(-max_scroll, 0.); style.top = Val::Px(scrolling_list.position); From b6b549e3ff1ace18712ca771ee6233976074800b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Wed, 15 Mar 2023 21:45:56 +0100 Subject: [PATCH 39/68] Fix look_to resulting in NaN rotations (#7817) Co-authored-by: Alice Cecile Co-authored-by: Liam Gallagher --- .../src/components/transform.rs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index a7615cb9b9..1e122ae08f 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -120,6 +120,11 @@ impl Transform { /// Returns this [`Transform`] with a new rotation so that [`Transform::forward`] /// points towards the `target` position and [`Transform::up`] points towards `up`. + /// + /// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases: + /// * if `target` is the same as the transform translation, `Vec3::Z` is used instead + /// * if `up` is zero, `Vec3::Y` is used instead + /// * if the resulting forward direction is parallel with `up`, an orthogonal vector is used as the "right" direction #[inline] #[must_use] pub fn looking_at(mut self, target: Vec3, up: Vec3) -> Self { @@ -129,6 +134,11 @@ impl Transform { /// Returns this [`Transform`] with a new rotation so that [`Transform::forward`] /// points in the given `direction` and [`Transform::up`] points towards `up`. + /// + /// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases: + /// * if `direction` is zero, `Vec3::Z` is used instead + /// * if `up` is zero, `Vec3::Y` is used instead + /// * if `direction` is parallel with `up`, an orthogonal vector is used as the "right" direction #[inline] #[must_use] pub fn looking_to(mut self, direction: Vec3, up: Vec3) -> Self { @@ -325,6 +335,11 @@ impl Transform { /// Rotates this [`Transform`] so that [`Transform::forward`] points towards the `target` position, /// and [`Transform::up`] points towards `up`. + /// + /// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases: + /// * if `target` is the same as the transtorm translation, `Vec3::Z` is used instead + /// * if `up` is zero, `Vec3::Y` is used instead + /// * if the resulting forward direction is parallel with `up`, an orthogonal vector is used as the "right" direction #[inline] pub fn look_at(&mut self, target: Vec3, up: Vec3) { self.look_to(target - self.translation, up); @@ -332,10 +347,19 @@ impl Transform { /// Rotates this [`Transform`] so that [`Transform::forward`] points in the given `direction` /// and [`Transform::up`] points towards `up`. + /// + /// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases: + /// * if `direction` is zero, `Vec3::Z` is used instead + /// * if `up` is zero, `Vec3::Y` is used instead + /// * if `direction` is parallel with `up`, an orthogonal vector is used as the "right" direction #[inline] pub fn look_to(&mut self, direction: Vec3, up: Vec3) { - let forward = -direction.normalize(); - let right = up.cross(forward).normalize(); + let forward = -direction.try_normalize().unwrap_or(Vec3::Z); + let up = up.try_normalize().unwrap_or(Vec3::Y); + let right = up + .cross(forward) + .try_normalize() + .unwrap_or_else(|| up.any_orthonormal_vector()); let up = forward.cross(right); self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, forward)); } From 9d1193df6c300dede75b00ab092caa119a7e80ad Mon Sep 17 00:00:00 2001 From: IceSentry Date: Wed, 15 Mar 2023 23:22:17 -0400 Subject: [PATCH 40/68] Add low level post process example using a custom render pass (#6909) Co-authored-by: JMS55 <47158642+JMS55@users.noreply.github.com> Co-authored-by: Robert Swain --- Cargo.toml | 12 +- assets/shaders/post_process_pass.wgsl | 48 +++ examples/README.md | 3 +- examples/shader/post_process_pass.rs | 412 ++++++++++++++++++++++++++ 4 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 assets/shaders/post_process_pass.wgsl create mode 100644 examples/shader/post_process_pass.rs diff --git a/Cargo.toml b/Cargo.toml index 47d8ea3c9f..d47dab1d6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1385,11 +1385,21 @@ name = "post_processing" path = "examples/shader/post_processing.rs" [package.metadata.example.post_processing] -name = "Post Processing" +name = "Post Processing - Render To Texture" description = "A custom post processing effect, using two cameras, with one reusing the render texture of the first one" category = "Shaders" wasm = true +[[example]] +name = "post_process_pass" +path = "examples/shader/post_process_pass.rs" + +[package.metadata.example.post_process_pass] +name = "Post Processing - Custom Render Pass" +description = "A custom post processing effect, using a custom render pass that runs after the main pass" +category = "Shaders" +wasm = true + [[example]] name = "shader_defs" path = "examples/shader/shader_defs.rs" diff --git a/assets/shaders/post_process_pass.wgsl b/assets/shaders/post_process_pass.wgsl new file mode 100644 index 0000000000..b25b5788cc --- /dev/null +++ b/assets/shaders/post_process_pass.wgsl @@ -0,0 +1,48 @@ +// This shader computes the chromatic aberration effect + +#import bevy_pbr::utils + +// Since post processing is a fullscreen effect, we use the fullscreen vertex shader provided by bevy. +// This will import a vertex shader that renders a single fullscreen triangle. +// +// A fullscreen triangle is a single triangle that covers the entire screen. +// The box in the top left in that diagram is the screen. The 4 x are the corner of the screen +// +// Y axis +// 1 | x-----x...... +// 0 | | s | . ´ +// -1 | x_____x´ +// -2 | : .´ +// -3 | :´ +// +--------------- X axis +// -1 0 1 2 3 +// +// As you can see, the triangle ends up bigger than the screen. +// +// You don't need to worry about this too much since bevy will compute the correct UVs for you. +#import bevy_core_pipeline::fullscreen_vertex_shader + +@group(0) @binding(0) +var screen_texture: texture_2d; +@group(0) @binding(1) +var texture_sampler: sampler; +struct PostProcessSettings { + intensity: f32, +} +@group(0) @binding(2) +var settings: PostProcessSettings; + +@fragment +fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { + // Chromatic aberration strength + let offset_strength = settings.intensity; + + // Sample each color channel with an arbitrary shift + return vec4( + textureSample(screen_texture, texture_sampler, in.uv + vec2(offset_strength, -offset_strength)).r, + textureSample(screen_texture, texture_sampler, in.uv + vec2(-offset_strength, 0.0)).g, + textureSample(screen_texture, texture_sampler, in.uv + vec2(0.0, offset_strength)).b, + 1.0 + ); +} + diff --git a/examples/README.md b/examples/README.md index 8fd8f7c29b..6b30aad3ed 100644 --- a/examples/README.md +++ b/examples/README.md @@ -278,7 +278,8 @@ Example | Description [Material - GLSL](../examples/shader/shader_material_glsl.rs) | A shader that uses the GLSL shading language [Material - Screenspace Texture](../examples/shader/shader_material_screenspace_texture.rs) | A shader that samples a texture with view-independent UV coordinates [Material Prepass](../examples/shader/shader_prepass.rs) | A shader that uses the various textures generated by the prepass -[Post Processing](../examples/shader/post_processing.rs) | A custom post processing effect, using two cameras, with one reusing the render texture of the first one +[Post Processing - Custom Render Pass](../examples/shader/post_process_pass.rs) | A custom post processing effect, using a custom render pass that runs after the main pass +[Post Processing - Render To Texture](../examples/shader/post_processing.rs) | A custom post processing effect, using two cameras, with one reusing the render texture of the first one [Shader Defs](../examples/shader/shader_defs.rs) | A shader that uses "shaders defs" (a bevy tool to selectively toggle parts of a shader) [Texture Binding Array (Bindless Textures)](../examples/shader/texture_binding_array.rs) | A shader that shows how to bind and sample multiple textures as a binding array (a.k.a. bindless textures). diff --git a/examples/shader/post_process_pass.rs b/examples/shader/post_process_pass.rs new file mode 100644 index 0000000000..ca91b7ee44 --- /dev/null +++ b/examples/shader/post_process_pass.rs @@ -0,0 +1,412 @@ +//! This example shows how to create a custom render pass that runs after the main pass +//! and reads the texture generated by the main pass. +//! +//! The example shader is a very simple implementation of chromatic aberration. +//! +//! This is a fairly low level example and assumes some familiarity with rendering concepts and wgpu. + +use bevy::{ + core_pipeline::{ + clear_color::ClearColorConfig, core_3d, + fullscreen_vertex_shader::fullscreen_shader_vertex_state, + }, + prelude::*, + render::{ + extract_component::{ + ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, + }, + render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType}, + render_resource::{ + BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, + BindGroupLayoutEntry, BindingResource, BindingType, CachedRenderPipelineId, + ColorTargetState, ColorWrites, FragmentState, MultisampleState, Operations, + PipelineCache, PrimitiveState, RenderPassColorAttachment, RenderPassDescriptor, + RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages, + ShaderType, TextureFormat, TextureSampleType, TextureViewDimension, + }, + renderer::{RenderContext, RenderDevice}, + texture::BevyDefault, + view::{ExtractedView, ViewTarget}, + RenderApp, + }, +}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins.set(AssetPlugin { + // Hot reloading the shader works correctly + watch_for_changes: true, + ..default() + })) + .add_plugin(PostProcessPlugin) + .add_startup_system(setup) + .add_system(rotate) + .add_system(update_settings) + .run(); +} + +/// It is generally encouraged to set up post processing effects as a plugin +struct PostProcessPlugin; +impl Plugin for PostProcessPlugin { + fn build(&self, app: &mut App) { + app + // The settings will be a component that lives in the main world but will + // be extracted to the render world every frame. + // This makes it possible to control the effect from the main world. + // This plugin will take care of extracting it automatically. + // It's important to derive [`ExtractComponent`] on [`PostProcessingSettings`] for this plugin to work correctly. + .add_plugin(ExtractComponentPlugin::::default()) + // The settings will also be the data used in the shader. + // This plugin will prepare the component for the GPU by creating a uniform buffer + // and writing the data to that buffer every frame. + .add_plugin(UniformComponentPlugin::::default()); + + // We need to get the render app from the main app + let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + // Initialize the pipeline + render_app.init_resource::(); + + // Bevy's renderer uses a render graph which is a collection of nodes in a directed acyclic graph. + // It currently runs on each view/camera and executes each node in the specified order. + // It will make sure that any node that needs a dependency from another node only runs when that dependency is done. + // + // Each node can execute arbitrary work, but it generally runs at least one render pass. + // A node only has access to the render world, so if you need data from the main world + // you need to extract it manually or with the plugin like above. + + // Create the node with the render world + let node = PostProcessNode::new(&mut render_app.world); + + // Get the render graph for the entire app + let mut graph = render_app.world.resource_mut::(); + + // Get the render graph for 3d cameras/views + let core_3d_graph = graph.get_sub_graph_mut(core_3d::graph::NAME).unwrap(); + + // Register the post process node in the 3d render graph + core_3d_graph.add_node(PostProcessNode::NAME, node); + + // A slot edge tells the render graph which input/output value should be passed to the node. + // In this case, the view entity, which is the entity associated with the + // camera on which the graph is running. + core_3d_graph.add_slot_edge( + core_3d_graph.input_node().id, + core_3d::graph::input::VIEW_ENTITY, + PostProcessNode::NAME, + PostProcessNode::IN_VIEW, + ); + + // We now need to add an edge between our node and the nodes from bevy + // to make sure our node is ordered correctly relative to other nodes. + // + // Here we want our effect to run after tonemapping and before the end of the main pass post processing + core_3d_graph.add_node_edge(core_3d::graph::node::TONEMAPPING, PostProcessNode::NAME); + core_3d_graph.add_node_edge( + PostProcessNode::NAME, + core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, + ); + } +} + +/// The post process node used for the render graph +struct PostProcessNode { + // The node needs a query to gather data from the ECS in order to do its rendering, + // but it's not a normal system so we need to define it manually. + query: QueryState<&'static ViewTarget, With>, +} + +impl PostProcessNode { + pub const IN_VIEW: &str = "view"; + pub const NAME: &str = "post_process"; + + fn new(world: &mut World) -> Self { + Self { + query: QueryState::new(world), + } + } +} + +impl Node for PostProcessNode { + // This defines the input slot of the node and tells the render graph what + // we will need when running the node. + fn input(&self) -> Vec { + // In this case we tell the graph that our node will use the view entity. + // Currently, every node in bevy uses this pattern, so it's safe to just copy it. + vec![SlotInfo::new(PostProcessNode::IN_VIEW, SlotType::Entity)] + } + + // This will run every frame before the run() method + // The important difference is that `self` is `mut` here + fn update(&mut self, world: &mut World) { + // Since this is not a system we need to update the query manually. + // This is mostly boilerplate. There are plans to remove this in the future. + // For now, you can just copy it. + self.query.update_archetypes(world); + } + + // Runs the node logic + // This is where you encode draw commands. + // + // This will run on every view on which the graph is running. If you don't want your effect to run on every camera, + // you'll need to make sure you have a marker component to identify which camera(s) should run the effect. + fn run( + &self, + graph_context: &mut RenderGraphContext, + render_context: &mut RenderContext, + world: &World, + ) -> Result<(), NodeRunError> { + // Get the entity of the view for the render graph where this node is running + let view_entity = graph_context.get_input_entity(PostProcessNode::IN_VIEW)?; + + // We get the data we need from the world based on the view entity passed to the node. + // The data is the query that was defined earlier in the [`PostProcessNode`] + let Ok(view_target) = self.query.get_manual(world, view_entity) else { + return Ok(()); + }; + + // Get the pipeline resource that contains the global data we need to create the render pipeline + let post_process_pipeline = world.resource::(); + + // The pipeline cache is a cache of all previously created pipelines. + // It is required to avoid creating a new pipeline each frame, which is expensive due to shader compilation. + let pipeline_cache = world.resource::(); + + // Get the pipeline from the cache + let Some(pipeline) = pipeline_cache.get_render_pipeline(post_process_pipeline.pipeline_id) else { + return Ok(()); + }; + + // Get the settings uniform binding + let settings_uniforms = world.resource::>(); + let Some(settings_binding) = settings_uniforms.uniforms().binding() else { + return Ok(()); + }; + + // This will start a new "post process write", obtaining two texture + // views from the view target - a `source` and a `destination`. + // `source` is the "current" main texture and you _must_ write into + // `destination` because calling `post_process_write()` on the + // [`ViewTarget`] will internally flip the [`ViewTarget`]'s main + // texture to the `destination` texture. Failing to do so will cause + // the current main texture information to be lost. + let post_process = view_target.post_process_write(); + + // The bind_group gets created each frame. + // + // Normally, you would create a bind_group in the Queue stage, but this doesn't work with the post_process_write(). + // The reason it doesn't work is because each post_process_write will alternate the source/destination. + // The only way to have the correct source/destination for the bind_group is to make sure you get it during the node execution. + let bind_group = render_context + .render_device() + .create_bind_group(&BindGroupDescriptor { + label: Some("post_process_bind_group"), + layout: &post_process_pipeline.layout, + // It's important for this to match the BindGroupLayout defined in the PostProcessPipeline + entries: &[ + BindGroupEntry { + binding: 0, + // Make sure to use the source view + resource: BindingResource::TextureView(post_process.source), + }, + BindGroupEntry { + binding: 1, + // Use the sampler created for the pipeline + resource: BindingResource::Sampler(&post_process_pipeline.sampler), + }, + BindGroupEntry { + binding: 2, + // Set the settings binding + resource: settings_binding.clone(), + }, + ], + }); + + // Begin the render pass + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("post_process_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + // We need to specify the post process destination view here + // to make sure we write to the appropriate texture. + view: post_process.destination, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + }); + + // This is mostly just wgpu boilerplate for drawing a fullscreen triangle, + // using the pipeline/bind_group created above + render_pass.set_render_pipeline(pipeline); + render_pass.set_bind_group(0, &bind_group, &[]); + render_pass.draw(0..3, 0..1); + + Ok(()) + } +} + +// This contains global data used by the render pipeline. This will be created once on startup. +#[derive(Resource)] +struct PostProcessPipeline { + layout: BindGroupLayout, + sampler: Sampler, + pipeline_id: CachedRenderPipelineId, +} + +impl FromWorld for PostProcessPipeline { + fn from_world(world: &mut World) -> Self { + let render_device = world.resource::(); + + // We need to define the bind group layout used for our pipeline + let layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("post_process_bind_group_layout"), + entries: &[ + // The screen texture + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + // The sampler that will be used to sample the screen texture + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + // The settings uniform that will control the effect + BindGroupLayoutEntry { + binding: 2, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: bevy::render::render_resource::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + + // We can create the sampler here since it won't change at runtime and doesn't depend on the view + let sampler = render_device.create_sampler(&SamplerDescriptor::default()); + + // Get the shader handle + let shader = world + .resource::() + .load("shaders/post_process_pass.wgsl"); + + let pipeline_id = world + .resource_mut::() + // This will add the pipeline to the cache and queue it's creation + .queue_render_pipeline(RenderPipelineDescriptor { + label: Some("post_process_pipeline".into()), + layout: vec![layout.clone()], + // This will setup a fullscreen triangle for the vertex state + vertex: fullscreen_shader_vertex_state(), + fragment: Some(FragmentState { + shader, + shader_defs: vec![], + // Make sure this matches the entry point of your shader. + // It can be anything as long as it matches here and in the shader. + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format: TextureFormat::bevy_default(), + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + // All of the following property are not important for this effect so just use the default values. + // This struct doesn't have the Default trai implemented because not all field can have a default value. + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + push_constant_ranges: vec![], + }); + + Self { + layout, + sampler, + pipeline_id, + } + } +} + +// This is the component that will get passed to the shader +#[derive(Component, Default, Clone, Copy, ExtractComponent, ShaderType)] +struct PostProcessSettings { + intensity: f32, +} + +/// Set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // camera + commands.spawn(( + Camera3dBundle { + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 5.0)) + .looking_at(Vec3::default(), Vec3::Y), + camera_3d: Camera3d { + clear_color: ClearColorConfig::Custom(Color::WHITE), + ..default() + }, + ..default() + }, + // Add the setting to the camera. + // This component is also used to determine on which camera to run the post processing effect. + PostProcessSettings { intensity: 0.02 }, + )); + + // cube + commands.spawn(( + PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..default() + }, + Rotates, + )); + // light + commands.spawn(PointLightBundle { + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), + ..default() + }); +} + +#[derive(Component)] +struct Rotates; + +/// Rotates any entity around the x and y axis +fn rotate(time: Res

) -> Self { - let Self { system, schedule } = self; - Self { - system: system.run_if(condition), - schedule, - } - } - - fn ambiguous_with(self, set: impl IntoSystemSet) -> Self { - let Self { system, schedule } = self; - Self { - system: system.ambiguous_with(set), - schedule, - } - } - - fn ambiguous_with_all(self) -> Self { - let Self { system, schedule } = self; - Self { - system: system.ambiguous_with_all(), - schedule, - } - } -} - -impl IntoSystemAppConfig<()> for SystemAppConfig { - fn into_app_config(self) -> SystemAppConfig { - self - } -} - -impl IntoSystemAppConfig for T -where - T: IntoSystemConfig, -{ - fn into_app_config(self) -> SystemAppConfig { - SystemAppConfig { - system: self.into_config(), - schedule: None, - } - } -} - -/// A collection of [`SystemAppConfig`]s. -pub struct SystemAppConfigs(pub(crate) InnerConfigs); - -pub(crate) enum InnerConfigs { - /// This came from an instance of `SystemConfigs`. - /// All systems are in the same schedule. - Blanket { - systems: SystemConfigs, - schedule: Option, - }, - /// This came from several separate instances of `SystemAppConfig`. - /// Each system gets its own schedule. - Granular(Vec), -} - -/// Types that can convert into [`SystemAppConfigs`]. -pub trait IntoSystemAppConfigs: Sized { - /// Converts to [`SystemAppConfigs`]. - fn into_app_configs(self) -> SystemAppConfigs; - - /// Adds the systems to the provided `schedule`. - /// - /// If a schedule with specified label does not exist, it will be created. - /// - /// If a schedule with the specified label does not exist, an empty one will be created. - /// - /// - /// [`App`]: crate::App - /// - /// # Panics - /// - /// If any of the systems have already been assigned to a schedule. - #[track_caller] - fn in_schedule(self, label: impl ScheduleLabel) -> SystemAppConfigs { - let mut configs = self.into_app_configs(); - - match &mut configs.0 { - InnerConfigs::Blanket { schedule, .. } => { - if schedule.is_some() { - panic!( - "Cannot add systems to the schedule '{label:?}: they are already in '{schedule:?}'" - ); - } - *schedule = Some(Box::new(label)); - } - InnerConfigs::Granular(configs) => { - for SystemAppConfig { schedule, .. } in configs { - if schedule.is_some() { - panic!( - "Cannot add system to the schedule '{label:?}': it is already in '{schedule:?}'." - ); - } - *schedule = Some(label.dyn_clone()); - } - } - } - - configs - } - - /// Adds the systems to [`CoreSchedule::Startup`]. - /// This is a shorthand for `self.in_schedule(CoreSchedule::Startup)`. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn startup_system_a() {} - /// # fn startup_system_b() {} - /// # fn startup_system_c() {} - /// # - /// app.add_systems( - /// ( - /// startup_system_a, - /// startup_system_b, - /// startup_system_c, - /// ) - /// .on_startup() - /// ); - /// ``` - /// - /// # Panics - /// - /// If any of the systems have already been assigned to a schedule. - #[track_caller] - fn on_startup(self) -> SystemAppConfigs { - self.in_schedule(CoreSchedule::Startup) - } -} - -impl IntoSystemAppConfigs<()> for SystemAppConfigs { - fn into_app_configs(self) -> SystemAppConfigs { - self - } -} - -impl IntoSystemAppConfigs<()> for SystemConfigs { - fn into_app_configs(self) -> SystemAppConfigs { - SystemAppConfigs(InnerConfigs::Blanket { - systems: self, - schedule: None, - }) - } -} - -macro_rules! impl_system_collection { - ($(($param: ident, $sys: ident)),*) => { - impl<$($param, $sys),*> IntoSystemAppConfigs<($($param,)*)> for ($($sys,)*) - where - $($sys: IntoSystemAppConfig<$param>),* - { - #[allow(non_snake_case)] - fn into_app_configs(self) -> SystemAppConfigs { - let ($($sys,)*) = self; - SystemAppConfigs(InnerConfigs::Granular(vec![$($sys.into_app_config(),)*])) - } - } - } -} - -all_tuples!(impl_system_collection, 0, 15, P, S); diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 165e9ebd70..8094d0458e 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -3,7 +3,7 @@ #![warn(missing_docs)] mod app; -mod config; +mod main_schedule; mod plugin; mod plugin_group; mod schedule_runner; @@ -13,7 +13,7 @@ mod ci_testing; pub use app::*; pub use bevy_derive::DynamicPlugin; -pub use config::*; +pub use main_schedule::*; pub use plugin::*; pub use plugin_group::*; pub use schedule_runner::*; @@ -26,197 +26,10 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ app::App, - config::{IntoSystemAppConfig, IntoSystemAppConfigs}, - CoreSchedule, CoreSet, DynamicPlugin, Plugin, PluginGroup, StartupSet, + main_schedule::{ + First, FixedUpdate, Last, Main, PostStartup, PostUpdate, PreStartup, PreUpdate, + Startup, StateTransition, Update, + }, + DynamicPlugin, Plugin, PluginGroup, }; } - -use bevy_ecs::{ - schedule::{ - apply_system_buffers, IntoSystemConfig, IntoSystemSetConfigs, Schedule, ScheduleLabel, - SystemSet, - }, - system::Local, - world::World, -}; - -/// The names of the default [`App`] schedules. -/// -/// The corresponding [`Schedule`](bevy_ecs::schedule::Schedule) objects are added by [`App::add_default_schedules`]. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] -pub enum CoreSchedule { - /// The schedule that runs once when the app starts. - Startup, - /// The schedule that contains the app logic that is evaluated each tick of [`App::update()`]. - Main, - /// The schedule that controls which schedules run. - /// - /// This is typically created using the [`CoreSchedule::outer_schedule`] method, - /// and does not need to manipulated during ordinary use. - Outer, - /// The schedule that contains systems which only run after a fixed period of time has elapsed. - /// - /// The exclusive `run_fixed_update_schedule` system runs this schedule during the [`CoreSet::FixedUpdate`] system set. - FixedUpdate, -} - -impl CoreSchedule { - /// An exclusive system that controls which schedule should be running. - /// - /// [`CoreSchedule::Main`] is always run. - /// - /// If this is the first time this system has been run, [`CoreSchedule::Startup`] will run before [`CoreSchedule::Main`]. - pub fn outer_loop(world: &mut World, mut run_at_least_once: Local) { - if !*run_at_least_once { - world.run_schedule(CoreSchedule::Startup); - *run_at_least_once = true; - } - - world.run_schedule(CoreSchedule::Main); - } - - /// Initializes a single threaded schedule for [`CoreSchedule::Outer`] that contains the [`outer_loop`](CoreSchedule::outer_loop) system. - pub fn outer_schedule() -> Schedule { - let mut schedule = Schedule::new(); - schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded); - schedule.add_system(Self::outer_loop); - schedule - } -} - -/// The names of the default [`App`] system sets. -/// -/// These are ordered in the same order they are listed. -/// -/// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_schedules`]. -/// -/// The `*Flush` sets are assigned to the copy of [`apply_system_buffers`] -/// that runs immediately after the matching system set. -/// These can be useful for ordering, but you almost never want to add your systems to these sets. -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -#[system_set(base)] -pub enum CoreSet { - /// Runs before all other members of this set. - First, - /// The copy of [`apply_system_buffers`] that runs immediately after `First`. - FirstFlush, - /// Runs before [`CoreSet::Update`]. - PreUpdate, - /// The copy of [`apply_system_buffers`] that runs immediately after `PreUpdate`. - PreUpdateFlush, - /// Applies [`State`](bevy_ecs::schedule::State) transitions - StateTransitions, - /// Runs systems that should only occur after a fixed period of time. - /// - /// The `run_fixed_update_schedule` system runs the [`CoreSchedule::FixedUpdate`] system in this system set. - FixedUpdate, - /// Responsible for doing most app logic. Systems should be registered here by default. - Update, - /// The copy of [`apply_system_buffers`] that runs immediately after `Update`. - UpdateFlush, - /// Runs after [`CoreSet::Update`]. - PostUpdate, - /// The copy of [`apply_system_buffers`] that runs immediately after `PostUpdate`. - PostUpdateFlush, - /// Runs after all other members of this set. - Last, - /// The copy of [`apply_system_buffers`] that runs immediately after `Last`. - LastFlush, -} - -impl CoreSet { - /// Sets up the base structure of [`CoreSchedule::Main`]. - /// - /// The sets defined in this enum are configured to run in order, - /// and a copy of [`apply_system_buffers`] is inserted into each `*Flush` set. - pub fn base_schedule() -> Schedule { - use CoreSet::*; - let mut schedule = Schedule::new(); - - // Create "stage-like" structure using buffer flushes + ordering - schedule - .set_default_base_set(Update) - .add_systems(( - apply_system_buffers.in_base_set(FirstFlush), - apply_system_buffers.in_base_set(PreUpdateFlush), - apply_system_buffers.in_base_set(UpdateFlush), - apply_system_buffers.in_base_set(PostUpdateFlush), - apply_system_buffers.in_base_set(LastFlush), - )) - .configure_sets( - ( - First, - FirstFlush, - PreUpdate, - PreUpdateFlush, - StateTransitions, - FixedUpdate, - Update, - UpdateFlush, - PostUpdate, - PostUpdateFlush, - Last, - LastFlush, - ) - .chain(), - ); - schedule - } -} - -/// The names of the default [`App`] startup sets, which live in [`CoreSchedule::Startup`]. -/// -/// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_schedules`]. -/// -/// The `*Flush` sets are assigned to the copy of [`apply_system_buffers`] -/// that runs immediately after the matching system set. -/// These can be useful for ordering, but you almost never want to add your systems to these sets. -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -#[system_set(base)] -pub enum StartupSet { - /// Runs once before [`StartupSet::Startup`]. - PreStartup, - /// The copy of [`apply_system_buffers`] that runs immediately after `PreStartup`. - PreStartupFlush, - /// Runs once when an [`App`] starts up. - Startup, - /// The copy of [`apply_system_buffers`] that runs immediately after `Startup`. - StartupFlush, - /// Runs once after [`StartupSet::Startup`]. - PostStartup, - /// The copy of [`apply_system_buffers`] that runs immediately after `PostStartup`. - PostStartupFlush, -} - -impl StartupSet { - /// Sets up the base structure of [`CoreSchedule::Startup`]. - /// - /// The sets defined in this enum are configured to run in order, - /// and a copy of [`apply_system_buffers`] is inserted into each `*Flush` set. - pub fn base_schedule() -> Schedule { - use StartupSet::*; - let mut schedule = Schedule::new(); - schedule.set_default_base_set(Startup); - - // Create "stage-like" structure using buffer flushes + ordering - schedule.add_systems(( - apply_system_buffers.in_base_set(PreStartupFlush), - apply_system_buffers.in_base_set(StartupFlush), - apply_system_buffers.in_base_set(PostStartupFlush), - )); - - schedule.configure_sets( - ( - PreStartup, - PreStartupFlush, - Startup, - StartupFlush, - PostStartup, - PostStartupFlush, - ) - .chain(), - ); - - schedule - } -} diff --git a/crates/bevy_app/src/main_schedule.rs b/crates/bevy_app/src/main_schedule.rs new file mode 100644 index 0000000000..2042b9e01f --- /dev/null +++ b/crates/bevy_app/src/main_schedule.rs @@ -0,0 +1,168 @@ +use crate::{App, Plugin}; +use bevy_ecs::{ + schedule::{ExecutorKind, Schedule, ScheduleLabel}, + system::{Local, Resource}, + world::{Mut, World}, +}; + +/// The schedule that contains the app logic that is evaluated each tick of [`App::update()`]. +/// +/// By default, it will run the following schedules in the given order: +/// +/// On the first run of the schedule (and only on the first run), it will run: +/// * [`PreStartup`] +/// * [`Startup`] +/// * [`PostStartup`] +/// +/// Then it will run: +/// * [`First`] +/// * [`PreUpdate`] +/// * [`StateTransition`] +/// * [`RunFixedUpdateLoop`] +/// * This will run [`FixedUpdate`] zero to many times, based on how much time has elapsed. +/// * [`Update`] +/// * [`PostUpdate`] +/// * [`Last`] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Main; + +/// The schedule that runs before [`Startup`]. +/// This is run by the [`Main`] schedule. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct PreStartup; + +/// The schedule that runs once when the app starts. +/// This is run by the [`Main`] schedule. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Startup; + +/// The schedule that runs once after [`Startup`]. +/// This is run by the [`Main`] schedule. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct PostStartup; + +/// Runs first in the schedule. +/// This is run by the [`Main`] schedule. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct First; + +/// The schedule that contains logic that must run before [`Update`]. For example, a system that reads raw keyboard +/// input OS events into an `Events` resource. This enables systems in [`Update`] to consume the events from the `Events` +/// resource without actually knowing about (or taking a direct scheduler dependency on) the "os-level keyboard event sytsem". +/// +/// [`PreUpdate`] exists to do "engine/plugin preparation work" that ensures the APIs consumed in [`Update`] are "ready". +/// [`PreUpdate`] abstracts out "pre work implementation details". +/// +/// This is run by the [`Main`] schedule. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct PreUpdate; + +/// Runs [state transitions](bevy_ecs::schedule::States). +/// This is run by the [`Main`] schedule. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct StateTransition; + +/// Runs the [`FixedUpdate`] schedule in a loop according until all relevant elapsed time has been "consumed". +/// This is run by the [`Main`] schedule. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct RunFixedUpdateLoop; + +/// The schedule that contains systems which only run after a fixed period of time has elapsed. +/// +/// The exclusive `run_fixed_update_schedule` system runs this schedule. +/// This is run by the [`RunFixedUpdateLoop`] schedule. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct FixedUpdate; + +/// The schedule that contains app logic. +/// This is run by the [`Main`] schedule. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Update; + +/// The schedule that contains logic that must run after [`Update`]. For example, synchronizing "local transforms" in a hierarchy +/// to "global" absolute transforms. This enables the [`PostUpdate`] transform-sync system to react to "local transform" changes in +/// [`Update`] without the [`Update`] systems needing to know about (or add scheduler dependencies for) the "global transform sync system". +/// +/// [`PostUpdate`] exists to do "engine/plugin response work" to things that happened in [`Update`]. +/// [`PostUpdate`] abstracts out "implementation details" from users defining systems in [`Update`]. +/// +/// This is run by the [`Main`] schedule. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct PostUpdate; + +/// Runs last in the schedule. +/// This is run by the [`Main`] schedule. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Last; + +/// Defines the schedules to be run for the [`Main`] schedule, including +/// their order. +#[derive(Resource, Debug)] +pub struct MainScheduleOrder { + /// The labels to run for the [`Main`] schedule (in the order they will be run). + pub labels: Vec>, +} + +impl Default for MainScheduleOrder { + fn default() -> Self { + Self { + labels: vec![ + Box::new(First), + Box::new(PreUpdate), + Box::new(StateTransition), + Box::new(RunFixedUpdateLoop), + Box::new(Update), + Box::new(PostUpdate), + Box::new(Last), + ], + } + } +} + +impl MainScheduleOrder { + /// Adds the given `schedule` after the `after` schedule + pub fn insert_after(&mut self, after: impl ScheduleLabel, schedule: impl ScheduleLabel) { + let index = self + .labels + .iter() + .position(|current| (**current).eq(&after)) + .unwrap_or_else(|| panic!("Expected {after:?} to exist")); + self.labels.insert(index + 1, Box::new(schedule)); + } +} + +impl Main { + /// A system that runs the "main schedule" + pub fn run_main(world: &mut World, mut run_at_least_once: Local) { + if !*run_at_least_once { + let _ = world.try_run_schedule(PreStartup); + let _ = world.try_run_schedule(Startup); + let _ = world.try_run_schedule(PostStartup); + *run_at_least_once = true; + } + + world.resource_scope(|world, order: Mut| { + for label in &order.labels { + let _ = world.try_run_schedule_ref(&**label); + } + }); + } +} + +/// Initializes the [`Main`] schedule, sub schedules, and resources for a given [`App`]. +pub struct MainSchedulePlugin; + +impl Plugin for MainSchedulePlugin { + fn build(&self, app: &mut App) { + // simple "facilitator" schedules benefit from simpler single threaded scheduling + let mut main_schedule = Schedule::new(); + main_schedule.set_executor_kind(ExecutorKind::SingleThreaded); + let mut fixed_update_loop_schedule = Schedule::new(); + fixed_update_loop_schedule.set_executor_kind(ExecutorKind::SingleThreaded); + + app.add_schedule(Main, main_schedule) + .add_schedule(RunFixedUpdateLoop, fixed_update_loop_schedule) + .init_resource::() + .add_systems(Main, Main::run_main); + } +} diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index dd6a8ae307..e456c7eb44 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -644,7 +644,7 @@ pub fn free_unused_assets_system(asset_server: Res) { mod test { use super::*; use crate::{loader::LoadedAsset, update_asset_storage_system}; - use bevy_app::App; + use bevy_app::{App, Update}; use bevy_ecs::prelude::*; use bevy_reflect::TypeUuid; use bevy_utils::BoxedFuture; @@ -852,10 +852,13 @@ mod test { let mut app = App::new(); app.insert_resource(assets); app.insert_resource(asset_server); - app.add_systems(( - free_unused_assets_system.in_set(FreeUnusedAssets), - update_asset_storage_system::.after(FreeUnusedAssets), - )); + app.add_systems( + Update, + ( + free_unused_assets_system.in_set(FreeUnusedAssets), + update_asset_storage_system::.after(FreeUnusedAssets), + ), + ); fn load_asset(path: AssetPath, world: &World) -> HandleUntyped { let asset_server = world.resource::(); diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 40d390187f..3fffa1d413 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -1,6 +1,6 @@ use crate::{ - update_asset_storage_system, Asset, AssetLoader, AssetServer, AssetSet, Handle, HandleId, - RefChange, ReflectAsset, ReflectHandle, + update_asset_storage_system, Asset, AssetEvents, AssetLoader, AssetServer, Handle, HandleId, + LoadAssets, RefChange, ReflectAsset, ReflectHandle, }; use bevy_app::{App, AppTypeRegistry}; use bevy_ecs::prelude::*; @@ -331,10 +331,8 @@ impl AddAsset for App { }; self.insert_resource(assets) - .add_systems(( - Assets::::asset_event_system.in_base_set(AssetSet::AssetEvents), - update_asset_storage_system::.in_base_set(AssetSet::LoadAssets), - )) + .add_systems(LoadAssets, update_asset_storage_system::) + .add_systems(AssetEvents, Assets::::asset_event_system) .register_type::>() .add_event::>() } @@ -362,9 +360,9 @@ impl AddAsset for App { { #[cfg(feature = "debug_asset_server")] { - self.add_system( - crate::debug_asset_server::sync_debug_assets:: - .in_base_set(bevy_app::CoreSet::Update), + self.add_systems( + bevy_app::Update, + crate::debug_asset_server::sync_debug_assets::, ); let mut app = self .world diff --git a/crates/bevy_asset/src/debug_asset_server.rs b/crates/bevy_asset/src/debug_asset_server.rs index 7b506faeed..955fadb80f 100644 --- a/crates/bevy_asset/src/debug_asset_server.rs +++ b/crates/bevy_asset/src/debug_asset_server.rs @@ -2,7 +2,7 @@ //! //! Internal assets (e.g. shaders) are bundled directly into an application and can't be hot //! reloaded using the conventional API. -use bevy_app::{App, Plugin}; +use bevy_app::{App, Plugin, Update}; use bevy_ecs::{prelude::*, system::SystemState}; use bevy_tasks::{IoTaskPool, TaskPoolBuilder}; use bevy_utils::HashMap; @@ -75,7 +75,7 @@ impl Plugin for DebugAssetServerPlugin { watch_for_changes: true, }); app.insert_non_send_resource(DebugAssetApp(debug_asset_app)); - app.add_system(run_debug_asset_app); + app.add_systems(Update, run_debug_asset_app); } } diff --git a/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs b/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs index d36cd2969d..a5ce2546db 100644 --- a/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs +++ b/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs @@ -18,8 +18,8 @@ impl Default for AssetCountDiagnosticsPlugin { impl Plugin for AssetCountDiagnosticsPlugin { fn build(&self, app: &mut App) { - app.add_startup_system(Self::setup_system) - .add_system(Self::diagnostic_system); + app.add_systems(Startup, Self::setup_system) + .add_systems(Update, Self::diagnostic_system); } } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 59b0b2cb32..3265690165 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -46,18 +46,15 @@ pub use loader::*; pub use path::*; pub use reflect::*; -use bevy_app::prelude::*; -use bevy_ecs::prelude::*; +use bevy_app::{prelude::*, MainScheduleOrder}; +use bevy_ecs::schedule::ScheduleLabel; -/// [`SystemSet`]s for asset loading in an [`App`] schedule. -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -#[system_set(base)] -pub enum AssetSet { - /// Asset storages are updated. - LoadAssets, - /// Asset events are generated. - AssetEvents, -} +/// Asset storages are updated. +#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)] +pub struct LoadAssets; +/// Asset events are generated. +#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)] +pub struct AssetEvents; /// Adds support for [`Assets`] to an App. /// @@ -106,24 +103,19 @@ impl Plugin for AssetPlugin { app.insert_resource(asset_server); } - app.register_type::(); - - app.configure_set( - AssetSet::LoadAssets - .before(CoreSet::PreUpdate) - .after(CoreSet::First), - ) - .configure_set( - AssetSet::AssetEvents - .after(CoreSet::PostUpdate) - .before(CoreSet::Last), - ) - .add_system(asset_server::free_unused_assets_system.in_base_set(CoreSet::PreUpdate)); + app.register_type::() + .add_systems(PreUpdate, asset_server::free_unused_assets_system); + app.init_schedule(LoadAssets); + app.init_schedule(AssetEvents); #[cfg(all( feature = "filesystem_watcher", all(not(target_arch = "wasm32"), not(target_os = "android")) ))] - app.add_system(io::filesystem_watcher_system.in_base_set(AssetSet::LoadAssets)); + app.add_systems(LoadAssets, io::filesystem_watcher_system); + + let mut order = app.world.resource_mut::(); + order.insert_after(First, LoadAssets); + order.insert_after(PostUpdate, AssetEvents); } } diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index af751a8364..468ff3d622 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -4,13 +4,13 @@ //! # use bevy_ecs::{system::Res, event::EventWriter}; //! # use bevy_audio::{Audio, AudioPlugin}; //! # use bevy_asset::{AssetPlugin, AssetServer}; -//! # use bevy_app::{App, AppExit, NoopPluginGroup as MinimalPlugins}; +//! # use bevy_app::{App, AppExit, NoopPluginGroup as MinimalPlugins, Startup}; //! fn main() { //! App::new() //! .add_plugins(MinimalPlugins) //! .add_plugin(AssetPlugin::default()) //! .add_plugin(AudioPlugin) -//! .add_startup_system(play_background_audio) +//! .add_systems(Startup, play_background_audio) //! .run(); //! } //! @@ -47,7 +47,6 @@ pub use sinks::*; use bevy_app::prelude::*; use bevy_asset::{AddAsset, Asset}; -use bevy_ecs::prelude::*; /// Adds support for audio playback to a Bevy Application /// @@ -62,7 +61,7 @@ impl Plugin for AudioPlugin { .add_asset::() .add_asset::() .init_resource::>() - .add_system(play_queued_audio_system::.in_base_set(CoreSet::PostUpdate)); + .add_systems(PostUpdate, play_queued_audio_system::); #[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))] app.init_asset_loader::(); @@ -78,6 +77,6 @@ impl AddAudioSource for App { self.add_asset::() .init_resource::>() .init_resource::>() - .add_system(play_queued_audio_system::.in_base_set(CoreSet::PostUpdate)) + .add_systems(PostUpdate, play_queued_audio_system::) } } diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index 7e58685c9f..daf43cf86e 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -102,12 +102,12 @@ pub struct TaskPoolPlugin { } impl Plugin for TaskPoolPlugin { - fn build(&self, app: &mut App) { + fn build(&self, _app: &mut App) { // Setup the default bevy task pools self.task_pool_options.create_default_pools(); #[cfg(not(target_arch = "wasm32"))] - app.add_system(tick_global_task_pools.in_base_set(bevy_app::CoreSet::Last)); + _app.add_systems(Last, tick_global_task_pools); } } /// A dummy type that is [`!Send`](Send), to force systems to run on the main thread. @@ -124,7 +124,7 @@ fn tick_global_task_pools(_main_thread_marker: Option>) { /// Maintains a count of frames rendered since the start of the application. /// -/// [`FrameCount`] is incremented during [`CoreSet::Last`], providing predictable +/// [`FrameCount`] is incremented during [`Last`], providing predictable /// behaviour: it will be 0 during the first update, 1 during the next, and so forth. /// /// # Overflows @@ -142,7 +142,7 @@ pub struct FrameCountPlugin; impl Plugin for FrameCountPlugin { fn build(&self, app: &mut App) { app.init_resource::(); - app.add_system(update_frame_count.in_base_set(CoreSet::Last)); + app.add_systems(Last, update_frame_count); } } diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 5ff8cfba4e..62e577ea23 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -7,13 +7,7 @@ pub use settings::{BloomCompositeMode, BloomPrefilterSettings, BloomSettings}; use crate::{core_2d, core_3d}; use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, HandleUntyped}; -use bevy_ecs::{ - prelude::{Component, Entity}, - query::{QueryState, With}, - schedule::IntoSystemConfig, - system::{Commands, Query, Res, ResMut}, - world::World, -}; +use bevy_ecs::prelude::*; use bevy_math::UVec2; use bevy_reflect::TypeUuid; use bevy_render::{ @@ -27,7 +21,7 @@ use bevy_render::{ renderer::{RenderContext, RenderDevice}, texture::{CachedTexture, TextureCache}, view::ViewTarget, - RenderApp, RenderSet, + Render, RenderApp, RenderSet, }; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; @@ -71,12 +65,15 @@ impl Plugin for BloomPlugin { .init_resource::() .init_resource::>() .init_resource::>() - .add_systems(( - prepare_bloom_textures.in_set(RenderSet::Prepare), - prepare_downsampling_pipeline.in_set(RenderSet::Prepare), - prepare_upsampling_pipeline.in_set(RenderSet::Prepare), - queue_bloom_bind_groups.in_set(RenderSet::Queue), - )); + .add_systems( + Render, + ( + prepare_bloom_textures.in_set(RenderSet::Prepare), + prepare_downsampling_pipeline.in_set(RenderSet::Prepare), + prepare_upsampling_pipeline.in_set(RenderSet::Prepare), + queue_bloom_bind_groups.in_set(RenderSet::Queue), + ), + ); // Add bloom to the 3d render graph { diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index a94da4d0b3..e682257dc9 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -20,7 +20,7 @@ pub mod graph { pub use camera_2d::*; pub use main_pass_2d_node::*; -use bevy_app::{App, IntoSystemAppConfig, Plugin}; +use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; use bevy_render::{ camera::Camera, @@ -31,7 +31,7 @@ use bevy_render::{ DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase, }, render_resource::CachedRenderPipelineId, - Extract, ExtractSchedule, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_utils::FloatOrd; use std::ops::Range; @@ -52,13 +52,16 @@ impl Plugin for Core2dPlugin { render_app .init_resource::>() - .add_systems(( - extract_core_2d_camera_phases.in_schedule(ExtractSchedule), - sort_phase_system::.in_set(RenderSet::PhaseSort), - batch_phase_system:: - .after(sort_phase_system::) - .in_set(RenderSet::PhaseSort), - )); + .add_systems(ExtractSchedule, extract_core_2d_camera_phases) + .add_systems( + Render, + ( + sort_phase_system::.in_set(RenderSet::PhaseSort), + batch_phase_system:: + .after(sort_phase_system::) + .in_set(RenderSet::PhaseSort), + ), + ); let pass_node_2d = MainPass2dNode::new(&mut render_app.world); let tonemapping = TonemappingNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 166f6765e0..9bfa5486df 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -23,7 +23,7 @@ use std::cmp::Reverse; pub use camera_3d::*; pub use main_pass_3d_node::*; -use bevy_app::{App, IntoSystemAppConfig, Plugin}; +use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; use bevy_render::{ camera::{Camera, ExtractedCamera}, @@ -41,7 +41,7 @@ use bevy_render::{ renderer::RenderDevice, texture::TextureCache, view::ViewDepthTexture, - Extract, ExtractSchedule, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_utils::{FloatOrd, HashMap}; @@ -68,15 +68,18 @@ impl Plugin for Core3dPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .add_systems(( - extract_core_3d_camera_phases.in_schedule(ExtractSchedule), - prepare_core_3d_depth_textures - .in_set(RenderSet::Prepare) - .after(bevy_render::view::prepare_windows), - sort_phase_system::.in_set(RenderSet::PhaseSort), - sort_phase_system::.in_set(RenderSet::PhaseSort), - sort_phase_system::.in_set(RenderSet::PhaseSort), - )); + .add_systems(ExtractSchedule, extract_core_3d_camera_phases) + .add_systems( + Render, + ( + prepare_core_3d_depth_textures + .in_set(RenderSet::Prepare) + .after(bevy_render::view::prepare_windows), + sort_phase_system::.in_set(RenderSet::PhaseSort), + sort_phase_system::.in_set(RenderSet::PhaseSort), + sort_phase_system::.in_set(RenderSet::PhaseSort), + ), + ); let prepass_node = PrepassNode::new(&mut render_app.world); let pass_node_3d = MainPass3dNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index 0892299f24..943e2d39b8 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -14,7 +14,7 @@ use bevy_render::{ renderer::RenderDevice, texture::BevyDefault, view::{ExtractedView, ViewTarget}, - RenderApp, RenderSet, + Render, RenderApp, RenderSet, }; mod node; @@ -90,7 +90,7 @@ impl Plugin for FxaaPlugin { render_app .init_resource::() .init_resource::>() - .add_system(prepare_fxaa_pipelines.in_set(RenderSet::Prepare)); + .add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare)); { let fxaa_node = FxaaNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/msaa_writeback.rs b/crates/bevy_core_pipeline/src/msaa_writeback.rs index 2f8122d193..115b085efa 100644 --- a/crates/bevy_core_pipeline/src/msaa_writeback.rs +++ b/crates/bevy_core_pipeline/src/msaa_writeback.rs @@ -6,7 +6,7 @@ use bevy_render::{ render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType}, renderer::RenderContext, view::{Msaa, ViewTarget}, - RenderSet, + Render, RenderSet, }; use bevy_render::{render_resource::*, RenderApp}; @@ -20,7 +20,10 @@ impl Plugin for MsaaWritebackPlugin { return }; - render_app.add_system(queue_msaa_writeback_pipelines.in_set(RenderSet::Queue)); + render_app.add_systems( + Render, + queue_msaa_writeback_pipelines.in_set(RenderSet::Queue), + ); let msaa_writeback_2d = MsaaWritebackNode::new(&mut render_app.world); let msaa_writeback_3d = MsaaWritebackNode::new(&mut render_app.world); let mut graph = render_app.world.resource_mut::(); diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index d6c2f3a062..31ecd12177 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -10,7 +10,7 @@ use bevy_render::render_asset::RenderAssets; use bevy_render::renderer::RenderDevice; use bevy_render::texture::{CompressedImageFormats, Image, ImageSampler, ImageType}; use bevy_render::view::{ViewTarget, ViewUniform}; -use bevy_render::{render_resource::*, RenderApp, RenderSet}; +use bevy_render::{render_resource::*, Render, RenderApp, RenderSet}; mod node; @@ -94,7 +94,10 @@ impl Plugin for TonemappingPlugin { render_app .init_resource::() .init_resource::>() - .add_system(queue_view_tonemapping_pipelines.in_set(RenderSet::Queue)); + .add_systems( + Render, + queue_view_tonemapping_pipelines.in_set(RenderSet::Queue), + ); } } } diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index 441f9f7775..f3594397d5 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -3,7 +3,7 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_render::camera::{CameraOutputMode, ExtractedCamera}; use bevy_render::view::ViewTarget; -use bevy_render::{render_resource::*, RenderApp, RenderSet}; +use bevy_render::{render_resource::*, Render, RenderApp, RenderSet}; mod node; @@ -14,7 +14,10 @@ pub struct UpscalingPlugin; impl Plugin for UpscalingPlugin { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system(queue_view_upscaling_pipelines.in_set(RenderSet::Queue)); + render_app.add_systems( + Render, + queue_view_upscaling_pipelines.in_set(RenderSet::Queue), + ); } } } diff --git a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs index fd6b21117c..5c4415c09b 100644 --- a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs @@ -9,8 +9,8 @@ pub struct EntityCountDiagnosticsPlugin; impl Plugin for EntityCountDiagnosticsPlugin { fn build(&self, app: &mut App) { - app.add_startup_system(Self::setup_system) - .add_system(Self::diagnostic_system); + app.add_systems(Startup, Self::setup_system) + .add_systems(Update, Self::diagnostic_system); } } diff --git a/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs index 8e26025bf2..4dbc3c4cfa 100644 --- a/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs @@ -10,8 +10,8 @@ pub struct FrameTimeDiagnosticsPlugin; impl Plugin for FrameTimeDiagnosticsPlugin { fn build(&self, app: &mut bevy_app::App) { - app.add_startup_system(Self::setup_system) - .add_system(Self::diagnostic_system); + app.add_systems(Startup, Self::setup_system) + .add_systems(Update, Self::diagnostic_system); } } diff --git a/crates/bevy_diagnostic/src/lib.rs b/crates/bevy_diagnostic/src/lib.rs index b2d127f115..854f6e74cd 100644 --- a/crates/bevy_diagnostic/src/lib.rs +++ b/crates/bevy_diagnostic/src/lib.rs @@ -17,8 +17,10 @@ pub struct DiagnosticsPlugin; impl Plugin for DiagnosticsPlugin { fn build(&self, app: &mut App) { - app.init_resource::() - .add_startup_system(system_information_diagnostics_plugin::internal::log_system_info); + app.init_resource::().add_systems( + Startup, + system_information_diagnostics_plugin::internal::log_system_info, + ); } } diff --git a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs index dd80c8c8f2..65f212dcc6 100644 --- a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs @@ -37,9 +37,9 @@ impl Plugin for LogDiagnosticsPlugin { }); if self.debug { - app.add_system(Self::log_diagnostics_debug_system.in_base_set(CoreSet::PostUpdate)); + app.add_systems(PostUpdate, Self::log_diagnostics_debug_system); } else { - app.add_system(Self::log_diagnostics_system.in_base_set(CoreSet::PostUpdate)); + app.add_systems(PostUpdate, Self::log_diagnostics_system); } } } diff --git a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs index 096c9b13a6..b33cba5ca4 100644 --- a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs @@ -14,8 +14,8 @@ use bevy_app::prelude::*; pub struct SystemInformationDiagnosticsPlugin; impl Plugin for SystemInformationDiagnosticsPlugin { fn build(&self, app: &mut App) { - app.add_startup_system(internal::setup_system) - .add_system(internal::diagnostic_system); + app.add_systems(Startup, internal::setup_system) + .add_systems(Update, internal::diagnostic_system); } } diff --git a/crates/bevy_ecs/README.md b/crates/bevy_ecs/README.md index ee9126e1b5..457f386e89 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -150,7 +150,7 @@ fn main() { let mut schedule = Schedule::default(); // Add our system to the schedule - schedule.add_system(movement); + schedule.add_systems(movement); // Run the schedule once. If your app has a "loop", you would run this once per loop schedule.run(&mut world); diff --git a/crates/bevy_ecs/examples/change_detection.rs b/crates/bevy_ecs/examples/change_detection.rs index 0abbf487a0..8bfa18d735 100644 --- a/crates/bevy_ecs/examples/change_detection.rs +++ b/crates/bevy_ecs/examples/change_detection.rs @@ -1,4 +1,4 @@ -use bevy_ecs::{prelude::*, schedule::IntoSystemConfig}; +use bevy_ecs::prelude::*; use rand::Rng; use std::ops::Deref; diff --git a/crates/bevy_ecs/examples/events.rs b/crates/bevy_ecs/examples/events.rs index 2dae9475cf..27bd13b5f6 100644 --- a/crates/bevy_ecs/examples/events.rs +++ b/crates/bevy_ecs/examples/events.rs @@ -16,7 +16,7 @@ fn main() { #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub struct FlushEvents; - schedule.add_system(Events::::update_system.in_set(FlushEvents)); + schedule.add_systems(Events::::update_system.in_set(FlushEvents)); // Add systems sending and receiving events after the events are flushed. schedule.add_systems(( diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index dbafe0d9e3..0e3374f757 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -469,7 +469,7 @@ pub fn derive_schedule_label(input: TokenStream) -> TokenStream { } /// Derive macro generating an impl of the trait `SystemSet`. -#[proc_macro_derive(SystemSet, attributes(system_set))] +#[proc_macro_derive(SystemSet)] pub fn derive_system_set(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut trait_path = bevy_ecs_path(); diff --git a/crates/bevy_ecs/macros/src/set.rs b/crates/bevy_ecs/macros/src/set.rs index 054735c858..66bfb601ec 100644 --- a/crates/bevy_ecs/macros/src/set.rs +++ b/crates/bevy_ecs/macros/src/set.rs @@ -1,9 +1,5 @@ use proc_macro::TokenStream; -use quote::{format_ident, quote, ToTokens}; -use syn::parse::{Parse, ParseStream}; - -pub static SYSTEM_SET_ATTRIBUTE_NAME: &str = "system_set"; -pub static BASE_ATTRIBUTE_NAME: &str = "base"; +use quote::quote; /// Derive a set trait /// @@ -12,55 +8,8 @@ pub static BASE_ATTRIBUTE_NAME: &str = "base"; /// - `input`: The [`syn::DeriveInput`] for the struct that we want to derive the set trait for /// - `trait_path`: The [`syn::Path`] to the set trait pub fn derive_set(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream { - let mut base_trait_path = trait_path.clone(); - let ident = &mut base_trait_path.segments.last_mut().unwrap().ident; - *ident = format_ident!("Base{ident}"); - - let mut free_trait_path = trait_path.clone(); - let ident = &mut free_trait_path.segments.last_mut().unwrap().ident; - *ident = format_ident!("Free{ident}"); - let ident = input.ident; - let mut is_base = false; - for attr in &input.attrs { - if !attr - .path - .get_ident() - .map_or(false, |ident| ident == SYSTEM_SET_ATTRIBUTE_NAME) - { - continue; - } - - attr.parse_args_with(|input: ParseStream| { - let meta = input.parse_terminated::(syn::Meta::parse)?; - for meta in meta { - let ident = meta.path().get_ident().unwrap_or_else(|| { - panic!( - "Unrecognized attribute: `{}`", - meta.path().to_token_stream() - ) - }); - if ident == BASE_ATTRIBUTE_NAME { - if let syn::Meta::Path(_) = meta { - is_base = true; - } else { - panic!( - "The `{BASE_ATTRIBUTE_NAME}` attribute is expected to have no value or arguments", - ); - } - } else { - panic!( - "Unrecognized attribute: `{}`", - meta.path().to_token_stream() - ); - } - } - Ok(()) - }) - .unwrap_or_else(|_| panic!("Invalid `{SYSTEM_SET_ATTRIBUTE_NAME}` attribute format")); - } - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { where_token: Default::default(), @@ -73,28 +22,12 @@ pub fn derive_set(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStrea .unwrap(), ); - let marker_impl = if is_base { - quote! { - impl #impl_generics #base_trait_path for #ident #ty_generics #where_clause {} - } - } else { - quote! { - impl #impl_generics #free_trait_path for #ident #ty_generics #where_clause {} - } - }; - (quote! { impl #impl_generics #trait_path for #ident #ty_generics #where_clause { - fn is_base(&self) -> bool { - #is_base - } - fn dyn_clone(&self) -> std::boxed::Box { std::boxed::Box::new(std::clone::Clone::clone(self)) } } - - #marker_impl }) .into() } diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index 0bf6429175..3cdfc19445 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -939,7 +939,7 @@ mod tests { world.send_event(TestEvent { i: 4 }); let mut schedule = Schedule::new(); - schedule.add_system(|mut events: EventReader| { + schedule.add_systems(|mut events: EventReader| { let mut iter = events.iter(); assert_eq!(iter.next(), Some(&TestEvent { i: 0 })); diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 1c574befe7..8107de37e8 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -39,9 +39,8 @@ pub mod prelude { removal_detection::RemovedComponents, schedule::{ apply_state_transition, apply_system_buffers, common_conditions::*, Condition, - IntoSystemConfig, IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig, - IntoSystemSetConfigs, NextState, OnEnter, OnExit, OnTransition, OnUpdate, Schedule, - Schedules, State, States, SystemSet, + IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig, IntoSystemSetConfigs, NextState, + OnEnter, OnExit, OnTransition, OnUpdate, Schedule, Schedules, State, States, SystemSet, }, system::{ adapter as system_adapter, diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 5a93006a44..0ad7f05917 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -31,7 +31,7 @@ pub trait Condition: sealed::Condition { /// # let mut app = Schedule::new(); /// # let mut world = World::new(); /// # fn my_system() {} - /// app.add_system( + /// app.add_systems( /// // The `resource_equals` run condition will panic since we don't initialize `R`, /// // just like if we used `Res` in a system. /// my_system.run_if(resource_equals(R(0))), @@ -48,7 +48,7 @@ pub trait Condition: sealed::Condition { /// # let mut app = Schedule::new(); /// # let mut world = World::new(); /// # fn my_system() {} - /// app.add_system( + /// app.add_systems( /// // `resource_equals` will only get run if the resource `R` exists. /// my_system.run_if(resource_exists::().and_then(resource_equals(R(0)))), /// ); @@ -86,7 +86,7 @@ pub trait Condition: sealed::Condition { /// # let mut world = World::new(); /// # #[derive(Resource)] struct C(bool); /// # fn my_system(mut c: ResMut) { c.0 = true; } - /// app.add_system( + /// app.add_systems( /// // Only run the system if either `A` or `B` exist. /// my_system.run_if(resource_exists::().or_else(resource_exists::())), /// ); @@ -159,7 +159,7 @@ pub mod common_conditions { /// # let mut app = Schedule::new(); /// # let mut world = World::new(); /// # world.init_resource::(); - /// app.add_system( + /// app.add_systems( /// // `run_once` will only return true the first time it's evaluated /// my_system.run_if(run_once()), /// ); @@ -199,7 +199,7 @@ pub mod common_conditions { /// # struct Counter(u8); /// # let mut app = Schedule::new(); /// # let mut world = World::new(); - /// app.add_system( + /// app.add_systems( /// // `resource_exsists` will only return true if the given resource exsists in the world /// my_system.run_if(resource_exists::()), /// ); @@ -239,7 +239,7 @@ pub mod common_conditions { /// # let mut app = Schedule::new(); /// # let mut world = World::new(); /// # world.init_resource::(); - /// app.add_system( + /// app.add_systems( /// // `resource_equals` will only return true if the given resource equals the given value /// my_system.run_if(resource_equals(Counter(0))), /// ); @@ -276,7 +276,7 @@ pub mod common_conditions { /// # struct Counter(u8); /// # let mut app = Schedule::new(); /// # let mut world = World::new(); - /// app.add_system( + /// app.add_systems( /// // `resource_exists_and_equals` will only return true /// // if the given resource exsists and equals the given value /// my_system.run_if(resource_exists_and_equals(Counter(0))), @@ -319,7 +319,7 @@ pub mod common_conditions { /// # struct Counter(u8); /// # let mut app = Schedule::new(); /// # let mut world = World::new(); - /// app.add_system( + /// app.add_systems( /// // `resource_added` will only return true if the /// // given resource was just added /// my_system.run_if(resource_added::()), @@ -370,7 +370,7 @@ pub mod common_conditions { /// # let mut app = Schedule::new(); /// # let mut world = World::new(); /// # world.init_resource::(); - /// app.add_system( + /// app.add_systems( /// // `resource_changed` will only return true if the /// // given resource was just changed (or added) /// my_system.run_if( @@ -423,7 +423,7 @@ pub mod common_conditions { /// # struct Counter(u8); /// # let mut app = Schedule::new(); /// # let mut world = World::new(); - /// app.add_system( + /// app.add_systems( /// // `resource_exists_and_changed` will only return true if the /// // given resource exsists and was just changed (or added) /// my_system.run_if( @@ -485,7 +485,7 @@ pub mod common_conditions { /// # let mut app = Schedule::new(); /// # let mut world = World::new(); /// # world.init_resource::(); - /// app.add_system( + /// app.add_systems( /// // `resource_changed_or_removed` will only return true if the /// // given resource was just changed or removed (or added) /// my_system.run_if( @@ -555,7 +555,7 @@ pub mod common_conditions { /// # let mut app = Schedule::new(); /// # let mut world = World::new(); /// # world.init_resource::(); - /// app.add_system( + /// app.add_systems( /// // `resource_removed` will only return true if the /// // given resource was just removed /// my_system.run_if(resource_removed::()), @@ -617,7 +617,7 @@ pub mod common_conditions { /// Paused, /// } /// - /// app.add_system( + /// app.add_systems( /// // `state_exists` will only return true if the /// // given state exsists /// my_system.run_if(state_exists::()), @@ -784,7 +784,7 @@ pub mod common_conditions { /// /// world.init_resource::>(); /// - /// app.add_system( + /// app.add_systems( /// // `state_changed` will only return true if the /// // given states value has just been updated or /// // the state has just been added @@ -826,9 +826,9 @@ pub mod common_conditions { /// # let mut world = World::new(); /// # world.init_resource::(); /// # world.init_resource::>(); - /// # app.add_system(Events::::update_system.before(my_system)); + /// # app.add_systems(Events::::update_system.before(my_system)); /// - /// app.add_system( + /// app.add_systems( /// my_system.run_if(on_event::()), /// ); /// @@ -868,7 +868,7 @@ pub mod common_conditions { /// # let mut app = Schedule::new(); /// # let mut world = World::new(); /// # world.init_resource::(); - /// app.add_system( + /// app.add_systems( /// my_system.run_if(any_with_component::()), /// ); /// @@ -904,7 +904,7 @@ pub mod common_conditions { /// # let mut app = Schedule::new(); /// # let mut world = World::new(); /// # world.init_resource::(); - /// app.add_system( + /// app.add_systems( /// // `not` will inverse any condition you pass in. /// // Since the condition we choose always returns true /// // this system will never run @@ -1073,15 +1073,11 @@ where mod tests { use super::{common_conditions::*, Condition}; use crate as bevy_ecs; - use crate::{ - change_detection::ResMut, - component::Component, - schedule::{ - common_conditions::not, IntoSystemConfig, IntoSystemConfigs, Schedule, State, States, - }, - system::Local, - world::World, - }; + use crate::component::Component; + use crate::schedule::IntoSystemConfigs; + use crate::schedule::{common_conditions::not, State, States}; + use crate::system::Local; + use crate::{change_detection::ResMut, schedule::Schedule, world::World}; use bevy_ecs_macros::Resource; #[derive(Resource, Default)] @@ -1103,7 +1099,7 @@ mod tests { let mut schedule = Schedule::new(); // Run every other cycle - schedule.add_system(increment_counter.run_if(every_other_time)); + schedule.add_systems(increment_counter.run_if(every_other_time)); schedule.run(&mut world); schedule.run(&mut world); @@ -1113,7 +1109,7 @@ mod tests { assert_eq!(world.resource::().0, 2); // Run every other cycle oppsite to the last one - schedule.add_system(increment_counter.run_if(not(every_other_time))); + schedule.add_systems(increment_counter.run_if(not(every_other_time))); schedule.run(&mut world); schedule.run(&mut world); @@ -1130,9 +1126,9 @@ mod tests { let mut schedule = Schedule::new(); // Always run - schedule.add_system(increment_counter.run_if(every_other_time.or_else(|| true))); + schedule.add_systems(increment_counter.run_if(every_other_time.or_else(|| true))); // Run every other cycle - schedule.add_system(increment_counter.run_if(every_other_time.and_then(|| true))); + schedule.add_systems(increment_counter.run_if(every_other_time.and_then(|| true))); schedule.run(&mut world); assert_eq!(world.resource::().0, 2); @@ -1147,9 +1143,9 @@ mod tests { let mut schedule = Schedule::new(); // Run every other cycle - schedule.add_system(increment_counter.run_if(every_other_time).run_if(|| true)); + schedule.add_systems(increment_counter.run_if(every_other_time).run_if(|| true)); // Never run - schedule.add_system(increment_counter.run_if(every_other_time).run_if(|| false)); + schedule.add_systems(increment_counter.run_if(every_other_time).run_if(|| false)); schedule.run(&mut world); assert_eq!(world.resource::().0, 1); @@ -1166,7 +1162,7 @@ mod tests { // This should never run, if multiple run conditions worked // like an OR condition then it would always run - schedule.add_system( + schedule.add_systems( increment_counter .run_if(every_other_time) .run_if(not(every_other_time)), diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index ce0a45ae44..50aa7bfe90 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -5,57 +5,11 @@ use crate::{ condition::{BoxedCondition, Condition}, graph_utils::{Ambiguity, Dependency, DependencyKind, GraphInfo}, set::{BoxedSystemSet, IntoSystemSet, SystemSet}, + ScheduleLabel, }, system::{BoxedSystem, IntoSystem, System}, }; -use super::{BaseSystemSet, FreeSystemSet}; - -/// A [`SystemSet`] with scheduling metadata. -pub struct SystemSetConfig { - pub(super) set: BoxedSystemSet, - pub(super) graph_info: GraphInfo, - pub(super) conditions: Vec, -} - -impl SystemSetConfig { - fn new(set: BoxedSystemSet) -> Self { - // system type sets are automatically populated - // to avoid unintentionally broad changes, they cannot be configured - assert!( - set.system_type().is_none(), - "configuring system type sets is not allowed" - ); - - Self { - set, - graph_info: GraphInfo::system_set(), - conditions: Vec::new(), - } - } -} - -/// A [`System`] with scheduling metadata. -pub struct SystemConfig { - pub(super) system: BoxedSystem, - pub(super) graph_info: GraphInfo, - pub(super) conditions: Vec, -} - -impl SystemConfig { - fn new(system: BoxedSystem) -> Self { - // include system in its default sets - let sets = system.default_system_sets().into_iter().collect(); - let mut graph_info = GraphInfo::system(); - graph_info.sets = sets; - Self { - system, - graph_info, - conditions: Vec::new(), - } - } -} - fn new_condition(condition: impl Condition) -> BoxedCondition { let condition_system = IntoSystem::into_system(condition); assert!( @@ -79,6 +33,370 @@ fn ambiguous_with(graph_info: &mut GraphInfo, set: BoxedSystemSet) { } } +impl IntoSystemConfigs for F +where + F: IntoSystem<(), (), Marker>, +{ + fn into_configs(self) -> SystemConfigs { + SystemConfigs::new_system(Box::new(IntoSystem::into_system(self))) + } +} + +impl IntoSystemConfigs<()> for BoxedSystem<(), ()> { + fn into_configs(self) -> SystemConfigs { + SystemConfigs::new_system(self) + } +} + +pub struct SystemConfig { + pub(crate) system: BoxedSystem, + pub(crate) graph_info: GraphInfo, + pub(crate) conditions: Vec, +} + +/// A collection of [`SystemConfig`]. +pub enum SystemConfigs { + SystemConfig(SystemConfig), + Configs { + configs: Vec, + /// If `true`, adds `before -> after` ordering constraints between the successive elements. + chained: bool, + }, +} + +impl SystemConfigs { + fn new_system(system: BoxedSystem) -> Self { + // include system in its default sets + let sets = system.default_system_sets().into_iter().collect(); + Self::SystemConfig(SystemConfig { + system, + graph_info: GraphInfo { + sets, + ..Default::default() + }, + conditions: Vec::new(), + }) + } + + fn in_set_inner(&mut self, set: BoxedSystemSet) { + match self { + SystemConfigs::SystemConfig(config) => { + config.graph_info.sets.push(set); + } + SystemConfigs::Configs { configs, .. } => { + for config in configs { + config.in_set_inner(set.dyn_clone()); + } + } + } + } + + fn before_inner(&mut self, set: BoxedSystemSet) { + match self { + SystemConfigs::SystemConfig(config) => { + config + .graph_info + .dependencies + .push(Dependency::new(DependencyKind::Before, set)); + } + SystemConfigs::Configs { configs, .. } => { + for config in configs { + config.before_inner(set.dyn_clone()); + } + } + } + } + + fn after_inner(&mut self, set: BoxedSystemSet) { + match self { + SystemConfigs::SystemConfig(config) => { + config + .graph_info + .dependencies + .push(Dependency::new(DependencyKind::After, set)); + } + SystemConfigs::Configs { configs, .. } => { + for config in configs { + config.after_inner(set.dyn_clone()); + } + } + } + } + + fn distributive_run_if_inner(&mut self, condition: impl Condition + Clone) { + match self { + SystemConfigs::SystemConfig(config) => { + config.conditions.push(new_condition(condition)); + } + SystemConfigs::Configs { configs, .. } => { + for config in configs { + config.distributive_run_if_inner(condition.clone()); + } + } + } + } + + fn ambiguous_with_inner(&mut self, set: BoxedSystemSet) { + match self { + SystemConfigs::SystemConfig(config) => { + ambiguous_with(&mut config.graph_info, set); + } + SystemConfigs::Configs { configs, .. } => { + for config in configs { + config.ambiguous_with_inner(set.dyn_clone()); + } + } + } + } + + fn ambiguous_with_all_inner(&mut self) { + match self { + SystemConfigs::SystemConfig(config) => { + config.graph_info.ambiguous_with = Ambiguity::IgnoreAll; + } + SystemConfigs::Configs { configs, .. } => { + for config in configs { + config.ambiguous_with_all_inner(); + } + } + } + } + + fn run_if_inner(&mut self, condition: BoxedCondition) { + match self { + SystemConfigs::SystemConfig(config) => { + config.conditions.push(condition); + } + SystemConfigs::Configs { .. } => { + todo!("run_if is not implemented for groups of systems yet") + } + } + } +} + +/// Types that can convert into a [`SystemConfigs`]. +pub trait IntoSystemConfigs +where + Self: Sized, +{ + /// Convert into a [`SystemConfigs`]. + #[doc(hidden)] + fn into_configs(self) -> SystemConfigs; + + /// Add these systems to the provided `set`. + #[track_caller] + fn in_set(self, set: impl SystemSet) -> SystemConfigs { + self.into_configs().in_set(set) + } + + /// Run before all systems in `set`. + fn before(self, set: impl IntoSystemSet) -> SystemConfigs { + self.into_configs().before(set) + } + + /// Run after all systems in `set`. + fn after(self, set: impl IntoSystemSet) -> SystemConfigs { + self.into_configs().after(set) + } + + /// Add a run condition to each contained system. + /// + /// Each system will receive its own clone of the [`Condition`] and will only run + /// if the `Condition` is true. + /// + /// Each individual condition will be evaluated at most once (per schedule run), + /// right before the corresponding system prepares to run. + /// + /// This is equivalent to calling [`run_if`](IntoSystemConfigs::run_if) on each individual + /// system, as shown below: + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # let mut app = Schedule::new(); + /// # fn a() {} + /// # fn b() {} + /// # fn condition() -> bool { true } + /// app.add_systems((a, b).distributive_run_if(condition)); + /// app.add_systems((a.run_if(condition), b.run_if(condition))); + /// ``` + /// + /// # Note + /// + /// Because the conditions are evaluated separately for each system, there is no guarantee + /// that all evaluations in a single schedule run will yield the same result. If another + /// system is run inbetween two evaluations it could cause the result of the condition to change. + /// + /// Use [`run_if`](IntoSystemSetConfig::run_if) on a [`SystemSet`] if you want to make sure + /// that either all or none of the systems are run, or you don't want to evaluate the run + /// condition for each contained system separately. + fn distributive_run_if(self, condition: impl Condition + Clone) -> SystemConfigs { + self.into_configs().distributive_run_if(condition) + } + + /// Run the systems only if the [`Condition`] is `true`. + /// + /// The `Condition` will be evaluated at most once (per schedule run), + /// the first time a system in this set prepares to run. + fn run_if(self, condition: impl Condition) -> SystemConfigs { + self.into_configs().run_if(condition) + } + + /// Suppress warnings and errors that would result from these systems having ambiguities + /// (conflicting access but indeterminate order) with systems in `set`. + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfigs { + self.into_configs().ambiguous_with(set) + } + + /// Suppress warnings and errors that would result from these systems having ambiguities + /// (conflicting access but indeterminate order) with any other system. + fn ambiguous_with_all(self) -> SystemConfigs { + self.into_configs().ambiguous_with_all() + } + + /// Treat this collection as a sequence of systems. + /// + /// Ordering constraints will be applied between the successive elements. + fn chain(self) -> SystemConfigs { + self.into_configs().chain() + } + + /// This used to add the system to `CoreSchedule::Startup`. + /// This was a shorthand for `self.in_schedule(CoreSchedule::Startup)`. + /// + /// # Panics + /// + /// Always panics. Please migrate to the new `App::add_systems` with the `Startup` schedule: + /// Ex: `app.add_system(foo.on_startup())` -> `app.add_systems(Startup, foo)` + #[deprecated( + since = "0.11.0", + note = "`app.add_system(foo.on_startup())` has been deprecated in favor of `app.add_systems(Startup, foo)`. Please migrate to that API." + )] + fn on_startup(self) -> SystemConfigs { + panic!("`app.add_system(foo.on_startup())` has been deprecated in favor of `app.add_systems(Startup, foo)`. Please migrate to that API."); + } + + /// This used to add the system to the provided `schedule`. + /// + /// # Panics + /// + /// Always panics. Please migrate to the new `App::add_systems`: + /// Ex: `app.add_system(foo.in_schedule(SomeSchedule))` -> `app.add_systems(SomeSchedule, foo)` + #[deprecated( + since = "0.11.0", + note = "`app.add_system(foo.in_schedule(SomeSchedule))` has been deprecated in favor of `app.add_systems(SomeSchedule, foo)`. Please migrate to that API." + )] + fn in_schedule(self, _schedule: impl ScheduleLabel) -> SystemConfigs { + panic!("`app.add_system(foo.in_schedule(SomeSchedule))` has been deprecated in favor of `app.add_systems(SomeSchedule, foo)`. Please migrate to that API."); + } +} + +impl IntoSystemConfigs<()> for SystemConfigs { + fn into_configs(self) -> Self { + self + } + + #[track_caller] + fn in_set(mut self, set: impl SystemSet) -> Self { + assert!( + set.system_type().is_none(), + "adding arbitrary systems to a system type set is not allowed" + ); + + self.in_set_inner(set.dyn_clone()); + + self + } + + fn before(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + self.before_inner(set.dyn_clone()); + self + } + + fn after(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + self.after_inner(set.dyn_clone()); + self + } + + fn distributive_run_if(mut self, condition: impl Condition + Clone) -> SystemConfigs { + self.distributive_run_if_inner(condition); + self + } + + fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + self.ambiguous_with_inner(set.dyn_clone()); + self + } + + fn ambiguous_with_all(mut self) -> Self { + self.ambiguous_with_all_inner(); + self + } + + fn run_if(mut self, condition: impl Condition) -> SystemConfigs { + self.run_if_inner(new_condition(condition)); + self + } + + fn chain(mut self) -> Self { + match &mut self { + SystemConfigs::SystemConfig(_) => { /* no op */ } + SystemConfigs::Configs { chained, .. } => { + *chained = true; + } + } + self + } +} + +pub struct SystemConfigTupleMarker; + +macro_rules! impl_system_collection { + ($(($param: ident, $sys: ident)),*) => { + impl<$($param, $sys),*> IntoSystemConfigs<(SystemConfigTupleMarker, $($param,)*)> for ($($sys,)*) + where + $($sys: IntoSystemConfigs<$param>),* + { + #[allow(non_snake_case)] + fn into_configs(self) -> SystemConfigs { + let ($($sys,)*) = self; + SystemConfigs::Configs { + configs: vec![$($sys.into_configs(),)*], + chained: false, + } + } + } + } +} + +all_tuples!(impl_system_collection, 1, 20, P, S); + +/// A [`SystemSet`] with scheduling metadata. +pub struct SystemSetConfig { + pub(super) set: BoxedSystemSet, + pub(super) graph_info: GraphInfo, + pub(super) conditions: Vec, +} + +impl SystemSetConfig { + fn new(set: BoxedSystemSet) -> Self { + // system type sets are automatically populated + // to avoid unintentionally broad changes, they cannot be configured + assert!( + set.system_type().is_none(), + "configuring system type sets is not allowed" + ); + + Self { + set, + graph_info: GraphInfo::default(), + conditions: Vec::new(), + } + } +} + /// Types that can be converted into a [`SystemSetConfig`]. /// /// This has been implemented for all types that implement [`SystemSet`] and boxed trait objects. @@ -88,18 +406,9 @@ pub trait IntoSystemSetConfig: Sized { fn into_config(self) -> SystemSetConfig; /// Add to the provided `set`. #[track_caller] - fn in_set(self, set: impl FreeSystemSet) -> SystemSetConfig { + fn in_set(self, set: impl SystemSet) -> SystemSetConfig { self.into_config().in_set(set) } - /// Add to the provided "base" `set`. For more information on base sets, see [`SystemSet::is_base`]. - #[track_caller] - fn in_base_set(self, set: impl BaseSystemSet) -> SystemSetConfig { - self.into_config().in_base_set(set) - } - /// Add this set to the schedules's default base set. - fn in_default_base_set(self) -> SystemSetConfig { - self.into_config().in_default_base_set() - } /// Run before all systems in `set`. fn before(self, set: impl IntoSystemSet) -> SystemSetConfig { self.into_config().before(set) @@ -125,6 +434,35 @@ pub trait IntoSystemSetConfig: Sized { fn ambiguous_with_all(self) -> SystemSetConfig { self.into_config().ambiguous_with_all() } + + /// This used to configure the set in the `CoreSchedule::Startup` schedule. + /// This was a shorthand for `self.in_schedule(CoreSchedule::Startup)`. + /// + /// # Panics + /// + /// Always panics. Please migrate to the new `App::configure_set` with the `Startup` schedule: + /// Ex: `app.configure_set(MySet.on_startup())` -> `app.configure_set(Startup, MySet)` + #[deprecated( + since = "0.11.0", + note = "`app.configure_set(MySet.on_startup())` has been deprecated in favor of `app.configure_set(Startup, MySet)`. Please migrate to that API." + )] + fn on_startup(self) -> SystemSetConfigs { + panic!("`app.configure_set(MySet.on_startup())` has been deprecated in favor of `app.configure_set(Startup, MySet)`. Please migrate to that API."); + } + + /// This used to configure the set in the provided `schedule`. + /// + /// # Panics + /// + /// Always panics. Please migrate to the new `App::configure_set`: + /// Ex: `app.configure_set(MySet.in_schedule(SomeSchedule))` -> `app.configure_set(SomeSchedule, MySet)` + #[deprecated( + since = "0.11.0", + note = "`app.configure_set(MySet.in_schedule(SomeSchedule))` has been deprecated in favor of `app.configure_set(SomeSchedule, MySet)`. Please migrate to that API." + )] + fn in_schedule(self, _schedule: impl ScheduleLabel) -> SystemSetConfigs { + panic!("`app.configure_set(MySet.in_schedule(SomeSchedule))` has been deprecated in favor of `app.configure_set(SomeSchedule, MySet)`. Please migrate to that API."); + } } impl IntoSystemSetConfig for S { @@ -150,41 +488,10 @@ impl IntoSystemSetConfig for SystemSetConfig { set.system_type().is_none(), "adding arbitrary systems to a system type set is not allowed" ); - assert!( - !set.is_base(), - "Sets cannot be added to 'base' system sets using 'in_set'. Use 'in_base_set' instead." - ); - assert!( - !self.set.is_base(), - "Base system sets cannot be added to other sets." - ); self.graph_info.sets.push(Box::new(set)); self } - #[track_caller] - fn in_base_set(mut self, set: impl SystemSet) -> Self { - assert!( - set.system_type().is_none(), - "System type sets cannot be base sets." - ); - assert!( - set.is_base(), - "Sets cannot be added to normal sets using 'in_base_set'. Use 'in_set' instead." - ); - assert!( - !self.set.is_base(), - "Base system sets cannot be added to other sets." - ); - self.graph_info.set_base_set(Box::new(set)); - self - } - - fn in_default_base_set(mut self) -> SystemSetConfig { - self.graph_info.add_default_base_set = true; - self - } - fn before(mut self, set: impl IntoSystemSet) -> Self { self.graph_info.dependencies.push(Dependency::new( DependencyKind::Before, @@ -217,332 +524,6 @@ impl IntoSystemSetConfig for SystemSetConfig { } } -/// Types that can be converted into a [`SystemConfig`]. -/// -/// This has been implemented for boxed [`System`](crate::system::System) -/// trait objects and all functions that turn into such. -pub trait IntoSystemConfig: Sized -where - Config: IntoSystemConfig<(), Config>, -{ - /// Convert into a [`SystemConfig`]. - #[doc(hidden)] - fn into_config(self) -> Config; - /// Add to `set` membership. - #[track_caller] - fn in_set(self, set: impl FreeSystemSet) -> Config { - self.into_config().in_set(set) - } - /// Add to the provided "base" `set`. For more information on base sets, see [`SystemSet::is_base`]. - #[track_caller] - fn in_base_set(self, set: impl BaseSystemSet) -> Config { - self.into_config().in_base_set(set) - } - /// Don't add this system to the schedules's default set. - fn no_default_base_set(self) -> Config { - self.into_config().no_default_base_set() - } - /// Run before all systems in `set`. - fn before(self, set: impl IntoSystemSet) -> Config { - self.into_config().before(set) - } - /// Run after all systems in `set`. - fn after(self, set: impl IntoSystemSet) -> Config { - self.into_config().after(set) - } - /// Run only if the [`Condition`] is `true`. - /// - /// The `Condition` will be evaluated at most once (per schedule run), - /// when the system prepares to run. - fn run_if(self, condition: impl Condition) -> Config { - self.into_config().run_if(condition) - } - /// Suppress warnings and errors that would result from this system having ambiguities - /// (conflicting access but indeterminate order) with systems in `set`. - fn ambiguous_with(self, set: impl IntoSystemSet) -> Config { - self.into_config().ambiguous_with(set) - } - /// Suppress warnings and errors that would result from this system having ambiguities - /// (conflicting access but indeterminate order) with any other system. - fn ambiguous_with_all(self) -> Config { - self.into_config().ambiguous_with_all() - } -} - -impl IntoSystemConfig for F -where - F: IntoSystem<(), (), Marker>, -{ - fn into_config(self) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))) - } -} - -impl IntoSystemConfig<()> for BoxedSystem<(), ()> { - fn into_config(self) -> SystemConfig { - SystemConfig::new(self) - } -} - -impl IntoSystemConfig<()> for SystemConfig { - fn into_config(self) -> Self { - self - } - - #[track_caller] - fn in_set(mut self, set: impl SystemSet) -> Self { - assert!( - set.system_type().is_none(), - "adding arbitrary systems to a system type set is not allowed" - ); - assert!( - !set.is_base(), - "Systems cannot be added to 'base' system sets using 'in_set'. Use 'in_base_set' instead." - ); - self.graph_info.sets.push(Box::new(set)); - self - } - - #[track_caller] - fn in_base_set(mut self, set: impl SystemSet) -> Self { - assert!( - set.system_type().is_none(), - "System type sets cannot be base sets." - ); - assert!( - set.is_base(), - "Systems cannot be added to normal sets using 'in_base_set'. Use 'in_set' instead." - ); - self.graph_info.set_base_set(Box::new(set)); - self - } - - fn no_default_base_set(mut self) -> SystemConfig { - self.graph_info.add_default_base_set = false; - self - } - - fn before(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info.dependencies.push(Dependency::new( - DependencyKind::Before, - Box::new(set.into_system_set()), - )); - self - } - - fn after(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info.dependencies.push(Dependency::new( - DependencyKind::After, - Box::new(set.into_system_set()), - )); - self - } - - fn run_if(mut self, condition: impl Condition) -> Self { - self.conditions.push(new_condition(condition)); - self - } - - fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { - ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); - self - } - - fn ambiguous_with_all(mut self) -> Self { - self.graph_info.ambiguous_with = Ambiguity::IgnoreAll; - self - } -} - -/// A collection of [`SystemConfig`]. -pub struct SystemConfigs { - pub(super) systems: Vec, - /// If `true`, adds `before -> after` ordering constraints between the successive elements. - pub(super) chained: bool, -} - -/// Types that can convert into a [`SystemConfigs`]. -pub trait IntoSystemConfigs -where - Self: Sized, -{ - /// Convert into a [`SystemConfigs`]. - #[doc(hidden)] - fn into_configs(self) -> SystemConfigs; - - /// Add these systems to the provided `set`. - #[track_caller] - fn in_set(self, set: impl FreeSystemSet) -> SystemConfigs { - self.into_configs().in_set(set) - } - - /// Add these systems to the provided "base" `set`. For more information on base sets, see [`SystemSet::is_base`]. - #[track_caller] - fn in_base_set(self, set: impl BaseSystemSet) -> SystemConfigs { - self.into_configs().in_base_set(set) - } - - /// Run before all systems in `set`. - fn before(self, set: impl IntoSystemSet) -> SystemConfigs { - self.into_configs().before(set) - } - - /// Run after all systems in `set`. - fn after(self, set: impl IntoSystemSet) -> SystemConfigs { - self.into_configs().after(set) - } - - /// Add a run condition to each contained system. - /// - /// The [`Condition`] must be [`Clone`]. Each system will receive its own clone - /// of the `Condition` and will only run if the `Condition` is true. - /// - /// Each individual condition will be evaluated at most once (per schedule run), - /// right before the corresponding system prepares to run. - /// - /// This is equivalent to calling [`run_if`](IntoSystemConfig::run_if) on each individual - /// system, as shown below: - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # let mut app = Schedule::new(); - /// # fn a() {} - /// # fn b() {} - /// # fn condition() -> bool { true } - /// app.add_systems((a, b).distributive_run_if(condition)); - /// app.add_systems((a.run_if(condition), b.run_if(condition))); - /// ``` - /// - /// # Note - /// - /// Because the conditions are evaluated separately for each system, there is no guarantee - /// that all evaluations in a single schedule run will yield the same result. If another - /// system is run inbetween two evaluations it could cause the result of the condition to change. - /// - /// Use [`run_if`](IntoSystemSetConfig::run_if) on a [`SystemSet`] if you want to make sure - /// that either all or none of the systems are run, or you don't want to evaluate the run - /// condition for each contained system separately. - /// - /// The [`Condition`] is cloned for each system. - /// Cloned instances of [`FunctionSystem`](crate::system::FunctionSystem) will be de-initialized. - fn distributive_run_if(self, condition: impl Condition + Clone) -> SystemConfigs { - self.into_configs().distributive_run_if(condition) - } - - /// Suppress warnings and errors that would result from these systems having ambiguities - /// (conflicting access but indeterminate order) with systems in `set`. - fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfigs { - self.into_configs().ambiguous_with(set) - } - - /// Suppress warnings and errors that would result from these systems having ambiguities - /// (conflicting access but indeterminate order) with any other system. - fn ambiguous_with_all(self) -> SystemConfigs { - self.into_configs().ambiguous_with_all() - } - - /// Treat this collection as a sequence of systems. - /// - /// Ordering constraints will be applied between the successive elements. - fn chain(self) -> SystemConfigs { - self.into_configs().chain() - } -} - -impl IntoSystemConfigs<()> for SystemConfigs { - fn into_configs(self) -> Self { - self - } - - #[track_caller] - fn in_set(mut self, set: impl SystemSet) -> Self { - assert!( - set.system_type().is_none(), - "adding arbitrary systems to a system type set is not allowed" - ); - assert!( - !set.is_base(), - "Systems cannot be added to 'base' system sets using 'in_set'. Use 'in_base_set' instead." - ); - for config in &mut self.systems { - config.graph_info.sets.push(set.dyn_clone()); - } - - self - } - - #[track_caller] - fn in_base_set(mut self, set: impl SystemSet) -> Self { - assert!( - set.system_type().is_none(), - "System type sets cannot be base sets." - ); - assert!( - set.is_base(), - "Systems cannot be added to normal sets using 'in_base_set'. Use 'in_set' instead." - ); - for config in &mut self.systems { - config.graph_info.set_base_set(set.dyn_clone()); - } - - self - } - - fn before(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - for config in &mut self.systems { - config - .graph_info - .dependencies - .push(Dependency::new(DependencyKind::Before, set.dyn_clone())); - } - - self - } - - fn after(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - for config in &mut self.systems { - config - .graph_info - .dependencies - .push(Dependency::new(DependencyKind::After, set.dyn_clone())); - } - - self - } - - fn distributive_run_if(mut self, condition: impl Condition + Clone) -> SystemConfigs { - for config in &mut self.systems { - config.conditions.push(new_condition(condition.clone())); - } - - self - } - - fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - for config in &mut self.systems { - ambiguous_with(&mut config.graph_info, set.dyn_clone()); - } - - self - } - - fn ambiguous_with_all(mut self) -> Self { - for config in &mut self.systems { - config.graph_info.ambiguous_with = Ambiguity::IgnoreAll; - } - - self - } - - fn chain(mut self) -> Self { - self.chained = true; - self - } -} - /// A collection of [`SystemSetConfig`]. pub struct SystemSetConfigs { pub(super) sets: Vec, @@ -561,16 +542,10 @@ where /// Add these system sets to the provided `set`. #[track_caller] - fn in_set(self, set: impl FreeSystemSet) -> SystemSetConfigs { + fn in_set(self, set: impl SystemSet) -> SystemSetConfigs { self.into_configs().in_set(set) } - /// Add these system sets to the provided "base" `set`. For more information on base sets, see [`SystemSet::is_base`]. - #[track_caller] - fn in_base_set(self, set: impl BaseSystemSet) -> SystemSetConfigs { - self.into_configs().in_base_set(set) - } - /// Run before all systems in `set`. fn before(self, set: impl IntoSystemSet) -> SystemSetConfigs { self.into_configs().before(set) @@ -599,6 +574,35 @@ where fn chain(self) -> SystemSetConfigs { self.into_configs().chain() } + + /// This used to configure the sets in the `CoreSchedule::Startup` schedule. + /// This was a shorthand for `self.in_schedule(CoreSchedule::Startup)`. + /// + /// # Panics + /// + /// Always panics. Please migrate to the new `App::configure_sets` with the `Startup` schedule: + /// Ex: `app.configure_sets((A, B).on_startup())` -> `app.configure_sets(Startup, (A, B))` + #[deprecated( + since = "0.11.0", + note = "`app.configure_sets((A, B).on_startup())` has been deprecated in favor of `app.configure_sets(Startup, (A, B))`. Please migrate to that API." + )] + fn on_startup(self) -> SystemSetConfigs { + panic!("`app.configure_sets((A, B).on_startup())` has been deprecated in favor of `app.configure_sets(Startup, (A, B))`. Please migrate to that API."); + } + + /// This used to configure the sets in the provided `schedule`. + /// + /// # Panics + /// + /// Always panics. Please migrate to the new `App::configure_set`: + /// Ex: `app.configure_sets((A, B).in_schedule(SomeSchedule))` -> `app.configure_sets(SomeSchedule, (A, B))` + #[deprecated( + since = "0.11.0", + note = "`app.configure_sets((A, B).in_schedule(SomeSchedule))` has been deprecated in favor of `app.configure_sets(SomeSchedule, (A, B))`. Please migrate to that API." + )] + fn in_schedule(self, _schedule: impl ScheduleLabel) -> SystemSetConfigs { + panic!("`app.configure_sets((A, B).in_schedule(SomeSchedule))` has been deprecated in favor of `app.configure_sets(SomeSchedule, (A, B))`. Please migrate to that API."); + } } impl IntoSystemSetConfigs for SystemSetConfigs { @@ -612,42 +616,13 @@ impl IntoSystemSetConfigs for SystemSetConfigs { set.system_type().is_none(), "adding arbitrary systems to a system type set is not allowed" ); - assert!( - !set.is_base(), - "Sets cannot be added to 'base' system sets using 'in_set'. Use 'in_base_set' instead." - ); for config in &mut self.sets { - assert!( - !config.set.is_base(), - "Base system sets cannot be added to other sets." - ); config.graph_info.sets.push(set.dyn_clone()); } self } - #[track_caller] - fn in_base_set(mut self, set: impl SystemSet) -> Self { - assert!( - set.system_type().is_none(), - "System type sets cannot be base sets." - ); - assert!( - set.is_base(), - "Sets cannot be added to normal sets using 'in_base_set'. Use 'in_set' instead." - ); - for config in &mut self.sets { - assert!( - !config.set.is_base(), - "Base system sets cannot be added to other sets." - ); - config.graph_info.set_base_set(set.dyn_clone()); - } - - self - } - fn before(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); for config in &mut self.sets { @@ -695,24 +670,6 @@ impl IntoSystemSetConfigs for SystemSetConfigs { } } -macro_rules! impl_system_collection { - ($(($param: ident, $sys: ident)),*) => { - impl<$($param, $sys),*> IntoSystemConfigs<($($param,)*)> for ($($sys,)*) - where - $($sys: IntoSystemConfig<$param>),* - { - #[allow(non_snake_case)] - fn into_configs(self) -> SystemConfigs { - let ($($sys,)*) = self; - SystemConfigs { - systems: vec![$($sys.into_config(),)*], - chained: false, - } - } - } - } -} - macro_rules! impl_system_set_collection { ($($set: ident),*) => { impl<$($set: IntoSystemSetConfig),*> IntoSystemSetConfigs for ($($set,)*) @@ -729,5 +686,4 @@ macro_rules! impl_system_set_collection { } } -all_tuples!(impl_system_collection, 0, 15, P, S); all_tuples!(impl_system_set_collection, 0, 15, S); diff --git a/crates/bevy_ecs/src/schedule/graph_utils.rs b/crates/bevy_ecs/src/schedule/graph_utils.rs index f7e28e6bb5..4a0311c8be 100644 --- a/crates/bevy_ecs/src/schedule/graph_utils.rs +++ b/crates/bevy_ecs/src/schedule/graph_utils.rs @@ -67,56 +67,14 @@ pub(crate) enum Ambiguity { IgnoreAll, } -#[derive(Clone)] +#[derive(Clone, Default)] pub(crate) struct GraphInfo { pub(crate) sets: Vec, pub(crate) dependencies: Vec, pub(crate) ambiguous_with: Ambiguity, - pub(crate) add_default_base_set: bool, pub(crate) base_set: Option, } -impl Default for GraphInfo { - fn default() -> Self { - GraphInfo { - sets: Vec::new(), - base_set: None, - dependencies: Vec::new(), - ambiguous_with: Ambiguity::default(), - add_default_base_set: true, - } - } -} - -impl GraphInfo { - pub(crate) fn system() -> GraphInfo { - GraphInfo { - // systems get the default base set automatically - add_default_base_set: true, - ..Default::default() - } - } - - pub(crate) fn system_set() -> GraphInfo { - GraphInfo { - // sets do not get the default base set automatically - add_default_base_set: false, - ..Default::default() - } - } - - #[track_caller] - pub(crate) fn set_base_set(&mut self, set: BoxedSystemSet) { - if let Some(current) = &self.base_set { - panic!( - "Cannot set the base set because base set {current:?} has already been configured." - ); - } else { - self.base_set = Some(set); - } - } -} - /// Converts 2D row-major pair of indices into a 1D array index. pub(crate) fn index(row: usize, col: usize, num_cols: usize) -> usize { debug_assert!(col < num_cols); diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 0a5f92a4dd..05aaba9f8b 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -23,7 +23,7 @@ mod tests { use std::sync::atomic::{AtomicU32, Ordering}; pub use crate as bevy_ecs; - pub use crate::schedule::{IntoSystemConfig, IntoSystemSetConfig, Schedule, SystemSet}; + pub use crate::schedule::{IntoSystemSetConfig, Schedule, SystemSet}; pub use crate::system::{Res, ResMut}; pub use crate::{prelude::World, system::Resource}; @@ -75,7 +75,7 @@ mod tests { world.init_resource::(); - schedule.add_system(make_function_system(0)); + schedule.add_systems(make_function_system(0)); schedule.run(&mut world); assert_eq!(world.resource::().0, vec![0]); @@ -88,7 +88,7 @@ mod tests { world.init_resource::(); - schedule.add_system(make_exclusive_system(0)); + schedule.add_systems(make_exclusive_system(0)); schedule.run(&mut world); assert_eq!(world.resource::().0, vec![0]); @@ -108,7 +108,7 @@ mod tests { for _ in 0..thread_count { let inner = barrier.clone(); - schedule.add_system(move || { + schedule.add_systems(move || { inner.wait(); }); } @@ -195,6 +195,50 @@ mod tests { schedule.run(&mut world); assert_eq!(world.resource::().0, vec![0, 1, 2, 3]); } + + #[test] + fn add_systems_correct_order_nested() { + let mut world = World::new(); + let mut schedule = Schedule::new(); + + world.init_resource::(); + + schedule.add_systems( + ( + (make_function_system(0), make_function_system(1)).chain(), + make_function_system(2), + (make_function_system(3), make_function_system(4)).chain(), + ( + make_function_system(5), + (make_function_system(6), make_function_system(7)), + ), + ( + (make_function_system(8), make_function_system(9)).chain(), + make_function_system(10), + ), + ) + .chain(), + ); + + schedule.run(&mut world); + let order = &world.resource::().0; + assert_eq!( + &order[0..5], + &[0, 1, 2, 3, 4], + "first five items should be exactly ordered" + ); + let unordered = &order[5..8]; + assert!( + unordered.contains(&5) && unordered.contains(&6) && unordered.contains(&7), + "unordered must be 5, 6, and 7 in any order" + ); + let partially_ordered = &order[8..11]; + assert!( + partially_ordered == [8, 9, 10] || partially_ordered == [10, 8, 9], + "partially_ordered must be [8, 9, 10] or [10, 8, 9]" + ); + assert!(order.len() == 11, "must have exacty 11 order entries"); + } } mod conditions { @@ -210,7 +254,7 @@ mod tests { world.init_resource::(); world.init_resource::(); - schedule.add_system( + schedule.add_systems( make_function_system(0).run_if(|condition: Res| condition.0), ); @@ -256,7 +300,7 @@ mod tests { world.init_resource::(); world.init_resource::(); - schedule.add_system( + schedule.add_systems( make_exclusive_system(0).run_if(|condition: Res| condition.0), ); @@ -294,13 +338,13 @@ mod tests { world.init_resource::(); schedule.configure_set(TestSet::A.run_if(|| false).run_if(|| false)); - schedule.add_system(counting_system.in_set(TestSet::A)); + schedule.add_systems(counting_system.in_set(TestSet::A)); schedule.configure_set(TestSet::B.run_if(|| true).run_if(|| false)); - schedule.add_system(counting_system.in_set(TestSet::B)); + schedule.add_systems(counting_system.in_set(TestSet::B)); schedule.configure_set(TestSet::C.run_if(|| false).run_if(|| true)); - schedule.add_system(counting_system.in_set(TestSet::C)); + schedule.add_systems(counting_system.in_set(TestSet::C)); schedule.configure_set(TestSet::D.run_if(|| true).run_if(|| true)); - schedule.add_system(counting_system.in_set(TestSet::D)); + schedule.add_systems(counting_system.in_set(TestSet::D)); schedule.run(&mut world); assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); @@ -314,13 +358,13 @@ mod tests { world.init_resource::(); schedule.configure_set(TestSet::A.run_if(|| false)); - schedule.add_system(counting_system.in_set(TestSet::A).run_if(|| false)); + schedule.add_systems(counting_system.in_set(TestSet::A).run_if(|| false)); schedule.configure_set(TestSet::B.run_if(|| true)); - schedule.add_system(counting_system.in_set(TestSet::B).run_if(|| false)); + schedule.add_systems(counting_system.in_set(TestSet::B).run_if(|| false)); schedule.configure_set(TestSet::C.run_if(|| false)); - schedule.add_system(counting_system.in_set(TestSet::C).run_if(|| true)); + schedule.add_systems(counting_system.in_set(TestSet::C).run_if(|| true)); schedule.configure_set(TestSet::D.run_if(|| true)); - schedule.add_system(counting_system.in_set(TestSet::D).run_if(|| true)); + schedule.add_systems(counting_system.in_set(TestSet::D).run_if(|| true)); schedule.run(&mut world); assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); @@ -337,7 +381,7 @@ mod tests { world.init_resource::(); let mut schedule = Schedule::default(); - schedule.add_system( + schedule.add_systems( counting_system .run_if(|res1: Res| res1.is_changed()) .run_if(|res2: Res| res2.is_changed()), @@ -391,7 +435,7 @@ mod tests { .run_if(|res2: Res| res2.is_changed()), ); - schedule.add_system(counting_system.in_set(TestSet::A)); + schedule.add_systems(counting_system.in_set(TestSet::A)); // both resource were just added. schedule.run(&mut world); @@ -438,7 +482,7 @@ mod tests { schedule .configure_set(TestSet::A.run_if(|res1: Res| res1.is_changed())); - schedule.add_system( + schedule.add_systems( counting_system .run_if(|res2: Res| res2.is_changed()) .in_set(TestSet::A), @@ -544,7 +588,7 @@ mod tests { assert!(result.is_ok()); // Schedule another `foo`. - schedule.add_system(foo); + schedule.add_systems(foo); // When there are multiple instances of `foo`, dependencies on // `foo` are no longer allowed. Too much ambiguity. @@ -556,11 +600,11 @@ mod tests { // same goes for `ambiguous_with` let mut schedule = Schedule::new(); - schedule.add_system(foo); - schedule.add_system(bar.ambiguous_with(foo)); + schedule.add_systems(foo); + schedule.add_systems(bar.ambiguous_with(foo)); let result = schedule.initialize(&mut world); assert!(result.is_ok()); - schedule.add_system(foo); + schedule.add_systems(foo); let result = schedule.initialize(&mut world); assert!(matches!( result, @@ -626,7 +670,7 @@ mod tests { fn foo() {} // Add `foo` to both `A` and `C`. - schedule.add_system(foo.in_set(TestSet::A).in_set(TestSet::C)); + schedule.add_systems(foo.in_set(TestSet::A).in_set(TestSet::C)); // Order `A -> B -> C`. schedule.configure_sets(( @@ -664,140 +708,4 @@ mod tests { assert!(matches!(result, Err(ScheduleBuildError::Ambiguity))); } } - - mod base_sets { - use super::*; - - #[derive(SystemSet, Hash, Debug, Eq, PartialEq, Clone)] - #[system_set(base)] - enum Base { - A, - B, - } - - #[derive(SystemSet, Hash, Debug, Eq, PartialEq, Clone)] - enum Normal { - X, - Y, - } - - #[test] - #[should_panic] - fn disallow_adding_base_sets_to_sets() { - let mut schedule = Schedule::new(); - schedule.configure_set(Base::A.in_set(Normal::X)); - } - - #[test] - #[should_panic] - fn disallow_adding_base_sets_to_base_sets() { - let mut schedule = Schedule::new(); - schedule.configure_set(Base::A.in_base_set(Base::B)); - } - - #[test] - #[should_panic] - fn disallow_adding_set_to_multiple_base_sets() { - let mut schedule = Schedule::new(); - schedule.configure_set(Normal::X.in_base_set(Base::A).in_base_set(Base::B)); - } - - #[test] - #[should_panic] - fn disallow_adding_sets_to_multiple_base_sets() { - let mut schedule = Schedule::new(); - schedule.configure_sets( - (Normal::X, Normal::Y) - .in_base_set(Base::A) - .in_base_set(Base::B), - ); - } - - #[test] - #[should_panic] - fn disallow_adding_system_to_multiple_base_sets() { - let mut schedule = Schedule::new(); - schedule.add_system(named_system.in_base_set(Base::A).in_base_set(Base::B)); - } - - #[test] - #[should_panic] - fn disallow_adding_systems_to_multiple_base_sets() { - let mut schedule = Schedule::new(); - schedule.add_systems( - (make_function_system(0), make_function_system(1)) - .in_base_set(Base::A) - .in_base_set(Base::B), - ); - } - - #[test] - fn disallow_multiple_base_sets() { - let mut world = World::new(); - - let mut schedule = Schedule::new(); - schedule - .configure_set(Normal::X.in_base_set(Base::A)) - .configure_set(Normal::Y.in_base_set(Base::B)) - .add_system(named_system.in_set(Normal::X).in_set(Normal::Y)); - - let result = schedule.initialize(&mut world); - assert!(matches!( - result, - Err(ScheduleBuildError::SystemInMultipleBaseSets { .. }) - )); - - let mut schedule = Schedule::new(); - schedule - .configure_set(Normal::X.in_base_set(Base::A)) - .configure_set(Normal::Y.in_base_set(Base::B).in_set(Normal::X)); - - let result = schedule.initialize(&mut world); - assert!(matches!( - result, - Err(ScheduleBuildError::SetInMultipleBaseSets { .. }) - )); - } - - #[test] - fn allow_same_base_sets() { - let mut world = World::new(); - - let mut schedule = Schedule::new(); - schedule - .configure_set(Normal::X.in_base_set(Base::A)) - .configure_set(Normal::Y.in_base_set(Base::A)) - .add_system(named_system.in_set(Normal::X).in_set(Normal::Y)); - - let result = schedule.initialize(&mut world); - assert!(matches!(result, Ok(()))); - - let mut schedule = Schedule::new(); - schedule - .configure_set(Normal::X.in_base_set(Base::A)) - .configure_set(Normal::Y.in_base_set(Base::A).in_set(Normal::X)); - - let result = schedule.initialize(&mut world); - assert!(matches!(result, Ok(()))); - } - - #[test] - fn default_base_set_ordering() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule - .set_default_base_set(Base::A) - .configure_set(Base::A.before(Base::B)) - .add_systems(( - make_function_system(0).in_base_set(Base::B), - make_function_system(1), - )); - schedule.run(&mut world); - - assert_eq!(world.resource::().0, vec![1, 0]); - } - } } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 2f75213510..303ac8dd29 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -43,17 +43,11 @@ impl Schedules { /// and the old schedule is returned. Otherwise, `None` is returned. pub fn insert(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> Option { let label = label.dyn_clone(); - if self.inner.contains_key(&label) { - warn!("schedule with label {:?} already exists", label); - } self.inner.insert(label, schedule) } /// Removes the schedule corresponding to the `label` from the map, returning it if it existed. pub fn remove(&mut self, label: &dyn ScheduleLabel) -> Option { - if !self.inner.contains_key(label) { - warn!("schedule with label {:?} not found", label); - } self.inner.remove(label) } @@ -62,9 +56,6 @@ impl Schedules { &mut self, label: &dyn ScheduleLabel, ) -> Option<(Box, Schedule)> { - if !self.inner.contains_key(label) { - warn!("schedule with label {:?} not found", label); - } self.inner.remove_entry(label) } @@ -134,7 +125,7 @@ fn make_executor(kind: ExecutorKind) -> Box { /// fn main() { /// let mut world = World::new(); /// let mut schedule = Schedule::default(); -/// schedule.add_system(hello_world); +/// schedule.add_systems(hello_world); /// /// schedule.run(&mut world); /// } @@ -183,21 +174,16 @@ impl Schedule { } } - pub fn set_default_base_set(&mut self, default_base_set: impl SystemSet) -> &mut Self { - self.graph - .set_default_base_set(Some(Box::new(default_base_set))); - self - } - /// Add a system to the schedule. - pub fn add_system(&mut self, system: impl IntoSystemConfig) -> &mut Self { - self.graph.add_system(system); + #[deprecated(since = "0.11.0", note = "please use `add_systems` instead")] + pub fn add_system(&mut self, system: impl IntoSystemConfigs) -> &mut Self { + self.graph.add_systems_inner(system.into_configs(), false); self } /// Add a collection of systems to the schedule. pub fn add_systems(&mut self, systems: impl IntoSystemConfigs) -> &mut Self { - self.graph.add_systems(systems); + self.graph.add_systems_inner(systems.into_configs(), false); self } @@ -346,29 +332,14 @@ impl Dag { } } -/// Describes which base set (i.e. [`SystemSet`] where [`SystemSet::is_base`] returns true) -/// a system belongs to. -/// -/// Note that this is only populated once [`ScheduleGraph::build_schedule`] is called. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum BaseSetMembership { - Uncalculated, - None, - Some(NodeId), -} - /// A [`SystemSet`] with metadata, stored in a [`ScheduleGraph`]. struct SystemSetNode { inner: BoxedSystemSet, - base_set_membership: BaseSetMembership, } impl SystemSetNode { pub fn new(set: BoxedSystemSet) -> Self { - Self { - inner: set, - base_set_membership: BaseSetMembership::Uncalculated, - } + Self { inner: set } } pub fn name(&self) -> String { @@ -383,14 +354,12 @@ impl SystemSetNode { /// A [`BoxedSystem`] with metadata, stored in a [`ScheduleGraph`]. struct SystemNode { inner: Option, - base_set_membership: BaseSetMembership, } impl SystemNode { pub fn new(system: BoxedSystem) -> Self { Self { inner: Some(system), - base_set_membership: BaseSetMembership::Uncalculated, } } @@ -401,10 +370,6 @@ impl SystemNode { pub fn get_mut(&mut self) -> Option<&mut BoxedSystem> { self.inner.as_mut() } - - pub fn name(&self) -> String { - format!("{:?}", &self.inner) - } } /// Metadata for a [`Schedule`]. @@ -416,7 +381,6 @@ pub struct ScheduleGraph { system_set_conditions: Vec>>, system_set_ids: HashMap, uninit: Vec<(NodeId, usize)>, - maybe_default_base_set: Vec, hierarchy: Dag, dependency: Dag, dependency_flattened: Dag, @@ -426,7 +390,6 @@ pub struct ScheduleGraph { conflicting_systems: Vec<(NodeId, NodeId, Vec)>, changed: bool, settings: ScheduleBuildSettings, - default_base_set: Option, } impl ScheduleGraph { @@ -437,7 +400,6 @@ impl ScheduleGraph { system_sets: Vec::new(), system_set_conditions: Vec::new(), system_set_ids: HashMap::new(), - maybe_default_base_set: Vec::new(), uninit: Vec::new(), hierarchy: Dag::new(), dependency: Dag::new(), @@ -448,7 +410,6 @@ impl ScheduleGraph { conflicting_systems: Vec::new(), changed: false, settings: default(), - default_base_set: None, } } @@ -491,44 +452,29 @@ impl ScheduleGraph { } /// Returns an iterator over all systems in this schedule. - /// - /// Note that the [`BaseSetMembership`] will only be initialized after [`ScheduleGraph::build_schedule`] is called. pub fn systems( &self, - ) -> impl Iterator< - Item = ( - NodeId, - &dyn System, - BaseSetMembership, - &[BoxedCondition], - ), - > { + ) -> impl Iterator, &[BoxedCondition])> { self.systems .iter() .zip(self.system_conditions.iter()) .enumerate() .filter_map(|(i, (system_node, condition))| { let system = system_node.inner.as_deref()?; - let base_set_membership = system_node.base_set_membership; let condition = condition.as_ref()?.as_slice(); - Some((NodeId::System(i), system, base_set_membership, condition)) + Some((NodeId::System(i), system, condition)) }) } /// Returns an iterator over all system sets in this schedule. - /// - /// Note that the [`BaseSetMembership`] will only be initialized after [`ScheduleGraph::build_schedule`] is called. - pub fn system_sets( - &self, - ) -> impl Iterator { + pub fn system_sets(&self) -> impl Iterator { self.system_set_ids.iter().map(|(_, node_id)| { let set_node = &self.system_sets[node_id.index()]; let set = &*set_node.inner; - let base_set_membership = set_node.base_set_membership; let conditions = self.system_set_conditions[node_id.index()] .as_deref() .unwrap_or(&[]); - (*node_id, set, base_set_membership, conditions) + (*node_id, set, conditions) }) } @@ -556,47 +502,144 @@ impl ScheduleGraph { &self.conflicting_systems } - fn add_systems(&mut self, systems: impl IntoSystemConfigs) { - let SystemConfigs { systems, chained } = systems.into_configs(); - let mut system_iter = systems.into_iter(); - if chained { - let Some(prev) = system_iter.next() else { return }; - let mut prev_id = self.add_system_inner(prev).unwrap(); - for next in system_iter { - let next_id = self.add_system_inner(next).unwrap(); - self.dependency.graph.add_edge(prev_id, next_id, ()); - prev_id = next_id; + /// Adds the systems to the graph. Returns a vector of all node ids contained the nested `SystemConfigs` + /// if `ancestor_chained` is true. Also returns true if "densely chained", meaning that all nested items + /// are linearly chained in the order they are defined + fn add_systems_inner( + &mut self, + configs: SystemConfigs, + ancestor_chained: bool, + ) -> AddSystemsInnerResult { + match configs { + SystemConfigs::SystemConfig(config) => { + let node_id = self.add_system_inner(config).unwrap(); + if ancestor_chained { + AddSystemsInnerResult { + densely_chained: true, + nodes: vec![node_id], + } + } else { + AddSystemsInnerResult { + densely_chained: true, + nodes: Vec::new(), + } + } } - } else { - for system in system_iter { - self.add_system_inner(system).unwrap(); + SystemConfigs::Configs { configs, chained } => { + let mut config_iter = configs.into_iter(); + let mut nodes_in_scope = Vec::new(); + let mut densely_chained = true; + if chained { + let Some(prev) = config_iter.next() else { + return AddSystemsInnerResult { + nodes: Vec::new(), + densely_chained: true + } + }; + let mut previous_result = self.add_systems_inner(prev, true); + densely_chained = previous_result.densely_chained; + for current in config_iter { + let current_result = self.add_systems_inner(current, true); + densely_chained = densely_chained && current_result.densely_chained; + match ( + previous_result.densely_chained, + current_result.densely_chained, + ) { + // Both groups are "densely" chained, so we can simplify the graph by only + // chaining the last in the previous list to the first in the current list + (true, true) => { + let last_in_prev = previous_result.nodes.last().unwrap(); + let first_in_current = current_result.nodes.first().unwrap(); + self.dependency.graph.add_edge( + *last_in_prev, + *first_in_current, + (), + ); + } + // The previous group is "densely" chained, so we can simplify the graph by only + // chaining the last item from the previous list to every item in the current list + (true, false) => { + let last_in_prev = previous_result.nodes.last().unwrap(); + for current_node in ¤t_result.nodes { + self.dependency.graph.add_edge( + *last_in_prev, + *current_node, + (), + ); + } + } + // The current list is currently "densely" chained, so we can simplify the graph by + // only chaining every item in the previous list to the first item in the current list + (false, true) => { + let first_in_current = current_result.nodes.first().unwrap(); + for previous_node in &previous_result.nodes { + self.dependency.graph.add_edge( + *previous_node, + *first_in_current, + (), + ); + } + } + // Neither of the lists are "densely" chained, so we must chain every item in the first + // list to every item in the second list + (false, false) => { + for previous_node in &previous_result.nodes { + for current_node in ¤t_result.nodes { + self.dependency.graph.add_edge( + *previous_node, + *current_node, + (), + ); + } + } + } + } + + if ancestor_chained { + nodes_in_scope.append(&mut previous_result.nodes); + } + + previous_result = current_result; + } + + // ensure the last config's nodes are added + if ancestor_chained { + nodes_in_scope.append(&mut previous_result.nodes); + } + } else { + let more_than_one_entry = config_iter.len() > 1; + for config in config_iter { + let result = self.add_systems_inner(config, ancestor_chained); + densely_chained = densely_chained && result.densely_chained; + if ancestor_chained { + nodes_in_scope.extend(result.nodes); + } + } + + // an "unchained" SystemConfig is only densely chained if it has exactly one densely chained entry + if more_than_one_entry { + densely_chained = false; + } + } + + AddSystemsInnerResult { + nodes: nodes_in_scope, + densely_chained, + } } } } - fn add_system(&mut self, system: impl IntoSystemConfig) { - self.add_system_inner(system).unwrap(); - } - - fn add_system_inner( - &mut self, - system: impl IntoSystemConfig, - ) -> Result { - let SystemConfig { - system, - graph_info, - conditions, - } = system.into_config(); - + fn add_system_inner(&mut self, config: SystemConfig) -> Result { let id = NodeId::System(self.systems.len()); // graph updates are immediate - self.update_graphs(id, graph_info, false)?; + self.update_graphs(id, config.graph_info)?; // system init has to be deferred (need `&mut World`) self.uninit.push((id, 0)); - self.systems.push(SystemNode::new(system)); - self.system_conditions.push(Some(conditions)); + self.systems.push(SystemNode::new(config.system)); + self.system_conditions.push(Some(config.conditions)); Ok(id) } @@ -639,7 +682,7 @@ impl ScheduleGraph { }; // graph updates are immediate - self.update_graphs(id, graph_info, set.is_base())?; + self.update_graphs(id, graph_info)?; // system init has to be deferred (need `&mut World`) let system_set_conditions = @@ -724,7 +767,6 @@ impl ScheduleGraph { &mut self, id: NodeId, graph_info: GraphInfo, - is_base_set: bool, ) -> Result<(), ScheduleBuildError> { self.check_sets(&id, &graph_info)?; self.check_edges(&id, &graph_info)?; @@ -734,8 +776,6 @@ impl ScheduleGraph { sets, dependencies, ambiguous_with, - base_set, - add_default_base_set, .. } = graph_info; @@ -749,30 +789,6 @@ impl ScheduleGraph { self.dependency.graph.add_node(set); } - // If the current node is not a base set, set the base set if it was configured - if !is_base_set { - if let Some(base_set) = base_set { - let set_id = self.system_set_ids[&base_set]; - self.hierarchy.graph.add_edge(set_id, id, ()); - } else if let Some(default_base_set) = &self.default_base_set { - if add_default_base_set { - match id { - NodeId::System(_) => { - // Queue the default base set. We queue systems instead of adding directly to allow - // sets to define base sets, which will override the default inheritance behavior - self.maybe_default_base_set.push(id); - } - NodeId::Set(_) => { - // Sets should be added automatically because developers explicitly called - // in_default_base_set() - let set_id = self.system_set_ids[default_base_set]; - self.hierarchy.graph.add_edge(set_id, id, ()); - } - } - } - } - } - if !self.dependency.graph.contains_node(id) { self.dependency.graph.add_node(id); } @@ -832,149 +848,15 @@ impl ScheduleGraph { } } - /// Calculates the base set for each node and caches the results on the node - fn calculate_base_sets_and_detect_cycles(&mut self) -> Result<(), ScheduleBuildError> { - let set_ids = (0..self.system_sets.len()).map(NodeId::Set); - let system_ids = (0..self.systems.len()).map(NodeId::System); - let mut visited_sets = vec![false; self.system_sets.len()]; - // reset base set membership, as this can change when the schedule updates - for system in &mut self.systems { - system.base_set_membership = BaseSetMembership::Uncalculated; - } - for system_set in &mut self.system_sets { - system_set.base_set_membership = BaseSetMembership::Uncalculated; - } - for node_id in set_ids.chain(system_ids) { - Self::calculate_base_set( - &self.hierarchy, - &mut self.system_sets, - &mut self.systems, - &mut visited_sets, - node_id, - )?; - } - Ok(()) - } - - fn calculate_base_set( - hierarchy: &Dag, - system_sets: &mut [SystemSetNode], - systems: &mut [SystemNode], - visited_sets: &mut [bool], - node_id: NodeId, - ) -> Result, ScheduleBuildError> { - let base_set_membership = match node_id { - // systems only have - NodeId::System(_) => BaseSetMembership::Uncalculated, - NodeId::Set(index) => { - let set_node = &mut system_sets[index]; - if set_node.inner.is_base() { - set_node.base_set_membership = BaseSetMembership::Some(node_id); - } - set_node.base_set_membership - } - }; - let base_set = match base_set_membership { - BaseSetMembership::None => None, - BaseSetMembership::Some(node_id) => Some(node_id), - BaseSetMembership::Uncalculated => { - let mut base_set: Option = None; - if let NodeId::Set(index) = node_id { - if visited_sets[index] { - return Err(ScheduleBuildError::HierarchyCycle); - } - visited_sets[index] = true; - } - for neighbor in hierarchy - .graph - .neighbors_directed(node_id, Direction::Incoming) - { - if let Some(calculated_base_set) = Self::calculate_base_set( - hierarchy, - system_sets, - systems, - visited_sets, - neighbor, - )? { - if let Some(first_set) = base_set { - if first_set != calculated_base_set { - return Err(match node_id { - NodeId::System(index) => { - ScheduleBuildError::SystemInMultipleBaseSets { - system: systems[index].name(), - first_set: system_sets[first_set.index()].name(), - second_set: system_sets[calculated_base_set.index()] - .name(), - } - } - NodeId::Set(index) => { - ScheduleBuildError::SetInMultipleBaseSets { - set: system_sets[index].name(), - first_set: system_sets[first_set.index()].name(), - second_set: system_sets[calculated_base_set.index()] - .name(), - } - } - }); - } - } - base_set = Some(calculated_base_set); - } - } - - match node_id { - NodeId::System(index) => { - systems[index].base_set_membership = if let Some(base_set) = base_set { - BaseSetMembership::Some(base_set) - } else { - BaseSetMembership::None - }; - } - NodeId::Set(index) => { - system_sets[index].base_set_membership = if let Some(base_set) = base_set { - BaseSetMembership::Some(base_set) - } else { - BaseSetMembership::None - }; - } - } - base_set - } - }; - Ok(base_set) - } - /// Build a [`SystemSchedule`] optimized for scheduler access from the [`ScheduleGraph`]. /// /// This method also - /// - calculates [`BaseSetMembership`] /// - checks for dependency or hierarchy cycles /// - checks for system access conflicts and reports ambiguities pub fn build_schedule( &mut self, components: &Components, ) -> Result { - self.calculate_base_sets_and_detect_cycles()?; - - // Add missing base set membership to systems that defaulted to using the - // default base set and weren't added to a set that belongs to a base set. - if let Some(default_base_set) = &self.default_base_set { - let default_set_id = self.system_set_ids[default_base_set]; - for system_id in std::mem::take(&mut self.maybe_default_base_set) { - let system_node = &mut self.systems[system_id.index()]; - if system_node.base_set_membership == BaseSetMembership::None { - self.hierarchy.graph.add_edge(default_set_id, system_id, ()); - system_node.base_set_membership = BaseSetMembership::Some(default_set_id); - } - - debug_assert_ne!( - system_node.base_set_membership, - BaseSetMembership::Uncalculated, - "base set membership should have been calculated" - ); - } - } - // check hierarchy for cycles self.hierarchy.topsort = self .topsort_graph(&self.hierarchy.graph, ReportCycles::Hierarchy) @@ -1340,17 +1222,14 @@ impl ScheduleGraph { Ok(()) } +} - fn set_default_base_set(&mut self, set: Option) { - if let Some(set) = set { - self.default_base_set = Some(set.dyn_clone()); - if self.system_set_ids.get(&set).is_none() { - self.add_set(set); - } - } else { - self.default_base_set = None; - } - } +/// Values returned by `ScheduleGraph::add_systems_inner` +struct AddSystemsInnerResult { + /// All nodes contained inside this add_systems_inner call's SystemConfigs hierarchy + nodes: Vec, + /// True if and only if all nodes are "densely chained" + densely_chained: bool, } /// Used to select the appropriate reporting function. diff --git a/crates/bevy_ecs/src/schedule/set.rs b/crates/bevy_ecs/src/schedule/set.rs index 23058a3de9..56deb5c398 100644 --- a/crates/bevy_ecs/src/schedule/set.rs +++ b/crates/bevy_ecs/src/schedule/set.rs @@ -23,34 +23,10 @@ pub trait SystemSet: DynHash + Debug + Send + Sync + 'static { None } - /// Returns `true` if this set is a "base system set". Systems - /// can only belong to one base set at a time. Systems and Sets - /// can only be added to base sets using specialized `in_base_set` - /// APIs. This enables "mutually exclusive" behaviors. It also - /// enables schedules to have a "default base set", which can be used - /// to apply default configuration to systems. - fn is_base(&self) -> bool { - false - } - /// Creates a boxed clone of the label corresponding to this system set. fn dyn_clone(&self) -> Box; } -/// A marker trait for `SystemSet` types where [`is_base`] returns `true`. -/// This should only be implemented for types that satisfy this requirement. -/// It is automatically implemented for base set types by `#[derive(SystemSet)]`. -/// -/// [`is_base`]: SystemSet::is_base -pub trait BaseSystemSet: SystemSet {} - -/// A marker trait for `SystemSet` types where [`is_base`] returns `false`. -/// This should only be implemented for types that satisfy this requirement. -/// It is automatically implemented for non-base set types by `#[derive(SystemSet)]`. -/// -/// [`is_base`]: SystemSet::is_base -pub trait FreeSystemSet: SystemSet {} - impl PartialEq for dyn SystemSet { fn eq(&self, other: &Self) -> bool { self.dyn_eq(other.as_dyn_eq()) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index ec782e715b..671a29431c 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -50,7 +50,7 @@ use super::{ReadOnlySystem, System}; /// # world.init_resource::(); /// # /// # let mut app = Schedule::new(); -/// app.add_system(my_system.run_if(Xor::new( +/// app.add_systems(my_system.run_if(Xor::new( /// IntoSystem::into_system(resource_equals(A(1))), /// IntoSystem::into_system(resource_equals(B(1))), /// // The name of the combined system. diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 73df904177..fb0f1b388f 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -582,9 +582,9 @@ impl<'w, 's> Commands<'w, 's> { /// # world.init_resource::(); /// # /// # let mut setup_schedule = Schedule::new(); -/// # setup_schedule.add_system(setup); +/// # setup_schedule.add_systems(setup); /// # let mut assert_schedule = Schedule::new(); -/// # assert_schedule.add_system(assert_names); +/// # assert_schedule.add_systems(assert_names); /// # /// # setup_schedule.run(&mut world); /// # assert_schedule.run(&mut world); diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 6b622ca85d..19a68b1713 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -64,7 +64,7 @@ //! //! // Configure this system to run in between the other two systems //! // using explicit dependencies. -//! schedule.add_system(print_mid.after(print_first).before(print_last)); +//! schedule.add_systems(print_mid.after(print_first).before(print_last)); //! // Prints "Hello, World!" //! schedule.run(&mut world); //! @@ -214,7 +214,7 @@ mod tests { fn run_system>(world: &mut World, system: S) { let mut schedule = Schedule::default(); - schedule.add_system(system); + schedule.add_systems(system); schedule.run(world); } diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 307dd2bc7d..b8c11b6262 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -17,7 +17,7 @@ use std::borrow::Cow; /// /// Systems are executed in parallel, in opportunistic order; data access is managed automatically. /// It's possible to specify explicit execution order between specific systems, -/// see [`IntoSystemConfig`](crate::schedule::IntoSystemConfig). +/// see [`IntoSystemConfigs`](crate::schedule::IntoSystemConfigs). pub trait System: Send + Sync + 'static { /// The system's input. See [`In`](crate::system::In) for /// [`FunctionSystem`](crate::system::FunctionSystem)s. diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 8f54f0b28c..47f668d8ae 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -674,7 +674,7 @@ unsafe impl SystemParam for &'_ World { /// move |mut val| val.0 = value.0 /// } /// -/// // .add_system(reset_to_system(my_config)) +/// // .add_systems(reset_to_system(my_config)) /// # assert_is_system(reset_to_system(Config(10))); /// ``` pub struct Local<'s, T: FromWorld + Send + 'static>(pub(crate) &'s mut T); diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index b1551e64f3..dc1f3dc55a 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -138,7 +138,7 @@ pub mod adapter { /// /// // Building a new schedule/app... /// let mut sched = Schedule::default(); - /// sched.add_system( + /// sched.add_systems( /// // Panic if the load system returns an error. /// load_save_system.pipe(system_adapter::unwrap) /// ) @@ -169,7 +169,7 @@ pub mod adapter { /// /// // Building a new schedule/app... /// let mut sched = Schedule::default(); - /// sched.add_system( + /// sched.add_systems( /// // Prints system information. /// data_pipe_system.pipe(system_adapter::info) /// ) @@ -196,7 +196,7 @@ pub mod adapter { /// /// // Building a new schedule/app... /// let mut sched = Schedule::default(); - /// sched.add_system( + /// sched.add_systems( /// // Prints debug data from system. /// parse_message_system.pipe(system_adapter::dbg) /// ) @@ -223,7 +223,7 @@ pub mod adapter { /// /// // Building a new schedule/app... /// # let mut sched = Schedule::default(); - /// sched.add_system( + /// sched.add_systems( /// // Prints system warning if system returns an error. /// warning_pipe_system.pipe(system_adapter::warn) /// ) @@ -251,7 +251,7 @@ pub mod adapter { /// use bevy_ecs::prelude::*; /// // Building a new schedule/app... /// let mut sched = Schedule::default(); - /// sched.add_system( + /// sched.add_systems( /// // Prints system error if system fails. /// parse_error_message_system.pipe(system_adapter::error) /// ) @@ -287,7 +287,7 @@ pub mod adapter { /// /// // Building a new schedule/app... /// # let mut sched = Schedule::default(); sched - /// .add_system( + /// .add_systems( /// // If the system fails, just move on and try again next frame. /// fallible_system.pipe(system_adapter::ignore) /// ) diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index f011710100..abbf34307c 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -1,7 +1,7 @@ mod converter; mod gilrs_system; -use bevy_app::{App, CoreSet, Plugin, StartupSet}; +use bevy_app::{App, Plugin, PreStartup, PreUpdate}; use bevy_ecs::prelude::*; use bevy_input::InputSystem; use bevy_utils::tracing::error; @@ -20,14 +20,8 @@ impl Plugin for GilrsPlugin { { Ok(gilrs) => { app.insert_non_send_resource(gilrs) - .add_startup_system( - gilrs_event_startup_system.in_base_set(StartupSet::PreStartup), - ) - .add_system( - gilrs_event_system - .before(InputSystem) - .in_base_set(CoreSet::PreUpdate), - ); + .add_systems(PreStartup, gilrs_event_startup_system) + .add_systems(PreUpdate, gilrs_event_system.before(InputSystem)); } Err(err) => error!("Failed to start Gilrs. {}", err), } diff --git a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs index 557450f9af..ad8c6c1ec3 100644 --- a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs +++ b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use bevy_app::{App, CoreSet, Plugin}; +use bevy_app::{App, Last, Plugin}; use bevy_core::Name; use bevy_ecs::prelude::*; use bevy_log::warn; @@ -96,10 +96,10 @@ impl Default for ValidParentCheckPlugin { impl Plugin for ValidParentCheckPlugin { fn build(&self, app: &mut App) { - app.init_resource::>().add_system( + app.init_resource::>().add_systems( + Last, check_hierarchy_component_has_valid_parent:: - .run_if(resource_equals(ReportHierarchyIssue::::new(true))) - .in_base_set(CoreSet::Last), + .run_if(resource_equals(ReportHierarchyIssue::::new(true))), ); } } diff --git a/crates/bevy_input/src/common_conditions.rs b/crates/bevy_input/src/common_conditions.rs index dd93d2bdf1..8b85b8a858 100644 --- a/crates/bevy_input/src/common_conditions.rs +++ b/crates/bevy_input/src/common_conditions.rs @@ -11,7 +11,7 @@ use std::hash::Hash; /// fn main() { /// App::new() /// .add_plugins(DefaultPlugins) -/// .add_system(pause_menu.run_if(input_toggle_active(false, KeyCode::Escape))) +/// .add_systems(Update, pause_menu.run_if(input_toggle_active(false, KeyCode::Escape))) /// .run(); /// } /// @@ -33,7 +33,7 @@ use std::hash::Hash; /// App::new() /// .add_plugins(DefaultPlugins) /// .init_resource::() -/// .add_system(pause_menu.run_if(|paused: Res| paused.0)) +/// .add_systems(Update, pause_menu.run_if(|paused: Res| paused.0)) /// .run(); /// } /// @@ -75,7 +75,7 @@ where /// fn main() { /// App::new() /// .add_plugins(DefaultPlugins) -/// .add_system(jump.run_if(input_just_pressed(KeyCode::Space))) +/// .add_systems(Update, jump.run_if(input_just_pressed(KeyCode::Space))) /// .run(); /// } /// diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 7fc988171c..9da05a2e25 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -53,18 +53,18 @@ pub struct InputSystem; impl Plugin for InputPlugin { fn build(&self, app: &mut App) { - app.configure_set(InputSystem.in_base_set(CoreSet::PreUpdate)) + app // keyboard .add_event::() .init_resource::>() .init_resource::>() - .add_system(keyboard_input_system.in_set(InputSystem)) + .add_systems(PreUpdate, keyboard_input_system.in_set(InputSystem)) // mouse .add_event::() .add_event::() .add_event::() .init_resource::>() - .add_system(mouse_button_input_system.in_set(InputSystem)) + .add_systems(PreUpdate, mouse_button_input_system.in_set(InputSystem)) // gamepad .add_event::() .add_event::() @@ -76,6 +76,7 @@ impl Plugin for InputPlugin { .init_resource::>() .init_resource::>() .add_systems( + PreUpdate, ( gamepad_event_system, gamepad_connection_system.after(gamepad_event_system), @@ -91,7 +92,7 @@ impl Plugin for InputPlugin { // touch .add_event::() .init_resource::() - .add_system(touch_screen_input_system.in_set(InputSystem)); + .add_systems(PreUpdate, touch_screen_input_system.in_set(InputSystem)); // Register common types app.register_type::(); diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 544984d5c1..bf47c70450 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -55,7 +55,7 @@ use bevy_render::{ render_phase::sort_phase_system, render_resource::Shader, view::{ViewSet, VisibilitySystems}, - ExtractSchedule, RenderApp, RenderSet, + ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::TransformSystem; use environment_map::EnvironmentMapPlugin; @@ -176,59 +176,58 @@ impl Plugin for PbrPlugin { .init_resource::() .add_plugin(ExtractResourcePlugin::::default()) .configure_sets( + PostUpdate, ( SimulationLightSystems::AddClusters, - SimulationLightSystems::AddClustersFlush - .after(SimulationLightSystems::AddClusters) - .before(SimulationLightSystems::AssignLightsToClusters), + SimulationLightSystems::AddClustersFlush, SimulationLightSystems::AssignLightsToClusters, - SimulationLightSystems::CheckLightVisibility, - SimulationLightSystems::UpdateDirectionalLightCascades, - SimulationLightSystems::UpdateLightFrusta, ) - .in_base_set(CoreSet::PostUpdate), + .chain(), ) .add_plugin(FogPlugin) - .add_systems(( - add_clusters.in_set(SimulationLightSystems::AddClusters), - apply_system_buffers.in_set(SimulationLightSystems::AddClustersFlush), - assign_lights_to_clusters - .in_set(SimulationLightSystems::AssignLightsToClusters) - .after(TransformSystem::TransformPropagate) - .after(VisibilitySystems::CheckVisibility) - .after(CameraUpdateSystem), - update_directional_light_cascades - .in_set(SimulationLightSystems::UpdateDirectionalLightCascades) - .after(TransformSystem::TransformPropagate) - .after(CameraUpdateSystem), - update_directional_light_frusta - .in_set(SimulationLightSystems::UpdateLightFrusta) - // This must run after CheckVisibility because it relies on ComputedVisibility::is_visible() - .after(VisibilitySystems::CheckVisibility) - .after(TransformSystem::TransformPropagate) - .after(SimulationLightSystems::UpdateDirectionalLightCascades) - // We assume that no entity will be both a directional light and a spot light, - // so these systems will run independently of one another. - // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. - .ambiguous_with(update_spot_light_frusta), - update_point_light_frusta - .in_set(SimulationLightSystems::UpdateLightFrusta) - .after(TransformSystem::TransformPropagate) - .after(SimulationLightSystems::AssignLightsToClusters), - update_spot_light_frusta - .in_set(SimulationLightSystems::UpdateLightFrusta) - .after(TransformSystem::TransformPropagate) - .after(SimulationLightSystems::AssignLightsToClusters), - check_light_mesh_visibility - .in_set(SimulationLightSystems::CheckLightVisibility) - .after(VisibilitySystems::CalculateBoundsFlush) - .after(TransformSystem::TransformPropagate) - .after(SimulationLightSystems::UpdateLightFrusta) - // NOTE: This MUST be scheduled AFTER the core renderer visibility check - // because that resets entity ComputedVisibility for the first view - // which would override any results from this otherwise - .after(VisibilitySystems::CheckVisibility), - )); + .add_systems( + PostUpdate, + ( + add_clusters.in_set(SimulationLightSystems::AddClusters), + apply_system_buffers.in_set(SimulationLightSystems::AddClustersFlush), + assign_lights_to_clusters + .in_set(SimulationLightSystems::AssignLightsToClusters) + .after(TransformSystem::TransformPropagate) + .after(VisibilitySystems::CheckVisibility) + .after(CameraUpdateSystem), + update_directional_light_cascades + .in_set(SimulationLightSystems::UpdateDirectionalLightCascades) + .after(TransformSystem::TransformPropagate) + .after(CameraUpdateSystem), + update_directional_light_frusta + .in_set(SimulationLightSystems::UpdateLightFrusta) + // This must run after CheckVisibility because it relies on ComputedVisibility::is_visible() + .after(VisibilitySystems::CheckVisibility) + .after(TransformSystem::TransformPropagate) + .after(SimulationLightSystems::UpdateDirectionalLightCascades) + // We assume that no entity will be both a directional light and a spot light, + // so these systems will run independently of one another. + // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. + .ambiguous_with(update_spot_light_frusta), + update_point_light_frusta + .in_set(SimulationLightSystems::UpdateLightFrusta) + .after(TransformSystem::TransformPropagate) + .after(SimulationLightSystems::AssignLightsToClusters), + update_spot_light_frusta + .in_set(SimulationLightSystems::UpdateLightFrusta) + .after(TransformSystem::TransformPropagate) + .after(SimulationLightSystems::AssignLightsToClusters), + check_light_mesh_visibility + .in_set(SimulationLightSystems::CheckLightVisibility) + .after(VisibilitySystems::CalculateBoundsFlush) + .after(TransformSystem::TransformPropagate) + .after(SimulationLightSystems::UpdateLightFrusta) + // NOTE: This MUST be scheduled AFTER the core renderer visibility check + // because that resets entity ComputedVisibility for the first view + // which would override any results from this otherwise + .after(VisibilitySystems::CheckVisibility), + ), + ); app.world .resource_mut::>() @@ -248,31 +247,39 @@ impl Plugin for PbrPlugin { // Extract the required data from the main world render_app - .configure_set(RenderLightSystems::PrepareLights.in_set(RenderSet::Prepare)) - .configure_set(RenderLightSystems::PrepareClusters.in_set(RenderSet::Prepare)) - .configure_set(RenderLightSystems::QueueShadows.in_set(RenderSet::Queue)) + .configure_sets( + Render, + ( + RenderLightSystems::PrepareLights.in_set(RenderSet::Prepare), + RenderLightSystems::PrepareClusters.in_set(RenderSet::Prepare), + RenderLightSystems::QueueShadows.in_set(RenderSet::Queue), + ), + ) .add_systems( + ExtractSchedule, ( render::extract_clusters.in_set(RenderLightSystems::ExtractClusters), render::extract_lights.in_set(RenderLightSystems::ExtractLights), - ) - .in_schedule(ExtractSchedule), + ), + ) + .add_systems( + Render, + ( + render::prepare_lights + .before(ViewSet::PrepareUniforms) + .in_set(RenderLightSystems::PrepareLights), + // A sync is needed after prepare_lights, before prepare_view_uniforms, + // because prepare_lights creates new views for shadow mapping + apply_system_buffers + .in_set(RenderSet::Prepare) + .after(RenderLightSystems::PrepareLights) + .before(ViewSet::PrepareUniforms), + render::prepare_clusters + .after(render::prepare_lights) + .in_set(RenderLightSystems::PrepareClusters), + sort_phase_system::.in_set(RenderSet::PhaseSort), + ), ) - .add_systems(( - render::prepare_lights - .before(ViewSet::PrepareUniforms) - .in_set(RenderLightSystems::PrepareLights), - // A sync is needed after prepare_lights, before prepare_view_uniforms, - // because prepare_lights creates new views for shadow mapping - apply_system_buffers - .in_set(RenderSet::Prepare) - .after(RenderLightSystems::PrepareLights) - .before(ViewSet::PrepareUniforms), - render::prepare_clusters - .after(render::prepare_lights) - .in_set(RenderLightSystems::PrepareClusters), - sort_phase_system::.in_set(RenderSet::PhaseSort), - )) .init_resource::() .init_resource::() .init_resource::(); diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 0072474083..be28f2e13a 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -3,7 +3,7 @@ use crate::{ MeshUniform, PrepassPipelinePlugin, PrepassPlugin, RenderLightSystems, SetMeshBindGroup, SetMeshViewBindGroup, Shadow, }; -use bevy_app::{App, IntoSystemAppConfig, Plugin}; +use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; use bevy_core_pipeline::{ core_3d::{AlphaMask3d, Opaque3d, Transparent3d}, @@ -35,7 +35,7 @@ use bevy_render::{ renderer::RenderDevice, texture::FallbackImage, view::{ExtractedView, Msaa, VisibleEntities}, - Extract, ExtractSchedule, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_utils::{tracing::error, HashMap, HashSet}; use std::hash::Hash; @@ -199,14 +199,17 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_systems(( - extract_materials::.in_schedule(ExtractSchedule), - prepare_materials:: - .in_set(RenderSet::Prepare) - .after(PrepareAssetSet::PreAssetPrepare), - render::queue_shadows::.in_set(RenderLightSystems::QueueShadows), - queue_material_meshes::.in_set(RenderSet::Queue), - )); + .add_systems(ExtractSchedule, extract_materials::) + .add_systems( + Render, + ( + prepare_materials:: + .in_set(RenderSet::Prepare) + .after(PrepareAssetSet::PreAssetPrepare), + render::queue_shadows::.in_set(RenderLightSystems::QueueShadows), + queue_material_meshes::.in_set(RenderSet::Queue), + ), + ); } // PrepassPipelinePlugin is required for shadow mapping and the optional PrepassPlugin diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 2766f985d8..b2a50c5f37 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -1,4 +1,4 @@ -use bevy_app::{IntoSystemAppConfig, Plugin}; +use bevy_app::Plugin; use bevy_asset::{load_internal_asset, AssetServer, Handle, HandleUntyped}; use bevy_core_pipeline::{ prelude::Camera3d, @@ -39,7 +39,7 @@ use bevy_render::{ renderer::RenderDevice, texture::{FallbackImagesDepth, FallbackImagesMsaa, TextureCache}, view::{ExtractedView, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities}, - Extract, ExtractSchedule, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_utils::{tracing::error, HashMap}; @@ -102,7 +102,10 @@ where }; render_app - .add_system(queue_prepass_view_bind_group::.in_set(RenderSet::Queue)) + .add_systems( + Render, + queue_prepass_view_bind_group::.in_set(RenderSet::Queue), + ) .init_resource::>() .init_resource::() .init_resource::>>(); @@ -130,15 +133,18 @@ where }; render_app - .add_systems(( - extract_camera_prepass_phase.in_schedule(ExtractSchedule), - prepare_prepass_textures - .in_set(RenderSet::Prepare) - .after(bevy_render::view::prepare_windows), - queue_prepass_material_meshes::.in_set(RenderSet::Queue), - sort_phase_system::.in_set(RenderSet::PhaseSort), - sort_phase_system::.in_set(RenderSet::PhaseSort), - )) + .add_systems(ExtractSchedule, extract_camera_prepass_phase) + .add_systems( + Render, + ( + prepare_prepass_textures + .in_set(RenderSet::Prepare) + .after(bevy_render::view::prepare_windows), + queue_prepass_material_meshes::.in_set(RenderSet::Queue), + sort_phase_system::.in_set(RenderSet::PhaseSort), + sort_phase_system::.in_set(RenderSet::PhaseSort), + ), + ) .init_resource::>() .init_resource::>() .add_render_command::>() diff --git a/crates/bevy_pbr/src/render/fog.rs b/crates/bevy_pbr/src/render/fog.rs index 2c658bb7a1..efa9a4c15f 100644 --- a/crates/bevy_pbr/src/render/fog.rs +++ b/crates/bevy_pbr/src/render/fog.rs @@ -8,7 +8,7 @@ use bevy_render::{ render_resource::{DynamicUniformBuffer, Shader, ShaderType}, renderer::{RenderDevice, RenderQueue}, view::ExtractedView, - RenderApp, RenderSet, + Render, RenderApp, RenderSet, }; use crate::{FogFalloff, FogSettings}; @@ -142,8 +142,11 @@ impl Plugin for FogPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .add_system(prepare_fog.in_set(RenderFogSystems::PrepareFog)) - .configure_set(RenderFogSystems::PrepareFog.in_set(RenderSet::Prepare)); + .add_systems(Render, prepare_fog.in_set(RenderFogSystems::PrepareFog)) + .configure_set( + Render, + RenderFogSystems::PrepareFog.in_set(RenderSet::Prepare), + ); } } } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 1faf66bc4c..a76ff0f470 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -4,7 +4,7 @@ use crate::{ ViewClusterBindings, ViewFogUniformOffset, ViewLightsUniformOffset, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS, }; -use bevy_app::{IntoSystemAppConfigs, Plugin}; +use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; use bevy_core_pipeline::{ prepass::ViewPrepassTextures, @@ -36,7 +36,7 @@ use bevy_render::{ FallbackImagesMsaa, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, }, view::{ComputedVisibility, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, - Extract, ExtractSchedule, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::components::GlobalTransform; use std::num::NonZeroU64; @@ -107,12 +107,15 @@ impl Plugin for MeshRenderPlugin { render_app .init_resource::() .init_resource::() - .add_systems((extract_meshes, extract_skinned_meshes).in_schedule(ExtractSchedule)) - .add_systems(( - prepare_skinned_meshes.in_set(RenderSet::Prepare), - queue_mesh_bind_group.in_set(RenderSet::Queue), - queue_mesh_view_bind_groups.in_set(RenderSet::Queue), - )); + .add_systems(ExtractSchedule, (extract_meshes, extract_skinned_meshes)) + .add_systems( + Render, + ( + prepare_skinned_meshes.in_set(RenderSet::Prepare), + queue_mesh_bind_group.in_set(RenderSet::Queue), + queue_mesh_view_bind_groups.in_set(RenderSet::Queue), + ), + ); } } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 34d93c9185..7e885f9231 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -7,6 +7,7 @@ use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::{Reflect, TypeUuid}; use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin}; +use bevy_render::Render; use bevy_render::{ extract_resource::{ExtractResource, ExtractResourcePlugin}, mesh::{Mesh, MeshVertexBufferLayout}, @@ -47,7 +48,7 @@ impl Plugin for WireframePlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_system(queue_wireframes.in_set(RenderSet::Queue)); + .add_systems(Render, queue_wireframes.in_set(RenderSet::Queue)); } } } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 27687fc863..8b0e44ab0b 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -492,7 +492,7 @@ impl NormalizedRenderTarget { /// The system function is generic over the camera projection type, and only instances of /// [`OrthographicProjection`] and [`PerspectiveProjection`] are automatically added to /// the app, as well as the runtime-selected [`Projection`]. -/// The system runs during [`CoreSet::PostUpdate`]. +/// The system runs during [`PostUpdate`](bevy_app::PostUpdate). /// /// ## World Resources /// @@ -502,7 +502,6 @@ impl NormalizedRenderTarget { /// [`OrthographicProjection`]: crate::camera::OrthographicProjection /// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection /// [`Projection`]: crate::camera::Projection -/// [`CoreSet::PostUpdate`]: bevy_app::CoreSet::PostUpdate pub fn camera_system( mut window_resized_events: EventReader, mut window_created_events: EventReader, diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 91ea69d941..44d72cb03e 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -7,9 +7,9 @@ pub use camera::*; pub use camera_driver_node::*; pub use projection::*; -use crate::{render_graph::RenderGraph, ExtractSchedule, RenderApp, RenderSet}; -use bevy_app::{App, IntoSystemAppConfig, Plugin}; -use bevy_ecs::schedule::IntoSystemConfig; +use crate::{render_graph::RenderGraph, ExtractSchedule, Render, RenderApp, RenderSet}; +use bevy_app::{App, Plugin}; +use bevy_ecs::schedule::IntoSystemConfigs; #[derive(Default)] pub struct CameraPlugin; @@ -29,8 +29,8 @@ impl Plugin for CameraPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .add_system(extract_cameras.in_schedule(ExtractSchedule)) - .add_system(sort_cameras.in_set(RenderSet::Prepare)); + .add_systems(ExtractSchedule, extract_cameras) + .add_systems(Render, sort_cameras.in_set(RenderSet::Prepare)); let camera_driver_node = CameraDriverNode::new(&mut render_app.world); let mut render_graph = render_app.world.resource_mut::(); render_graph.add_node(crate::main_graph::node::CAMERA_DRIVER, camera_driver_node); diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index fd82cd72da..ad3731be45 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use bevy_app::{App, CoreSchedule, CoreSet, IntoSystemAppConfig, Plugin, StartupSet}; +use bevy_app::{App, Plugin, PostStartup, PostUpdate}; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_math::{Mat4, Rect, Vec2}; use bevy_reflect::{ @@ -27,25 +27,24 @@ pub struct CameraUpdateSystem; impl Plugin for CameraProjectionPlugin { fn build(&self, app: &mut App) { app.register_type::() - .edit_schedule(CoreSchedule::Startup, |schedule| { - schedule.configure_set(CameraUpdateSystem.in_base_set(StartupSet::PostStartup)); - }) - .configure_set(CameraUpdateSystem.in_base_set(CoreSet::PostUpdate)) - .add_systems(( - crate::camera::camera_system:: - .on_startup() - .in_set(CameraUpdateSystem) - // We assume that each camera will only have one projection, - // so we can ignore ambiguities with all other monomorphizations. - // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. - .ambiguous_with(CameraUpdateSystem), + .add_systems( + PostStartup, crate::camera::camera_system:: .in_set(CameraUpdateSystem) // We assume that each camera will only have one projection, // so we can ignore ambiguities with all other monomorphizations. // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. .ambiguous_with(CameraUpdateSystem), - )); + ) + .add_systems( + PostUpdate, + crate::camera::camera_system:: + .in_set(CameraUpdateSystem) + // We assume that each camera will only have one projection, + // so we can ignore ambiguities with all other monomorphizations. + // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. + .ambiguous_with(CameraUpdateSystem), + ); } } diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 2b70a897a1..606c8ccbe0 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -2,9 +2,9 @@ use crate::{ render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType}, renderer::{RenderDevice, RenderQueue}, view::ComputedVisibility, - Extract, ExtractSchedule, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_app::{App, IntoSystemAppConfig, Plugin}; +use bevy_app::{App, Plugin}; use bevy_asset::{Asset, Handle}; use bevy_ecs::{ component::Component, @@ -83,7 +83,10 @@ impl Plugin for UniformComponentP if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .insert_resource(ComponentUniforms::::default()) - .add_system(prepare_uniform_components::.in_set(RenderSet::Prepare)); + .add_systems( + Render, + prepare_uniform_components::.in_set(RenderSet::Prepare), + ); } } } @@ -180,9 +183,9 @@ impl Plugin for ExtractComponentPlugin { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { if self.only_extract_visible { - render_app.add_system(extract_visible_components::.in_schedule(ExtractSchedule)); + render_app.add_systems(ExtractSchedule, extract_visible_components::); } else { - render_app.add_system(extract_components::.in_schedule(ExtractSchedule)); + render_app.add_systems(ExtractSchedule, extract_components::); } } } diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_render/src/extract_resource.rs index a1e8b122f0..ea8f7e81fd 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_render/src/extract_resource.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use bevy_app::{App, IntoSystemAppConfig, Plugin}; +use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; pub use bevy_render_macros::ExtractResource; @@ -32,7 +32,7 @@ impl Default for ExtractResourcePlugin { impl Plugin for ExtractResourcePlugin { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system(extract_resource::.in_schedule(ExtractSchedule)); + render_app.add_systems(ExtractSchedule, extract_resource::); } } } diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index 6b9fbdbc7f..ef585000b8 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -3,9 +3,9 @@ use crate::{ prelude::Shader, render_resource::{ShaderType, UniformBuffer}, renderer::{RenderDevice, RenderQueue}, - Extract, ExtractSchedule, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_app::{App, IntoSystemAppConfigs, Plugin}; +use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_core::FrameCount; use bevy_ecs::prelude::*; @@ -26,8 +26,8 @@ impl Plugin for GlobalsPlugin { render_app .init_resource::() .init_resource:: { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .init_resource::>() + .init_resource::>() + .init_resource::>() + .add_systems(ExtractSchedule, extract_render_asset::) .configure_sets( + Render, ( PrepareAssetSet::PreAssetPrepare, PrepareAssetSet::AssetPrepare, @@ -89,13 +94,10 @@ impl Plugin for RenderAssetPlugin { .chain() .in_set(RenderSet::Prepare), ) - .init_resource::>() - .init_resource::>() - .init_resource::>() - .add_systems(( - extract_render_asset::.in_schedule(ExtractSchedule), + .add_systems( + Render, prepare_assets::.in_set(self.prepare_asset_set.clone()), - )); + ); } } } diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index f9b32a1eb7..1f4f45a83b 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -33,7 +33,7 @@ pub use texture_cache::*; use crate::{ render_asset::{PrepareAssetSet, RenderAssetPlugin}, renderer::RenderDevice, - RenderApp, RenderSet, + Render, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, Assets}; @@ -115,7 +115,10 @@ impl Plugin for ImagePlugin { .init_resource::() .init_resource::() .init_resource::() - .add_system(update_texture_cache_system.in_set(RenderSet::Cleanup)); + .add_systems( + Render, + update_texture_cache_system.in_set(RenderSet::Cleanup), + ); } } } diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index fa8bf29603..39bc2d1419 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -14,7 +14,7 @@ use crate::{ render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, TextureCache}, - RenderApp, RenderSet, + Render, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; @@ -55,14 +55,17 @@ impl Plugin for ViewPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .configure_set(ViewSet::PrepareUniforms.in_set(RenderSet::Prepare)) - .add_systems(( - prepare_view_uniforms.in_set(ViewSet::PrepareUniforms), - prepare_view_targets - .after(WindowSystem::Prepare) - .in_set(RenderSet::Prepare) - .after(crate::render_asset::prepare_assets::), - )); + .configure_set(Render, ViewSet::PrepareUniforms.in_set(RenderSet::Prepare)) + .add_systems( + Render, + ( + prepare_view_uniforms.in_set(ViewSet::PrepareUniforms), + prepare_view_targets + .after(WindowSystem::Prepare) + .in_set(RenderSet::Prepare) + .after(crate::render_asset::prepare_assets::), + ), + ); } } } diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 3163f7a831..25f0fc02fa 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -2,7 +2,7 @@ mod render_layers; pub use render_layers::*; -use bevy_app::{CoreSet, Plugin}; +use bevy_app::{Plugin, PostUpdate}; use bevy_asset::{Assets, Handle}; use bevy_ecs::prelude::*; use bevy_hierarchy::{Children, Parent}; @@ -91,8 +91,8 @@ impl ComputedVisibility { /// Whether this entity is visible to something this frame. This is true if and only if [`Self::is_visible_in_hierarchy`] and [`Self::is_visible_in_view`] /// are true. This is the canonical method to call to determine if an entity should be drawn. - /// This value is updated in [`CoreSet::PostUpdate`] by the [`VisibilitySystems::CheckVisibility`] system set. - /// Reading it during [`CoreSet::Update`] will yield the value from the previous frame. + /// This value is updated in [`PostUpdate`] by the [`VisibilitySystems::CheckVisibility`] system set. + /// Reading it during [`Update`](bevy_app::Update) will yield the value from the previous frame. #[inline] pub fn is_visible(&self) -> bool { self.flags.bits == ComputedVisibilityFlags::all().bits @@ -100,7 +100,7 @@ impl ComputedVisibility { /// Whether this entity is visible in the entity hierarchy, which is determined by the [`Visibility`] component. /// This takes into account "visibility inheritance". If any of this entity's ancestors (see [`Parent`]) are hidden, this entity - /// will be hidden as well. This value is updated in the [`VisibilitySystems::VisibilityPropagate`], which lives under the [`CoreSet::PostUpdate`] set. + /// will be hidden as well. This value is updated in the [`VisibilitySystems::VisibilityPropagate`], which lives in the [`PostUpdate`] schedule. #[inline] pub fn is_visible_in_hierarchy(&self) -> bool { self.flags @@ -110,8 +110,8 @@ impl ComputedVisibility { /// Whether this entity is visible in _any_ view (Cameras, Lights, etc). Each entity type (and view type) should choose how to set this /// value. For cameras and drawn entities, this will take into account [`RenderLayers`]. /// - /// This value is reset to `false` every frame in [`VisibilitySystems::VisibilityPropagate`] during [`CoreSet::PostUpdate`]. - /// Each entity type then chooses how to set this field in the [`VisibilitySystems::CheckVisibility`] system set, under [`CoreSet::PostUpdate`]. + /// This value is reset to `false` every frame in [`VisibilitySystems::VisibilityPropagate`] during [`PostUpdate`]. + /// Each entity type then chooses how to set this field in the [`VisibilitySystems::CheckVisibility`] system set, in [`PostUpdate`]. /// Meshes might use frustum culling to decide if they are visible in a view. /// Other entities might just set this to `true` every frame. #[inline] @@ -210,52 +210,49 @@ impl Plugin for VisibilityPlugin { fn build(&self, app: &mut bevy_app::App) { use VisibilitySystems::*; - app.configure_set(CalculateBounds.in_base_set(CoreSet::PostUpdate)) + app // We add an AABB component in CalculateBounds, which must be ready on the same frame. - .add_system(apply_system_buffers.in_set(CalculateBoundsFlush)) - .configure_set( - CalculateBoundsFlush - .after(CalculateBounds) - .in_base_set(CoreSet::PostUpdate), + .add_systems( + PostUpdate, + apply_system_buffers.in_set(CalculateBoundsFlush), ) - .configure_set(UpdateOrthographicFrusta.in_base_set(CoreSet::PostUpdate)) - .configure_set(UpdatePerspectiveFrusta.in_base_set(CoreSet::PostUpdate)) - .configure_set(UpdateProjectionFrusta.in_base_set(CoreSet::PostUpdate)) - .configure_set(CheckVisibility.in_base_set(CoreSet::PostUpdate)) - .configure_set(VisibilityPropagate.in_base_set(CoreSet::PostUpdate)) - .add_systems(( - calculate_bounds.in_set(CalculateBounds), - update_frusta:: - .in_set(UpdateOrthographicFrusta) - .after(camera_system::) - .after(TransformSystem::TransformPropagate) - // We assume that no camera will have more than one projection component, - // so these systems will run independently of one another. - // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. - .ambiguous_with(update_frusta::) - .ambiguous_with(update_frusta::), - update_frusta:: - .in_set(UpdatePerspectiveFrusta) - .after(camera_system::) - .after(TransformSystem::TransformPropagate) - // We assume that no camera will have more than one projection component, - // so these systems will run independently of one another. - // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. - .ambiguous_with(update_frusta::), - update_frusta:: - .in_set(UpdateProjectionFrusta) - .after(camera_system::) - .after(TransformSystem::TransformPropagate), - visibility_propagate_system.in_set(VisibilityPropagate), - check_visibility - .in_set(CheckVisibility) - .after(CalculateBoundsFlush) - .after(UpdateOrthographicFrusta) - .after(UpdatePerspectiveFrusta) - .after(UpdateProjectionFrusta) - .after(VisibilityPropagate) - .after(TransformSystem::TransformPropagate), - )); + .configure_set(PostUpdate, CalculateBoundsFlush.after(CalculateBounds)) + .add_systems( + PostUpdate, + ( + calculate_bounds.in_set(CalculateBounds), + update_frusta:: + .in_set(UpdateOrthographicFrusta) + .after(camera_system::) + .after(TransformSystem::TransformPropagate) + // We assume that no camera will have more than one projection component, + // so these systems will run independently of one another. + // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. + .ambiguous_with(update_frusta::) + .ambiguous_with(update_frusta::), + update_frusta:: + .in_set(UpdatePerspectiveFrusta) + .after(camera_system::) + .after(TransformSystem::TransformPropagate) + // We assume that no camera will have more than one projection component, + // so these systems will run independently of one another. + // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. + .ambiguous_with(update_frusta::), + update_frusta:: + .in_set(UpdateProjectionFrusta) + .after(camera_system::) + .after(TransformSystem::TransformPropagate), + visibility_propagate_system.in_set(VisibilityPropagate), + check_visibility + .in_set(CheckVisibility) + .after(CalculateBoundsFlush) + .after(UpdateOrthographicFrusta) + .after(UpdatePerspectiveFrusta) + .after(UpdateProjectionFrusta) + .after(VisibilityPropagate) + .after(TransformSystem::TransformPropagate), + ), + ); } } @@ -457,7 +454,7 @@ mod test { #[test] fn visibility_propagation() { let mut app = App::new(); - app.add_system(visibility_propagate_system); + app.add_systems(Update, visibility_propagate_system); let root1 = app .world @@ -576,7 +573,7 @@ mod test { #[test] fn visibility_propagation_unconditional_visible() { let mut app = App::new(); - app.add_system(visibility_propagate_system); + app.add_systems(Update, visibility_propagate_system); let root1 = app .world diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index ca9fac5aab..8118e4fd06 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -1,9 +1,9 @@ use crate::{ render_resource::TextureView, renderer::{RenderAdapter, RenderDevice, RenderInstance}, - Extract, ExtractSchedule, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_app::{App, IntoSystemAppConfig, Plugin}; +use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; use bevy_utils::{tracing::debug, HashMap, HashSet}; use bevy_window::{ @@ -32,9 +32,9 @@ impl Plugin for WindowRenderPlugin { .init_resource::() .init_resource::() .init_non_send_resource::() - .add_system(extract_windows.in_schedule(ExtractSchedule)) - .configure_set(WindowSystem::Prepare.in_set(RenderSet::Prepare)) - .add_system(prepare_windows.in_set(WindowSystem::Prepare)); + .add_systems(ExtractSchedule, extract_windows) + .configure_set(Render, WindowSystem::Prepare.in_set(RenderSet::Prepare)) + .add_systems(Render, prepare_windows.in_set(WindowSystem::Prepare)); } } } diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 2d7ef33951..3488064f10 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -24,7 +24,6 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_asset::AddAsset; -use bevy_ecs::prelude::*; #[derive(Default)] pub struct ScenePlugin; @@ -36,9 +35,9 @@ impl Plugin for ScenePlugin { .add_asset::() .init_asset_loader::() .init_resource::() - .add_system(scene_spawner_system) + .add_systems(Update, scene_spawner_system) // Systems `*_bundle_spawner` must run before `scene_spawner_system` - .add_system(scene_spawner.in_base_set(CoreSet::PreUpdate)); + .add_systems(PreUpdate, scene_spawner); } } diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index d7a02c3ff7..b624b7bad8 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -34,7 +34,7 @@ use bevy_reflect::TypeUuid; use bevy_render::{ render_phase::AddRenderCommand, render_resource::{Shader, SpecializedRenderPipelines}, - ExtractSchedule, RenderApp, RenderSet, + ExtractSchedule, Render, RenderApp, RenderSet, }; #[derive(Default)] @@ -71,13 +71,14 @@ impl Plugin for SpritePlugin { .init_resource::() .add_render_command::() .add_systems( + ExtractSchedule, ( extract_sprites.in_set(SpriteSystem::ExtractSprites), extract_sprite_events, - ) - .in_schedule(ExtractSchedule), + ), ) - .add_system( + .add_systems( + Render, queue_sprites .in_set(RenderSet::Queue) .ambiguous_with(queue_material2d_meshes::), diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 4ee87cff02..ed03db9a48 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -1,4 +1,4 @@ -use bevy_app::{App, IntoSystemAppConfig, Plugin}; +use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; use bevy_core_pipeline::{ core_2d::Transparent2d, @@ -32,7 +32,7 @@ use bevy_render::{ renderer::RenderDevice, texture::FallbackImage, view::{ComputedVisibility, ExtractedView, Msaa, Visibility, VisibleEntities}, - Extract, ExtractSchedule, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::components::{GlobalTransform, Transform}; use bevy_utils::{FloatOrd, HashMap, HashSet}; @@ -161,13 +161,16 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_systems(( - extract_materials_2d::.in_schedule(ExtractSchedule), - prepare_materials_2d:: - .in_set(RenderSet::Prepare) - .after(PrepareAssetSet::PreAssetPrepare), - queue_material2d_meshes::.in_set(RenderSet::Queue), - )); + .add_systems(ExtractSchedule, extract_materials_2d::) + .add_systems( + Render, + ( + prepare_materials_2d:: + .in_set(RenderSet::Prepare) + .after(PrepareAssetSet::PreAssetPrepare), + queue_material2d_meshes::.in_set(RenderSet::Queue), + ), + ); } } } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index a001b00dc3..070898df55 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -1,4 +1,4 @@ -use bevy_app::{IntoSystemAppConfig, Plugin}; +use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Handle, HandleUntyped}; use bevy_ecs::{ @@ -22,7 +22,7 @@ use bevy_render::{ view::{ ComputedVisibility, ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, }, - Extract, ExtractSchedule, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::components::GlobalTransform; @@ -103,11 +103,14 @@ impl Plugin for Mesh2dRenderPlugin { render_app .init_resource::() .init_resource::>() - .add_systems(( - extract_mesh2d.in_schedule(ExtractSchedule), - queue_mesh2d_bind_group.in_set(RenderSet::Queue), - queue_mesh2d_view_bind_groups.in_set(RenderSet::Queue), - )); + .add_systems(ExtractSchedule, extract_mesh2d) + .add_systems( + Render, + ( + queue_mesh2d_bind_group.in_set(RenderSet::Queue), + queue_mesh2d_view_bind_groups.in_set(RenderSet::Queue), + ), + ); } } } diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 767b926490..e740e525a2 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -79,9 +79,9 @@ impl Plugin for TextPlugin { .init_resource::() .init_resource::() .insert_resource(TextPipeline::default()) - .add_system( + .add_systems( + PostUpdate, update_text2d_layout - .in_base_set(CoreSet::PostUpdate) // Potential conflict: `Assets` // In practice, they run independently since `bevy_render::camera_update_system` // will only ever observe its own render target, and `update_text2d_layout` @@ -90,10 +90,9 @@ impl Plugin for TextPlugin { ); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system( - extract_text2d_sprite - .after(SpriteSystem::ExtractSprites) - .in_schedule(ExtractSchedule), + render_app.add_systems( + ExtractSchedule, + extract_text2d_sprite.after(SpriteSystem::ExtractSprites), ); } } diff --git a/crates/bevy_time/src/common_conditions.rs b/crates/bevy_time/src/common_conditions.rs index 1f5625515e..974de24dfa 100644 --- a/crates/bevy_time/src/common_conditions.rs +++ b/crates/bevy_time/src/common_conditions.rs @@ -8,14 +8,14 @@ use bevy_utils::Duration; /// If used for a fixed timestep system, use [`on_fixed_timer`] instead. /// /// ```rust,no_run -/// # use bevy_app::{App, IntoSystemAppConfig, NoopPluginGroup as DefaultPlugins, PluginGroup}; -/// # use bevy_ecs::schedule::IntoSystemConfig; +/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, Update}; +/// # use bevy_ecs::schedule::IntoSystemConfigs; /// # use bevy_utils::Duration; /// # use bevy_time::common_conditions::on_timer; /// fn main() { /// App::new() /// .add_plugins(DefaultPlugins) -/// .add_system(tick.run_if(on_timer(Duration::from_secs(1)))) +/// .add_systems(Update, tick.run_if(on_timer(Duration::from_secs(1)))) /// .run(); /// } /// fn tick() { @@ -46,16 +46,15 @@ pub fn on_timer(duration: Duration) -> impl FnMut(Res() .init_resource::() - .add_systems(( - // This pair of systems has an ambiguous order, - // as their data access conflicts, and there's no order between them. - reads_a, - writes_a, - // This pair of systems has conflicting data access, - // but it's resolved with an explicit ordering: - // the .after relationship here means that we will always double after adding. - adds_one_to_b, - doubles_b.after(adds_one_to_b), - // This system isn't ambiguous with adds_one_to_b, - // due to the transitive ordering created by our constraints: - // if A is before B is before C, then A must be before C as well. - reads_b.after(doubles_b), - // This system will conflict with all of our writing systems - // but we've silenced its ambiguity with adds_one_to_b. - // This should only be done in the case of clear false positives: - // leave a comment in your code justifying the decision! - reads_a_and_b.ambiguous_with(adds_one_to_b), - )) + .add_systems( + Update, + ( + // This pair of systems has an ambiguous order, + // as their data access conflicts, and there's no order between them. + reads_a, + writes_a, + // This pair of systems has conflicting data access, + // but it's resolved with an explicit ordering: + // the .after relationship here means that we will always double after adding. + adds_one_to_b, + doubles_b.after(adds_one_to_b), + // This system isn't ambiguous with adds_one_to_b, + // due to the transitive ordering created by our constraints: + // if A is before B is before C, then A must be before C as well. + reads_b.after(doubles_b), + // This system will conflict with all of our writing systems + // but we've silenced its ambiguity with adds_one_to_b. + // This should only be done in the case of clear false positives: + // leave a comment in your code justifying the decision! + reads_a_and_b.ambiguous_with(adds_one_to_b), + ), + ) // Be mindful, internal ambiguities are reported too! // If there are any ambiguities due solely to DefaultPlugins, // or between DefaultPlugins and any of your third party plugins, diff --git a/examples/ecs/parallel_query.rs b/examples/ecs/parallel_query.rs index 987962a164..d68f50b1f7 100644 --- a/examples/ecs/parallel_query.rs +++ b/examples/ecs/parallel_query.rs @@ -69,6 +69,7 @@ fn bounce_system(windows: Query<&Window>, mut sprites: Query<(&Transform, &mut V fn main() { App::new() .add_plugins(DefaultPlugins) - .add_systems((spawn_system.on_startup(), move_system, bounce_system)) + .add_systems(Startup, spawn_system) + .add_systems(Update, (move_system, bounce_system)) .run(); } diff --git a/examples/ecs/removal_detection.rs b/examples/ecs/removal_detection.rs index 96f0c6e1ca..b7d339afa8 100644 --- a/examples/ecs/removal_detection.rs +++ b/examples/ecs/removal_detection.rs @@ -14,11 +14,9 @@ fn main() { // `CoreSet::Update', and the system that reacts on the removal in `CoreSet::PostUpdate`. App::new() .add_plugins(DefaultPlugins) - .add_systems(( - setup.on_startup(), - remove_component, - react_on_removal.in_base_set(CoreSet::PostUpdate), - )) + .add_systems(Startup, setup) + .add_systems(Update, remove_component) + .add_systems(PostUpdate, react_on_removal) .run(); } diff --git a/examples/ecs/run_conditions.rs b/examples/ecs/run_conditions.rs index 557aa4b50b..413982d6e0 100644 --- a/examples/ecs/run_conditions.rs +++ b/examples/ecs/run_conditions.rs @@ -11,37 +11,40 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .init_resource::() - .add_systems(( - increment_input_counter - // The common_conditions module has a few useful run conditions - // for checking resources and states. These are included in the prelude. - .run_if(resource_exists::()) - // This is a custom run condition, defined using a system that returns - // a `bool` and which has read-only `SystemParam`s. - // Both run conditions must return `true` in order for the system to run. - // Note that this second run condition will be evaluated even if the first returns `false`. - .run_if(has_user_input), - print_input_counter - // `.and_then()` is a run condition combinator that only evaluates the second condition - // if the first condition returns `true`. This behavior is known as "short-circuiting", - // and is how the `&&` operator works in Rust (as well as most C-family languages). - // In this case, the short-circuiting behavior prevents the second run condition from - // panicking if the `InputCounter` resource has not been initialized. - .run_if(resource_exists::().and_then( - // This is a custom run condition in the form of a closure. - // This is useful for small, simple run conditions you don't need to reuse. - // All the normal rules still apply: all parameters must be read only except for local parameters. - |counter: Res| counter.is_changed() && !counter.is_added(), - )), - print_time_message - // This function returns a custom run condition, much like the common conditions module. - // It will only return true once 2 seconds have passed. - .run_if(time_passed(2.0)) - // You can use the `not` condition from the common_conditions module - // to inverse a run condition. In this case it will return true if - // less than 2.5 seconds have elapsed since the app started. - .run_if(not(time_passed(2.5))), - )) + .add_systems( + Update, + ( + increment_input_counter + // The common_conditions module has a few useful run conditions + // for checking resources and states. These are included in the prelude. + .run_if(resource_exists::()) + // This is a custom run condition, defined using a system that returns + // a `bool` and which has read-only `SystemParam`s. + // Both run conditions must return `true` in order for the system to run. + // Note that this second run condition will be evaluated even if the first returns `false`. + .run_if(has_user_input), + print_input_counter + // `.and_then()` is a run condition combinator that only evaluates the second condition + // if the first condition returns `true`. This behavior is known as "short-circuiting", + // and is how the `&&` operator works in Rust (as well as most C-family languages). + // In this case, the short-circuiting behavior prevents the second run condition from + // panicking if the `InputCounter` resource has not been initialized. + .run_if(resource_exists::().and_then( + // This is a custom run condition in the form of a closure. + // This is useful for small, simple run conditions you don't need to reuse. + // All the normal rules still apply: all parameters must be read only except for local parameters. + |counter: Res| counter.is_changed() && !counter.is_added(), + )), + print_time_message + // This function returns a custom run condition, much like the common conditions module. + // It will only return true once 2 seconds have passed. + .run_if(time_passed(2.0)) + // You can use the `not` condition from the common_conditions module + // to inverse a run condition. In this case it will return true if + // less than 2.5 seconds have elapsed since the app started. + .run_if(not(time_passed(2.5))), + ), + ) .run(); } diff --git a/examples/ecs/startup_system.rs b/examples/ecs/startup_system.rs index f1a3e892cd..bb303e85ad 100644 --- a/examples/ecs/startup_system.rs +++ b/examples/ecs/startup_system.rs @@ -4,8 +4,8 @@ use bevy::prelude::*; fn main() { App::new() - .add_startup_system(startup_system) - .add_system(normal_system) + .add_systems(Startup, startup_system) + .add_systems(Update, normal_system) .run(); } diff --git a/examples/ecs/state.rs b/examples/ecs/state.rs index ae7f9d9950..b61ba01adb 100644 --- a/examples/ecs/state.rs +++ b/examples/ecs/state.rs @@ -11,19 +11,20 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_state::() - .add_system(setup.on_startup()) - // This system runs when we enter `AppState::Menu`, during `CoreSet::StateTransitions`. + .add_systems(Startup, setup) + // This system runs when we enter `AppState::Menu`, during the `StateTransitions` schedule. // All systems from the exit schedule of the state we're leaving are run first, // and then all systems from the enter schedule of the state we're entering are run second. - .add_system(setup_menu.in_schedule(OnEnter(AppState::Menu))) - // By contrast, on_update systems are stored in the main schedule, during CoreSet::Update, - // and simply check the value of the `State` resource to see if they should run each frame. - .add_systems(( - menu.in_set(OnUpdate(AppState::Menu)), - cleanup_menu.in_schedule(OnExit(AppState::Menu)), - setup_game.in_schedule(OnEnter(AppState::InGame)), - )) - .add_systems((movement, change_color).in_set(OnUpdate(AppState::InGame))) + .add_systems(OnEnter(AppState::Menu), setup_menu) + // By contrast, update systems are stored in the `Update` schedule. They simply + // check the value of the `State` resource to see if they should run each frame. + .add_systems(Update, menu.in_set(OnUpdate(AppState::Menu))) + .add_systems(OnExit(AppState::Menu), cleanup_menu) + .add_systems(OnEnter(AppState::InGame), setup_game) + .add_systems( + Update, + (movement, change_color).in_set(OnUpdate(AppState::InGame)), + ) .run(); } diff --git a/examples/ecs/system_closure.rs b/examples/ecs/system_closure.rs index 952d690b2b..7c6c077f64 100644 --- a/examples/ecs/system_closure.rs +++ b/examples/ecs/system_closure.rs @@ -27,15 +27,15 @@ fn main() { App::new() .add_plugin(LogPlugin::default()) // we can use a closure as a system - .add_system(simple_closure) + .add_systems(Update, simple_closure) // or we can use a more complex closure, and pass an argument to initialize a Local variable. - .add_system(complex_closure("foo".into())) + .add_systems(Update, complex_closure("foo".into())) // we can also inline a closure - .add_system(|| { + .add_systems(Update, || { info!("Hello from an inlined closure!"); }) // or use variables outside a closure - .add_system(move || { + .add_systems(Update, move || { info!( "Hello from an inlined closure that captured the 'outside_variable'! {:?}", outside_variable diff --git a/examples/ecs/system_param.rs b/examples/ecs/system_param.rs index 4304ec1699..ebe5c1713a 100644 --- a/examples/ecs/system_param.rs +++ b/examples/ecs/system_param.rs @@ -5,7 +5,8 @@ use bevy::{ecs::system::SystemParam, prelude::*}; fn main() { App::new() .insert_resource(PlayerCount(0)) - .add_systems((spawn.on_startup(), count_players)) + .add_systems(Startup, spawn) + .add_systems(Update, count_players) .run(); } diff --git a/examples/ecs/system_piping.rs b/examples/ecs/system_piping.rs index c0a772d6a4..c3416c3994 100644 --- a/examples/ecs/system_piping.rs +++ b/examples/ecs/system_piping.rs @@ -15,14 +15,17 @@ fn main() { level: Level::TRACE, filter: "".to_string(), }) - .add_systems(( - parse_message_system.pipe(handler_system), - data_pipe_system.pipe(info), - parse_message_system.pipe(dbg), - warning_pipe_system.pipe(warn), - parse_error_message_system.pipe(error), - parse_message_system.pipe(ignore), - )) + .add_systems( + Update, + ( + parse_message_system.pipe(handler_system), + data_pipe_system.pipe(info), + parse_message_system.pipe(dbg), + warning_pipe_system.pipe(warn), + parse_error_message_system.pipe(error), + parse_message_system.pipe(ignore), + ), + ) .run(); } diff --git a/examples/ecs/timers.rs b/examples/ecs/timers.rs index 8376f39a0b..7b3f09c808 100644 --- a/examples/ecs/timers.rs +++ b/examples/ecs/timers.rs @@ -6,7 +6,8 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .init_resource::() - .add_systems((setup.on_startup(), countdown, print_when_completed)) + .add_systems(Startup, setup) + .add_systems(Update, (countdown, print_when_completed)) .run(); } diff --git a/examples/games/alien_cake_addict.rs b/examples/games/alien_cake_addict.rs index 7fbc2961c8..ed9fb30529 100644 --- a/examples/games/alien_cake_addict.rs +++ b/examples/games/alien_cake_addict.rs @@ -17,18 +17,17 @@ struct BonusSpawnTimer(Timer); fn main() { App::new() + .add_plugins(DefaultPlugins) .init_resource::() .insert_resource(BonusSpawnTimer(Timer::from_seconds( 5.0, TimerMode::Repeating, ))) - .add_plugins(DefaultPlugins) .add_state::() - .add_systems(( - setup_cameras.on_startup(), - setup.in_schedule(OnEnter(GameState::Playing)), - )) + .add_systems(Startup, setup_cameras) + .add_systems(OnEnter(GameState::Playing), setup) .add_systems( + Update, ( move_player, focus_camera, @@ -38,13 +37,16 @@ fn main() { ) .in_set(OnUpdate(GameState::Playing)), ) - .add_systems(( - teardown.in_schedule(OnExit(GameState::Playing)), - display_score.in_schedule(OnEnter(GameState::GameOver)), - gameover_keyboard.in_set(OnUpdate(GameState::GameOver)), - teardown.in_schedule(OnExit(GameState::GameOver)), - bevy::window::close_on_esc, - )) + .add_systems(OnExit(GameState::Playing), teardown) + .add_systems(OnEnter(GameState::GameOver), display_score) + .add_systems( + Update, + ( + gameover_keyboard.in_set(OnUpdate(GameState::GameOver)), + bevy::window::close_on_esc, + ), + ) + .add_systems(OnExit(GameState::GameOver), teardown) .run(); } diff --git a/examples/games/breakout.rs b/examples/games/breakout.rs index 206da2fe4a..a10b194edb 100644 --- a/examples/games/breakout.rs +++ b/examples/games/breakout.rs @@ -52,10 +52,13 @@ fn main() { .add_plugins(DefaultPlugins) .insert_resource(Scoreboard { score: 0 }) .insert_resource(ClearColor(BACKGROUND_COLOR)) - .add_startup_system(setup) .add_event::() + // Configure how frequently our gameplay systems are run + .insert_resource(FixedTime::new_from_secs(1.0 / 60.0)) + .add_systems(Startup, setup) // Add our gameplay simulation systems to the fixed timestep schedule .add_systems( + FixedUpdate, ( check_for_collisions, apply_velocity.before(check_for_collisions), @@ -63,13 +66,9 @@ fn main() { .before(check_for_collisions) .after(apply_velocity), play_collision_sound.after(check_for_collisions), - ) - .in_schedule(CoreSchedule::FixedUpdate), + ), ) - // Configure how frequently our gameplay systems are run - .insert_resource(FixedTime::new_from_secs(1.0 / 60.0)) - .add_system(update_scoreboard) - .add_system(bevy::window::close_on_esc) + .add_systems(Update, (update_scoreboard, bevy::window::close_on_esc)) .run(); } diff --git a/examples/games/contributors.rs b/examples/games/contributors.rs index f18ae839f1..f76e0778f0 100644 --- a/examples/games/contributors.rs +++ b/examples/games/contributors.rs @@ -11,14 +11,17 @@ use std::{ fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_systems((setup_contributor_selection, setup)) - .add_systems(( - velocity_system, - move_system, - collision_system, - select_system, - )) .init_resource::() + .add_systems(Startup, (setup_contributor_selection, setup)) + .add_systems( + Update, + ( + velocity_system, + move_system, + collision_system, + select_system, + ), + ) .run(); } diff --git a/examples/games/game_menu.rs b/examples/games/game_menu.rs index 8dc39e701d..3e38e34f38 100644 --- a/examples/games/game_menu.rs +++ b/examples/games/game_menu.rs @@ -33,9 +33,9 @@ fn main() { // Insert as resource the initial value for the settings resources .insert_resource(DisplayQuality::Medium) .insert_resource(Volume(7)) - .add_startup_system(setup) // Declare the game state, whose starting value is determined by the `Default` trait .add_state::() + .add_systems(Startup, setup) // Adds the plugins for each state .add_plugin(splash::SplashPlugin) .add_plugin(menu::MenuPlugin) @@ -58,14 +58,13 @@ mod splash { impl Plugin for SplashPlugin { fn build(&self, app: &mut App) { // As this plugin is managing the splash screen, it will focus on the state `GameState::Splash` - app.add_systems(( + app // When entering the state, spawn everything needed for this screen - splash_setup.in_schedule(OnEnter(GameState::Splash)), + .add_systems(OnEnter(GameState::Splash), splash_setup) // While in this state, run the `countdown` system - countdown.in_set(OnUpdate(GameState::Splash)), + .add_systems(Update, countdown.in_set(OnUpdate(GameState::Splash))) // When exiting the state, despawn everything that was spawned for this screen - despawn_screen::.in_schedule(OnExit(GameState::Splash)), - )); + .add_systems(OnExit(GameState::Splash), despawn_screen::); } } @@ -131,11 +130,9 @@ mod game { impl Plugin for GamePlugin { fn build(&self, app: &mut App) { - app.add_systems(( - game_setup.in_schedule(OnEnter(GameState::Game)), - game.in_set(OnUpdate(GameState::Game)), - despawn_screen::.in_schedule(OnExit(GameState::Game)), - )); + app.add_systems(OnEnter(GameState::Game), game_setup) + .add_systems(Update, game.in_set(OnUpdate(GameState::Game))) + .add_systems(OnExit(GameState::Game), despawn_screen::); } } @@ -268,33 +265,47 @@ mod menu { // entering the `GameState::Menu` state. // Current screen in the menu is handled by an independent state from `GameState` .add_state::() - .add_system(menu_setup.in_schedule(OnEnter(GameState::Menu))) + .add_systems(OnEnter(GameState::Menu), menu_setup) // Systems to handle the main menu screen - .add_systems(( - main_menu_setup.in_schedule(OnEnter(MenuState::Main)), - despawn_screen::.in_schedule(OnExit(MenuState::Main)), - )) + .add_systems(OnEnter(MenuState::Main), main_menu_setup) + .add_systems(OnExit(MenuState::Main), despawn_screen::) // Systems to handle the settings menu screen - .add_systems(( - settings_menu_setup.in_schedule(OnEnter(MenuState::Settings)), - despawn_screen::.in_schedule(OnExit(MenuState::Settings)), - )) + .add_systems(OnEnter(MenuState::Settings), settings_menu_setup) + .add_systems( + OnExit(MenuState::Settings), + despawn_screen::, + ) // Systems to handle the display settings screen - .add_systems(( - display_settings_menu_setup.in_schedule(OnEnter(MenuState::SettingsDisplay)), - setting_button::.in_set(OnUpdate(MenuState::SettingsDisplay)), - despawn_screen:: - .in_schedule(OnExit(MenuState::SettingsDisplay)), - )) + .add_systems( + OnEnter(MenuState::SettingsDisplay), + display_settings_menu_setup, + ) + .add_systems( + Update, + ( + setting_button:: + .in_set(OnUpdate(MenuState::SettingsDisplay)), + ), + ) + .add_systems( + OnExit(MenuState::SettingsDisplay), + despawn_screen::, + ) // Systems to handle the sound settings screen - .add_systems(( - sound_settings_menu_setup.in_schedule(OnEnter(MenuState::SettingsSound)), + .add_systems(OnEnter(MenuState::SettingsSound), sound_settings_menu_setup) + .add_systems( + Update, setting_button::.in_set(OnUpdate(MenuState::SettingsSound)), - despawn_screen:: - .in_schedule(OnExit(MenuState::SettingsSound)), - )) + ) + .add_systems( + OnExit(MenuState::SettingsSound), + despawn_screen::, + ) // Common systems to all screens that handles buttons behaviour - .add_systems((menu_action, button_system).in_set(OnUpdate(GameState::Menu))); + .add_systems( + Update, + (menu_action, button_system).in_set(OnUpdate(GameState::Menu)), + ); } } diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 3f09f5c4c7..9b5dc363de 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; fn main() { - App::new().add_system(hello_world_system).run(); + App::new().add_systems(Update, hello_world_system).run(); } fn hello_world_system() { diff --git a/examples/input/char_input_events.rs b/examples/input/char_input_events.rs index 1fbb7af53b..141f4ce806 100644 --- a/examples/input/char_input_events.rs +++ b/examples/input/char_input_events.rs @@ -5,7 +5,7 @@ use bevy::{prelude::*, window::ReceivedCharacter}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_system(print_char_event_system) + .add_systems(Update, print_char_event_system) .run(); } diff --git a/examples/input/gamepad_input.rs b/examples/input/gamepad_input.rs index 10adf077e7..9abc87bafd 100644 --- a/examples/input/gamepad_input.rs +++ b/examples/input/gamepad_input.rs @@ -5,7 +5,7 @@ use bevy::{input::gamepad::GamepadButton, prelude::*}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_system(gamepad_system) + .add_systems(Update, gamepad_system) .run(); } diff --git a/examples/input/gamepad_input_events.rs b/examples/input/gamepad_input_events.rs index a53976b196..1e21f7d0f3 100644 --- a/examples/input/gamepad_input_events.rs +++ b/examples/input/gamepad_input_events.rs @@ -10,7 +10,7 @@ use bevy::{ fn main() { App::new() .add_plugins(DefaultPlugins) - .add_systems((gamepad_events, gamepad_ordered_events)) + .add_systems(Update, (gamepad_events, gamepad_ordered_events)) .run(); } diff --git a/examples/input/keyboard_input.rs b/examples/input/keyboard_input.rs index 27c2592927..1072e3695e 100644 --- a/examples/input/keyboard_input.rs +++ b/examples/input/keyboard_input.rs @@ -5,7 +5,7 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_system(keyboard_input_system) + .add_systems(Update, keyboard_input_system) .run(); } diff --git a/examples/input/keyboard_input_events.rs b/examples/input/keyboard_input_events.rs index 1353ed3587..bac3b68316 100644 --- a/examples/input/keyboard_input_events.rs +++ b/examples/input/keyboard_input_events.rs @@ -5,7 +5,7 @@ use bevy::{input::keyboard::KeyboardInput, prelude::*}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_system(print_keyboard_event_system) + .add_systems(Update, print_keyboard_event_system) .run(); } diff --git a/examples/input/keyboard_modifiers.rs b/examples/input/keyboard_modifiers.rs index a7242e7d5f..271e133d7a 100644 --- a/examples/input/keyboard_modifiers.rs +++ b/examples/input/keyboard_modifiers.rs @@ -5,7 +5,7 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_system(keyboard_input_system) + .add_systems(Update, keyboard_input_system) .run(); } diff --git a/examples/input/mouse_grab.rs b/examples/input/mouse_grab.rs index 988e6e4571..33e2574180 100644 --- a/examples/input/mouse_grab.rs +++ b/examples/input/mouse_grab.rs @@ -5,7 +5,7 @@ use bevy::{prelude::*, window::CursorGrabMode}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_system(grab_mouse) + .add_systems(Update, grab_mouse) .run(); } diff --git a/examples/input/mouse_input.rs b/examples/input/mouse_input.rs index 918a6713c2..0e812646ea 100644 --- a/examples/input/mouse_input.rs +++ b/examples/input/mouse_input.rs @@ -5,7 +5,7 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_system(mouse_click_system) + .add_systems(Update, mouse_click_system) .run(); } diff --git a/examples/input/mouse_input_events.rs b/examples/input/mouse_input_events.rs index 3d3cf40e34..c15a60b0e1 100644 --- a/examples/input/mouse_input_events.rs +++ b/examples/input/mouse_input_events.rs @@ -8,7 +8,7 @@ use bevy::{ fn main() { App::new() .add_plugins(DefaultPlugins) - .add_system(print_mouse_events_system) + .add_systems(Update, print_mouse_events_system) .run(); } diff --git a/examples/input/text_input.rs b/examples/input/text_input.rs index e083f76d25..97e90ad033 100644 --- a/examples/input/text_input.rs +++ b/examples/input/text_input.rs @@ -9,14 +9,17 @@ use bevy::{input::keyboard::KeyboardInput, prelude::*}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_systems(( - setup_scene.on_startup(), - toggle_ime, - listen_ime_events, - listen_received_character_events, - listen_keyboard_input_events, - bubbling_text, - )) + .add_systems(Startup, setup_scene) + .add_systems( + Update, + ( + toggle_ime, + listen_ime_events, + listen_received_character_events, + listen_keyboard_input_events, + bubbling_text, + ), + ) .run(); } diff --git a/examples/input/touch_input.rs b/examples/input/touch_input.rs index b2427aebc4..e3fa437c56 100644 --- a/examples/input/touch_input.rs +++ b/examples/input/touch_input.rs @@ -5,7 +5,7 @@ use bevy::{input::touch::*, prelude::*}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_system(touch_system) + .add_systems(Update, touch_system) .run(); } diff --git a/examples/input/touch_input_events.rs b/examples/input/touch_input_events.rs index 665abd271e..b8e918334e 100644 --- a/examples/input/touch_input_events.rs +++ b/examples/input/touch_input_events.rs @@ -5,7 +5,7 @@ use bevy::{input::touch::*, prelude::*}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_system(touch_event_system) + .add_systems(Update, touch_event_system) .run(); } diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index 66ae669676..c964835211 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -12,8 +12,8 @@ fn main() { }), ..default() })) - .add_startup_systems((setup_scene, setup_music)) - .add_systems((touch_camera, button_handler)) + .add_systems(Startup, (setup_scene, setup_music)) + .add_systems(Update, (touch_camera, button_handler)) .run(); } diff --git a/examples/reflection/generic_reflection.rs b/examples/reflection/generic_reflection.rs index b91a4fa073..854bbb5ca1 100644 --- a/examples/reflection/generic_reflection.rs +++ b/examples/reflection/generic_reflection.rs @@ -8,7 +8,7 @@ fn main() { .add_plugins(DefaultPlugins) // You must manually register each instance of a generic type .register_type::>() - .add_startup_system(setup) + .add_systems(Startup, setup) .run(); } diff --git a/examples/reflection/reflection.rs b/examples/reflection/reflection.rs index 447db38a3e..bd23d5a1a8 100644 --- a/examples/reflection/reflection.rs +++ b/examples/reflection/reflection.rs @@ -18,7 +18,7 @@ fn main() { .add_plugins(DefaultPlugins) .register_type::() .register_type::() - .add_startup_system(setup) + .add_systems(Startup, setup) .run(); } diff --git a/examples/reflection/reflection_types.rs b/examples/reflection/reflection_types.rs index db9dc4b61d..584f45822a 100644 --- a/examples/reflection/reflection_types.rs +++ b/examples/reflection/reflection_types.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) + .add_systems(Startup, setup) .run(); } diff --git a/examples/reflection/trait_reflection.rs b/examples/reflection/trait_reflection.rs index c675abd003..218d0da98e 100644 --- a/examples/reflection/trait_reflection.rs +++ b/examples/reflection/trait_reflection.rs @@ -5,8 +5,8 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup) .register_type::() + .add_systems(Startup, setup) .run(); } diff --git a/examples/scene/scene.rs b/examples/scene/scene.rs index 33a6c426ef..e6477d7ded 100644 --- a/examples/scene/scene.rs +++ b/examples/scene/scene.rs @@ -14,8 +14,11 @@ fn main() { })) .register_type::() .register_type::() - .add_startup_systems((save_scene_system, load_scene_system, infotext_system)) - .add_system(log_system) + .add_systems( + Startup, + (save_scene_system, load_scene_system, infotext_system), + ) + .add_systems(Update, log_system) .run(); } diff --git a/examples/shader/animate_shader.rs b/examples/shader/animate_shader.rs index cb5d09038a..c4add68af9 100644 --- a/examples/shader/animate_shader.rs +++ b/examples/shader/animate_shader.rs @@ -7,7 +7,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(MaterialPlugin::::default()) - .add_startup_system(setup) + .add_systems(Startup, setup) .run(); } diff --git a/examples/shader/array_texture.rs b/examples/shader/array_texture.rs index 5066779818..ee2ebe3070 100644 --- a/examples/shader/array_texture.rs +++ b/examples/shader/array_texture.rs @@ -11,7 +11,8 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(MaterialPlugin::::default()) - .add_systems((setup.on_startup(), create_array_texture)) + .add_systems(Startup, setup) + .add_systems(Update, create_array_texture) .run(); } diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index 736e0b3f11..29a41b3e83 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -11,7 +11,7 @@ use bevy::{ render_graph::{self, RenderGraph}, render_resource::*, renderer::{RenderContext, RenderDevice}, - RenderApp, RenderSet, + Render, RenderApp, RenderSet, }, window::WindowPlugin, }; @@ -32,7 +32,7 @@ fn main() { ..default() })) .add_plugin(GameOfLifeComputePlugin) - .add_startup_system(setup) + .add_systems(Startup, setup) .run(); } @@ -74,7 +74,7 @@ impl Plugin for GameOfLifeComputePlugin { let render_app = app.sub_app_mut(RenderApp); render_app .init_resource::() - .add_system(queue_bind_group.in_set(RenderSet::Queue)); + .add_systems(Render, queue_bind_group.in_set(RenderSet::Queue)); let mut render_graph = render_app.world.resource_mut::(); render_graph.add_node("game_of_life", GameOfLifeNode::default()); diff --git a/examples/shader/custom_vertex_attribute.rs b/examples/shader/custom_vertex_attribute.rs index e98ca56a24..628e145f64 100644 --- a/examples/shader/custom_vertex_attribute.rs +++ b/examples/shader/custom_vertex_attribute.rs @@ -17,7 +17,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(MaterialPlugin::::default()) - .add_startup_system(setup) + .add_systems(Startup, setup) .run(); } diff --git a/examples/shader/post_process_pass.rs b/examples/shader/post_process_pass.rs index ca91b7ee44..0bb3b8aaa4 100644 --- a/examples/shader/post_process_pass.rs +++ b/examples/shader/post_process_pass.rs @@ -39,9 +39,8 @@ fn main() { ..default() })) .add_plugin(PostProcessPlugin) - .add_startup_system(setup) - .add_system(rotate) - .add_system(update_settings) + .add_systems(Startup, setup) + .add_systems(Update, (rotate, update_settings)) .run(); } diff --git a/examples/shader/post_processing.rs b/examples/shader/post_processing.rs index ce62816fca..d23200c911 100644 --- a/examples/shader/post_processing.rs +++ b/examples/shader/post_processing.rs @@ -23,7 +23,8 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(Material2dPlugin::::default()) - .add_systems((setup.on_startup(), main_camera_cube_rotator_system)) + .add_systems(Startup, setup) + .add_systems(Update, main_camera_cube_rotator_system) .run(); } diff --git a/examples/shader/shader_defs.rs b/examples/shader/shader_defs.rs index 4735406ace..fe38783215 100644 --- a/examples/shader/shader_defs.rs +++ b/examples/shader/shader_defs.rs @@ -16,7 +16,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(MaterialPlugin::::default()) - .add_startup_system(setup) + .add_systems(Startup, setup) .run(); } diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 902860aaa7..dd533e32af 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -19,7 +19,7 @@ use bevy::{ render_resource::*, renderer::RenderDevice, view::{ExtractedView, NoFrustumCulling}, - RenderApp, RenderSet, + Render, RenderApp, RenderSet, }, }; use bytemuck::{Pod, Zeroable}; @@ -28,7 +28,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(CustomMaterialPlugin) - .add_startup_system(setup) + .add_systems(Startup, setup) .run(); } @@ -85,10 +85,13 @@ impl Plugin for CustomMaterialPlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_systems(( - queue_custom.in_set(RenderSet::Queue), - prepare_instance_buffers.in_set(RenderSet::Prepare), - )); + .add_systems( + Render, + ( + queue_custom.in_set(RenderSet::Queue), + prepare_instance_buffers.in_set(RenderSet::Prepare), + ), + ); } } diff --git a/examples/shader/shader_material.rs b/examples/shader/shader_material.rs index d501d6c092..ae8ea78e54 100644 --- a/examples/shader/shader_material.rs +++ b/examples/shader/shader_material.rs @@ -10,7 +10,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(MaterialPlugin::::default()) - .add_startup_system(setup) + .add_systems(Startup, setup) .run(); } diff --git a/examples/shader/shader_material_glsl.rs b/examples/shader/shader_material_glsl.rs index 01409b2634..3afae668f2 100644 --- a/examples/shader/shader_material_glsl.rs +++ b/examples/shader/shader_material_glsl.rs @@ -16,7 +16,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(MaterialPlugin::::default()) - .add_startup_system(setup) + .add_systems(Startup, setup) .run(); } diff --git a/examples/shader/shader_material_screenspace_texture.rs b/examples/shader/shader_material_screenspace_texture.rs index 71b73ae998..7ead718c14 100644 --- a/examples/shader/shader_material_screenspace_texture.rs +++ b/examples/shader/shader_material_screenspace_texture.rs @@ -10,7 +10,8 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(MaterialPlugin::::default()) - .add_systems((setup.on_startup(), rotate_camera)) + .add_systems(Startup, setup) + .add_systems(Update, rotate_camera) .run(); } diff --git a/examples/shader/shader_prepass.rs b/examples/shader/shader_prepass.rs index 0271977128..07b7f1890a 100644 --- a/examples/shader/shader_prepass.rs +++ b/examples/shader/shader_prepass.rs @@ -28,7 +28,8 @@ fn main() { prepass_enabled: false, ..default() }) - .add_systems((setup.on_startup(), rotate, toggle_prepass_view)) + .add_systems(Startup, setup) + .add_systems(Update, (rotate, toggle_prepass_view)) .run(); } diff --git a/examples/shader/texture_binding_array.rs b/examples/shader/texture_binding_array.rs index 94e96d7762..0a88718ca7 100644 --- a/examples/shader/texture_binding_array.rs +++ b/examples/shader/texture_binding_array.rs @@ -33,7 +33,7 @@ fn main() { } app.add_plugin(MaterialPlugin::::default()) - .add_startup_system(setup) + .add_systems(Startup, setup) .run(); } diff --git a/examples/stress_tests/bevymark.rs b/examples/stress_tests/bevymark.rs index e4131e029f..83c1abfa56 100644 --- a/examples/stress_tests/bevymark.rs +++ b/examples/stress_tests/bevymark.rs @@ -43,14 +43,17 @@ fn main() { count: 0, color: Color::WHITE, }) - .add_systems(( - setup.on_startup(), - mouse_handler, - movement_system, - collision_system, - counter_system, - scheduled_spawner.in_schedule(CoreSchedule::FixedUpdate), - )) + .add_systems(Startup, setup) + .add_systems(FixedUpdate, scheduled_spawner) + .add_systems( + Update, + ( + mouse_handler, + movement_system, + collision_system, + counter_system, + ), + ) .insert_resource(FixedTime::new_from_secs(0.2)) .run(); } diff --git a/examples/stress_tests/many_animated_sprites.rs b/examples/stress_tests/many_animated_sprites.rs index a5d6b4e938..e933763f44 100644 --- a/examples/stress_tests/many_animated_sprites.rs +++ b/examples/stress_tests/many_animated_sprites.rs @@ -32,12 +32,15 @@ fn main() { }), ..default() })) - .add_systems(( - setup.on_startup(), - animate_sprite, - print_sprite_count, - move_camera.after(print_sprite_count), - )) + .add_systems(Startup, setup) + .add_systems( + Update, + ( + animate_sprite, + print_sprite_count, + move_camera.after(print_sprite_count), + ), + ) .run(); } diff --git a/examples/stress_tests/many_buttons.rs b/examples/stress_tests/many_buttons.rs index 826aa5395c..f25175cd11 100644 --- a/examples/stress_tests/many_buttons.rs +++ b/examples/stress_tests/many_buttons.rs @@ -21,7 +21,8 @@ fn main() { .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) .init_resource::() - .add_systems((setup.on_startup(), button_system)) + .add_systems(Startup, setup) + .add_systems(Update, button_system) .run(); } diff --git a/examples/stress_tests/many_cubes.rs b/examples/stress_tests/many_cubes.rs index 4a87d6f4dc..6d0d7db3f1 100644 --- a/examples/stress_tests/many_cubes.rs +++ b/examples/stress_tests/many_cubes.rs @@ -30,7 +30,8 @@ fn main() { })) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) - .add_systems((setup.on_startup(), move_camera, print_mesh_count)) + .add_systems(Startup, setup) + .add_systems(Update, (move_camera, print_mesh_count)) .run(); } diff --git a/examples/stress_tests/many_foxes.rs b/examples/stress_tests/many_foxes.rs index 2b42d8602f..9244861d8b 100644 --- a/examples/stress_tests/many_foxes.rs +++ b/examples/stress_tests/many_foxes.rs @@ -41,12 +41,15 @@ fn main() { color: Color::WHITE, brightness: 1.0, }) - .add_systems(( - setup.on_startup(), - setup_scene_once_loaded, - keyboard_animation_control, - update_fox_rings.after(keyboard_animation_control), - )) + .add_systems(Startup, setup) + .add_systems( + Update, + ( + setup_scene_once_loaded, + keyboard_animation_control, + update_fox_rings.after(keyboard_animation_control), + ), + ) .run(); } diff --git a/examples/stress_tests/many_glyphs.rs b/examples/stress_tests/many_glyphs.rs index b5c7fc0f37..ec320e7d81 100644 --- a/examples/stress_tests/many_glyphs.rs +++ b/examples/stress_tests/many_glyphs.rs @@ -20,7 +20,7 @@ fn main() { })) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) - .add_startup_system(setup) + .add_systems(Startup, setup) .run(); } diff --git a/examples/stress_tests/many_lights.rs b/examples/stress_tests/many_lights.rs index 0060ab354d..c00e8c6cf5 100644 --- a/examples/stress_tests/many_lights.rs +++ b/examples/stress_tests/many_lights.rs @@ -8,7 +8,7 @@ use bevy::{ math::{DVec2, DVec3}, pbr::{ExtractedPointLight, GlobalLightMeta}, prelude::*, - render::{camera::ScalingMode, RenderApp, RenderSet}, + render::{camera::ScalingMode, Render, RenderApp, RenderSet}, window::{PresentMode, WindowPlugin}, }; use rand::{thread_rng, Rng}; @@ -26,7 +26,8 @@ fn main() { })) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) - .add_systems((setup.on_startup(), move_camera, print_light_count)) + .add_systems(Startup, setup) + .add_systems(Update, (move_camera, print_light_count)) .add_plugin(LogVisibleLights) .run(); } @@ -154,7 +155,7 @@ impl Plugin for LogVisibleLights { Err(_) => return, }; - render_app.add_system(print_visible_light_count.in_set(RenderSet::Prepare)); + render_app.add_systems(Render, print_visible_light_count.in_set(RenderSet::Prepare)); } } diff --git a/examples/stress_tests/many_sprites.rs b/examples/stress_tests/many_sprites.rs index f22359d6d9..7ab23d1b48 100644 --- a/examples/stress_tests/many_sprites.rs +++ b/examples/stress_tests/many_sprites.rs @@ -40,11 +40,11 @@ fn main() { }), ..default() })) - .add_systems(( - setup.on_startup(), - print_sprite_count, - move_camera.after(print_sprite_count), - )) + .add_systems(Startup, setup) + .add_systems( + Update, + (print_sprite_count, move_camera.after(print_sprite_count)), + ) .run(); } diff --git a/examples/stress_tests/text_pipeline.rs b/examples/stress_tests/text_pipeline.rs index f5396ca90c..6229772d00 100644 --- a/examples/stress_tests/text_pipeline.rs +++ b/examples/stress_tests/text_pipeline.rs @@ -20,7 +20,8 @@ fn main() { })) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) - .add_systems((spawn.on_startup(), update_text_bounds)) + .add_systems(Startup, spawn) + .add_systems(Update, update_text_bounds) .run(); } diff --git a/examples/stress_tests/transform_hierarchy.rs b/examples/stress_tests/transform_hierarchy.rs index 785f225bef..47985fbbf6 100644 --- a/examples/stress_tests/transform_hierarchy.rs +++ b/examples/stress_tests/transform_hierarchy.rs @@ -185,10 +185,10 @@ fn main() { .insert_resource(cfg) .add_plugins(MinimalPlugins) .add_plugin(TransformPlugin::default()) - .add_startup_system(setup) + .add_systems(Startup, setup) // Updating transforms *must* be done before `CoreSet::PostUpdate` // or the hierarchy will momentarily be in an invalid state. - .add_system(update) + .add_systems(Update, update) .run(); } @@ -242,10 +242,10 @@ struct UpdateFilter { /// update component with some per-component value #[derive(Component)] -struct Update(f32); +struct UpdateValue(f32); /// update positions system -fn update(time: Res