From 86e5a5ad9c7a973d689ddb8f18f9665f5bfeeb32 Mon Sep 17 00:00:00 2001 From: Chris Russell <8494645+chescock@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:59:52 -0400 Subject: [PATCH] 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. --- crates/bevy_ecs/src/system/builder.rs | 278 +++++++++++++++++---- crates/bevy_ecs/src/system/mod.rs | 8 + crates/bevy_ecs/src/system/system_param.rs | 54 +--- crates/bevy_ecs/src/world/entity_ref.rs | 60 +++++ 4 files changed, 304 insertions(+), 96 deletions(-) diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index 6dc37ab1ab..a9411dd176 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -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, query: Query<&A>, param: MyParam) { -/// // ... +/// fn some_system(param: MyParam) {} +/// +/// fn build_system(builder: impl SystemParamBuilder) { +/// 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::(), -/// ) -/// .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::(); -/// }), -/// ParamBuilder, -/// ) -/// .build_state(&mut world) -/// .build_system(my_system); -/// -/// fn single_parameter_system(local: Local) { -/// // ... +/// fn build_closure_system_infer(builder: impl SystemParamBuilder) { +/// 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(|param, res| { +/// let param: MyParam = param; +/// let res: Res = res; +/// }); /// } /// -/// // Note that the builder for a system must be a tuple, even if there is only one parameter. -/// let system = (LocalBuilder(2),) -/// .build_state(&mut world) -/// .build_system(single_parameter_system); -/// -/// world.run_system_once(system); +/// fn build_closure_system_explicit(builder: impl SystemParamBuilder) { +/// 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_any_system(|param: MyParam, res: Res| {}); +/// } /// ``` /// +/// 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

` +/// 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: 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, param: MyParam, query: Query<&A>) { +/// // ... +/// } +/// +/// let system = ( +/// // A plain ParamBuilder can build any parameter type. +/// ParamBuilder, +/// // The `of::

()` method returns a `ParamBuilder` +/// // typed as `impl SystemParamBuilder

`. +/// ParamBuilder::of::(), +/// // 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::(); +/// }),) +/// .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::(); +/// }), +/// QueryParamBuilder::new_box(|builder| { +/// builder.without::(); +/// }), +/// ],) +/// .build_state(&mut world) +/// .build_system(|query: Vec>| {}); +/// ``` pub struct QueryParamBuilder(T); impl QueryParamBuilder { @@ -229,7 +324,74 @@ unsafe impl> SystemParamBuilder> /// 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::(); +/// }), +/// QueryParamBuilder::new(|builder| { +/// builder.with::(); +/// }), +/// 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::(); +/// }), +/// QueryParamBuilder::new_box(|builder| { +/// builder.with::(); +/// }), +/// ]),) +/// .build_state(&mut world) +/// .build_system(buildable_system_with_vec); +/// # world.run_system_once(system); +/// +/// fn buildable_system_with_vec(mut set: ParamSet>>) { +/// // 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(pub T); macro_rules! impl_param_set_builder_tuple { @@ -302,6 +464,7 @@ unsafe impl<'w, 's, P: SystemParam, B: SystemParamBuilder

> } /// A [`SystemParamBuilder`] for a [`DynSystemParam`]. +/// See the [`DynSystemParam`] docs for examples. pub struct DynParamBuilder<'a>( Box DynSystemParamState + 'a>, ); @@ -331,6 +494,23 @@ unsafe impl<'a, 'w, 's> SystemParamBuilder> 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| { +/// assert_eq!(*local, 100); +/// }); +/// # world.run_system_once(system); +/// ``` pub struct LocalBuilder(pub T); // SAFETY: `Local` performs no world access. diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index f557b273f4..acf28fac37 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -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

`] where `P: SystemParam` +//! - [`ParamSet>`] where `P: SystemParam` mod adapter_system; mod builder; diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 2d05a23778..5a1b9c6493 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -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::(); -/// }), -/// QueryParamBuilder::new(|builder| { -/// builder.with::(); -/// }), -/// 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 ReadOnlySystemParam for PhantomData {} /// # #[derive(Default, Resource)] /// # struct B; /// # -/// let mut world = World::new(); -/// world.init_resource::(); -/// world.init_resource::(); -/// +/// # let mut world = World::new(); +/// # world.init_resource::(); +/// # world.init_resource::(); +/// # /// // If the inner parameter doesn't require any special building, use `ParamBuilder`. /// // Either specify the type parameter on `DynParamBuilder::new()` ... /// let system = (DynParamBuilder::new::>(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::()),) /// .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 ReadOnlySystemParam for PhantomData {} /// let local: Local = param.downcast::>().unwrap(); /// assert_eq!(*local, 10); /// }); -/// world.run_system_once(system); +/// # world.run_system_once(system); /// ``` pub struct DynSystemParam<'w, 's> { /// A `ParamState` wrapping the state for the underlying system param. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index f207ee2d3b..3eb20be577 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -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::::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::().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::::new(&mut world) +/// .data::<&mut A>() +/// .build(); +/// +/// let mut filtered_entity: FilteredEntityMut = query.single_mut(&mut world); +/// let component: Mut = 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::().is_none()); +/// ``` pub struct FilteredEntityMut<'w> { entity: UnsafeEntityCell<'w>, access: Access,