diff --git a/_release-content/migration-guides/deprecate_filtered_resources.md b/_release-content/migration-guides/deprecate_filtered_resources.md new file mode 100644 index 0000000000000..f63b0dce51404 --- /dev/null +++ b/_release-content/migration-guides/deprecate_filtered_resources.md @@ -0,0 +1,72 @@ +--- +title: "`FilteredResources` and similar structs have been deprecated" +pull_requests: [25331] +--- + +`FilteredResources`, `FilteredResourcesMut`, `FilteredResourcesBuilder`, `FilteredResourcesMutBuilder`, `FilteredResourcesParamBuilder`, and `FilteredResourcesMutParamBuilder`, have been deprecated in favor of `QueryBuilder` and `QueryParamBuilder`. + +The API has changed somewhat, below we provide an example. + +```rust +// 0.19 +let system = + FilteredResourcesParamBuilder::new(|builder| { + builder.add_read::(); + }) + .build_state(&mut world) + .build_system(resource_system); + +fn resource_system(filtered: FilteredResources) { + let resource_a: Ref = filtered.get::().unwrap(); +} + +// 0.20 +let system = + QueryParamBuilder::new(|builder| { + builder.data::>().with::(); + }) + .build_state(&mut world) + .build_system(resource_system); + +fn resource_system(query: Query<()>) { + let resource_a: Ref = query.single().unwrap(); +} +``` + +So instead of a `FilteredResourcesParamBuilder` that provides a `FilteredResourcesBuilder`, which resolves to `FilteredResources`, we have a `QueryParamBuilder` that provides a `QueryBuilder` that resolves to a `Query`. The `Mut` variants also turn into `Query`, `QueryParam`, and `QueryParamBuilder`. +Most of the migration should be rather straightforward, but there are some specifics we need to clear up. +First, change detection was automatically included for `FilteredResources` and `FilteredResourcesMut`, which is now opt-in. You have to specify `Ref` and `Mut` in `QueryBuilder::data` if you want change detection. +Secondly, when is adding `.with::` necessary? In general, `.with::` is used to stop system conflicts. Take a look at the following example: + +```rust +// 0.20 +fn resource_system(resource_query: Query<()>, broad_query: Query) {} + +let system = ( + QueryParamBuilder::new(|builder| { + builder.data::<&mut ResA>(); + }), + ParamBuilder, +) + .build_state(&mut world) + .build_system(resource_system); // panic! +``` + +Here, `.build_system` panics, because `broad_query` also has mutable access to `ResA`, just as `resource_query` does. +In order to avoid conflicts, you can add an `IsResource` filter, like so: + +```rust +// 0.20 +fn resource_system(resource_query: Query<()>, broad_query: Query>) {} + +let system = ( + QueryParamBuilder::new(|builder| { + builder.data::<&mut ResA>().with::(); + }), + ParamBuilder, +) + .build_state(&mut world) + .build_system(resource_system); // works! +``` + +Adding `IsResource` is therefor only occasionally necessary, as these conflicts arise. Still, since a resource entity always has an `IsResource` marker attached, it can't hurt. diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 01779922dd6e4..6f85e76a2a307 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -99,12 +99,12 @@ pub mod prelude { SystemParamFunction, }, template::{template, FromTemplate, Template}, - world::{ - EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut, - FromWorld, World, - }, + world::{EntityMut, EntityRef, EntityWorldMut, FromWorld, World}, }; + #[expect(deprecated, reason = "`FilteredResources` will be removed.")] + pub use crate::world::{FilteredResources, FilteredResourcesMut}; + #[doc(hidden)] #[cfg(feature = "std")] pub use crate::system::ParallelCommands; diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index 3cdc00a86385b..b716a3ebd4564 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -15,12 +15,14 @@ use crate::{ Local, ParamSet, Query, ReadOnlySystem, System, SystemInput, SystemMeta, SystemParam, SystemParamFunction, SystemParamValidationError, }, - world::{ - unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FilteredResources, - FilteredResourcesBuilder, FilteredResourcesMut, FilteredResourcesMutBuilder, FromWorld, - World, - }, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World}, +}; + +#[expect(deprecated, reason = "`FilteredResources` will be removed.")] +use crate::world::{ + FilteredResources, FilteredResourcesBuilder, FilteredResourcesMut, FilteredResourcesMutBuilder, }; + use core::{fmt::Debug, marker::PhantomData, mem}; use super::{Res, ResMut, RunSystemError, SystemState, SystemStateFlags}; @@ -802,8 +804,13 @@ unsafe impl<'s, T: FromWorld + Send + 'static> SystemParamBuilder> /// A [`SystemParamBuilder`] for a [`FilteredResources`]. /// See the [`FilteredResources`] docs for examples. #[derive(Clone)] +#[deprecated(since = "0.20.0", note = "Use `QueryParamBuilder` instead.")] pub struct FilteredResourcesParamBuilder(T); +#[expect( + deprecated, + reason = "`FilteredResourcesParamBuilder` will be removed." +)] impl FilteredResourcesParamBuilder { /// Creates a [`SystemParamBuilder`] for a [`FilteredResources`] that accepts a callback to configure the [`FilteredResourcesBuilder`]. pub fn new(f: T) -> Self @@ -814,6 +821,10 @@ impl FilteredResourcesParamBuilder { } } +#[expect( + deprecated, + reason = "`FilteredResourcesParamBuilder` will be removed." +)] impl<'a> FilteredResourcesParamBuilder> { /// Creates a [`SystemParamBuilder`] for a [`FilteredResources`] that accepts a callback to configure the [`FilteredResourcesBuilder`]. /// This boxes the callback so that it has a common type. @@ -822,6 +833,10 @@ impl<'a> FilteredResourcesParamBuilder SystemParamBuilder> for FilteredResourcesParamBuilder @@ -836,8 +851,13 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesBuilder)> /// A [`SystemParamBuilder`] for a [`FilteredResourcesMut`]. /// See the [`FilteredResourcesMut`] docs for examples. #[derive(Clone)] +#[deprecated(since = "0.20.0", note = "Use `QueryParamBuilder` instead.")] pub struct FilteredResourcesMutParamBuilder(T); +#[expect( + deprecated, + reason = "`FilteredResourcesMutParamBuilder` will be removed." +)] impl FilteredResourcesMutParamBuilder { /// Creates a [`SystemParamBuilder`] for a [`FilteredResourcesMut`] that accepts a callback to configure the [`FilteredResourcesMutBuilder`]. pub fn new(f: T) -> Self @@ -848,6 +868,10 @@ impl FilteredResourcesMutParamBuilder { } } +#[expect( + deprecated, + reason = "`FilteredResourcesMutParamBuilder` will be removed." +)] impl<'a> FilteredResourcesMutParamBuilder> { /// Creates a [`SystemParamBuilder`] for a [`FilteredResourcesMut`] that accepts a callback to configure the [`FilteredResourcesMutBuilder`]. /// This boxes the callback so that it has a common type. @@ -856,6 +880,10 @@ impl<'a> FilteredResourcesMutParamBuilder SystemParamBuilder> for FilteredResourcesMutParamBuilder @@ -1287,6 +1315,7 @@ mod tests { } #[test] + #[expect(deprecated, reason = "`FilteredResources` will be removed.")] fn filtered_resource_conflicts_read_with_res() { let mut world = World::new(); ( @@ -1301,6 +1330,7 @@ mod tests { #[test] #[should_panic] + #[expect(deprecated, reason = "`FilteredResources` will be removed.")] fn filtered_resource_conflicts_read_with_resmut() { let mut world = World::new(); ( @@ -1315,6 +1345,7 @@ mod tests { #[test] #[should_panic] + #[expect(deprecated, reason = "`FilteredResources` will be removed.")] fn filtered_resource_conflicts_read_all_with_resmut() { let mut world = World::new(); ( @@ -1328,6 +1359,7 @@ mod tests { } #[test] + #[expect(deprecated, reason = "`FilteredResourcesMut` will be removed.")] fn filtered_resource_mut_conflicts_read_with_res() { let mut world = World::new(); ( @@ -1342,6 +1374,7 @@ mod tests { #[test] #[should_panic] + #[expect(deprecated, reason = "`FilteredResourcesMut` will be removed.")] fn filtered_resource_mut_conflicts_read_with_resmut() { let mut world = World::new(); ( @@ -1356,6 +1389,7 @@ mod tests { #[test] #[should_panic] + #[expect(deprecated, reason = "`FilteredResourcesMut` will be removed.")] fn filtered_resource_mut_conflicts_write_with_res() { let mut world = World::new(); ( @@ -1370,6 +1404,7 @@ mod tests { #[test] #[should_panic] + #[expect(deprecated, reason = "`FilteredResourcesMut` will be removed.")] fn filtered_resource_mut_conflicts_write_all_with_res() { let mut world = World::new(); ( @@ -1384,6 +1419,7 @@ mod tests { #[test] #[should_panic] + #[expect(deprecated, reason = "`FilteredResourcesMut` will be removed.")] fn filtered_resource_mut_conflicts_write_with_resmut() { let mut world = World::new(); ( diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index fb8da35c900e5..b7742018f2dd1 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -16,11 +16,12 @@ use crate::{ }, resource::{Resource, IS_RESOURCE}, system::{Query, Single, SystemMeta}, - world::{ - unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FilteredResources, FilteredResourcesMut, - FromWorld, World, - }, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World}, }; + +#[expect(deprecated, reason = "`FilteredResources` will be removed.")] +use crate::world::{FilteredResources, FilteredResourcesMut}; + use alloc::{borrow::Cow, boxed::Box, vec::Vec}; pub use bevy_ecs_macros::SystemParam; use bevy_platform::cell::SyncCell; @@ -2567,6 +2568,7 @@ unsafe impl SystemParam for DynSystemParam<'_, '_> { // SAFETY: Resource ComponentId access is applied to the access. If this FilteredResources // conflicts with any prior access, a panic will occur. +#[expect(deprecated, reason = "`FilteredResources` will be removed.")] unsafe impl SystemParam for FilteredResources<'_, '_> { type State = Access; @@ -2609,10 +2611,12 @@ unsafe impl SystemParam for FilteredResources<'_, '_> { } // SAFETY: FilteredResources only reads resources. +#[expect(deprecated, reason = "`FilteredResources` will be removed.")] unsafe impl ReadOnlySystemParam for FilteredResources<'_, '_> {} // SAFETY: Resource ComponentId access is applied to the access. If this FilteredResourcesMut // conflicts with any prior access, a panic will occur. +#[expect(deprecated, reason = "`FilteredResourcesMut` will be removed.")] unsafe impl SystemParam for FilteredResourcesMut<'_, '_> { type State = Access; diff --git a/crates/bevy_ecs/src/world/filtered_resource.rs b/crates/bevy_ecs/src/world/filtered_resource.rs index d6507204dbe73..2475f0175032a 100644 --- a/crates/bevy_ecs/src/world/filtered_resource.rs +++ b/crates/bevy_ecs/src/world/filtered_resource.rs @@ -115,6 +115,10 @@ use super::error::ResourceFetchError; /// # world.run_system_once(system); /// ``` #[derive(Clone, Copy)] +#[deprecated( + since = "0.20.0", + note = "Use `QueryState` and `QueryBuilder` instead." +)] pub struct FilteredResources<'w, 's> { world: UnsafeWorldCell<'w>, access: &'s Access, @@ -122,6 +126,7 @@ pub struct FilteredResources<'w, 's> { this_run: Tick, } +#[expect(deprecated, reason = "`FilteredResources` will be removed.")] impl<'w, 's> FilteredResources<'w, 's> { /// Creates a new [`FilteredResources`]. /// # Safety @@ -188,6 +193,7 @@ impl<'w, 's> FilteredResources<'w, 's> { } } +#[expect(deprecated, reason = "`FilteredResources` will be removed.")] impl<'w, 's> From> for FilteredResources<'w, 's> { fn from(resources: FilteredResourcesMut<'w, 's>) -> Self { // SAFETY: @@ -203,6 +209,7 @@ impl<'w, 's> From> for FilteredResources<'w, 's> { } } +#[expect(deprecated, reason = "`FilteredResources` will be removed.")] impl<'w, 's> From<&'w FilteredResourcesMut<'_, 's>> for FilteredResources<'w, 's> { fn from(resources: &'w FilteredResourcesMut<'_, 's>) -> Self { // SAFETY: @@ -218,6 +225,7 @@ impl<'w, 's> From<&'w FilteredResourcesMut<'_, 's>> for FilteredResources<'w, 's } } +#[expect(deprecated, reason = "`FilteredResources` will be removed.")] impl<'w> From<&'w World> for FilteredResources<'w, 'static> { fn from(value: &'w World) -> Self { const READ_ALL_RESOURCES: &Access = const { &Access::new_read_all() }; @@ -236,6 +244,7 @@ impl<'w> From<&'w World> for FilteredResources<'w, 'static> { } } +#[expect(deprecated, reason = "`FilteredResources` will be removed.")] impl<'w> From<&'w mut World> for FilteredResources<'w, 'static> { fn from(value: &'w mut World) -> Self { Self::from(&*value) @@ -364,6 +373,10 @@ impl<'w> From<&'w mut World> for FilteredResources<'w, 'static> { /// # /// # world.run_system_once(system); /// ``` +#[deprecated( + since = "0.20.0", + note = "Use `QueryState` and `QueryBuilder` instead." +)] pub struct FilteredResourcesMut<'w, 's> { world: UnsafeWorldCell<'w>, access: &'s Access, @@ -371,6 +384,7 @@ pub struct FilteredResourcesMut<'w, 's> { this_run: Tick, } +#[expect(deprecated, reason = "`FilteredResourcesMut` will be removed.")] impl<'w, 's> FilteredResourcesMut<'w, 's> { /// Creates a new [`FilteredResources`]. /// # Safety @@ -507,6 +521,7 @@ impl<'w, 's> FilteredResourcesMut<'w, 's> { } } +#[expect(deprecated, reason = "`FilteredResourcesMut` will be removed.")] impl<'w> From<&'w mut World> for FilteredResourcesMut<'w, 'static> { fn from(value: &'w mut World) -> Self { const WRITE_ALL_RESOURCES: &Access = const { &Access::new_write_all() }; @@ -528,11 +543,13 @@ impl<'w> From<&'w mut World> for FilteredResourcesMut<'w, 'static> { /// Builder struct to define the access for a [`FilteredResources`]. /// /// This is passed to a callback in [`FilteredResourcesParamBuilder`](crate::system::FilteredResourcesParamBuilder). +#[deprecated(since = "0.20.0", note = "Use `QueryBuilder` instead.")] pub struct FilteredResourcesBuilder<'w> { world: &'w mut World, access: Access, } +#[expect(deprecated, reason = "`FilteredResourcesBuilder` will be removed.")] impl<'w> FilteredResourcesBuilder<'w> { /// Creates a new builder with no access. pub fn new(world: &'w mut World) -> Self { @@ -577,11 +594,13 @@ impl<'w> FilteredResourcesBuilder<'w> { /// Builder struct to define the access for a [`FilteredResourcesMut`]. /// /// This is passed to a callback in [`FilteredResourcesMutParamBuilder`](crate::system::FilteredResourcesMutParamBuilder). +#[deprecated(since = "0.20.0", note = "Use `QueryBuilder` instead.")] pub struct FilteredResourcesMutBuilder<'w> { world: &'w mut World, access: Access, } +#[expect(deprecated, reason = "`FilteredResourcesMutBuilder` will be removed.")] impl<'w> FilteredResourcesMutBuilder<'w> { /// Creates a new builder with no access. pub fn new(world: &'w mut World) -> Self {