Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How are Queries constructed? #9

Open
CraigBuilds opened this issue Sep 27, 2024 · 0 comments
Open

How are Queries constructed? #9

CraigBuilds opened this issue Sep 27, 2024 · 0 comments

Comments

@CraigBuilds
Copy link

CraigBuilds commented Sep 27, 2024

Thank you so much for this book, it has been very enlightening. I understand that you are busy and are yet to finish it, but could I ask in advance how queries are constructed? I have always been baffled at how Bevy knows which queries it needs to construct, and where / how it makes them.

Systems being called with resources makes sense. Resources are added to the Schedule, and then the magic happens in the System trait and its implementations:

impl<F, T1: SystemParam, T2: SystemParam> System for FunctionSystem<(T1, T2), F>
where
    for<'a, 'b> &'a mut F:
        FnMut(T1, T2) + FnMut(<T1 as SystemParam>::Item<'b>, <T2 as SystemParam>::Item<'b>),
{
    fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>) {
        fn call_inner<T1, T2>(mut f: impl FnMut(T1, T2), T1: T1, T2: T2) {
            f(T1, T2)
        }
        let T1 = T1::retrieve(resources);
        let T2 = T2::retrieve(resources);
        call_inner(&mut self.f, T1, T2)
    }
}

When the Schedule calls run, it gives it the resources map, and then the implementation for each number of resources gets that resource from the map using T1::retrieve(resources). Resources are keyed by type ID. Basically, we have converted a function with any number of arguments, into a FunctionSystem, which is always run with one argument (the resources map), where it then retrieves any number of parameters from that map and passes them to the function.

So before a system that uses resources is run, we must add the resources to the schedule. This is the same as how resources work in Bevy.

However, Queries, do not work like this. We do not have to manually add the queries to the scheduler before the system is ran. Bevy somehow automatically constructs the queries and then passes them to the systems. I attempted this by copying the way resources have been implemented, but instead of adding a resource, we can register a query, where a default version is added to the map. I then expect that Bevy automatically registers the queries when the function is converted into a system. Is this correct?

In reality, it's unlikely creates a Default Query. Queries are iterators, so instead of Default, they are probably FromWorld, so it constructs them from the table of components that will be iterated over. However, I'm more interested in where Bevy constructs them and how it knows what types to construct, so making a default version that yields a default value is fine.

Here is my attempt at adding Queries, without automatic registration. This has many issues, and you need to manually call register_query, but otherwise, am I starting in the right direction? Thanks.

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

//
// System Params
//

trait SystemParam {
   type Item<'new>;
   fn retrieve<'r>(resources: &'r HashMap<TypeId, Box<dyn Any>>, queries: &'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>>, queries: &'r HashMap<TypeId, Box<dyn Any>>) -> Self::Item<'r> {
       let value = resources
           .get(&TypeId::of::<T>())
           .unwrap()
           .downcast_ref()
           .unwrap();
       Res { value }
   }
}
struct Query<'a, T: 'static> {
   value: &'a T,
}
impl<'res, T: Default + 'static> SystemParam for Query<'res, T> {
   type Item<'new> = Query<'new, T>;
   fn retrieve<'r>(resources: &'r HashMap<TypeId, Box<dyn Any>>, queries: &'r HashMap<TypeId, Box<dyn Any>>) -> Self::Item<'r> {
       let value = queries
           .get(&TypeId::of::<T>())
           .unwrap()
           .downcast_ref()
           .unwrap();
       Query { value }
   }
}

//
// System Trait
//

