Search code examples
rustbevy

How does Bevy "scope" its systems based on the type of the arguments?


Bevy, a new Rust game engine and ECS, has a feature where it "scopes" its systems based on the type of the arguments. From its docs:

The parameters we pass in to a "system function" define what entities the system runs on. In this case, greet_people will run on all entities with the Person and Name component.

It looks like this:

struct Person;
struct Name(String);

fn greet_people(person: &Person, name: &Name) {
    println!("hello {}", name.0);
}

How is Bevy able to make this happen? I thought I read somewhere that Rust didn't support reflection in this way.


Solution

  • Bevy defines a set of traits (IntoSystem, which is implemented for all functions that implement SystemParamFunction/ExclusiveSystemParamFunction) that are implemented for all functions that can be used as systems. Those traits are then exported by the Bevy prelude. One of the limitations of this is that you can only convert functions up to a certain number of arguments into systems, and the arguments must be in a particular order ([command?], [resources...], [queries/components...]).

    You can do this yourself by implementing traits in a similar manner:

    trait MyTrait<Args> {
        fn invoke(&mut self, args: Args);
    }
    
    impl<F> MyTrait<()> for F
    where
        F: FnMut() -> (),
    {
        fn invoke(&mut self, (): ()) {
            (self)()
        }
    }
    
    // More impls for functions up to N arity...
    // Usually a helper macro is used for this
    
    fn test() {
        println!("Hello, world!");
    }
    
    fn main() {
        test.invoke(());
    }
    

    Playground