Reorganize SystemParamBuilder docs and examples. (#15102)

# Objective

Improve the documentation of `SystemParamBuilder`. Not all builder types
have documentation, and the documentation is spread around and not
linked together well.

## Solution

Reorganize `SystemParamBuilder` docs and examples. All builder types now
have their own examples, and the list of builder types is linked from
the `SystemParamBuilder` trait. Add some examples to `FilteredEntityRef`
and `FilteredEntityMut` so that `QueryParamBuilder` can reference them.
This commit is contained in:
Chris Russell 2024-09-30 12:59:52 -04:00 committed by GitHub
parent 2ec164d279
commit 86e5a5ad9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 304 additions and 96 deletions

View file

@ -4,8 +4,7 @@ use crate::{
prelude::QueryBuilder,
query::{QueryData, QueryFilter, QueryState},
system::{
system_param::{DynSystemParam, DynSystemParamState, Local, ParamSet, SystemParam},
Query, SystemMeta,
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam,
},
world::{FromWorld, World},
};
@ -13,66 +12,85 @@ use core::fmt::Debug;
use super::{init_query_param, Res, ResMut, Resource, SystemState};
/// A builder that can create a [`SystemParam`]
/// A builder that can create a [`SystemParam`].
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs_macros::SystemParam;
/// # use bevy_ecs::system::{RunSystemOnce, ParamBuilder, LocalBuilder, QueryParamBuilder};
/// #
/// # #[derive(Component)]
/// # struct A;
/// #
/// # #[derive(Component)]
/// # struct B;
/// #
/// # use bevy_ecs::{
/// # prelude::*,
/// # system::{SystemParam, ParamBuilder},
/// # };
/// # #[derive(Resource)]
/// # struct R;
/// #
/// # #[derive(SystemParam)]
/// # struct MyParam;
/// #
/// # let mut world = World::new();
/// # world.insert_resource(R);
/// #
/// fn my_system(res: Res<R>, query: Query<&A>, param: MyParam) {
/// // ...
/// fn some_system(param: MyParam) {}
///
/// fn build_system(builder: impl SystemParamBuilder<MyParam>) {
/// let mut world = World::new();
/// // To build a system, create a tuple of `SystemParamBuilder`s
/// // with a builder for each parameter.
/// // Note that the builder for a system must be a tuple,
/// // even if there is only one parameter.
/// (builder,)
/// .build_state(&mut world)
/// .build_system(some_system);
/// }
///
/// // To build a system, create a tuple of `SystemParamBuilder`s with a builder for each param.
/// // `ParamBuilder` can be used to build a parameter using its default initialization,
/// // and has helper methods to create typed builders.
/// let system = (
/// ParamBuilder,
/// ParamBuilder::query::<&A>(),
/// ParamBuilder::of::<MyParam>(),
/// )
/// fn build_closure_system_infer(builder: impl SystemParamBuilder<MyParam>) {
/// let mut world = World::new();
/// // Closures can be used in addition to named functions.
/// // If a closure is used, the parameter types must all be inferred
/// // from the builders, so you cannot use plain `ParamBuilder`.
/// (builder, ParamBuilder::resource())
/// .build_state(&mut world)
/// .build_system(my_system);
///
/// // Other implementations of `SystemParamBuilder` can be used to configure the parameters.
/// let system = (
/// ParamBuilder,
/// QueryParamBuilder::new::<&A, ()>(|builder| {
/// builder.with::<B>();
/// }),
/// ParamBuilder,
/// )
/// .build_state(&mut world)
/// .build_system(my_system);
///
/// fn single_parameter_system(local: Local<u64>) {
/// // ...
/// .build_system(|param, res| {
/// let param: MyParam = param;
/// let res: Res<R> = res;
/// });
/// }
///
/// // Note that the builder for a system must be a tuple, even if there is only one parameter.
/// let system = (LocalBuilder(2),)
/// fn build_closure_system_explicit(builder: impl SystemParamBuilder<MyParam>) {
/// let mut world = World::new();
/// // Alternately, you can provide all types in the closure
/// // parameter list and call `build_any_system()`.
/// (builder, ParamBuilder)
/// .build_state(&mut world)
/// .build_system(single_parameter_system);
///
/// world.run_system_once(system);
/// .build_any_system(|param: MyParam, res: Res<R>| {});
/// }
/// ```
///
/// See the documentation for individual builders for more examples.
///
/// # List of Builders
///
/// [`ParamBuilder`] can be used for parameters that don't require any special building.
/// Using a `ParamBuilder` will build the system parameter the same way it would be initialized in an ordinary system.
///
/// `ParamBuilder` also provides factory methods that return a `ParamBuilder` typed as `impl SystemParamBuilder<P>`
/// for common system parameters that can be used to guide closure parameter inference.
///
/// [`QueryParamBuilder`] can build a [`Query`] to add additional filters,
/// or to configure the components available to [`FilteredEntityRef`](crate::world::FilteredEntityRef) or [`FilteredEntityMut`](crate::world::FilteredEntityMut).
/// You can also use a [`QueryState`] to build a [`Query`].
///
/// [`LocalBuilder`] can build a [`Local`] to supply the initial value for the `Local`.
///
/// [`DynParamBuilder`] can build a [`DynSystemParam`] to determine the type of the inner parameter,
/// and to supply any `SystemParamBuilder` it needs.
///
/// Tuples of builders can build tuples of parameters, one builder for each element.
/// Note that since systems require a tuple as a parameter, the outer builder for a system will always be a tuple.
///
/// A [`Vec`] of builders can build a `Vec` of parameters, one builder for each element.
///
/// A [`ParamSetBuilder`] can build a [`ParamSet`].
/// This can wrap either a tuple or a `Vec`, one builder for each element.
///
/// A custom system param created with `#[derive(SystemParam)]` can be buildable if it includes a `#[system_param(builder)]` attribute.
/// See [the documentation for `SystemParam` derives](SystemParam#builders).
///
/// # Safety
///
/// The implementor must ensure the following is true.
@ -97,6 +115,44 @@ pub unsafe trait SystemParamBuilder<P: SystemParam>: Sized {
}
/// A [`SystemParamBuilder`] for any [`SystemParam`] that uses its default initialization.
///
/// ## Example
///
/// ```
/// # use bevy_ecs::{
/// # prelude::*,
/// # system::{SystemParam, ParamBuilder},
/// # };
/// #
/// # #[derive(Component)]
/// # struct A;
/// #
/// # #[derive(Resource)]
/// # struct R;
/// #
/// # #[derive(SystemParam)]
/// # struct MyParam;
/// #
/// # let mut world = World::new();
/// # world.insert_resource(R);
/// #
/// fn my_system(res: Res<R>, param: MyParam, query: Query<&A>) {
/// // ...
/// }
///
/// let system = (
/// // A plain ParamBuilder can build any parameter type.
/// ParamBuilder,
/// // The `of::<P>()` method returns a `ParamBuilder`
/// // typed as `impl SystemParamBuilder<P>`.
/// ParamBuilder::of::<MyParam>(),
/// // The other factory methods return typed builders
/// // for common parameter types.
/// ParamBuilder::query::<&A>(),
/// )
/// .build_state(&mut world)
/// .build_system(my_system);
/// ```
#[derive(Default, Debug, Copy, Clone)]
pub struct ParamBuilder;
@ -153,6 +209,45 @@ unsafe impl<'w, 's, D: QueryData + 'static, F: QueryFilter + 'static>
}
/// A [`SystemParamBuilder`] for a [`Query`].
/// This takes a closure accepting an `&mut` [`QueryBuilder`] and uses the builder to construct the query's state.
/// This can be used to add additional filters,
/// or to configure the components available to [`FilteredEntityRef`](crate::world::FilteredEntityRef) or [`FilteredEntityMut`](crate::world::FilteredEntityMut).
///
/// ## Example
///
/// ```
/// # use bevy_ecs::{
/// # prelude::*,
/// # system::{SystemParam, QueryParamBuilder},
/// # };
/// #
/// # #[derive(Component)]
/// # struct Player;
/// #
/// # let mut world = World::new();
/// let system = (QueryParamBuilder::new(|builder| {
/// builder.with::<Player>();
/// }),)
/// .build_state(&mut world)
/// .build_system(|query: Query<()>| {
/// for _ in &query {
/// // This only includes entities with an `Player` component.
/// }
/// });
///
/// // When collecting multiple builders into a `Vec`,
/// // use `new_box()` to erase the closure type.
/// let system = (vec![
/// QueryParamBuilder::new_box(|builder| {
/// builder.with::<Player>();
/// }),
/// QueryParamBuilder::new_box(|builder| {
/// builder.without::<Player>();
/// }),
/// ],)
/// .build_state(&mut world)
/// .build_system(|query: Vec<Query<()>>| {});
/// ```
pub struct QueryParamBuilder<T>(T);
impl<T> QueryParamBuilder<T> {
@ -229,7 +324,74 @@ unsafe impl<P: SystemParam, B: SystemParamBuilder<P>> SystemParamBuilder<Vec<P>>
/// A [`SystemParamBuilder`] for a [`ParamSet`].
///
/// To build a [`ParamSet`] with a tuple of system parameters, pass a tuple of matching [`SystemParamBuilder`]s.
/// To build a [`ParamSet`] with a `Vec` of system parameters, pass a `Vec` of matching [`SystemParamBuilder`]s.
/// To build a [`ParamSet`] with a [`Vec`] of system parameters, pass a `Vec` of matching [`SystemParamBuilder`]s.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::{prelude::*, system::*};
/// #
/// # #[derive(Component)]
/// # struct Health;
/// #
/// # #[derive(Component)]
/// # struct Enemy;
/// #
/// # #[derive(Component)]
/// # struct Ally;
/// #
/// # let mut world = World::new();
/// #
/// let system = (ParamSetBuilder((
/// QueryParamBuilder::new(|builder| {
/// builder.with::<Enemy>();
/// }),
/// QueryParamBuilder::new(|builder| {
/// builder.with::<Ally>();
/// }),
/// ParamBuilder,
/// )),)
/// .build_state(&mut world)
/// .build_system(buildable_system_with_tuple);
/// # world.run_system_once(system);
///
/// fn buildable_system_with_tuple(
/// mut set: ParamSet<(Query<&mut Health>, Query<&mut Health>, &World)>,
/// ) {
/// // The first parameter is built from the first builder,
/// // so this will iterate over enemies.
/// for mut health in set.p0().iter_mut() {}
/// // And the second parameter is built from the second builder,
/// // so this will iterate over allies.
/// for mut health in set.p1().iter_mut() {}
/// // Parameters that don't need special building can use `ParamBuilder`.
/// let entities = set.p2().entities();
/// }
///
/// let system = (ParamSetBuilder(vec![
/// QueryParamBuilder::new_box(|builder| {
/// builder.with::<Enemy>();
/// }),
/// QueryParamBuilder::new_box(|builder| {
/// builder.with::<Ally>();
/// }),
/// ]),)
/// .build_state(&mut world)
/// .build_system(buildable_system_with_vec);
/// # world.run_system_once(system);
///
/// fn buildable_system_with_vec(mut set: ParamSet<Vec<Query<&mut Health>>>) {
/// // As with tuples, the first parameter is built from the first builder,
/// // so this will iterate over enemies.
/// for mut health in set.get_mut(0).iter_mut() {}
/// // And the second parameter is built from the second builder,
/// // so this will iterate over allies.
/// for mut health in set.get_mut(1).iter_mut() {}
/// // You can iterate over the parameters either by index,
/// // or using the `for_each` method.
/// set.for_each(|mut query| for mut health in query.iter_mut() {});
/// }
/// ```
pub struct ParamSetBuilder<T>(pub T);
macro_rules! impl_param_set_builder_tuple {
@ -302,6 +464,7 @@ unsafe impl<'w, 's, P: SystemParam, B: SystemParamBuilder<P>>
}
/// A [`SystemParamBuilder`] for a [`DynSystemParam`].
/// See the [`DynSystemParam`] docs for examples.
pub struct DynParamBuilder<'a>(
Box<dyn FnOnce(&mut World, &mut SystemMeta) -> DynSystemParamState + 'a>,
);
@ -331,6 +494,23 @@ unsafe impl<'a, 'w, 's> SystemParamBuilder<DynSystemParam<'w, 's>> for DynParamB
/// A [`SystemParamBuilder`] for a [`Local`].
/// The provided value will be used as the initial value of the `Local`.
///
/// ## Example
///
/// ```
/// # use bevy_ecs::{
/// # prelude::*,
/// # system::{SystemParam, LocalBuilder, RunSystemOnce},
/// # };
/// #
/// # let mut world = World::new();
/// let system = (LocalBuilder(100),)
/// .build_state(&mut world)
/// .build_system(|local: Local<usize>| {
/// assert_eq!(*local, 100);
/// });
/// # world.run_system_once(system);
/// ```
pub struct LocalBuilder<T>(pub T);
// SAFETY: `Local` performs no world access.

