use alloc::{boxed::Box, vec::Vec};
use bevy_platform::cell::SyncCell;
use variadics_please::all_tuples;
use crate::{
prelude::QueryBuilder,
query::{QueryData, QueryFilter, QueryState},
resource::Resource,
system::{
DynSystemParam, DynSystemParamState, If, Local, ParamSet, Query, SystemParam,
SystemParamValidationError,
},
world::{
FilteredResources, FilteredResourcesBuilder, FilteredResourcesMut,
FilteredResourcesMutBuilder, FromWorld, World,
},
};
use core::fmt::Debug;
use super::{Res, ResMut, SystemState};
pub unsafe trait SystemParamBuilder<P: SystemParam>: Sized {
fn build(self, world: &mut World) -> P::State;
fn build_state(self, world: &mut World) -> SystemState<P> {
SystemState::from_builder(world, self)
}
}
#[derive(Default, Debug, Clone)]
pub struct ParamBuilder;
unsafe impl<P: SystemParam> SystemParamBuilder<P> for ParamBuilder {
fn build(self, world: &mut World) -> P::State {
P::init_state(world)
}
}
impl ParamBuilder {
pub fn of<T: SystemParam>() -> impl SystemParamBuilder<T> {
Self
}
pub fn resource<'w, T: Resource>() -> impl SystemParamBuilder<Res<'w, T>> {
Self
}
pub fn resource_mut<'w, T: Resource>() -> impl SystemParamBuilder<ResMut<'w, T>> {
Self
}
pub fn local<'s, T: FromWorld + Send + 'static>() -> impl SystemParamBuilder<Local<'s, T>> {
Self
}
pub fn query<'w, 's, D: QueryData + 'static>() -> impl SystemParamBuilder<Query<'w, 's, D, ()>>
{
Self
}
pub fn query_filtered<'w, 's, D: QueryData + 'static, F: QueryFilter + 'static>(
) -> impl SystemParamBuilder<Query<'w, 's, D, F>> {
Self
}
}
unsafe impl<'w, 's, D: QueryData + 'static, F: QueryFilter + 'static>
SystemParamBuilder<Query<'w, 's, D, F>> for QueryState<D, F>
{
fn build(self, world: &mut World) -> QueryState<D, F> {
self.validate_world(world.id());
self
}
}
#[derive(Clone)]
pub struct QueryParamBuilder<T>(T);
impl<T> QueryParamBuilder<T> {
pub fn new<D: QueryData, F: QueryFilter>(f: T) -> Self
where
T: FnOnce(&mut QueryBuilder<D, F>),
{
Self(f)
}
}
impl<'a, D: QueryData, F: QueryFilter>
QueryParamBuilder<Box<dyn FnOnce(&mut QueryBuilder<D, F>) + 'a>>
{
pub fn new_box(f: impl FnOnce(&mut QueryBuilder<D, F>) + 'a) -> Self {
Self(Box::new(f))
}
}
unsafe impl<
'w,
's,
D: QueryData + 'static,
F: QueryFilter + 'static,
T: FnOnce(&mut QueryBuilder<D, F>),
> SystemParamBuilder<Query<'w, 's, D, F>> for QueryParamBuilder<T>
{
fn build(self, world: &mut World) -> QueryState<D, F> {
let mut builder = QueryBuilder::new(world);
(self.0)(&mut builder);
builder.build()
}
}
macro_rules! impl_system_param_builder_tuple {
($(#[$meta:meta])* $(($param: ident, $builder: ident)),*) => {
#[expect(
clippy::allow_attributes,
reason = "This is in a macro; as such, the below lints may not always apply."
)]
#[allow(
unused_variables,
reason = "Zero-length tuples won't use any of the parameters."
)]
#[allow(
non_snake_case,
reason = "The variable names are provided by the macro caller, not by us."
)]
$(#[$meta])*
unsafe impl<$($param: SystemParam,)* $($builder: SystemParamBuilder<$param>,)*> SystemParamBuilder<($($param,)*)> for ($($builder,)*) {
fn build(self, world: &mut World) -> <($($param,)*) as SystemParam>::State {
let ($($builder,)*) = self;
#[allow(
clippy::unused_unit,
reason = "Zero-length tuples won't generate any calls to the system parameter builders."
)]
($($builder.build(world),)*)
}
}
};
}
all_tuples!(
#[doc(fake_variadic)]
impl_system_param_builder_tuple,
0,
16,
P,
B
);
unsafe impl<P: SystemParam, B: SystemParamBuilder<P>> SystemParamBuilder<Vec<P>> for Vec<B> {
fn build(self, world: &mut World) -> <Vec<P> as SystemParam>::State {
self.into_iter()
.map(|builder| builder.build(world))
.collect()
}
}
#[derive(Debug, Default, Clone)]
pub struct ParamSetBuilder<T>(pub T);
macro_rules! impl_param_set_builder_tuple {
($(($param: ident, $builder: ident)),*) => {
#[expect(
clippy::allow_attributes,
reason = "This is in a macro; as such, the below lints may not always apply."
)]
#[allow(
unused_variables,
reason = "Zero-length tuples won't use any of the parameters."
)]
#[allow(
non_snake_case,
reason = "The variable names are provided by the macro caller, not by us."
)]
unsafe impl<'w, 's, $($param: SystemParam,)* $($builder: SystemParamBuilder<$param>,)*> SystemParamBuilder<ParamSet<'w, 's, ($($param,)*)>> for ParamSetBuilder<($($builder,)*)> {
fn build(self, world: &mut World) -> <($($param,)*) as SystemParam>::State {
let ParamSetBuilder(($($builder,)*)) = self;
($($builder.build(world),)*)
}
}
};
}
all_tuples!(impl_param_set_builder_tuple, 1, 8, P, B);
unsafe impl<'w, 's, P: SystemParam, B: SystemParamBuilder<P>>
SystemParamBuilder<ParamSet<'w, 's, Vec<P>>> for ParamSetBuilder<Vec<B>>
{
fn build(self, world: &mut World) -> <Vec<P> as SystemParam>::State {
self.0
.into_iter()
.map(|builder| builder.build(world))
.collect()
}
}
pub struct DynParamBuilder<'a>(Box<dyn FnOnce(&mut World) -> DynSystemParamState + 'a>);
impl<'a> DynParamBuilder<'a> {
pub fn new<T: SystemParam + 'static>(builder: impl SystemParamBuilder<T> + 'a) -> Self {
Self(Box::new(|world| {
DynSystemParamState::new::<T>(builder.build(world))
}))
}
}
unsafe impl<'a, 'w, 's> SystemParamBuilder<DynSystemParam<'w, 's>> for DynParamBuilder<'a> {
fn build(self, world: &mut World) -> <DynSystemParam<'w, 's> as SystemParam>::State {
(self.0)(world)
}
}
#[derive(Default, Debug, Clone)]
pub struct LocalBuilder<T>(pub T);
unsafe impl<'s, T: FromWorld + Send + 'static> SystemParamBuilder<Local<'s, T>>
for LocalBuilder<T>
{
fn build(self, _world: &mut World) -> <Local<'s, T> as SystemParam>::State {
SyncCell::new(self.0)
}
}
#[derive(Clone)]
pub struct FilteredResourcesParamBuilder<T>(T);
impl<T> FilteredResourcesParamBuilder<T> {
pub fn new(f: T) -> Self
where
T: FnOnce(&mut FilteredResourcesBuilder),
{
Self(f)
}
}
impl<'a> FilteredResourcesParamBuilder<Box<dyn FnOnce(&mut FilteredResourcesBuilder) + 'a>> {
pub fn new_box(f: impl FnOnce(&mut FilteredResourcesBuilder) + 'a) -> Self {
Self(Box::new(f))
}
}
unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesBuilder)>
SystemParamBuilder<FilteredResources<'w, 's>> for FilteredResourcesParamBuilder<T>
{
fn build(self, world: &mut World) -> <FilteredResources<'w, 's> as SystemParam>::State {
let mut builder = FilteredResourcesBuilder::new(world);
(self.0)(&mut builder);
builder.build()
}
}
#[derive(Clone)]
pub struct FilteredResourcesMutParamBuilder<T>(T);
impl<T> FilteredResourcesMutParamBuilder<T> {
pub fn new(f: T) -> Self
where
T: FnOnce(&mut FilteredResourcesMutBuilder),
{
Self(f)
}
}
impl<'a> FilteredResourcesMutParamBuilder<Box<dyn FnOnce(&mut FilteredResourcesMutBuilder) + 'a>> {
pub fn new_box(f: impl FnOnce(&mut FilteredResourcesMutBuilder) + 'a) -> Self {
Self(Box::new(f))
}
}
unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)>
SystemParamBuilder<FilteredResourcesMut<'w, 's>> for FilteredResourcesMutParamBuilder<T>
{
fn build(self, world: &mut World) -> <FilteredResourcesMut<'w, 's> as SystemParam>::State {
let mut builder = FilteredResourcesMutBuilder::new(world);
(self.0)(&mut builder);
builder.build()
}
}
#[derive(Clone)]
pub struct OptionBuilder<T>(T);
unsafe impl<P: SystemParam, B: SystemParamBuilder<P>> SystemParamBuilder<Option<P>>
for OptionBuilder<B>
{
fn build(self, world: &mut World) -> <Option<P> as SystemParam>::State {
self.0.build(world)
}
}
#[derive(Clone)]
pub struct ResultBuilder<T>(T);
unsafe impl<P: SystemParam, B: SystemParamBuilder<P>>
SystemParamBuilder<Result<P, SystemParamValidationError>> for ResultBuilder<B>
{
fn build(
self,
world: &mut World,
) -> <Result<P, SystemParamValidationError> as SystemParam>::State {
self.0.build(world)
}
}
#[derive(Clone)]
pub struct IfBuilder<T>(T);
unsafe impl<P: SystemParam, B: SystemParamBuilder<P>> SystemParamBuilder<If<P>> for IfBuilder<B> {
fn build(self, world: &mut World) -> <If<P> as SystemParam>::State {
self.0.build(world)
}
}
#[cfg(test)]
mod tests {
use crate::{
entity::Entities,
error::Result,
prelude::{Component, Query},
reflect::ReflectResource,
system::{Local, RunSystemOnce},
};
use alloc::vec;
use bevy_reflect::{FromType, Reflect, ReflectRef};
use super::*;
#[derive(Component)]
struct A;
#[derive(Component)]
struct B;
#[derive(Component)]
struct C;
#[derive(Resource, Default, Reflect)]
#[reflect(Resource)]
struct R {
foo: usize,
}
fn local_system(local: Local<u64>) -> u64 {
*local
}
fn query_system(query: Query<()>) -> usize {
query.iter().count()
}
fn query_system_result(query: Query<()>) -> Result<usize> {
Ok(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 = (LocalBuilder(10),)
.build_state(&mut world)
.build_system(local_system);
let output = world.run_system_once(system).unwrap();
assert_eq!(output, 10);
}
#[test]
fn query_builder() {
let mut world = World::new();
world.spawn(A);
world.spawn_empty();
let system = (QueryParamBuilder::new(|query| {
query.with::<A>();
}),)
.build_state(&mut world)
.build_system(query_system);
let output = world.run_system_once(system).unwrap();
assert_eq!(output, 1);
}
#[test]
fn query_builder_result_fallible() {
let mut world = World::new();
world.spawn(A);
world.spawn_empty();
let system = (QueryParamBuilder::new(|query| {
query.with::<A>();
}),)
.build_state(&mut world)
.build_system(query_system_result);
let output: usize = world.run_system_once(system).unwrap();
assert_eq!(output, 1);
}
#[test]
fn query_builder_result_infallible() {
let mut world = World::new();
world.spawn(A);
world.spawn_empty();
let system = (QueryParamBuilder::new(|query| {
query.with::<A>();
}),)
.build_state(&mut world)
.build_system(query_system_result);
let output: Result<usize> = world.run_system_once(system).unwrap();
assert_eq!(output.unwrap(), 1);
}
#[test]
fn query_builder_state() {
let mut world = World::new();
world.spawn(A);
world.spawn_empty();
let state = QueryBuilder::new(&mut world).with::<A>().build();
let system = (state,).build_state(&mut world).build_system(query_system);
let output = world.run_system_once(system).unwrap();
assert_eq!(output, 1);
}
#[test]
fn multi_param_builder() {
let mut world = World::new();
world.spawn(A);
world.spawn_empty();
let system = (LocalBuilder(0), ParamBuilder)
.build_state(&mut world)
.build_system(multi_param_system);
let output = world.run_system_once(system).unwrap();
assert_eq!(output, 1);
}
#[test]
fn vec_builder() {
let mut world = World::new();
world.spawn((A, B, C));
world.spawn((A, B));
world.spawn((A, C));
world.spawn((A, C));
world.spawn_empty();
let system = (vec![
QueryParamBuilder::new_box(|builder| {
builder.with::<B>().without::<C>();
}),
QueryParamBuilder::new_box(|builder| {
builder.with::<C>().without::<B>();
}),
],)
.build_state(&mut world)
.build_system(|params: Vec<Query<&mut A>>| {
let mut count: usize = 0;
params
.into_iter()
.for_each(|mut query| count += query.iter_mut().count());
count
});
let output = world.run_system_once(system).unwrap();
assert_eq!(output, 3);
}
#[test]
fn multi_param_builder_inference() {
let mut world = World::new();
world.spawn(A);
world.spawn_empty();
let system = (LocalBuilder(0u64), ParamBuilder::local::<u64>())
.build_state(&mut world)
.build_system(|a, b| *a + *b + 1);
let output = world.run_system_once(system).unwrap();
assert_eq!(output, 1);
}
#[test]
fn param_set_builder() {
let mut world = World::new();
world.spawn((A, B, C));
world.spawn((A, B));
world.spawn((A, C));
world.spawn((A, C));
world.spawn_empty();
let system = (ParamSetBuilder((
QueryParamBuilder::new(|builder| {
builder.with::<B>();
}),
QueryParamBuilder::new(|builder| {
builder.with::<C>();
}),
)),)
.build_state(&mut world)
.build_system(|mut params: ParamSet<(Query<&mut A>, Query<&mut A>)>| {
params.p0().iter().count() + params.p1().iter().count()
});
let output = world.run_system_once(system).unwrap();
assert_eq!(output, 5);
}
#[test]
fn param_set_vec_builder() {
let mut world = World::new();
world.spawn((A, B, C));
world.spawn((A, B));
world.spawn((A, C));
world.spawn((A, C));
world.spawn_empty();
let system = (ParamSetBuilder(vec![
QueryParamBuilder::new_box(|builder| {
builder.with::<B>();
}),
QueryParamBuilder::new_box(|builder| {
builder.with::<C>();
}),
]),)
.build_state(&mut world)
.build_system(|mut params: ParamSet<Vec<Query<&mut A>>>| {
let mut count = 0;
params.for_each(|mut query| count += query.iter_mut().count());
count
});
let output = world.run_system_once(system).unwrap();
assert_eq!(output, 5);
}
#[test]
fn dyn_builder() {
let mut world = World::new();
world.spawn(A);
world.spawn_empty();
let system = (
DynParamBuilder::new(LocalBuilder(3_usize)),
DynParamBuilder::new::<Query<()>>(QueryParamBuilder::new(|builder| {
builder.with::<A>();
})),
DynParamBuilder::new::<&Entities>(ParamBuilder),
)
.build_state(&mut world)
.build_system(
|mut p0: DynSystemParam, mut p1: DynSystemParam, mut p2: DynSystemParam| {
let local = *p0.downcast_mut::<Local<usize>>().unwrap();
let query_count = p1.downcast_mut::<Query<()>>().unwrap().iter().count();
let _entities = p2.downcast_mut::<&Entities>().unwrap();
assert!(p0.downcast_mut::<Query<()>>().is_none());
local + query_count
},
);
let output = world.run_system_once(system).unwrap();
assert_eq!(output, 4);
}
#[derive(SystemParam)]
#[system_param(builder)]
struct CustomParam<'w, 's> {
query: Query<'w, 's, ()>,
local: Local<'s, usize>,
}
#[test]
fn custom_param_builder() {
let mut world = World::new();
world.spawn(A);
world.spawn_empty();
let system = (CustomParamBuilder {
local: LocalBuilder(100),
query: QueryParamBuilder::new(|builder| {
builder.with::<A>();
}),
},)
.build_state(&mut world)
.build_system(|param: CustomParam| *param.local + param.query.iter().count());
let output = world.run_system_once(system).unwrap();
assert_eq!(output, 101);
}
#[test]
fn filtered_resource_conflicts_read_with_res() {
let mut world = World::new();
(
ParamBuilder::resource(),
FilteredResourcesParamBuilder::new(|builder| {
builder.add_read::<R>();
}),
)
.build_state(&mut world)
.build_system(|_r: Res<R>, _fr: FilteredResources| {});
}
#[test]
#[should_panic]
fn filtered_resource_conflicts_read_with_resmut() {
let mut world = World::new();
(
ParamBuilder::resource_mut(),
FilteredResourcesParamBuilder::new(|builder| {
builder.add_read::<R>();
}),
)
.build_state(&mut world)
.build_system(|_r: ResMut<R>, _fr: FilteredResources| {});
}
#[test]
#[should_panic]
fn filtered_resource_conflicts_read_all_with_resmut() {
let mut world = World::new();
(
ParamBuilder::resource_mut(),
FilteredResourcesParamBuilder::new(|builder| {
builder.add_read_all();
}),
)
.build_state(&mut world)
.build_system(|_r: ResMut<R>, _fr: FilteredResources| {});
}
#[test]
fn filtered_resource_mut_conflicts_read_with_res() {
let mut world = World::new();
(
ParamBuilder::resource(),
FilteredResourcesMutParamBuilder::new(|builder| {
builder.add_read::<R>();
}),
)
.build_state(&mut world)
.build_system(|_r: Res<R>, _fr: FilteredResourcesMut| {});
}
#[test]
#[should_panic]
fn filtered_resource_mut_conflicts_read_with_resmut() {
let mut world = World::new();
(
ParamBuilder::resource_mut(),
FilteredResourcesMutParamBuilder::new(|builder| {
builder.add_read::<R>();
}),
)
.build_state(&mut world)
.build_system(|_r: ResMut<R>, _fr: FilteredResourcesMut| {});
}
#[test]
#[should_panic]
fn filtered_resource_mut_conflicts_write_with_res() {
let mut world = World::new();
(
ParamBuilder::resource(),
FilteredResourcesMutParamBuilder::new(|builder| {
builder.add_write::<R>();
}),
)
.build_state(&mut world)
.build_system(|_r: Res<R>, _fr: FilteredResourcesMut| {});
}
#[test]
#[should_panic]
fn filtered_resource_mut_conflicts_write_all_with_res() {
let mut world = World::new();
(
ParamBuilder::resource(),
FilteredResourcesMutParamBuilder::new(|builder| {
builder.add_write_all();
}),
)
.build_state(&mut world)
.build_system(|_r: Res<R>, _fr: FilteredResourcesMut| {});
}
#[test]
#[should_panic]
fn filtered_resource_mut_conflicts_write_with_resmut() {
let mut world = World::new();
(
ParamBuilder::resource_mut(),
FilteredResourcesMutParamBuilder::new(|builder| {
builder.add_write::<R>();
}),
)
.build_state(&mut world)
.build_system(|_r: ResMut<R>, _fr: FilteredResourcesMut| {});
}
#[test]
fn filtered_resource_reflect() {
let mut world = World::new();
world.insert_resource(R { foo: 7 });
let system = (FilteredResourcesParamBuilder::new(|builder| {
builder.add_read::<R>();
}),)
.build_state(&mut world)
.build_system(|res: FilteredResources| {
let reflect_resource = <ReflectResource as FromType<R>>::from_type();
let ReflectRef::Struct(reflect_struct) =
reflect_resource.reflect(res).unwrap().reflect_ref()
else {
panic!()
};
*reflect_struct
.field("foo")
.unwrap()
.try_downcast_ref::<usize>()
.unwrap()
});
let output = world.run_system_once(system).unwrap();
assert_eq!(output, 7);
}
}