我们成功的创建了一个元宇宙世界,让简单的角色进入和离开这个世界,并在这个世界里移动,通过 SDL2.0,我们成功的建立了这个世界的上帝视角,观看角色进入和离开,并在世界内移动。
但是,我们的游戏世界的运行是依赖于 SDL 的主循环,如果我们不去观察这个世界,这个世界就完全不运作了,这种量子力学的概念是我们所不喜欢的,所以我们需要世界自己运行,观察者通过函数或者消息的方式,获取世界的状态。
在 SDL2.0 主消息循环里面执行 游戏世界的逻辑
'running: loop {
for event in self.event_pump.poll_iter() {
match event {
}
}
world.step();
}
复制代码
我们需要将 游戏世界 World SDL 窗口 作为两个独立的线程执行。
std::thread::spawn(move || {
let mut display = display::Display::new("元宇宙 - 观察者", 1280, 1024, 512, "SourceHanSansHWSC-Regular.otf");
display.run(h);
});
std::thread::spawn(move || {
let mut w = World::new(512, tx, caller);
while !w.step() {
}
});
复制代码
注意这里面的 h
let (handler, caller) = crossbeam_channel::bounded(1024);
let h = handler.clone();
复制代码
这里面我们用到了 非标准的 channel 库,crossbeam_channel。
目前 Rust 的生态中,异步开发模式已经成为主流,当然我对此持有一定的怀疑态度,因为,异步模式固然有表达逻辑更加自然,某些情况下更加高效的特点,但是毋庸讳言,异步编程模式的学习曲线相对同步模式直线上升。
异步模式的本质是用语言本身的调度器来替代操作系统的调度器,从而不依赖于进程,甚至是线程就可以实现多个可能会有相互依赖的逻辑 任务能够协同执行,也就是异步 runtime 加上 任务,可以实现之前需要使用进程或者线程才能完成的多任务模式。
Rust 最流行的 异步模式是 tokio,某种程度上,这也是 Rust 社区缺省的异步模式,下面是一个简单的 读取命令行的 tokio 框架代码
use tokio::io::AsyncBufReadExt;
#[tokio::main]
async fn main() -> () {
let mut stdin = tokio::io::BufReader::new(tokio::io::stdin()).lines();
let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(50));
let mut stop = false;
while !stop {
tokio::select! {
msg = rx.recv() => {
println!("{:?}", msg);
}
_ = interval.tick()=> {
stop = display.run();
}
line = stdin.next_line() => {
match line {
Ok(Some(line))=> {
println!("{:?}", engine.run(&line));
},
_=> {}
}
}
}
}
复制代码
其中 rx 用来接受 tokio 异步消息,interval 周期性的发起任务,line 获得命令行输入之后,使用 脚本引擎 engine 执行命令行脚本,比如说 GM 指令。
本来我们可以不使用 tokio,完全使用 crossbeam 的通道和线程就能实现一个多线程的服务器,但是考虑到以后要支持 http/https 服务,或者 websocket 服务,甚至 基于 quinn 的 quic 协议服务,这些服务的第三方库基本上都是基于 tokio 或者自己的异步运行时,所以我们还是采用 上面的 tokio 主循环。
评论