0x03. 使用宏定義事件

Rust 的宏很強, 其有別於 C 語言那種字符串替換性質的宏, 如今把當前的事件處理方式改用宏來定義.express

宏的定義

重構事件處理代碼以前, 先過一遍 Rust 宏定義的語法.canvas

最簡單的宏定義

Rust 的宏使用 macro_rules! 來聲明規則ide

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

這是最簡單的一個宏, 執行的時候, 從左側小括號匹配規則, 從右側進行, 這裏是直接返回一個數字. 使用起來也很容易, 宏的使用, 用小括號, 中括號, 大括號均可以.函數

匹配 token trees

宏的匹配能力很是強, 還能夠匹配 token trees, 傳一個 fn 關鍵字都沒問題oop

macro_rules! foo {
    (fn) => { 1 }; } fn main() {
    let x = foo!(fn); assert_eq!(1, x);
}
複製代碼
給宏傳參數

上面有段代碼是 2 乘以從執行宏獲得的數字 10, 如今看一下給宏傳入表達式ui

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

這裏的 $e 是本身定的, 寫成 $a, $b, $foo 之類的均可以, 不過仍是推薦寫得語義化一些. expr 是表明表達式 expression 的縮寫, 此外還有其餘類型spa

item: 結構體, 函數, mod 之類的
block: 用大括號包起來的語句或者表達式, 也就是代碼塊
stmt: 一段 statement
pat: 一段 pattern
ty: 一個類型
ident: 標識符
path: 相似 foo::bar 這種路徑
meta: 元類型, 譬如#[...], #![...] 內的東西
tt: 一個 token treecode

隨便試驗一下, 就拿 patorm

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);
}
複製代碼
重複

這是 Rust Book 的宏那一節的例子, * 表示重複使用 $() 包裹的內容來處理傳進來的值, 這個例子中數字傳多個多能夠處理token

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];
}
複製代碼

使用宏統一事件處理

如今對 Rust 宏的語法有個大概的瞭解, 以後再把事件處理的用宏來處理. 在應用宏以前, 咱們先考慮一個問題, 通常咱們寫個函數是經過函數的功能, 須要的東西來決定函數簽名. 宏最好玩的地方只要想像力足夠, 你就能夠制定個本身喜歡的使用方式. 這裏先定好這個宏規則的名稱 events_macro

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

咱們會在宏規則內 use sdl 的事件類型, 對應的鍵盤按鍵之類的東西, 因此須要用到 ident 類型的, 經過宏的重複機制, 能夠把事件都按照一樣的方式處理, 除了鍵盤事件, 再處理一下退出事件, 就跟上面講的傳入一個 pat 類型的

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; }), *
                        _ => {}
                    }
                }
            }
        }
    };
}
複製代碼

咱們在 main.rs 使用這個宏

#![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;
        }
    }
}
複製代碼

如今可拓展性很好, 想定義新的鍵盤事件, 只要改一下宏的調用傳入就能夠了.

相關文章
相關標籤/搜索