mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 22:18:33 +00:00
Implement a SystemBuilder for building SystemParams (#13123)
# Objective - Implement a general purpose mechanism for building `SystemParam`. - Unblock the usage of dynamic queries in regular systems. ## Solution - Implement a `SystemBuilder` type. ## Examples Here are some simple test cases for the builder: ```rust fn local_system(local: Local<u64>) -> u64 { *local } fn query_system(query: Query<()>) -> usize { query.iter().count() } fn multi_param_system(a: Local<u64>, b: Local<u64>) -> u64 { *a + *b + 1 } #[test] fn local_builder() { let mut world = World::new(); let system = SystemBuilder::<()>::new(&mut world) .builder::<Local<u64>>(|x| *x = 10) .build(local_system); let result = world.run_system_once(system); assert_eq!(result, 10); } #[test] fn query_builder() { let mut world = World::new(); world.spawn(A); world.spawn_empty(); let system = SystemBuilder::<()>::new(&mut world) .builder::<Query<()>>(|query| { query.with::<A>(); }) .build(query_system); let result = world.run_system_once(system); assert_eq!(result, 1); } #[test] fn multi_param_builder() { let mut world = World::new(); world.spawn(A); world.spawn_empty(); let system = SystemBuilder::<()>::new(&mut world) .param::<Local<u64>>() .param::<Local<u64>>() .build(multi_param_system); let result = world.run_system_once(system); assert_eq!(result, 1); } ``` This will be expanded as this PR is iterated.
This commit is contained in:
parent
a785e3c20d
commit
182fe3292e
5 changed files with 313 additions and 4 deletions
|
@ -54,7 +54,8 @@ pub mod prelude {
|
||||||
},
|
},
|
||||||
system::{
|
system::{
|
||||||
Commands, Deferred, In, IntoSystem, Local, NonSend, NonSendMut, ParallelCommands,
|
Commands, Deferred, In, IntoSystem, Local, NonSend, NonSendMut, ParallelCommands,
|
||||||
ParamSet, Query, ReadOnlySystem, Res, ResMut, Resource, System, SystemParamFunction,
|
ParamSet, Query, ReadOnlySystem, Res, ResMut, Resource, System, SystemBuilder,
|
||||||
|
SystemParamFunction,
|
||||||
},
|
},
|
||||||
world::{EntityMut, EntityRef, EntityWorldMut, FromWorld, World},
|
world::{EntityMut, EntityRef, EntityWorldMut, FromWorld, World},
|
||||||
};
|
};
|
||||||
|
|
214
crates/bevy_ecs/src/system/builder.rs
Normal file
214
crates/bevy_ecs/src/system/builder.rs
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
use bevy_utils::all_tuples;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
BuildableSystemParam, FunctionSystem, Local, Res, ResMut, Resource, SystemMeta, SystemParam,
|
||||||
|
SystemParamFunction, SystemState,
|
||||||
|
};
|
||||||
|
use crate::prelude::{FromWorld, Query, World};
|
||||||
|
use crate::query::{QueryData, QueryFilter};
|
||||||
|
|
||||||
|
/// Builder struct used to construct state for [`SystemParam`] passed to a system.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_ecs::prelude::*;
|
||||||
|
/// # use bevy_ecs_macros::SystemParam;
|
||||||
|
/// # use bevy_ecs::system::RunSystemOnce;
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Component)]
|
||||||
|
/// # struct A;
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Component)]
|
||||||
|
/// # struct B;
|
||||||
|
/// #
|
||||||
|
/// # #[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) {
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Create a builder from the world, helper methods exist to add `SystemParam`,
|
||||||
|
/// // alternatively use `.param::<T>()` for any other `SystemParam` types.
|
||||||
|
/// let system = SystemBuilder::<()>::new(&mut world)
|
||||||
|
/// .resource::<R>()
|
||||||
|
/// .query::<&A>()
|
||||||
|
/// .param::<MyParam>()
|
||||||
|
/// .build(my_system);
|
||||||
|
///
|
||||||
|
/// // Parameters that the builder is initialised with will appear first in the arguments.
|
||||||
|
/// let system = SystemBuilder::<(Res<R>, Query<&A>)>::new(&mut world)
|
||||||
|
/// .param::<MyParam>()
|
||||||
|
/// .build(my_system);
|
||||||
|
///
|
||||||
|
/// // Parameters that implement `BuildableSystemParam` can use `.builder::<T>()` to build in place.
|
||||||
|
/// let system = SystemBuilder::<()>::new(&mut world)
|
||||||
|
/// .resource::<R>()
|
||||||
|
/// .builder::<Query<&A>>(|builder| { builder.with::<B>(); })
|
||||||
|
/// .param::<MyParam>()
|
||||||
|
/// .build(my_system);
|
||||||
|
///
|
||||||
|
/// world.run_system_once(system);
|
||||||
|
///```
|
||||||
|
pub struct SystemBuilder<'w, T: SystemParam = ()> {
|
||||||
|
pub(crate) meta: SystemMeta,
|
||||||
|
pub(crate) state: T::State,
|
||||||
|
pub(crate) world: &'w mut World,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, T: SystemParam> SystemBuilder<'w, T> {
|
||||||
|
/// Construct a new builder with the default state for `T`
|
||||||
|
pub fn new(world: &'w mut World) -> Self {
|
||||||
|
let mut meta = SystemMeta::new::<T>();
|
||||||
|
Self {
|
||||||
|
state: T::init_state(world, &mut meta),
|
||||||
|
meta,
|
||||||
|
world,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct the a system with the built params
|
||||||
|
pub fn build<F, Marker>(self, func: F) -> FunctionSystem<Marker, F>
|
||||||
|
where
|
||||||
|
F: SystemParamFunction<Marker, Param = T>,
|
||||||
|
{
|
||||||
|
FunctionSystem::from_builder(self, func)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the constructed [`SystemState`]
|
||||||
|
pub fn state(self) -> SystemState<T> {
|
||||||
|
SystemState::from_builder(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_system_builder {
|
||||||
|
($($curr: ident),*) => {
|
||||||
|
impl<'w, $($curr: SystemParam,)*> SystemBuilder<'w, ($($curr,)*)> {
|
||||||
|
/// Add `T` as a parameter built from the world
|
||||||
|
pub fn param<T: SystemParam>(mut self) -> SystemBuilder<'w, ($($curr,)* T,)> {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let ($($curr,)*) = self.state;
|
||||||
|
SystemBuilder {
|
||||||
|
state: ($($curr,)* T::init_state(self.world, &mut self.meta),),
|
||||||
|
meta: self.meta,
|
||||||
|
world: self.world,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method for reading a [`Resource`] as a param, equivalent to `.param::<Res<T>>()`
|
||||||
|
pub fn resource<T: Resource>(self) -> SystemBuilder<'w, ($($curr,)* Res<'static, T>,)> {
|
||||||
|
self.param::<Res<T>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method for mutably accessing a [`Resource`] as a param, equivalent to `.param::<ResMut<T>>()`
|
||||||
|
pub fn resource_mut<T: Resource>(self) -> SystemBuilder<'w, ($($curr,)* ResMut<'static, T>,)> {
|
||||||
|
self.param::<ResMut<T>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method for adding a [`Local`] as a param, equivalent to `.param::<Local<T>>()`
|
||||||
|
pub fn local<T: Send + FromWorld>(self) -> SystemBuilder<'w, ($($curr,)* Local<'static, T>,)> {
|
||||||
|
self.param::<Local<T>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method for adding a [`Query`] as a param, equivalent to `.param::<Query<D>>()`
|
||||||
|
pub fn query<D: QueryData>(self) -> SystemBuilder<'w, ($($curr,)* Query<'static, 'static, D, ()>,)> {
|
||||||
|
self.query_filtered::<D, ()>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method for adding a filtered [`Query`] as a param, equivalent to `.param::<Query<D, F>>()`
|
||||||
|
pub fn query_filtered<D: QueryData, F: QueryFilter>(self) -> SystemBuilder<'w, ($($curr,)* Query<'static, 'static, D, F>,)> {
|
||||||
|
self.param::<Query<D, F>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add `T` as a parameter built with the given function
|
||||||
|
pub fn builder<T: BuildableSystemParam>(
|
||||||
|
mut self,
|
||||||
|
func: impl FnOnce(&mut T::Builder<'_>),
|
||||||
|
) -> SystemBuilder<'w, ($($curr,)* T,)> {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let ($($curr,)*) = self.state;
|
||||||
|
SystemBuilder {
|
||||||
|
state: ($($curr,)* T::build(self.world, &mut self.meta, func),),
|
||||||
|
meta: self.meta,
|
||||||
|
world: self.world,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
all_tuples!(impl_system_builder, 0, 15, P);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate as bevy_ecs;
|
||||||
|
use crate::prelude::{Component, Query};
|
||||||
|
use crate::system::{Local, RunSystemOnce};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct A;
|
||||||
|
|
||||||
|
fn local_system(local: Local<u64>) -> u64 {
|
||||||
|
*local
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_system(query: Query<()>) -> usize {
|
||||||
|
query.iter().count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn multi_param_system(a: Local<u64>, b: Local<u64>) -> u64 {
|
||||||
|
*a + *b + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn local_builder() {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
let system = SystemBuilder::<()>::new(&mut world)
|
||||||
|
.builder::<Local<u64>>(|x| *x = 10)
|
||||||
|
.build(local_system);
|
||||||
|
|
||||||
|
let result = world.run_system_once(system);
|
||||||
|
assert_eq!(result, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn query_builder() {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
world.spawn(A);
|
||||||
|
world.spawn_empty();
|
||||||
|
|
||||||
|
let system = SystemBuilder::<()>::new(&mut world)
|
||||||
|
.builder::<Query<()>>(|query| {
|
||||||
|
query.with::<A>();
|
||||||
|
})
|
||||||
|
.build(query_system);
|
||||||
|
|
||||||
|
let result = world.run_system_once(system);
|
||||||
|
assert_eq!(result, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_param_builder() {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
world.spawn(A);
|
||||||
|
world.spawn_empty();
|
||||||
|
|
||||||
|
let system = SystemBuilder::<()>::new(&mut world)
|
||||||
|
.local::<u64>()
|
||||||
|
.param::<Local<u64>>()
|
||||||
|
.build(multi_param_system);
|
||||||
|
|
||||||
|
let result = world.run_system_once(system);
|
||||||
|
assert_eq!(result, 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ use std::{borrow::Cow, marker::PhantomData};
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
use bevy_utils::tracing::{info_span, Span};
|
use bevy_utils::tracing::{info_span, Span};
|
||||||
|
|
||||||
use super::{In, IntoSystem, ReadOnlySystem};
|
use super::{In, IntoSystem, ReadOnlySystem, SystemBuilder};
|
||||||
|
|
||||||
/// The metadata of a [`System`].
|
/// The metadata of a [`System`].
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -202,6 +202,16 @@ impl<Param: SystemParam> SystemState<Param> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a [`SystemState`] from a [`SystemBuilder`]
|
||||||
|
pub(crate) fn from_builder(builder: SystemBuilder<Param>) -> Self {
|
||||||
|
Self {
|
||||||
|
meta: builder.meta,
|
||||||
|
param_state: builder.state,
|
||||||
|
world_id: builder.world.id(),
|
||||||
|
archetype_generation: ArchetypeGeneration::initial(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the metadata for this instance.
|
/// Gets the metadata for this instance.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn meta(&self) -> &SystemMeta {
|
pub fn meta(&self) -> &SystemMeta {
|
||||||
|
@ -397,6 +407,23 @@ where
|
||||||
marker: PhantomData<fn() -> Marker>,
|
marker: PhantomData<fn() -> Marker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Marker, F> FunctionSystem<Marker, F>
|
||||||
|
where
|
||||||
|
F: SystemParamFunction<Marker>,
|
||||||
|
{
|
||||||
|
// Create a [`FunctionSystem`] from a [`SystemBuilder`]
|
||||||
|
pub(crate) fn from_builder(builder: SystemBuilder<F::Param>, func: F) -> Self {
|
||||||
|
Self {
|
||||||
|
func,
|
||||||
|
param_state: Some(builder.state),
|
||||||
|
system_meta: builder.meta,
|
||||||
|
world_id: Some(builder.world.id()),
|
||||||
|
archetype_generation: ArchetypeGeneration::initial(),
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// De-initializes the cloned system.
|
// De-initializes the cloned system.
|
||||||
impl<Marker, F> Clone for FunctionSystem<Marker, F>
|
impl<Marker, F> Clone for FunctionSystem<Marker, F>
|
||||||
where
|
where
|
||||||
|
@ -517,9 +544,17 @@ where
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn initialize(&mut self, world: &mut World) {
|
fn initialize(&mut self, world: &mut World) {
|
||||||
self.world_id = Some(world.id());
|
if let Some(id) = self.world_id {
|
||||||
|
assert_eq!(
|
||||||
|
id,
|
||||||
|
world.id(),
|
||||||
|
"System built with a different world than the one it was added to.",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.world_id = Some(world.id());
|
||||||
|
self.param_state = Some(F::Param::init_state(world, &mut self.system_meta));
|
||||||
|
}
|
||||||
self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX);
|
self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX);
|
||||||
self.param_state = Some(F::Param::init_state(world, &mut self.system_meta));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) {
|
fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) {
|
||||||
|
|
|
@ -102,6 +102,7 @@
|
||||||
//! - [`()` (unit primitive type)](https://doc.rust-lang.org/stable/std/primitive.unit.html)
|
//! - [`()` (unit primitive type)](https://doc.rust-lang.org/stable/std/primitive.unit.html)
|
||||||
|
|
||||||
mod adapter_system;
|
mod adapter_system;
|
||||||
|
mod builder;
|
||||||
mod combinator;
|
mod combinator;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod exclusive_function_system;
|
mod exclusive_function_system;
|
||||||
|
@ -117,6 +118,7 @@ mod system_registry;
|
||||||
use std::{any::TypeId, borrow::Cow};
|
use std::{any::TypeId, borrow::Cow};
|
||||||
|
|
||||||
pub use adapter_system::*;
|
pub use adapter_system::*;
|
||||||
|
pub use builder::*;
|
||||||
pub use combinator::*;
|
pub use combinator::*;
|
||||||
pub use commands::*;
|
pub use commands::*;
|
||||||
pub use exclusive_function_system::*;
|
pub use exclusive_function_system::*;
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::{
|
||||||
change_detection::{Ticks, TicksMut},
|
change_detection::{Ticks, TicksMut},
|
||||||
component::{ComponentId, ComponentTicks, Components, Tick},
|
component::{ComponentId, ComponentTicks, Components, Tick},
|
||||||
entity::Entities,
|
entity::Entities,
|
||||||
|
prelude::QueryBuilder,
|
||||||
query::{
|
query::{
|
||||||
Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QueryState,
|
Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QueryState,
|
||||||
ReadOnlyQueryData,
|
ReadOnlyQueryData,
|
||||||
|
@ -175,6 +176,19 @@ pub unsafe trait SystemParam: Sized {
|
||||||
) -> Self::Item<'world, 'state>;
|
) -> Self::Item<'world, 'state>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A parameter that can be built with [`SystemBuilder`](crate::system::builder::SystemBuilder)
|
||||||
|
pub trait BuildableSystemParam: SystemParam {
|
||||||
|
/// A mutable reference to this type will be passed to the builder function
|
||||||
|
type Builder<'b>;
|
||||||
|
|
||||||
|
/// Constructs [`SystemParam::State`] for `Self` using a given builder function
|
||||||
|
fn build(
|
||||||
|
world: &mut World,
|
||||||
|
meta: &mut SystemMeta,
|
||||||
|
func: impl FnOnce(&mut Self::Builder<'_>),
|
||||||
|
) -> Self::State;
|
||||||
|
}
|
||||||
|
|
||||||
/// A [`SystemParam`] that only reads a given [`World`].
|
/// A [`SystemParam`] that only reads a given [`World`].
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -234,6 +248,35 @@ unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam for Qu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'w, 's, D: QueryData + 'static, F: QueryFilter + 'static> BuildableSystemParam
|
||||||
|
for Query<'w, 's, D, F>
|
||||||
|
{
|
||||||
|
type Builder<'b> = QueryBuilder<'b, D, F>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn build(
|
||||||
|
world: &mut World,
|
||||||
|
system_meta: &mut SystemMeta,
|
||||||
|
build: impl FnOnce(&mut Self::Builder<'_>),
|
||||||
|
) -> Self::State {
|
||||||
|
let mut builder = QueryBuilder::new(world);
|
||||||
|
build(&mut builder);
|
||||||
|
let state = builder.build();
|
||||||
|
assert_component_access_compatibility(
|
||||||
|
&system_meta.name,
|
||||||
|
std::any::type_name::<D>(),
|
||||||
|
std::any::type_name::<F>(),
|
||||||
|
&system_meta.component_access_set,
|
||||||
|
&state.component_access,
|
||||||
|
world,
|
||||||
|
);
|
||||||
|
system_meta
|
||||||
|
.component_access_set
|
||||||
|
.add(state.component_access.clone());
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn assert_component_access_compatibility(
|
fn assert_component_access_compatibility(
|
||||||
system_name: &str,
|
system_name: &str,
|
||||||
query_type: &'static str,
|
query_type: &'static str,
|
||||||
|
@ -777,6 +820,20 @@ unsafe impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'w, T: FromWorld + Send + 'static> BuildableSystemParam for Local<'w, T> {
|
||||||
|
type Builder<'b> = T;
|
||||||
|
|
||||||
|
fn build(
|
||||||
|
world: &mut World,
|
||||||
|
_meta: &mut SystemMeta,
|
||||||
|
func: impl FnOnce(&mut Self::Builder<'_>),
|
||||||
|
) -> Self::State {
|
||||||
|
let mut value = T::from_world(world);
|
||||||
|
func(&mut value);
|
||||||
|
SyncCell::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Types that can be used with [`Deferred<T>`] in systems.
|
/// Types that can be used with [`Deferred<T>`] in systems.
|
||||||
/// This allows storing system-local data which is used to defer [`World`] mutations.
|
/// This allows storing system-local data which is used to defer [`World`] mutations.
|
||||||
///
|
///
|
||||||
|
|
Loading…
Add table
Reference in a new issue