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, 传一个 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
随便试验一下, 就拿 pat
来orm
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;
}
}
}
复制代码
如今可拓展性很好, 想定义新的键盘事件, 只要改一下宏的调用传入就能够了.