0x03. Define events using macros

Time:2021-11-28

The macro of rust is very strong, which is different from the macro of string substitution in C language. Now, the current event processing method is defined by macro instead

Macro definition

Before refactoring the event handling code, go through the syntax defined by the rust macro

The simplest macro definition

Macro usage of rustmacro_rules!To declare rules

macro_rules! ten {
    () => { 10 };
}
fn main() {
    let x = 2 * ten!();
    assert_eq!(20, x); // ten![] or ten!{}
}

This is the simplest macro. When executing, match the rules from the left parentheses to the right. Here, a number is returned directly. It is also easy to use. For the use of macros, you can use parentheses, brackets and braces

Match token trees

Macros have very strong matching ability. They can also match token trees and pass onefnNo problem with keywords

macro_rules! foo {
    (fn) => { 1 };
}
fn main() {
    let x = foo!(fn);
    assert_eq!(1, x);
}
Pass parameters to macro

The above code is 2 times the number 10 obtained from executing the macro. Now let’s look at the expression passed in to the macro

macro_rules! ten_times {
    ($e: expr) => { 10 * $e };
}
fn main() {
    let x = ten_times!(2);
    assert_eq!(20, x);
}

there$eI made it myself. It’s written$a, $b, $fooAll kinds of things are OK, but it is recommended to write semanticallyexprIs an abbreviation for expression, and there are other types

Item: structure, function, mod, etc
Block: a statement or expression enclosed in curly braces, that is, a code block
Stmt: a statement
Pat: a pattern
TY: a type
Ident: identifier
Path: a path like foo:: Bar
Meta: meta type, such as #[…], #! Things in […]
TT: a token tree

Just try it and take itpatcome

macro_rules! test {
    ( $e: expr, $pattern: pat ) => {
        match $e {
            $pattern => {
                true
            },
            _ => false
        }
    }
}
fn main() {
    let a = Some(true);
    let x = test!(a, Some(true));
    println!("{}", x);
}
repeat

This is an example of the macro section of rust book,*Indicates reuse$()The content of the package is used to process the value passed in. In this example, multiple numbers can be processed

macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}
fn main() {
    let v = vec![1, 2, 3];
}

Unified event handling using macros

Now have a general understanding of the syntax of rust macro, and then use macro to handle the event processing
Before applying macros, we first consider a problem. Generally, we write a function to determine the function signature through the function and what we need. As long as your imagination is enough, you can make your favorite way to use macros
Here, first define the name of the macro ruleevents_macro

events_macro! {
    keyboard: {
        key_escape: Escape,
        key_up: Up,
        key_down: Down
    },
    else: {
        quit: Quit { .. }
    }
}

We will be in the macro ruleuseSDL event types, corresponding keyboard keys and other things, so they need to be usedidentType. Events can be processed in the same way through the macro repetition mechanism. In addition to keyboard events, exit events can be processed, just like the one passed in abovepatType

macro_rules! events_macro {
    ( keyboard: { $( $k_alias:ident : $k_sdl:ident ), * },
      else: { $( $e_alias:ident : $e_sdl:pat ), * }) => {
        use sdl2::EventPump;

        pub struct ImmediateEvents {
            $( pub $k_alias: Option<bool>, )*
            $( pub $e_alias: bool ),*
        }

        impl ImmediateEvents {
            fn new() -> Self {
                Self {
                    $( $k_alias: None, )*
                    $( $e_alias: false, )*
                }
            }
        }

        pub struct Events {
            pump: EventPump,
            pub now: ImmediateEvents,
            $( pub $k_alias: bool, )*
        }

        impl Events {
            pub fn new(pump: EventPump) -> Self {
                Self {
                    pump,
                    now: ImmediateEvents::new(),
                    $( $k_alias: false, )*
                }
            }
            pub fn pump(&mut self) {
                self.now = ImmediateEvents::new();
                for event in self.pump.poll_iter() {
                    use sdl2::event::Event::*;
                    use sdl2::keyboard::Keycode::*;
                    match event {
                        $(KeyDown { keycode: Some($k_sdl), .. } => {
                            if !self.$k_alias {
                                self.now.$k_alias = Some(true);
                            }
                            self.$k_alias = true;
                        }), *
                        $(KeyUp { keycode: Some($k_sdl), .. } => {
                            self.now.$k_alias = Some(false);
                            self.$k_alias = false;
                        }), *
                        $($e_sdl => { self.now.$e_alias = true; }), *
                        _ => {}
                    }
                }
            }
        }
    };
}

We aremain.rsUse this macro

#![feature(uniform_paths)]

use sdl2::pixels::Color;

#[macro_use]
mod events;
events_macro! {
    keyboard: {
        key_escape: Escape,
        key_up: Up,
        key_down: Down
    },
    else: {
        quit: Quit { .. }
    }
}

fn main() {
    let sdl2_context = sdl2::init().unwrap();
    let video = sdl2_context.video().unwrap();
    let window = video
        .window("Arcade Shooter", 800, 600)
        .position_centered()
        .opengl()
        .build()
        .unwrap();
    let mut canvas = window.renderer().accelerated().build().unwrap();

    let mut event = Events::new(sdl2_context.event_pump().unwrap());
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();

    'running: loop {
        event.pump();
        if event.now.quit || event.now.key_escape == Some(true) {
            break 'running;
        }
    }
}

Now the extensibility is very good. If you want to define a new keyboard event, just change the macro call incoming

This work adoptsCC agreement, reprint must indicate the author and the link to this article