View file

@ -99,7 +99,15 @@
//! - [`Components`](crate::component::Components) (Provides Components metadata)
//! - [`Entities`](crate::entity::Entities) (Provides Entities metadata)
//! - All tuples between 1 to 16 elements where each element implements [`SystemParam`]
//! - [`ParamSet`]
//! - [`()` (unit primitive type)](https://doc.rust-lang.org/stable/std/primitive.unit.html)
//!
//! In addition, the following parameters can be used when constructing a dynamic system with [`SystemParamBuilder`],
//! but will only provide an empty value when used with an ordinary system:
//!
//! - [`DynSystemParam`]
//! - [`Vec<P>`] where `P: SystemParam`
//! - [`ParamSet<Vec<P>>`] where `P: SystemParam`
mod adapter_system;
mod builder;

View file

@ -606,46 +606,6 @@ unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOn
/// }
/// # bevy_ecs::system::assert_is_system(event_system);
/// ```
///
/// If you want to use `ParamSet` with a [`SystemParamBuilder`](crate::system::SystemParamBuilder), use [`ParamSetBuilder`](crate::system::ParamSetBuilder) and pass a builder for each param.
/// ```
/// # use bevy_ecs::{prelude::*, system::*};
/// #
/// # #[derive(Component)]
/// # struct Health;
/// #
/// # #[derive(Component)]
/// # struct Enemy;
/// #
/// # #[derive(Component)]
/// # struct Ally;
/// #
/// let mut world = World::new();
///
/// let system = (ParamSetBuilder((
/// QueryParamBuilder::new(|builder| {
/// builder.with::<Enemy>();
/// }),
/// QueryParamBuilder::new(|builder| {
/// builder.with::<Ally>();
/// }),
/// ParamBuilder,
/// )),)
/// .build_state(&mut world)
/// .build_system(buildable_system);
/// world.run_system_once(system);
///
/// fn buildable_system(mut set: ParamSet<(Query<&mut Health>, Query<&mut Health>, &World)>) {
/// // The first parameter is built from the first builder,
/// // so this will iterate over enemies.
/// for mut health in set.p0().iter_mut() {}
/// // And the second parameter is built from the second builder,
/// // so this will iterate over allies.
/// for mut health in set.p1().iter_mut() {}
/// // Parameters that don't need special building can use `ParamBuilder`.
/// let entities = set.p2().entities();
/// }
/// ```
pub struct ParamSet<'w, 's, T: SystemParam> {
param_states: &'s mut T::State,
world: UnsafeWorldCell<'w>,
@ -2124,22 +2084,22 @@ unsafe impl<T: ?Sized> ReadOnlySystemParam for PhantomData<T> {}
/// # #[derive(Default, Resource)]
/// # struct B;
/// #
/// let mut world = World::new();
/// world.init_resource::<A>();
/// world.init_resource::<B>();
///
/// # let mut world = World::new();
/// # world.init_resource::<A>();
/// # world.init_resource::<B>();
/// #
/// // If the inner parameter doesn't require any special building, use `ParamBuilder`.
/// // Either specify the type parameter on `DynParamBuilder::new()` ...
/// let system = (DynParamBuilder::new::<Res<A>>(ParamBuilder),)
/// .build_state(&mut world)
/// .build_system(expects_res_a);
/// world.run_system_once(system);
/// # world.run_system_once(system);
///
/// // ... or use a factory method on `ParamBuilder` that returns a specific type.
/// let system = (DynParamBuilder::new(ParamBuilder::resource::<A>()),)
/// .build_state(&mut world)
/// .build_system(expects_res_a);
/// world.run_system_once(system);
/// # world.run_system_once(system);
///
/// fn expects_res_a(mut param: DynSystemParam) {
/// // Use the `downcast` methods to retrieve the inner parameter.
@ -2165,7 +2125,7 @@ unsafe impl<T: ?Sized> ReadOnlySystemParam for PhantomData<T> {}
/// let local: Local<usize> = param.downcast::<Local<usize>>().unwrap();
/// assert_eq!(*local, 10);
/// });
/// world.run_system_once(system);
/// # world.run_system_once(system);
/// ```
pub struct DynSystemParam<'w, 's> {
/// A `ParamState<T>` wrapping the state for the underlying system param.

View file

@ -1895,6 +1895,36 @@ impl<'w, 'a, T: Component> VacantEntry<'w, 'a, T> {
}
/// Provides read-only access to a single entity and some of its components defined by the contained [`Access`].
///
/// To define the access when used as a [`QueryData`](crate::query::QueryData),
/// use a [`QueryBuilder`](crate::query::QueryBuilder) or [`QueryParamBuilder`](crate::system::QueryParamBuilder).
/// The `FilteredEntityRef` must be the entire `QueryData`, and not nested inside a tuple with other data.
///
/// ```
/// # use bevy_ecs::{prelude::*, world::FilteredEntityRef};
/// #
/// # #[derive(Component)]
/// # struct A;
/// #
/// # let mut world = World::new();
/// # world.spawn(A);
/// #
/// // This gives the `FilteredEntityRef` access to `&A`.
/// let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world)
/// .data::<&A>()
/// .build();
///
/// let filtered_entity: FilteredEntityRef = query.single(&mut world);
/// let component: &A = filtered_entity.get().unwrap();
///
/// // Here `FilteredEntityRef` is nested in a tuple, so it does not have access to `&A`.
/// let mut query = QueryBuilder::<(Entity, FilteredEntityRef)>::new(&mut world)
/// .data::<&A>()
/// .build();
///
/// let (_, filtered_entity) = query.single(&mut world);
/// assert!(filtered_entity.get::<A>().is_none());
/// ```
#[derive(Clone)]
pub struct FilteredEntityRef<'w> {
entity: UnsafeEntityCell<'w>,
@ -2136,6 +2166,36 @@ impl<'a> From<&'a EntityWorldMut<'_>> for FilteredEntityRef<'a> {
}
/// Provides mutable access to a single entity and some of its components defined by the contained [`Access`].
///
/// To define the access when used as a [`QueryData`](crate::query::QueryData),
/// use a [`QueryBuilder`](crate::query::QueryBuilder) or [`QueryParamBuilder`](crate::system::QueryParamBuilder).
/// The `FilteredEntityMut` must be the entire `QueryData`, and not nested inside a tuple with other data.
///
/// ```
/// # use bevy_ecs::{prelude::*, world::FilteredEntityMut};
/// #
/// # #[derive(Component)]
/// # struct A;
/// #
/// # let mut world = World::new();
/// # world.spawn(A);
/// #
/// // This gives the `FilteredEntityMut` access to `&mut A`.
/// let mut query = QueryBuilder::<FilteredEntityMut>::new(&mut world)
/// .data::<&mut A>()
/// .build();
///
/// let mut filtered_entity: FilteredEntityMut = query.single_mut(&mut world);
/// let component: Mut<A> = filtered_entity.get_mut().unwrap();
///
/// // Here `FilteredEntityMut` is nested in a tuple, so it does not have access to `&mut A`.
/// let mut query = QueryBuilder::<(Entity, FilteredEntityMut)>::new(&mut world)
/// .data::<&mut A>()
/// .build();
///
/// let (_, mut filtered_entity) = query.single_mut(&mut world);
/// assert!(filtered_entity.get_mut::<A>().is_none());
/// ```
pub struct FilteredEntityMut<'w> {
entity: UnsafeEntityCell<'w>,
access: Access<ComponentId>,