LucioleMaléfique
LucioleMaléfique

Reputation: 760

Declarative macros

I'm trying to learn about macros, as meta programming is a concept I really loved when I found about it, and it can be really usefull in my current project.

I'm trying to make a macro to create entities with any number of components, something like :

let ecs = ECS::new();
let entity1 = create_entity!(ecs);
let entity2 = create_entity!(ecs, Position{x:1, y:2});
let entity3 = create_entity!(ecs, Position{x:3, y:-1}, Velocity{vx:0, vy:1});

There are several things I don't know how to do here :

As an example, here is a working function to create an entity with one component :

fn create_entity_1<C1, C2>(ecs: ECS, component1: C1, component2: C2) -> Entity {
    let result_entity = ecs.create_entity();
    ecs.components.add_comp_to_last(&result_entity, component1);
    ecs.components.add_comp_to_last(&result_entity, component2);
    return result_entity;
}

And obviously, with more component I "simply" need to add more of the second line.

This is what i've tried so far, but I'm struggling to understand what I'm doing :

// macros to create entities with any number of components
macro_rules! create_entity {
    ($ecs:expr; $($comp:literal),*) => {
        let result_entity = $ecs.create_entity();
        $ecs.components.add_comp_to_last(&result_entity, comp);
    }
}

I've tried to read both The book and The little book of macros, But I'm lacking concrete example or explanations of the whole thing.

Upvotes: 1

Views: 140

Answers (1)

kmdreko
kmdreko

Reputation: 59817

You look like you were on the right track so let me fill in the rest:

macro_rules! create_entity {
    ($ecs:expr) => { ECS::create_entity(&$ecs) };
    ($ecs:expr; $($comp:expr),*) => { {
        let result_entity = ECS::create_entity(&$ecs);
        $(
            $ecs.components.add_comp_to_last(&result_entity, $comp);
        )*
        result_entity
    } };
}
  • Separate $ecs:expr and $ecs:expr; $($comp:expr),* into separate rules, the former of which simply creates the entity without components.
  • Change $comp from literal to expr. Only things like "strings", 42, or true are literals and you likely want to be able to accept any expression that creates a component anyway.
  • Use a block and return the created entity: { ...; result_entity } (note the extra braces above { { ... } }). That way you can assign the result of the macro to a variable as you desired, otherwise let entity = create_entity!(ecs, ...); wouldn't work since the macro would yield multiple statements instead of a single block of statements.
  • Use macro repetition rules to expand $comp to multiple expressions using $(...)* within the macro output.
  • Use the fully qualified syntax to call ECS::create_entity() directly if you want to ensure that the $ecs is an ECS (or reference to one) and not simply accept variables that could act like an ECS. The println! macro works differently since a macro can distinguish between kinds of literals (first parameter can't just be an expression of type &str it must be a literal), but here you can't distinguish in a macro between a variable of type ECS and a variable of some other type. You simply have to construct the generated code to produce an error if it wasn't the expected type.

See it working on the playground.

You use a comma separator after ecs in your example usage but you use a semicolon when defining the macro rule (meaning create_entity!(ecs; ...)), I kept with the semicolon here but its trivial to change.

Upvotes: 4

Related Questions