0

I have an async select macro loop that handles user inputs and event intervals within a CLI game that I am writing. I would like to be able to conditionally set a sleep countdown within it as well. I have tried declaring an Option<Sleep> that's value gets set every iteration of the loop checking if the condition is met.

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 }; // check if  condition has been met yet

        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()
            },
        };
    }
}

This of course does not work as lock_interval is not a Future type and cannot be used as a branch within the select.

`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

I have tried quite a few other approaches such as using Duration::from_secs(u64::MAX) instead of None when the condition returns false, avoiding the need for an Option but it did not seem to work.

I have also attempted to use some advice from an older reddit thread: https://www.reddit.com/r/learnrust/comments/zh4deb/tokioselect_on_an_option_using_if/, but it was to no success.

In short, I need some way that I can conditionally set a sleep countdown within a select macro.

Any help or ideas would be much appreciated, I have limited asynchronous programming experience, and I'm sure there is a completely different approach to the problem.

Chambers
  • 21
  • 3
  • 1
    You could always sleep but use `game.locking` as a preconditionfor polling: `() = &mut sleep, if game.locking => ...` – isaactfa Jun 30 '23 at 18:12
  • @isaactfa I wasn't aware that you could do this. Sadly it did not completely solve the problem, the branch executes upon the condition being met, but the sleep duration is ignored. It executes immediately. – Chambers Jun 30 '23 at 18:36
  • 1
    Hmm that shouldn't be. Have you increased the duration and checked that 500ms isn't just too short to notice? Are you properly creating the sleep future? See the second example [here](https://docs.rs/tokio/latest/tokio/time/struct.Sleep.html#examples) (note the `tokio::pin!`) – isaactfa Jun 30 '23 at 18:43
  • Sleep is declared outside of the loop: `let lock_delay = sleep(Duration::from_millis(5000)); pin!(lock_delay);`. Select macro branch is written as you described: `() = &mut lock_delay, if game.locking => ...` – Chambers Jun 30 '23 at 18:48
  • 1
    Oh, presumably the sleep elapsed by the time the condition was true, you'll have to [`reset`](https://docs.rs/tokio/latest/tokio/time/struct.Sleep.html#method.reset) the sleep future as appropriate. – isaactfa Jun 30 '23 at 18:52
  • After further testing I have realized why this approach does not work. The delay is set and only executes once regardless of the condition being met. If I start the game it will occasionally work on the first try if i meet the condition, but it will not work again past that. I have tried substituting the sleep for an interval but it did not seem to work. I need the timer to be set when the condition is met. – Chambers Jun 30 '23 at 18:53
  • Can you just store the timer instead of the condition? – drewtato Jun 30 '23 at 18:59

1 Answers1

0

Instead of creating an Option, you can create an async block.

let lock_delay = async {
    if game.locking {
        sleep(Duration::from_millis(500)).await;
    } else {
        futures::future::pending().await;
    }
};
drewtato
  • 6,783
  • 1
  • 12
  • 17
  • This suggestion returns an incompatible types error. ``if` and `else` have incompatible types expected unit type `()` found struct `futures::future::Pending<_>`rustcClick for full compiler diagnostic main.rs(68, 9): expected because of this main.rs(67, 5): `if` and `else` have incompatible types` – Chambers Jun 30 '23 at 18:37
  • Sorry, forgot `.await` – drewtato Jun 30 '23 at 18:56