Skip to content

RFC: simple_system macro #498

@Xaeroxe

Description

@Xaeroxe

Summary

Provide a macro to simplify the creation of unit structure systems. i.e.

simple_system! {
    pub Test(arg: Read<'s, i32>, arg2: Write<'s, u32>) {
        *arg2 = 5;
        println!("{}", *arg);
    }
}

Motivation

System creation is one of the first things a new specs user has to interact with in order to do anything, meaning creating these in a low verbosity and straightforward manner should be a high priority goal.

Guide-level explanation

We need to create a new system for specs to use. Systems are structures with some execution instructions and metadata attached to them. Many systems don't need to store any data inside their own structure though, so for these we've provided a macro to simplify the declaration called simple_system!. This macro makes declaring the system very similar to declaring a function. First we enter the macro body:

simple_system! {

}

Then inside here we need first, the visibility of the system, i.e. "pub" or "pub(crate)". Or if you just want it private, you can omit this. Then after that we'll need the name of the system. Rust structure names are usually written in PascalCase i.e. MyCoolSystem. So thus far we should have something like

simple_system! {
    pub MyCoolSystem
}

After the name we'll take the resource parameters the system needs, surrounded by parentheses. This is where systems start to differ from a typical Rust function, the dispatcher needs to know what mutability you're expecting for your parameters. You specify this with Read and Write for resources. You'd use ReadStorage and WriteStorage for component storages.

simple_system! {
    pub MyCoolSystem(
        resource: Read<'s, MyResource>,
        writable_resource: Write<'s, MyWritableResource>,
        components: ReadStorage<'s, MyComponent>,
        writable_components: WriteStorage<'s, MyWritableComponent>,
    )
}

But wait! What's 's? That's the system lifetime. Most of the time you won't need this in your system body, but if you need to describe the lifetime of your resources it's exposed there.

Finally, let's add the system body. It's created by adding { and } after the parameter declaration i.e.

simple_system! {
    pub MyCoolSystem(
        resource: Read<'s, MyResource>,
        writable_resource: Write<'s, MyWritableResource>,
        components: ReadStorage<'s, MyComponent>,
        writable_components: WriteStorage<'s, MyWritableComponent>,
    ) {
        println!("I just read this resource: {}!", *resource);
    }
}

(Assuming MyResource has a Display implementation.)

Once you've added the system to a dispatcher with .with(MyCoolSystem) whatever you write inside this body will be executed every time you call dispatch() on the dispatcher.

Reference-level explanation

This proposal would add the following macro to shred.

#[macro_export]
macro_rules! simple_system {
    ($viz:vis $name:ident($($arg:ident: $type:ty),*)$body:block) => {
        $viz struct $name;

        impl<'s> $crate::System<'s> for $name {
            type SystemData = ($($type),*);

            #[allow(unused_mut)]
            fn run(&mut self, ($(mut $arg),*): Self::SystemData) $body
        }
    }
}

This macro utilizes existing traits and capabilities of the library, it just changes the way System structures might be declared. They can still be declared the original way, making this completely backwards compatible.

Drawbacks

The macro somewhat obscures what's going on behind the scenes in an effort to make specs more approachable from a front-end perspective. This can be resolved with good macro documentation, but it might make the fact that a trait is being implemented not quite as apparent.

Rationale and alternatives

The primary rationale is to reduce boilerplate in system declarations. We make a lot of systems so having a dedicated syntax and macro would be a boon.

Here's a comparison of this macro syntax and the current system syntax:

struct MyFirstSystem;

impl<'a> System<'a> for MyFirstSystem {
    type SystemData = ();

    fn run(&mut self, data: Self::SystemData) {
        println!("Hello!");
    }
}

vs

simple_system! {
    pub SimpleSystem() {
        println!("Hello World!");
    }
}

Alternatives:

  • Use a procedural macro instead. It could result in a nicer syntax for the macro but we don't have a Proof of Concept yet.
  • Do nothing. Leave system declaration in a mandatory verbose state.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions