Rust 的巨集很強, 其有別於 C 語言那種字串替換性質的巨集, 現在把當前的事件處理方式改用巨集來定義.
巨集的定義
重構事件處理程式碼之前, 先過一遍 Rust 巨集定義的語法.
最簡單的巨集定義
Rust 的巨集使用 macro_rules!
來宣告規則
macro_rules! ten {
() => { 10 };
}
fn main() {
let x = 2 * ten!();
assert_eq!(20, x); // ten![] or ten!{}
}
這是最簡單的一個巨集, 執行的時候, 從左側小括號匹配規則, 從右側進行, 這裡是直接返回一個數字. 使用起來也很容易, 巨集的使用, 用小括號, 中括號, 大括號都可以.
匹配 token trees
巨集的匹配能力非常強, 還可以匹配 token trees, 傳一個 fn
關鍵字都沒問題
macro_rules! foo {
(fn) => { 1 };
}
fn main() {
let x = foo!(fn);
assert_eq!(1, x);
}
給巨集傳引數
上面有段程式碼是 2 乘以從執行巨集得到的數字 10, 現在看一下給巨集傳入表示式
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 的縮寫, 此外還有其他型別
item: 結構體, 函式, mod 之類的
block: 用大括號包起來的語句或者表示式, 也就是程式碼塊
stmt: 一段 statement
pat: 一段 pattern
ty: 一個型別
ident: 識別符號
path: 類似 foo::bar 這種路徑
meta: 元型別, 譬如#[...], #![...] 內的東西
tt: 一個 token tree
隨便試驗一下, 就拿 pat
來
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 的巨集那一節的例子, *
表示重複使用 $()
包裹的內容來處理傳進來的值, 這個例子中數字傳多個多可以處理
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;
}
}
}
現在可擴充性很好, 想定義新的鍵盤事件, 只要改一下巨集的呼叫傳入就可以了.