aboutsummaryrefslogtreecommitdiff
path: root/planetwars-localdev/src/match_runner/bot_runner.rs
diff options
context:
space:
mode:
authorIlion Beyst <ilion.beyst@gmail.com>2021-12-25 14:45:05 +0100
committerIlion Beyst <ilion.beyst@gmail.com>2021-12-25 14:45:05 +0100
commitd0a0fcfdeda2c315a13ddd96b4fca958da0d9858 (patch)
tree6fb53e3b6e2999ac0c9e35f390d6be26a438a634 /planetwars-localdev/src/match_runner/bot_runner.rs
parentd0af8d3bbff506d4f4caf03750c3b85c69c4f168 (diff)
downloadplanetwars.dev-d0a0fcfdeda2c315a13ddd96b4fca958da0d9858.tar.xz
planetwars.dev-d0a0fcfdeda2c315a13ddd96b4fca958da0d9858.zip
cli for running matches
Diffstat (limited to 'planetwars-localdev/src/match_runner/bot_runner.rs')
-rw-r--r--planetwars-localdev/src/match_runner/bot_runner.rs120
1 files changed, 120 insertions, 0 deletions
diff --git a/planetwars-localdev/src/match_runner/bot_runner.rs b/planetwars-localdev/src/match_runner/bot_runner.rs
new file mode 100644
index 0000000..290df07
--- /dev/null
+++ b/planetwars-localdev/src/match_runner/bot_runner.rs
@@ -0,0 +1,120 @@
+use std::io;
+use std::process::Stdio;
+use std::sync::Arc;
+use std::sync::Mutex;
+use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, Lines};
+use tokio::process;
+use tokio::sync::mpsc;
+use tokio::time::timeout;
+
+use super::match_context::EventBus;
+use super::match_context::PlayerHandle;
+use super::match_context::RequestError;
+use super::match_context::RequestMessage;
+pub struct LocalBotHandle {
+ tx: mpsc::UnboundedSender<RequestMessage>,
+}
+
+impl PlayerHandle for LocalBotHandle {
+ fn send_request(&mut self, r: RequestMessage) {
+ self.tx
+ .send(r)
+ .expect("failed to send message to local bot");
+ }
+
+ fn send_info(&mut self, _msg: String) {
+ // TODO: log this somewhere
+ // drop info message
+ }
+}
+
+pub fn run_local_bot(player_id: u32, event_bus: Arc<Mutex<EventBus>>, bot: Bot) -> LocalBotHandle {
+ let (tx, rx) = mpsc::unbounded_channel();
+
+ let runner = LocalBotRunner {
+ event_bus,
+ rx,
+ player_id,
+ bot,
+ };
+ tokio::spawn(runner.run());
+
+ return LocalBotHandle { tx };
+}
+
+pub struct LocalBotRunner {
+ event_bus: Arc<Mutex<EventBus>>,
+ rx: mpsc::UnboundedReceiver<RequestMessage>,
+ player_id: u32,
+ bot: Bot,
+}
+
+impl LocalBotRunner {
+ pub async fn run(mut self) {
+ let mut process = self.bot.spawn_process();
+
+ while let Some(request) = self.rx.recv().await {
+ let resp_fut = process.communicate(&request.content);
+ let result = timeout(request.timeout, resp_fut)
+ .await
+ // TODO: how can this failure be handled cleanly?
+ .expect("process read failed");
+ let result = match result {
+ Ok(line) => Ok(line.into_bytes()),
+ Err(_elapsed) => Err(RequestError::Timeout),
+ };
+ let request_id = (self.player_id, request.request_id);
+
+ self.event_bus
+ .lock()
+ .unwrap()
+ .resolve_request(request_id, result);
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Bot {
+ pub working_dir: String,
+ pub argv: Vec<String>,
+}
+
+impl Bot {
+ pub fn spawn_process(&self) -> BotProcess {
+ let mut child = process::Command::new(&self.argv[0])
+ .args(&self.argv[1..])
+ .current_dir(self.working_dir.clone())
+ .kill_on_drop(true)
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::inherit())
+ .spawn()
+ .expect("spawning failed");
+
+ let stdout = child.stdout.take().unwrap();
+ let reader = BufReader::new(stdout).lines();
+
+ return BotProcess {
+ stdin: child.stdin.take().unwrap(),
+ stdout: reader,
+ child,
+ };
+ }
+}
+
+pub struct BotProcess {
+ #[allow(dead_code)]
+ child: process::Child,
+ stdin: process::ChildStdin,
+ stdout: Lines<BufReader<process::ChildStdout>>,
+}
+
+impl BotProcess {
+ // TODO: gracefully handle errors
+ pub async fn communicate(&mut self, input: &[u8]) -> io::Result<String> {
+ self.stdin.write_all(input).await?;
+ self.stdin.write_u8(b'\n').await?;
+ let line = self.stdout.next_line().await?;
+ line.ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "no response received"))
+ }
+}