More parameters

Now that we have SystemParam in place, it'll be easy to expand this to work with unlimited parameters. We just need one crucial idea: what if a tuple of SystemParam is, itself, a SystemParam? Let's implement:

use std::collections::HashMap;
use std::any::{Any, TypeId};
use std::marker::PhantomData;

trait SystemParam {
   type Item<'new>;

   fn retrieve<'r>(resources: &'r HashMap<TypeId, Box<dyn Any>>) -> Self::Item<'r>;
}

struct Res<'a, T: 'static> {
   value: &'a T,
}

impl<'res, T: 'static> SystemParam for Res<'res, T> {
   type Item<'new> = Res<'new, T>;

   fn retrieve<'r>(resources: &'r HashMap<TypeId, Box<dyn Any>>) -> Self::Item<'r> {
       Res { value: resources.get(&TypeId::of::<T>()).unwrap().downcast_ref().unwrap() }
   }
}

// struct ResMut<'a, T: 'static> {
//     value: &'a mut T,
// }

// impl<'a, T: 'static> SystemParam<'a> for ResMut<'a, T> {
//     fn retrieve(resources: &'a mut HashMap<TypeId, Box<dyn Any>>) -> Self {
//         let value = resources.get_mut(&TypeId::of::<T>()).unwrap().downcast_mut::<T>().unwrap();
//         ResMut { value }
//     }
// }

// struct ResOwned<T: 'static> {
//     value: T
// }

// impl<'a, T: 'static> SystemParam<'a> for ResOwned<T> {
//     fn retrieve(resources: &'a mut HashMap<TypeId, Box<dyn Any>>) -> Self {
//         let value = *resources.remove(&TypeId::of::<T>()).unwrap().downcast::<T>().unwrap();
//         ResOwned { value }
//     }
// }

struct FunctionSystem<Input, F> {
   f: F,
   marker: PhantomData<fn() -> Input>,
}

trait System {
   fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>);
}

macro_rules! impl_system {
   (
       $($params:ident),*
   ) => {
       #[allow(non_snake_case)]
       #[allow(unused)]
       impl<F, $($params: SystemParam),*> System for FunctionSystem<($($params,)*), F> 
           where
               for<'a, 'b> &'a mut F: 
                   FnMut( $($params),* ) + 
                   FnMut( $(<$params as SystemParam>::Item<'b>),* )
       {
           fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>) {
               fn call_inner<$($params),*>(
                   mut f: impl FnMut($($params),*),
                   $($params: $params),*
               ) {
                   f($($params),*)
               }

               $(
                   let $params = $params::retrieve(resources);
               )*

               call_inner(&mut self.f, $($params),*)
           }
       }
   }
}

impl_system!();
impl_system!(T1);
impl_system!(T1, T2);
impl_system!(T1, T2, T3);
impl_system!(T1, T2, T3, T4);

trait IntoSystem<Input> {
   type System: System;

   fn into_system(self) -> Self::System;
}

macro_rules! impl_into_system {
   (
       $($params:ident),*
   ) => {
       impl<F, $($params: SystemParam),*> IntoSystem<($($params,)*)> for F 
           where
               for<'a, 'b> &'a mut F: 
                   FnMut( $($params),* ) + 
                   FnMut( $(<$params as SystemParam>::Item<'b>),* )
       {
           type System = FunctionSystem<($($params,)*), Self>;

           fn into_system(self) -> Self::System {
               FunctionSystem {
                   f: self,
                   marker: Default::default(),
               }
           }
       }
   }
}

impl_into_system!();
impl_into_system!(T1);
impl_into_system!(T1, T2);
impl_into_system!(T1, T2, T3);
impl_into_system!(T1, T2, T3, T4);

type StoredSystem = Box<dyn System>;

struct Scheduler {
   systems: Vec<StoredSystem>,
   resources: HashMap<TypeId, Box<dyn Any>>,
}

impl Scheduler {
   pub fn run(&mut self) {
       for system in self.systems.iter_mut() {
           system.run(&mut self.resources);
       }
   }

   pub fn add_system<I, S: System + 'static>(&mut self, system: impl IntoSystem<I, System = S>) {
       self.systems.push(Box::new(system.into_system()));
   }

   pub fn add_resource<R: 'static>(&mut self, res: R) {
       self.resources.insert(TypeId::of::<R>(), Box::new(res));
   }
}

fn main() {
   let mut scheduler = Scheduler {
       systems: vec![],
       resources: HashMap::default(),
   };

   scheduler.add_system(foo);
   scheduler.add_resource(12i32);
   scheduler.add_resource(7u32);

   scheduler.run();
}
impl<T1: SystemParam, T2: SystemParam> SystemParam for (T1, T2) {
    type Item<'new> = (T1::Item<'new>, T2::Item<'new>);

    fn retrieve<'r>(resources: &'r HashMap<TypeId, Box<dyn Any>>) -> Self::Item<'r> {
        (
            T1::retrieve(resources),
            T2::retrieve(resources),
        )
    }
}

fn foo(int: (Res<i32>, Res<u32>)) {
    println!("int! {} uint! {}", int.0.value, int.1.value);
}

It just works!

Now you may wonder:

But this is only two items? This doesn't actually give us "unlimited" parameters, just slightly more?

But this is secretly sufficient to have unlimited parameters:

fn foo(int: (Res<One>, (Res<Two>, (Res<Three>, (Res<Four>))))) {
    // ...
}

And so on. As I hinted at in chapter 1, there's a syntax cost to this, but it's alleviated by implementing up to 16-tuples, so it's macro time:

macro_rules! impl_system_param {
    (
        $($params:ident),*
    ) => {
        #[allow(unused)]
        impl<$($params: SystemParam),*> SystemParam for ($($params,)*) {
            type Item<'new> = ($($params::Item<'new>,)*);

            fn retrieve<'r>(resources: &'r HashMap<TypeId, Box<dyn Any>>) -> Self::Item<'r> {
                (
                    $($params::retrieve(resources),)*
                )
            }
        }
    }
}

impl_system_param!();
impl_system_param!(T1);
impl_system_param!(T1, T2);
impl_system_param!(T1, T2, T3);
// and so on

And that's it, actually. We can nest parameters indefinitely to pass infinite parameters. Next time we'll go back to that aliasing issue and figure out how to get disjoint mutable access to resources.