From c14135d1500850fe7d37d59f2e2736069f3dfd46 Mon Sep 17 00:00:00 2001 From: Christian Hughes <9044780+ItsDoot@users.noreply.github.com> Date: Sat, 14 Dec 2024 23:59:34 -0600 Subject: [PATCH] Support `SystemInput` tuples up to 8 elements (#16814) # Objective - Writing an API, and I want to allow users to pass in extra data alongside the API provided input, and tuples are the most natural extension in this case. - Bring `SystemInput` up to par with `SystemParam` for tuple support. ## Solution - Added impls for tuples up to 8 elements. If you need a 9-arity tuple or more, write your own `SystemInput` type (it's incredibly simple to do). ## Testing - Added a test demonstrating this. --- ## Showcase Tuples of arbitrary`SystemInput`s are now supported: ```rust fn by_value((In(a), In(b)): (In, In)) -> usize { a + b } fn by_mut((InMut(a), In(b)): (InMut, In)) { *a += b; } let mut world = World::new(); let mut by_value = IntoSystem::into_system(by_value); let mut by_mut = IntoSystem::into_system(by_mut); by_value.initialize(&mut world); by_mut.initialize(&mut world); assert_eq!(by_value.run((12, 24), &mut world), 36); let mut a = 10; let b = 5; by_mut.run((&mut a, b), &mut world); assert_eq!(*a, 15); ``` --- crates/bevy_ecs/src/system/input.rs | 103 +++++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/system/input.rs b/crates/bevy_ecs/src/system/input.rs index 57032f71928ef..469403bc7345c 100644 --- a/crates/bevy_ecs/src/system/input.rs +++ b/crates/bevy_ecs/src/system/input.rs @@ -1,5 +1,7 @@ use core::ops::{Deref, DerefMut}; +use variadics_please::all_tuples; + use crate::{bundle::Bundle, prelude::Trigger, system::System}; /// Trait for types that can be used as input to [`System`]s. @@ -11,6 +13,28 @@ use crate::{bundle::Bundle, prelude::Trigger, system::System}; /// - [`InMut`]: For mutable references to values /// - [`Trigger`]: For [`ObserverSystem`]s /// - [`StaticSystemInput`]: For arbitrary [`SystemInput`]s in generic contexts +/// - Tuples of [`SystemInput`]s up to 8 elements +/// +/// For advanced usecases, you can implement this trait for your own types. +/// +/// # Examples +/// +/// ## Tuples of [`SystemInput`]s +/// +/// ``` +/// use bevy_ecs::prelude::*; +/// +/// fn add((InMut(a), In(b)): (InMut, In)) { +/// *a += b; +/// } +/// # let mut world = World::new(); +/// # let mut add = IntoSystem::into_system(add); +/// # add.initialize(&mut world); +/// # let mut a = 12; +/// # let b = 24; +/// # add.run((&mut a, b), &mut world); +/// # assert_eq!(a, 36); +/// ``` /// /// [`ObserverSystem`]: crate::system::ObserverSystem pub trait SystemInput: Sized { @@ -31,14 +55,6 @@ pub trait SystemInput: Sized { /// Shorthand way to get the [`System::In`] for a [`System`] as a [`SystemInput::Inner`]. pub type SystemIn<'a, S> = <::In as SystemInput>::Inner<'a>; -/// [`SystemInput`] type for systems that take no input. -impl SystemInput for () { - type Param<'i> = (); - type Inner<'i> = (); - - fn wrap(_this: Self::Inner<'_>) -> Self::Param<'_> {} -} - /// A [`SystemInput`] type which denotes that a [`System`] receives /// an input value of type `T` from its caller. /// @@ -47,6 +63,8 @@ impl SystemInput for () { /// with this `In` type, but only the first param of a function may be tagged as an input. This also /// means a system can only have one or zero input parameters. /// +/// See [`SystemInput`] to learn more about system inputs in general. +/// /// # Examples /// /// Here is a simple example of a system that takes a [`usize`] and returns the square of it. @@ -99,6 +117,8 @@ impl DerefMut for In { /// This is similar to [`In`] but takes a reference to a value instead of the value itself. /// See [`InMut`] for the mutable version. /// +/// See [`SystemInput`] to learn more about system inputs in general. +/// /// # Examples /// /// Here is a simple example of a system that logs the passed in message. @@ -150,6 +170,8 @@ impl<'i, T: ?Sized> Deref for InRef<'i, T> { /// This is similar to [`In`] but takes a mutable reference to a value instead of the value itself. /// See [`InRef`] for the read-only version. /// +/// See [`SystemInput`] to learn more about system inputs in general. +/// /// # Examples /// /// Here is a simple example of a system that takes a `&mut usize` and squares it. @@ -217,6 +239,8 @@ impl SystemInput for Trigger<'_, E, B> { /// /// This makes it useful for having arbitrary [`SystemInput`]s in /// function systems. +/// +/// See [`SystemInput`] to learn more about system inputs in general. pub struct StaticSystemInput<'a, I: SystemInput>(pub I::Inner<'a>); impl<'a, I: SystemInput> SystemInput for StaticSystemInput<'a, I> { @@ -227,3 +251,66 @@ impl<'a, I: SystemInput> SystemInput for StaticSystemInput<'a, I> { StaticSystemInput(this) } } + +macro_rules! impl_system_input_tuple { + ($(#[$meta:meta])* $($name:ident),*) => { + $(#[$meta])* + impl<$($name: SystemInput),*> SystemInput for ($($name,)*) { + type Param<'i> = ($($name::Param<'i>,)*); + type Inner<'i> = ($($name::Inner<'i>,)*); + + #[allow(non_snake_case)] + #[allow(clippy::unused_unit)] + fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { + let ($($name,)*) = this; + ($($name::wrap($name),)*) + } + } + }; +} + +all_tuples!( + #[doc(fake_variadic)] + impl_system_input_tuple, + 0, + 8, + I +); + +#[cfg(test)] +mod tests { + use crate::{ + system::{In, InMut, InRef, IntoSystem, System}, + world::World, + }; + + #[test] + fn two_tuple() { + fn by_value((In(a), In(b)): (In, In)) -> usize { + a + b + } + fn by_ref((InRef(a), InRef(b)): (InRef, InRef)) -> usize { + *a + *b + } + fn by_mut((InMut(a), In(b)): (InMut, In)) { + *a += b; + } + + let mut world = World::new(); + let mut by_value = IntoSystem::into_system(by_value); + let mut by_ref = IntoSystem::into_system(by_ref); + let mut by_mut = IntoSystem::into_system(by_mut); + + by_value.initialize(&mut world); + by_ref.initialize(&mut world); + by_mut.initialize(&mut world); + + let mut a = 12; + let b = 24; + + assert_eq!(by_value.run((a, b), &mut world), 36); + assert_eq!(by_ref.run((&a, &b), &mut world), 36); + by_mut.run((&mut a, b), &mut world); + assert_eq!(a, 36); + } +}