From 6254ce5cca5ad3113c2b0baf2b9956e88d5755a2 Mon Sep 17 00:00:00 2001 From: Dylan Date: Fri, 31 Oct 2025 15:30:00 -0500 Subject: [PATCH 1/7] documented run_system_cached #21617 --- crates/bevy_ecs/src/system/system_registry.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 2c8b003064147..8f3367d0a2b55 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -500,6 +500,16 @@ impl World { /// Runs a cached system, registering it if necessary. /// + /// # Type Inference Note + /// If the system returns `()`, you may need to explicitly constrain the output + /// type for error handling: + /// + /// ```rust + /// () = world.run_system_cached(my_system)?; + /// ``` + /// + /// Without this, Rust may fail to infer the system’s output type and produce + /// a `IntoResult` inference error. /// See [`World::register_system_cached`] for more information. pub fn run_system_cached + 'static>( &mut self, @@ -509,7 +519,18 @@ impl World { } /// Runs a cached system with an input, registering it if necessary. + /// Runs a cached system, registering it if necessary. + /// + /// # Type Inference Note + /// If the system returns `()`, you may need to explicitly constrain the output + /// type for error handling: + /// + /// ```rust + /// () = world.run_system_cached(my_system)?; + /// ``` /// + /// Without this, Rust may fail to infer the system’s output type and produce + /// a `IntoResult` inference error. /// See [`World::register_system_cached`] for more information. pub fn run_system_cached_with( &mut self, From 376435248d665c041dbcb345d5dc88e675f37990 Mon Sep 17 00:00:00 2001 From: Zackary Rippee Date: Fri, 31 Oct 2025 20:41:52 -0500 Subject: [PATCH 2/7] Documentation that chained .run_if(a).run_if(b) don't short-circuit, in contrast to run_if(a.and(b)) added. --- crates/bevy_ecs/src/schedule/config.rs | 52 +++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index a8dbfc810b638..613fc53d38570 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -386,6 +386,14 @@ pub trait IntoScheduleConfigs bool { true } + /// schedule.add_systems((my_system, my_system).run_if(condition)); + /// ``` + /// + /// When using chained systems, each maintains its own tick state: + /// ``` + /// # use bevy_ecs::prelude::*; + /// # let mut schedule = Schedule::default(); + /// # fn system_a() {} + /// # fn system_b() {} + /// # fn condition() -> bool { true } + /// // Each system's change detection works independently + /// schedule.add_systems(( + /// system_a.run_if(condition), + /// system_b + /// ).chain()); + /// ``` + /// + /// For per-system condition evaluation, use [`distributive_run_if`]. /// /// If this set contains more than one system, calling `run_if` is equivalent to adding each /// system to a common set and configuring the run condition on that set, as shown below: @@ -549,6 +581,22 @@ impl> IntoScheduleCo self } + /// Chain systems to run in sequence, with independent change detection. + /// + /// Systems in a chain run in order but maintain separate tick states. + /// Change detection works independently for each system, even when skipped by conditions. + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # let mut schedule = Schedule::default(); + /// # fn system_a() {} + /// # fn system_b() {} + /// # fn condition() -> bool { true } + /// schedule.add_systems(( + /// system_a.run_if(condition), + /// system_b // Will see system_a's changes, maintains own tick state + /// ).chain()); + /// ``` fn chain(self) -> Self { self.chain_inner() } From 943b59e2e1b0739013603cf2a5d7eb5dcd620e6a Mon Sep 17 00:00:00 2001 From: Zackary Rippee Date: Fri, 31 Oct 2025 20:45:45 -0500 Subject: [PATCH 3/7] better documentation for chaining .run_if against using .and --- crates/bevy_ecs/src/schedule/config.rs | 52 ++++++++------------------ 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 613fc53d38570..bad503523b333 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -425,33 +425,19 @@ pub trait IntoScheduleConfigs bool { true } - /// schedule.add_systems((my_system, my_system).run_if(condition)); - /// ``` - /// - /// When using chained systems, each maintains its own tick state: - /// ``` - /// # use bevy_ecs::prelude::*; - /// # let mut schedule = Schedule::default(); - /// # fn system_a() {} - /// # fn system_b() {} - /// # fn condition() -> bool { true } - /// // Each system's change detection works independently - /// schedule.add_systems(( - /// system_a.run_if(condition), - /// system_b - /// ).chain()); - /// ``` + /// Important: Multiple conditions added via chained `.run_if(a).run_if(b)` calls will all be evaluated + /// independently. In contrast, `run_if(a.and(b))` creates a single condition that short-circuits - + /// `b` will not be evaluated if `a` returns false. /// /// For per-system condition evaluation, use [`distributive_run_if`]. /// + /// When systems are chained together, each maintains independent tick tracking to ensure + /// accurate change detection throughout the chain. + /// /// If this set contains more than one system, calling `run_if` is equivalent to adding each /// system to a common set and configuring the run condition on that set, as shown below: /// @@ -583,20 +569,14 @@ impl> IntoScheduleCo /// Chain systems to run in sequence, with independent change detection. /// - /// Systems in a chain run in order but maintain separate tick states. - /// Change detection works independently for each system, even when skipped by conditions. + /// Systems in a chain: + /// - Execute in sequential order + /// - Each maintain their own tick state + /// - Have independent change detection + /// - Update ticks even when skipped by conditions /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # let mut schedule = Schedule::default(); - /// # fn system_a() {} - /// # fn system_b() {} - /// # fn condition() -> bool { true } - /// schedule.add_systems(( - /// system_a.run_if(condition), - /// system_b // Will see system_a's changes, maintains own tick state - /// ).chain()); - /// ``` + /// Later systems in the chain will see changes made by earlier systems while maintaining + /// their own change detection state. fn chain(self) -> Self { self.chain_inner() } From d4584af583a045f279b16136b643f5ee95bc3ec3 Mon Sep 17 00:00:00 2001 From: Zackary Rippee Date: Fri, 31 Oct 2025 20:50:09 -0500 Subject: [PATCH 4/7] simplified documentation --- crates/bevy_ecs/src/schedule/config.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index bad503523b333..4ac2dc58c419c 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -425,19 +425,12 @@ pub trait IntoScheduleConfigs Date: Thu, 6 Nov 2025 11:30:43 -0600 Subject: [PATCH 5/7] chore: sync system_registry.rs with upstream bevy --- crates/bevy_ecs/src/system/system_registry.rs | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 621b3399091b5..2488dbcfd0b47 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -500,16 +500,6 @@ impl World { /// Runs a cached system, registering it if necessary. /// - /// # Type Inference Note - /// If the system returns `()`, you may need to explicitly constrain the output - /// type for error handling: - /// - /// ```rust - /// () = world.run_system_cached(my_system)?; - /// ``` - /// - /// Without this, Rust may fail to infer the system’s output type and produce - /// a `IntoResult` inference error. /// See [`World::register_system_cached`] for more information. pub fn run_system_cached + 'static>( &mut self, @@ -519,18 +509,7 @@ impl World { } /// Runs a cached system with an input, registering it if necessary. - /// Runs a cached system, registering it if necessary. - /// - /// # Type Inference Note - /// If the system returns `()`, you may need to explicitly constrain the output - /// type for error handling: - /// - /// ```rust - /// () = world.run_system_cached(my_system)?; - /// ``` /// - /// Without this, Rust may fail to infer the system’s output type and produce - /// a `IntoResult` inference error. /// See [`World::register_system_cached`] for more information. pub fn run_system_cached_with( &mut self, From d8e901818f35f769d033bcbf3ea14504142223ba Mon Sep 17 00:00:00 2001 From: Zackary Rippee Date: Thu, 6 Nov 2025 12:41:36 -0600 Subject: [PATCH 6/7] Added suggestions for documentation --- crates/bevy_ecs/src/schedule/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 4ac2dc58c419c..dcc292d7e8d03 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -560,14 +560,14 @@ impl> IntoScheduleCo self } - /// Chain systems to run in sequence, with independent change detection. + /// Chain systems to run in sequence, by implicitly adding ordering constraints between them. /// /// Systems in a chain: /// - Execute in sequential order /// - Each maintain their own tick state /// - Have independent change detection /// - Update ticks even when skipped by conditions - /// + /// - Equivalent to using `before` / `after` system orderings. /// Later systems in the chain will see changes made by earlier systems while maintaining /// their own change detection state. fn chain(self) -> Self { From 723b4bd100358a14d1d62db97d4acf8014da18f2 Mon Sep 17 00:00:00 2001 From: Zackary Rippee Date: Sun, 23 Nov 2025 15:16:12 -0600 Subject: [PATCH 7/7] 10685 --- crates/bevy_asset/src/server/info.rs | 5 +++ crates/bevy_asset/src/server/mod.rs | 46 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/crates/bevy_asset/src/server/info.rs b/crates/bevy_asset/src/server/info.rs index 37f727870be00..666f32847122d 100644 --- a/crates/bevy_asset/src/server/info.rs +++ b/crates/bevy_asset/src/server/info.rs @@ -314,6 +314,11 @@ impl AssetInfos { self.get_index_handle(ErasedAssetIndex::new(index, type_id)) } + pub(crate) fn get_path_ref(&self, index: ErasedAssetIndex) -> Option<&AssetPath<'static>> { + let info = self.infos.get(&index)?; + info.path.as_ref() + } + pub(crate) fn get_path_indices<'a>( &'a self, path: &'a AssetPath<'_>, diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 3ae2ee1af6971..516527844a487 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -1365,6 +1365,31 @@ impl AssetServer { Some(info.path.as_ref()?.clone()) } + /// Returns a reference to the path for the given `id`, if it has one. + /// + /// This method is useful when you need to access the path without cloning it, + /// which can be more efficient when working with many asset handles. + /// + /// Note: The returned reference is tied to a temporary lock guard that lives + /// as long as the returned value is in scope. For more complex use cases, + /// consider using [`AssetServer::get_path`] instead. + pub fn get_path_ref<'a>( + &'a self, + id: impl Into, + ) -> Option> + 'a> { + let index = id.into().try_into().ok()?; + let infos = self.read_infos(); + + if infos.get_path_ref(index).is_some() { + Some(AssetPathRef { + _guard: infos, + index, + }) + } else { + None + } + } + /// Returns the [`AssetServerMode`] this server is currently in. pub fn mode(&self) -> AssetServerMode { self.data.mode @@ -2133,6 +2158,27 @@ fn format_missing_asset_ext(exts: &[String]) -> String { } } +/// A reference to an asset path that keeps the internal lock alive. +/// +/// This type is returned by [`AssetServer::get_path_ref`] and allows +/// accessing the path without cloning it, while keeping the necessary +/// read lock alive for the duration of its lifetime. +pub struct AssetPathRef<'a> { + _guard: RwLockReadGuard<'a, AssetInfos>, + index: ErasedAssetIndex, +} + +impl<'a> core::ops::Deref for AssetPathRef<'a> { + type Target = AssetPath<'static>; + + fn deref(&self) -> &Self::Target { + // SAFETY: We checked in get_path_ref that the path exists, so this unwrap is safe + self._guard + .get_path_ref(self.index) + .expect("AssetPathRef should only be created when path exists") + } +} + impl core::fmt::Debug for AssetServer { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("AssetServer")