Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions _release-content/migration-guides/deprecate_filtered_resources.md
Original file line number Diff line number Diff line change
@@ -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::<ResA>();
})
.build_state(&mut world)
.build_system(resource_system);

fn resource_system(filtered: FilteredResources) {
let resource_a: Ref<ResA> = filtered.get::<ResA>().unwrap();
}

// 0.20
let system =
QueryParamBuilder::new(|builder| {
builder.data::<Ref<ResA>>().with::<IsResource>();
})
.build_state(&mut world)
.build_system(resource_system);

fn resource_system(query: Query<()>) {
let resource_a: Ref<ResA> = query.single().unwrap();
Comment on lines +31 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't compile, does it? I think you want Query<FilteredEntityRef> and then to use get<R> or get_by_id to get the resource back out.

Suggested change
fn resource_system(query: Query<()>) {
let resource_a: Ref<ResA> = query.single().unwrap();
fn resource_system(query: Query<FilteredEntityRef>) {
let entity: FilteredEntityRef = query.single().unwrap(); // Or use `Single<FilteredEntityRef>` as a parameter!
let resource_a: &A = entity.get::<A>().unwrap();
// Or with change tracking
let resource_a: Ref<A> = entity.get_ref::<A>().unwrap();
// Or by ID
let resource: Ptr = entity.get_by_id(component_id).unwrap();
let change_ticks: ComponentTicks = entity.get_change_ticks_by_id(component_id).unwrap();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth including some guidance for uses of FilteredResource that read multiple resources. I think the simplest advice would be to create a separate Query for each resource. Users that need a variable number could use Vec<Query<FilteredEntityMut>>. (And the motivating case for this was a script runner that would already have such a Vec as a parameter for its other queries.)

Another option if we merge your components-as-entities PR would be to create a single query for resources that has all of the access and is accessed with query.get(entity)?.get_by_id(entity)?.

Or, hmm, in the meantime, would it make sense to impl SystemParam for &ResourceEntities, like we do for things like &Archetypes? Then users could do query.get(resource_entities.get(component_id)?)?.get_by_id(component_id)? if they need.

}
```

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, you can use & and &mut! (And I'd consider them more idiomatic.) Access to change ticks is always included with access to the data.

Secondly, when is adding `.with::<IsResource>` necessary? In general, `.with::<IsResource>` 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<EntityMut>) {}

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<EntityMut, Without<IsResource>>) {}

let system = (
QueryParamBuilder::new(|builder| {
builder.data::<&mut ResA>().with::<IsResource>();
}),
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.
8 changes: 4 additions & 4 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
46 changes: 41 additions & 5 deletions crates/bevy_ecs/src/system/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -802,8 +804,13 @@ unsafe impl<'s, T: FromWorld + Send + 'static> SystemParamBuilder<Local<'s, T>>
/// 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>(T);

#[expect(
deprecated,
reason = "`FilteredResourcesParamBuilder` will be removed."
)]
impl<T> FilteredResourcesParamBuilder<T> {
/// Creates a [`SystemParamBuilder`] for a [`FilteredResources`] that accepts a callback to configure the [`FilteredResourcesBuilder`].
pub fn new(f: T) -> Self
Expand All @@ -814,6 +821,10 @@ impl<T> FilteredResourcesParamBuilder<T> {
}
}

#[expect(
deprecated,
reason = "`FilteredResourcesParamBuilder` will be removed."
)]
impl<'a> FilteredResourcesParamBuilder<Box<dyn FnOnce(&mut FilteredResourcesBuilder) + 'a>> {
/// 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.
Expand All @@ -822,6 +833,10 @@ impl<'a> FilteredResourcesParamBuilder<Box<dyn FnOnce(&mut FilteredResourcesBuil
}
}

#[expect(
deprecated,
reason = "`FilteredResourcesParamBuilder` will be removed."
)]
// SAFETY: Any `Access` is a valid state for `FilteredResources`.
unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesBuilder)>
SystemParamBuilder<FilteredResources<'w, 's>> for FilteredResourcesParamBuilder<T>
Expand All @@ -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>(T);

#[expect(
deprecated,
reason = "`FilteredResourcesMutParamBuilder` will be removed."
)]
impl<T> FilteredResourcesMutParamBuilder<T> {
/// Creates a [`SystemParamBuilder`] for a [`FilteredResourcesMut`] that accepts a callback to configure the [`FilteredResourcesMutBuilder`].
pub fn new(f: T) -> Self
Expand All @@ -848,6 +868,10 @@ impl<T> FilteredResourcesMutParamBuilder<T> {
}
}

#[expect(
deprecated,
reason = "`FilteredResourcesMutParamBuilder` will be removed."
)]
impl<'a> FilteredResourcesMutParamBuilder<Box<dyn FnOnce(&mut FilteredResourcesMutBuilder) + 'a>> {
/// 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.
Expand All @@ -856,6 +880,10 @@ impl<'a> FilteredResourcesMutParamBuilder<Box<dyn FnOnce(&mut FilteredResourcesM
}
}

#[expect(
deprecated,
reason = "`FilteredResourcesMutParamBuilder` will be removed."
)]
// SAFETY: Any `Access` is a valid state for `FilteredResourcesMut`.
unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)>
SystemParamBuilder<FilteredResourcesMut<'w, 's>> for FilteredResourcesMutParamBuilder<T>
Expand Down Expand Up @@ -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();
(
Expand All @@ -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();
(
Expand All @@ -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();
(
Expand All @@ -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();
(
Expand All @@ -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();
(
Expand All @@ -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();
(
Expand All @@ -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();
(
Expand All @@ -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();
(
Expand Down
12 changes: 8 additions & 4 deletions crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

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

Expand Down
19 changes: 19 additions & 0 deletions crates/bevy_ecs/src/world/filtered_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,18 @@ 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,
last_run: Tick,
this_run: Tick,
}

#[expect(deprecated, reason = "`FilteredResources` will be removed.")]
impl<'w, 's> FilteredResources<'w, 's> {
/// Creates a new [`FilteredResources`].
/// # Safety
Expand Down Expand Up @@ -188,6 +193,7 @@ impl<'w, 's> FilteredResources<'w, 's> {
}
}

#[expect(deprecated, reason = "`FilteredResources` will be removed.")]
impl<'w, 's> From<FilteredResourcesMut<'w, 's>> for FilteredResources<'w, 's> {
fn from(resources: FilteredResourcesMut<'w, 's>) -> Self {
// SAFETY:
Expand All @@ -203,6 +209,7 @@ impl<'w, 's> From<FilteredResourcesMut<'w, 's>> 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:
Expand All @@ -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() };
Expand 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)
Expand Down Expand Up @@ -364,13 +373,18 @@ 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,
last_run: Tick,
this_run: Tick,
}

#[expect(deprecated, reason = "`FilteredResourcesMut` will be removed.")]
impl<'w, 's> FilteredResourcesMut<'w, 's> {
/// Creates a new [`FilteredResources`].
/// # Safety
Expand Down Expand Up @@ -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() };
Expand 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 {
Expand Down Expand Up @@ -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 {
Expand Down