use core::ops::{Deref, DerefMut};
use variadics_please::all_tuples;
use crate::{bundle::Bundle, event::Event, prelude::On, system::System};
pub trait SystemInput: Sized {
type Param<'i>: SystemInput;
type Inner<'i>;
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_>;
}
pub type SystemIn<'a, S> = <<S as System>::In as SystemInput>::Inner<'a>;
pub trait FromInput<In: SystemInput>: SystemInput {
fn from_inner<'i>(inner: In::Inner<'i>) -> Self::Inner<'i>;
}
impl<In: SystemInput> FromInput<In> for In {
#[inline]
fn from_inner<'i>(inner: In::Inner<'i>) -> Self::Inner<'i> {
inner
}
}
impl<'a, In: SystemInput> FromInput<In> for StaticSystemInput<'a, In> {
#[inline]
fn from_inner<'i>(inner: In::Inner<'i>) -> Self::Inner<'i> {
inner
}
}
#[derive(Debug)]
pub struct In<T>(pub T);
impl<T: 'static> SystemInput for In<T> {
type Param<'i> = In<T>;
type Inner<'i> = T;
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> {
In(this)
}
}
impl<T> Deref for In<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for In<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Debug)]
pub struct InRef<'i, T: ?Sized>(pub &'i T);
impl<T: ?Sized + 'static> SystemInput for InRef<'_, T> {
type Param<'i> = InRef<'i, T>;
type Inner<'i> = &'i T;
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> {
InRef(this)
}
}
impl<'i, T: ?Sized> Deref for InRef<'i, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0
}
}
#[derive(Debug)]
pub struct InMut<'a, T: ?Sized>(pub &'a mut T);
impl<T: ?Sized + 'static> SystemInput for InMut<'_, T> {
type Param<'i> = InMut<'i, T>;
type Inner<'i> = &'i mut T;
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> {
InMut(this)
}
}
impl<'i, T: ?Sized> Deref for InMut<'i, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<'i, T: ?Sized> DerefMut for InMut<'i, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0
}
}
impl<E: Event, B: Bundle> SystemInput for On<'_, '_, E, B> {
type Param<'i> = On<'i, 'i, E, B>;
type Inner<'i> = On<'i, 'i, E, B>;
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> {
this
}
}
pub struct StaticSystemInput<'a, I: SystemInput>(pub I::Inner<'a>);
impl<'a, I: SystemInput> SystemInput for StaticSystemInput<'a, I> {
type Param<'i> = StaticSystemInput<'i, I>;
type Inner<'i> = I::Inner<'i>;
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> {
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>,)*);
#[expect(
clippy::allow_attributes,
reason = "This is in a macro; as such, the below lints may not always apply."
)]
#[allow(
non_snake_case,
reason = "Certain variable names are provided by the caller, not by us."
)]
#[allow(
clippy::unused_unit,
reason = "Zero-length tuples won't have anything to wrap."
)]
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::{assert_is_system, In, InMut, InRef, IntoSystem, StaticSystemInput, System},
world::World,
};
#[test]
fn two_tuple() {
fn by_value((In(a), In(b)): (In<usize>, In<usize>)) -> usize {
a + b
}
fn by_ref((InRef(a), InRef(b)): (InRef<usize>, InRef<usize>)) -> usize {
*a + *b
}
fn by_mut((InMut(a), In(b)): (InMut<usize>, In<usize>)) {
*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).unwrap(), 36);
assert_eq!(by_ref.run((&a, &b), &mut world).unwrap(), 36);
by_mut.run((&mut a, b), &mut world).unwrap();
assert_eq!(a, 36);
}
#[test]
fn compatible_input() {
fn takes_usize(In(a): In<usize>) -> usize {
a
}
fn takes_static_usize(StaticSystemInput(b): StaticSystemInput<In<usize>>) -> usize {
b
}
assert_is_system::<In<usize>, usize, _>(takes_usize);
assert_is_system::<In<usize>, usize, _>(takes_static_usize);
}
}