Rust Tokio select宏中如何添加条件性Sleep分支?
问题:在Tokio select宏中实现条件性Sleep倒计时
我正在开发一款CLI游戏,使用异步select宏循环处理用户输入与事件间隔,希望在其中实现条件性Sleep倒计时功能。尝试声明一个Option<Sleep>,并在每次循环迭代时根据game.locking条件为其赋值,但代码无法运行。
尝试的代码
async fn run(game: &mut Game) { let mut reader = EventStream::new(); draw().unwrap(); let mut render_interval = interval(Duration::from_nanos(1_000_000_000 / TARGET_FRAME_RATE)); let mut drop_interval = interval(Duration::from_millis(1_000 / game.level as u64)); let mut lock_delay: Option<Sleep>; loop { lock_delay = if game.locking { Some(sleep(Duration::from_millis(500))) } else { None }; select! { maybe_event = reader.next().fuse() => { match maybe_event { Some(Ok(event)) => { if let Event::Key(key) = event { match key.code { KeyCode::Char('w') | KeyCode::Char('W') | KeyCode::Up => { game.rotate(RotationDirection::Clockwise) }, KeyCode::Char('a') | KeyCode::Char('A') | KeyCode::Left => { game.shift(ShiftDirection::Left) }, KeyCode::Char('s') | KeyCode::Char('S') | KeyCode::Down => { game.soft_drop() }, KeyCode::Char('d') | KeyCode::Char('D') | KeyCode::Right => { game.shift(ShiftDirection::Right) }, KeyCode::Char(' ') => { game.hard_drop() }, KeyCode::Char('z') | KeyCode::Char('Z') => { game.rotate(RotationDirection::CounterClockwise) }, KeyCode::Char('c') | KeyCode::Char('C') => { game.hold() }, KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => { break }, _ => (), } } }, Some(Err(error)) => panic!("{}", error), None => (), } }, _ = drop_interval.tick() => { game.shift(ShiftDirection::Down) }, _ = lock_delay => { debug_println!("PLACING"); game.place(); }, _ = render_interval.tick() => { render(game).unwrap() }, }; } }
编译器报错
`Option<Sleep>` is not a future the trait `futures::Future` is not implemented for `Option<Sleep>` Option<Sleep> must be a future or must implement `IntoFuture` to be awaitedrustcClick for full compiler diagnostic
我还尝试过用Duration::from_secs(u64::MAX)替代None以避免使用Option,以及参考Reddit旧帖的方案,但均未成功。由于异步编程经验有限,恳请提供在Tokio select宏中实现条件性Sleep倒计时的可行方案。
解决方案
方法1:利用Tokio select的条件分支特性
这是最简洁的方案,直接借助Tokio select宏支持的条件后缀语法,仅当game.locking为true时,才将Sleep分支纳入监听范围:
async fn run(game: &mut Game) { let mut reader = EventStream::new(); draw().unwrap(); let mut render_interval = interval(Duration::from_nanos(1_000_000_000 / TARGET_FRAME_RATE)); let mut drop_interval = interval(Duration::from_millis(1_000 / game.level as u64)); loop { select! { maybe_event = reader.next().fuse() => { // 原输入处理逻辑不变 match maybe_event { Some(Ok(event)) => { if let Event::Key(key) = event { match key.code { KeyCode::Char('w') | KeyCode::Char('W') | KeyCode::Up => { game.rotate(RotationDirection::Clockwise) }, KeyCode::Char('a') | KeyCode::Char('A') | KeyCode::Left => { game.shift(ShiftDirection::Left) }, KeyCode::Char('s') | KeyCode::Char('S') | KeyCode::Down => { game.soft_drop() }, KeyCode::Char('d') | KeyCode::Char('D') | KeyCode::Right => { game.shift(ShiftDirection::Right) }, KeyCode::Char(' ') => { game.hard_drop() }, KeyCode::Char('z') | KeyCode::Char('Z') => { game.rotate(RotationDirection::CounterClockwise) }, KeyCode::Char('c') | KeyCode::Char('C') => { game.hold() }, KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => { break }, _ => (), } } }, Some(Err(error)) => panic!("{}", error), None => (), } }, _ = drop_interval.tick() => { game.shift(ShiftDirection::Down) }, // 仅当game.locking为true时,才监听这个Sleep分支 _ = sleep(Duration::from_millis(500)), if game.locking => { debug_println!("PLACING"); game.place(); }, _ = render_interval.tick() => { render(game).unwrap() }, }; } }
方法2:使用futures::future::Either统一类型
如果需要更灵活的分支控制,可以引入futures crate的Either类型,将Sleep和一个永远pending的future包装为统一类型:
首先在Cargo.toml中添加依赖:
futures = "0.3"
修改代码:
use futures::future::{Either, pending}; use tokio::time::{sleep, interval, Duration}; async fn run(game: &mut Game) { let mut reader = EventStream::new(); draw().unwrap(); let mut render_interval = interval(Duration::from_nanos(1_000_000_000 / TARGET_FRAME_RATE)); let mut drop_interval = interval(Duration::from_millis(1_000 / game.level as u64)); loop { // 根据条件选择Sleep或pending future(pending永远不会触发) let lock_delay = if game.locking { Either::Left(sleep(Duration::from_millis(500))) } else { Either::Right(pending()) }; select! { maybe_event = reader.next().fuse() => { // 原输入处理逻辑不变 match maybe_event { Some(Ok(event)) => { if let Event::Key(key) = event { match key.code { KeyCode::Char('w') | KeyCode::Char('W') | KeyCode::Up => { game.rotate(RotationDirection::Clockwise) }, KeyCode::Char('a') | KeyCode::Char('A') | KeyCode::Left => { game.shift(ShiftDirection::Left) }, KeyCode::Char('s') | KeyCode::Char('S') | KeyCode::Down => { game.soft_drop() }, KeyCode::Char('d') | KeyCode::Char('D') | KeyCode::Right => { game.shift(ShiftDirection::Right) }, KeyCode::Char(' ') => { game.hard_drop() }, KeyCode::Char('z') | KeyCode::Char('Z') => { game.rotate(RotationDirection::CounterClockwise) }, KeyCode::Char('c') | KeyCode::Char('C') => { game.hold() }, KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => { break }, _ => (), } } }, Some(Err(error)) => panic!("{}", error), None => (), } }, _ = drop_interval.tick() => { game.shift(ShiftDirection::Down) }, _ = lock_delay => { // 额外判断确保只有条件满足时执行逻辑 if game.locking { debug_println!("PLACING"); game.place(); } }, _ = render_interval.tick() => { render(game).unwrap() }, }; } }
关键说明
- 方法1无需额外依赖,代码更简洁,每次循环都会重新创建Sleep future,保证
game.locking变为true时倒计时从500ms重新开始。 - 方法2适合需要更复杂分支逻辑的场景,通过统一类型让select宏能够正确识别分支。
内容的提问来源于stack exchange,提问作者Chambers
相关产品推荐
相关产品推荐

