use std::{io, process::Stdio};
use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
process::{Child, ChildStderr, ChildStdin, ChildStdout, Command},
};
use tokio_stream::{StreamExt, wrappers::LinesStream};
use crate::{Config, Error, Result};
mod request;
pub use request::*;
mod response;
pub use response::*;
#[derive(Debug, Clone)]
pub struct LaunchOptions {
pub katago_path: String,
pub config_path: String,
pub model_path: String,
pub inherit_stderr: bool,
pub human_model_path: Option<String>,
pub override_config: Option<Config>,
pub quit_without_waiting: bool,
}
impl LaunchOptions {
pub fn new(katago_path: String, config_path: String, model_path: String) -> Self {
Self {
katago_path,
config_path,
model_path,
inherit_stderr: false,
human_model_path: None,
override_config: None,
quit_without_waiting: false,
}
}
pub fn with_inherit_stderr(mut self) -> Self {
self.inherit_stderr = true;
self
}
pub fn with_human_model(mut self, human_model_path: String) -> Self {
self.human_model_path = Some(human_model_path);
self
}
pub fn with_override_config(mut self, config: Config) -> Self {
self.override_config = Some(config);
self
}
pub fn with_quit_without_waiting(mut self) -> Self {
self.quit_without_waiting = true;
self
}
}
#[derive(Debug)]
pub struct Engine {
pub stdin: EngineStdin,
pub stdout: EngineStdout,
pub stderr: Option<ChildStderr>,
pub child_process: Child,
}
impl Engine {
pub fn launch(config: &LaunchOptions) -> Result<Engine> {
let mut cmd = Command::new(&config.katago_path);
cmd.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(if config.inherit_stderr {
Stdio::inherit()
} else {
Stdio::piped()
})
.arg("analysis")
.arg("-config")
.arg(&config.config_path)
.arg("-model")
.arg(&config.model_path);
if let Some(human_model_path) = &config.human_model_path {
cmd.arg("-human-model").arg(human_model_path);
}
if let Some(override_config) = &config.override_config {
cmd.arg("-override-config").arg(
override_config
.to_command_line_arg()
.map_err(Error::UnserializableConfig)?,
);
}
if config.quit_without_waiting {
cmd.arg("-quit-without-waiting");
}
let mut child_process = cmd.spawn()?;
let stdin = child_process.stdin.take().ok_or(Error::StdinUnavailable)?;
let stdout = child_process
.stdout
.take()
.ok_or(Error::StdoutUnavailable)?;
let stdout_stream: EngineStdout = LinesStream::new(BufReader::new(stdout).lines())
.map(|line| Ok(serde_json::from_str::<Response>(&line?)?));
Ok(Engine {
stdin: EngineStdin(stdin),
stdout: stdout_stream,
stderr: child_process.stderr.take(),
child_process,
})
}
}
#[derive(Debug)]
pub struct EngineStdin(ChildStdin);
impl EngineStdin {
pub async fn send(&mut self, request: &Request) -> Result<()> {
let json = serde_json::to_string(request)?;
self.send_raw(&json).await
}
pub async fn send_raw(&mut self, request: &str) -> Result<()> {
self.0.write_all(request.as_bytes()).await?;
self.0.write_all(b"\n").await?;
Ok(())
}
}
pub type EngineStdout = tokio_stream::adapters::Map<
LinesStream<BufReader<ChildStdout>>,
fn(std::result::Result<String, io::Error>) -> Result<Response>,
>;