struct FunctionSystem<Input, F> {
   f: F,
   marker: PhantomData<fn() -> Input>,
}
//define trait
trait System {
   fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>, queries: &mut HashMap<TypeId, Box<dyn Any>>);
}
//implement trait for none, one and two parameters
impl<F> System for FunctionSystem<(), F>
where
   for<'a, 'b> &'a mut F: FnMut() + FnMut(),
{
   fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>, queries: &mut HashMap<TypeId, Box<dyn Any>>) {
       fn call_inner(mut f: impl FnMut()) {
           f()
       }
       call_inner(&mut self.f)
   }
}
impl<F, T1: SystemParam> System for FunctionSystem<(T1,), F>
where
   for<'a, 'b> &'a mut F: FnMut(T1) + FnMut(<T1 as SystemParam>::Item<'b>),
{
   fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>, queries: &mut HashMap<TypeId, Box<dyn Any>>) {
       fn call_inner<T1>(mut f: impl FnMut(T1), T1: T1) {
           f(T1)
       }
       let T1 = T1::retrieve(resources, queries);
       call_inner(&mut self.f, T1)
   }
}
impl<F, T1: SystemParam, T2: SystemParam> System for FunctionSystem<(T1, T2), F>
where
   for<'a, 'b> &'a mut F:
       FnMut(T1, T2) + FnMut(<T1 as SystemParam>::Item<'b>, <T2 as SystemParam>::Item<'b>),
{
   fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>, queries: &mut HashMap<TypeId, Box<dyn Any>>) {
       fn call_inner<T1, T2>(mut f: impl FnMut(T1, T2), T1: T1, T2: T2) {
           f(T1, T2)
       }
       let T1 = T1::retrieve(resources, queries);
       let T2 = T2::retrieve(resources, queries);
       call_inner(&mut self.f, T1, T2)
   }
}

//
// Convert Functions to Systems
//

//define trait
trait IntoSystem<Input> {
   type System: System;
   fn into_system(self) -> Self::System;
}
//implement trait for none, one and two parameters
impl<F> IntoSystem<()> for F
where
   for<'a, 'b> &'a mut F: FnMut() + FnMut(),
{
   type System = FunctionSystem<(), Self>;
   fn into_system(self) -> Self::System {
       FunctionSystem {
           f: self,
           marker: Default::default(),
       }
   }
}
impl<F, T1: SystemParam> IntoSystem<(T1,)> for F
where
   for<'a, 'b> &'a mut F: FnMut(T1) + FnMut(<T1 as SystemParam>::Item<'b>),
{
   type System = FunctionSystem<(T1,), Self>;
   fn into_system(self) -> Self::System {
       FunctionSystem {
           f: self,
           marker: Default::default(),
       }
   }
}
impl<F, T1: SystemParam, T2: SystemParam> IntoSystem<(T1, T2)> for F
where
   for<'a, 'b> &'a mut F:
       FnMut(T1, T2) + FnMut(<T1 as SystemParam>::Item<'b>, <T2 as SystemParam>::Item<'b>),
{
   type System = FunctionSystem<(T1, T2), Self>;
   fn into_system(self) -> Self::System {
       FunctionSystem {
           f: self,
           marker: Default::default(),
       }
   }
}

//
// Scheduler
//

type StoredSystem = Box<dyn System>;
struct Scheduler {
   systems: Vec<StoredSystem>,
   resources: HashMap<TypeId, Box<dyn Any>>,
   queries: HashMap<TypeId, Box<dyn Any>>,
}
impl Scheduler {
   pub fn run(&mut self) {
       for system in self.systems.iter_mut() {
           system.run(&mut self.resources, &mut self.queries);
       }
   }
   pub fn add_system<I, S: System + 'static>(&mut self, system: impl IntoSystem<I, System = S>) {
       //perhaps `register_query` should happen here? `into_system` is statically dispatched depending on the
       //number and type of parameters in the function. Each implementation has access to the types we need to register,
       //so maybe it should be done in there?
       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));
   }
   pub fn register_query<Q: Default + 'static>(&mut self) {
       self.queries.insert(TypeId::of::<Q>(), Box::new(Q::default()));
   }
}

//
// Example Usage
//

fn system_using_one_query(q: Query<f32>) {
   println!("called bar with query: {}", q.value);
}

fn main() {
   let mut scheduler = Scheduler {
       systems: Vec::new(),
       resources: HashMap::default(),
       queries: HashMap::default(),
   };
   scheduler.add_system(system_using_one_query);
   scheduler.register_query::<f32>(); //This needs to be called automatically. 
   scheduler.run();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant