client: don't freeze when bot does not produce output
This commit is contained in:
parent
2b71ca625e
commit
c7d9734067
2 changed files with 89 additions and 21 deletions
|
@ -7,12 +7,21 @@ pub mod pb {
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use pb::client_api_service_client::ClientApiServiceClient;
|
use pb::client_api_service_client::ClientApiServiceClient;
|
||||||
use planetwars_matchrunner::bot_runner::Bot;
|
use planetwars_matchrunner::bot_runner::{Bot, BotProcess};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{path::PathBuf, time::Duration};
|
use std::{
|
||||||
use tokio::sync::mpsc;
|
collections::VecDeque,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
io::{AsyncWriteExt, BufReader, Lines},
|
||||||
|
process::{ChildStdin, ChildStdout},
|
||||||
|
sync::mpsc::{self, UnboundedSender},
|
||||||
|
};
|
||||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||||
use tonic::{metadata::MetadataValue, transport::Channel, Request, Status};
|
use tonic::{metadata::MetadataValue, transport::Channel, Request, Status, Streaming};
|
||||||
|
|
||||||
#[derive(clap::Parser)]
|
#[derive(clap::Parser)]
|
||||||
struct PlayMatch {
|
struct PlayMatch {
|
||||||
|
@ -124,7 +133,11 @@ async fn run_player(
|
||||||
Ok(req)
|
Ok(req)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut bot_process = Bot {
|
let BotProcess {
|
||||||
|
child: _child,
|
||||||
|
stdin,
|
||||||
|
stdout,
|
||||||
|
} = Bot {
|
||||||
working_dir: PathBuf::from(
|
working_dir: PathBuf::from(
|
||||||
bot_config
|
bot_config
|
||||||
.working_directory
|
.working_directory
|
||||||
|
@ -134,27 +147,57 @@ async fn run_player(
|
||||||
}
|
}
|
||||||
.spawn_process();
|
.spawn_process();
|
||||||
|
|
||||||
|
let state = Arc::new(Mutex::new(BotRunnerState {
|
||||||
|
request_queue: VecDeque::new(),
|
||||||
|
}));
|
||||||
|
|
||||||
let (tx, rx) = mpsc::unbounded_channel();
|
let (tx, rx) = mpsc::unbounded_channel();
|
||||||
let mut stream = client
|
let stream = client
|
||||||
.connect_player(UnboundedReceiverStream::new(rx))
|
.connect_player(UnboundedReceiverStream::new(rx))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_inner();
|
.into_inner();
|
||||||
|
|
||||||
|
let output_handle = tokio::spawn(handle_bot_output(state.clone(), tx, stdout));
|
||||||
|
let input_handle = tokio::spawn(handle_server_messages(state.clone(), stream, stdin));
|
||||||
|
|
||||||
|
output_handle
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.map_err(RunPlayerError::RunBotError)?;
|
||||||
|
input_handle
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.map_err(RunPlayerError::RunBotError)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BotRunnerState {
|
||||||
|
request_queue: VecDeque<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_server_messages(
|
||||||
|
runner_state: Arc<Mutex<BotRunnerState>>,
|
||||||
|
mut stream: Streaming<pb::PlayerApiServerMessage>,
|
||||||
|
mut stdin: ChildStdin,
|
||||||
|
) -> io::Result<()> {
|
||||||
while let Some(message) = stream.message().await.unwrap() {
|
while let Some(message) = stream.message().await.unwrap() {
|
||||||
match message.server_message {
|
match message.server_message {
|
||||||
Some(pb::PlayerApiServerMessageType::ActionRequest(req)) => {
|
Some(pb::PlayerApiServerMessageType::ActionRequest(req)) => {
|
||||||
let moves = bot_process
|
{
|
||||||
.communicate(&req.content)
|
let mut state = runner_state.lock().unwrap();
|
||||||
.await
|
if !state.request_queue.is_empty() {
|
||||||
.map_err(RunPlayerError::RunBotError)?;
|
eprintln!("[WARN] new turn started before bot output was received");
|
||||||
let action = pb::PlayerAction {
|
eprintln!(
|
||||||
action_request_id: req.action_request_id,
|
"[WARN] this could be due to your bot taking too long, \
|
||||||
content: moves.as_bytes().to_vec(),
|
or failing to flush output buffers."
|
||||||
};
|
);
|
||||||
let msg = pb::PlayerApiClientMessage {
|
}
|
||||||
client_message: Some(pb::PlayerApiClientMessageType::Action(action)),
|
state.request_queue.push_back(req.action_request_id);
|
||||||
};
|
}
|
||||||
tx.send(msg).unwrap();
|
stdin.write_all(&req.content).await?;
|
||||||
|
stdin.write_u8(b'\n').await?;
|
||||||
}
|
}
|
||||||
_ => {} // pass
|
_ => {} // pass
|
||||||
}
|
}
|
||||||
|
@ -162,3 +205,28 @@ async fn run_player(
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
async fn handle_bot_output(
|
||||||
|
runner_state: Arc<Mutex<BotRunnerState>>,
|
||||||
|
tx: UnboundedSender<pb::PlayerApiClientMessage>,
|
||||||
|
mut bot_stdout: Lines<BufReader<ChildStdout>>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
while let Some(line) = bot_stdout.next_line().await? {
|
||||||
|
if let Some(request_id) = runner_state.lock().unwrap().request_queue.pop_front() {
|
||||||
|
let action = pb::PlayerAction {
|
||||||
|
action_request_id: request_id,
|
||||||
|
content: line.as_bytes().to_vec(),
|
||||||
|
};
|
||||||
|
let msg = pb::PlayerApiClientMessage {
|
||||||
|
client_message: Some(pb::PlayerApiClientMessageType::Action(action)),
|
||||||
|
};
|
||||||
|
tx.send(msg).unwrap();
|
||||||
|
} else {
|
||||||
|
eprintln!("[WARN] bot issued commands before a gamestate was received");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -108,9 +108,9 @@ impl Bot {
|
||||||
|
|
||||||
pub struct BotProcess {
|
pub struct BotProcess {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
child: process::Child,
|
pub child: process::Child,
|
||||||
stdin: process::ChildStdin,
|
pub stdin: process::ChildStdin,
|
||||||
stdout: Lines<BufReader<process::ChildStdout>>,
|
pub stdout: Lines<BufReader<process::ChildStdout>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BotProcess {
|
impl BotProcess {
|
||||||
|
|
Loading…
Reference in a new issue