I'm writing a program to play Chess. My Game
class has the async function play
. This function returns when the game ends, returning a Winner enum, which is either a stalemate or a win for some colour.
In my main
fn, I want to run game.play
, then run game.render()
repeatedly, until game.play
returns a Winner, at which point the loop should stop.
Having attempted to read the docs, I don't really understand why Pinning or Boxing is neccesary in order to poll the future, and I haven't been able to get it working.
The desired code for main
should look something like this:
let textures = piece::Piece::load_all_textures().await;
let mut game = game::Game::new(player::Player::User, player::Player::User);
let future = game.play();
while None = future.poll() {
game.render(textures).await;
next_frame().await;
}
// Do something now that the game has ended
I also understand that I will run into issues with borrow checking with this code, but that's a problem for me to deal with once I figure this one out.
What is the easiest way of attempting what I am trying to do?
CodePudding user response:
You can simply use loop
let textures = piece::Piece::load_all_textures().await;
let mut game = game::Game::new(player::Player::User, player::Player::User);
let future = game.play();
loop {
game.render(textures).await;
next_frame().await;
}
This will make the game run at the highest frame rate possible though, which is usually over kill, so you might want to add a sleep
loop {
game.render(textures).await;
next_frame().await;
async_std::task::sleep(Duration::from_millis(15)).await;
}
This will run it at around 60 frames/second. If you want to be very accurate you can use Instant
loop {
let now = Instant::now();
game.render(textures).await;
next_frame().await;
let elapsed_ms = now.elapsed().as_millis();
// rendering could take more than 17ms in that case substraction can
// overflow
let sleep_ms = 17.checked_sub(elapsed_ms).unwrap_or_default();
async_std::task::sleep(Duration::from_millis(sleep_ms)).await;
}
This would result in a 58.8 frames per second (you can use nanoseconds for 60frames/second which needs 16.66 ms per frame.)
CodePudding user response:
The code depends a bit on what async runtime you're using. Using the popular Tokio runtime you can do something like this (the code should be very similar for async-std):
#[tokio::main]
async fn main() {
let textures = Piece::load_all_textures().await;
let game = Arc::new(Game::new(Player::User, Player::User));
let game_ref = game.clone();
let play_handle = tokio::spawn(async move { game_ref.play().await });
let render_handle = tokio::spawn(async move {
loop {
game.render(&textures).await;
next_frame().await;
// Naive fixed delay; in reality you'd want to delay based on how long rendering took.
tokio::time::sleep(Duration::from_millis(10)).await;
}
});
let winner = play_handle.await.unwrap();
// Do something now that the game has ended
render_handle.abort();
println!("{winner:?}");
}
Playground link with full code
We use tokio::spawn()
to run a play task and render task concurrently. It returns a handle that you can .await
to get the task's return value. The handle also has an abort()
method that we use to end the render task once the play task finishes.
The Game
struct is stored in an Arc
so we can share it between the play and render tasks. The tokio::spawn()
function requires a 'static
future, so we can't just use plain references to share Game
.