From dacc05a41b77bf2e86e27ac354db9b047c661a7d Mon Sep 17 00:00:00 2001 From: Ilion Beyst Date: Tue, 28 Dec 2021 14:57:41 +0100 Subject: [PATCH] refactor workspace code --- planetwars-cli/Cargo.toml | 1 + planetwars-cli/assets/pw_workspace.toml | 12 +-- .../assets/simplebot/botconfig.toml | 2 + .../assets/{ => simplebot}/simplebot.py | 0 planetwars-cli/src/commands/init.rs | 4 +- planetwars-cli/src/commands/run_match.rs | 40 ++++------ planetwars-cli/src/commands/serve.rs | 7 +- planetwars-cli/src/lib.rs | 27 +------ planetwars-cli/src/match_runner/bot_runner.rs | 3 +- planetwars-cli/src/match_runner/mod.rs | 14 ++-- planetwars-cli/src/web/mod.rs | 18 ++--- planetwars-cli/src/workspace/bot.rs | 40 ++++++++++ planetwars-cli/src/workspace/mod.rs | 77 +++++++++++++++++++ 13 files changed, 161 insertions(+), 84 deletions(-) create mode 100644 planetwars-cli/assets/simplebot/botconfig.toml rename planetwars-cli/assets/{ => simplebot}/simplebot.py (100%) create mode 100644 planetwars-cli/src/workspace/bot.rs create mode 100644 planetwars-cli/src/workspace/mod.rs diff --git a/planetwars-cli/Cargo.toml b/planetwars-cli/Cargo.toml index 25b6e74..e1f0a8e 100644 --- a/planetwars-cli/Cargo.toml +++ b/planetwars-cli/Cargo.toml @@ -18,6 +18,7 @@ toml = "0.5" planetwars-rules = { path = "../planetwars-rules" } clap = { version = "3.0.0-rc.8", features = ["derive"] } chrono = { version = "0.4", features = ["serde"] } +shlex = "1.1" rust-embed = "6.3.0" axum = { version = "0.4", features = ["ws"] } diff --git a/planetwars-cli/assets/pw_workspace.toml b/planetwars-cli/assets/pw_workspace.toml index 85a4ab6..d82840f 100644 --- a/planetwars-cli/assets/pw_workspace.toml +++ b/planetwars-cli/assets/pw_workspace.toml @@ -1,10 +1,6 @@ -[bots] +[paths] +maps_dir = "maps" +matches_dir = "matches" -# define a bot called simplebot [bots.simplebot] - -# The working directory for the bot. -path = "./bots/simplebot" - -# What command to use for running the bot -argv = ["python", "simplebot.py"] \ No newline at end of file +path = "bots/simplebot" diff --git a/planetwars-cli/assets/simplebot/botconfig.toml b/planetwars-cli/assets/simplebot/botconfig.toml new file mode 100644 index 0000000..b3a4163 --- /dev/null +++ b/planetwars-cli/assets/simplebot/botconfig.toml @@ -0,0 +1,2 @@ +name = "simplebot" +run_command = "python3 simplebot.py" \ No newline at end of file diff --git a/planetwars-cli/assets/simplebot.py b/planetwars-cli/assets/simplebot/simplebot.py similarity index 100% rename from planetwars-cli/assets/simplebot.py rename to planetwars-cli/assets/simplebot/simplebot.py diff --git a/planetwars-cli/src/commands/init.rs b/planetwars-cli/src/commands/init.rs index 3c9cf08..c95626b 100644 --- a/planetwars-cli/src/commands/init.rs +++ b/planetwars-cli/src/commands/init.rs @@ -31,8 +31,8 @@ impl InitCommand { // create files copy_asset!(path, "pw_workspace.toml"); copy_asset!(path.join("maps"), "hex.json"); - copy_asset!(path.join("bots/simplebot"), "simplebot.py"); - + copy_asset!(path.join("bots/"), "simplebot/botconfig.toml"); + copy_asset!(path.join("bots/"), "simplebot/simplebot.py"); Ok(()) } } diff --git a/planetwars-cli/src/commands/run_match.rs b/planetwars-cli/src/commands/run_match.rs index 039d89a..5b7a3ec 100644 --- a/planetwars-cli/src/commands/run_match.rs +++ b/planetwars-cli/src/commands/run_match.rs @@ -1,14 +1,10 @@ -use std::env; use std::io; use clap::Parser; -use crate::match_runner; -use crate::match_runner::MatchBot; use crate::match_runner::MatchConfig; -use crate::resolve_bot_config; -use crate::WorkspaceConfig; - +use crate::match_runner::{self, MatchPlayer}; +use crate::workspace::Workspace; #[derive(Parser)] pub struct RunMatchCommand { /// map name @@ -19,30 +15,20 @@ pub struct RunMatchCommand { impl RunMatchCommand { pub async fn run(self) -> io::Result<()> { - let workspace_root = env::current_dir().unwrap(); - - let config_path = workspace_root.join("pw_workspace.toml"); - - let map_path = workspace_root.join(format!("maps/{}.json", self.map)); + let workspace = Workspace::open_current_dir()?; + let map_path = workspace.maps_dir().join(format!("{}.json", &self.map)); let timestamp = chrono::Local::now().format("%Y-%m-%d-%H-%M-%S"); - let log_path = workspace_root.join(format!("matches/{}.log", timestamp)); + let log_path = workspace.match_path(&format!("{}-{}", &self.map, ×tamp)); - let config_str = std::fs::read_to_string(config_path).unwrap(); - let workspace_config: WorkspaceConfig = toml::from_str(&config_str).unwrap(); - - let players = self - .bots - .into_iter() - .map(|bot_name| { - let bot_config = workspace_config.bots.get(&bot_name).unwrap().clone(); - let resolved_config = resolve_bot_config(&workspace_root, bot_config); - MatchBot { - name: bot_name, - bot_config: resolved_config, - } - }) - .collect(); + let mut players = Vec::new(); + for bot_name in &self.bots { + let bot = workspace.get_bot(&bot_name)?; + players.push(MatchPlayer { + name: bot_name.clone(), + bot, + }); + } let match_config = MatchConfig { map_name: self.map, diff --git a/planetwars-cli/src/commands/serve.rs b/planetwars-cli/src/commands/serve.rs index a078c83..aa8d149 100644 --- a/planetwars-cli/src/commands/serve.rs +++ b/planetwars-cli/src/commands/serve.rs @@ -1,18 +1,17 @@ -use std::env; use std::io; use clap::Parser; use crate::web; +use crate::workspace::Workspace; #[derive(Parser)] pub struct ServeCommand; impl ServeCommand { pub async fn run(self) -> io::Result<()> { - let workspace_root = env::current_dir().unwrap(); - - web::run(workspace_root).await; + let workspace = Workspace::open_current_dir()?; + web::run(workspace).await; Ok(()) } } diff --git a/planetwars-cli/src/lib.rs b/planetwars-cli/src/lib.rs index b71164f..e5566b0 100644 --- a/planetwars-cli/src/lib.rs +++ b/planetwars-cli/src/lib.rs @@ -1,23 +1,7 @@ -use serde::Deserialize; - mod commands; mod match_runner; mod web; - -use serde::Serialize; -use std::collections::HashMap; -use std::path::{Path, PathBuf}; - -#[derive(Serialize, Deserialize, Debug)] -struct WorkspaceConfig { - bots: HashMap, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct BotConfig { - path: String, - argv: Vec, -} +mod workspace; pub async fn run() { let res = commands::Cli::run().await; @@ -26,12 +10,3 @@ pub async fn run() { std::process::exit(1); } } - -fn resolve_bot_config(workspace_dir: &Path, config: BotConfig) -> BotConfig { - let mut path = PathBuf::from(workspace_dir); - path.push(&config.path); - BotConfig { - path: path.to_str().unwrap().to_string(), - argv: config.argv, - } -} diff --git a/planetwars-cli/src/match_runner/bot_runner.rs b/planetwars-cli/src/match_runner/bot_runner.rs index 290df07..70fc060 100644 --- a/planetwars-cli/src/match_runner/bot_runner.rs +++ b/planetwars-cli/src/match_runner/bot_runner.rs @@ -1,4 +1,5 @@ use std::io; +use std::path::PathBuf; use std::process::Stdio; use std::sync::Arc; use std::sync::Mutex; @@ -75,7 +76,7 @@ impl LocalBotRunner { #[derive(Debug, Clone)] pub struct Bot { - pub working_dir: String, + pub working_dir: PathBuf, pub argv: Vec, } diff --git a/planetwars-cli/src/match_runner/mod.rs b/planetwars-cli/src/match_runner/mod.rs index 50b7a3b..fdd02d5 100644 --- a/planetwars-cli/src/match_runner/mod.rs +++ b/planetwars-cli/src/match_runner/mod.rs @@ -12,7 +12,7 @@ use match_context::MatchCtx; use planetwars_rules::PwConfig; use serde::{Deserialize, Serialize}; -use crate::BotConfig; +use crate::workspace::bot::WorkspaceBot; use self::match_context::{EventBus, PlayerHandle}; @@ -20,7 +20,7 @@ pub struct MatchConfig { pub map_name: String, pub map_path: PathBuf, pub log_path: PathBuf, - pub players: Vec, + pub players: Vec, } #[derive(Serialize, Deserialize)] @@ -35,9 +35,9 @@ pub struct PlayerInfo { pub name: String, } -pub struct MatchBot { +pub struct MatchPlayer { pub name: String, - pub bot_config: BotConfig, + pub bot: WorkspaceBot, } pub async fn run_match(config: MatchConfig) { @@ -53,11 +53,11 @@ pub async fn run_match(config: MatchConfig) { .players .iter() .enumerate() - .map(|(player_id, bot)| { + .map(|(player_id, player)| { let player_id = (player_id + 1) as u32; let bot = bot_runner::Bot { - working_dir: bot.bot_config.path.clone(), - argv: bot.bot_config.argv.clone(), + working_dir: player.bot.path.clone(), + argv: player.bot.config.get_run_argv(), }; let handle = bot_runner::run_local_bot(player_id, event_bus.clone(), bot); (player_id, Box::new(handle) as Box) diff --git a/planetwars-cli/src/web/mod.rs b/planetwars-cli/src/web/mod.rs index 8394897..676d7af 100644 --- a/planetwars-cli/src/web/mod.rs +++ b/planetwars-cli/src/web/mod.rs @@ -18,20 +18,20 @@ use std::{ sync::Arc, }; -use crate::match_runner::MatchMeta; +use crate::{match_runner::MatchMeta, workspace::Workspace}; struct State { - workspace_root: PathBuf, + workspace: Workspace, } impl State { - fn new(workspace_root: PathBuf) -> Self { - Self { workspace_root } + fn new(workspace: Workspace) -> Self { + Self { workspace } } } -pub async fn run(workspace_root: PathBuf) { - let shared_state = Arc::new(State::new(workspace_root)); +pub async fn run(workspace: Workspace) { + let shared_state = Arc::new(State::new(workspace)); // build our application with a route let app = Router::new() @@ -80,8 +80,8 @@ struct MatchData { async fn list_matches(Extension(state): Extension>) -> Json> { let matches = state - .workspace_root - .join("matches") + .workspace + .matches_dir() .read_dir() .unwrap() .filter_map(|entry| { @@ -125,7 +125,7 @@ fn read_match_meta(path: &path::Path) -> io::Result { } async fn get_match(Extension(state): Extension>, Path(id): Path) -> String { - let mut match_path = state.workspace_root.join("matches").join(id); + let mut match_path = state.workspace.matches_dir().join(id); match_path.set_extension("log"); fs::read_to_string(match_path).unwrap() } diff --git a/planetwars-cli/src/workspace/bot.rs b/planetwars-cli/src/workspace/bot.rs new file mode 100644 index 0000000..3cd4f87 --- /dev/null +++ b/planetwars-cli/src/workspace/bot.rs @@ -0,0 +1,40 @@ +use shlex; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +use serde::{Deserialize, Serialize}; + +const BOT_CONFIG_FILENAME: &str = "botconfig.toml"; + +pub struct WorkspaceBot { + pub path: PathBuf, + pub config: BotConfig, +} + +impl WorkspaceBot { + pub fn open(path: &Path) -> io::Result { + let config_path = path.join(BOT_CONFIG_FILENAME); + let config_str = fs::read_to_string(config_path)?; + let bot_config: BotConfig = toml::from_str(&config_str)?; + + Ok(WorkspaceBot { + path: path.to_path_buf(), + config: bot_config, + }) + } +} + +#[derive(Serialize, Deserialize)] +pub struct BotConfig { + pub name: String, + pub run_command: String, +} + +impl BotConfig { + pub fn get_run_argv(&self) -> Vec { + // TODO: proper error handling + shlex::split(&self.run_command) + .expect("Failed to parse bot run command. Check for unterminated quotes.") + } +} diff --git a/planetwars-cli/src/workspace/mod.rs b/planetwars-cli/src/workspace/mod.rs new file mode 100644 index 0000000..64777c2 --- /dev/null +++ b/planetwars-cli/src/workspace/mod.rs @@ -0,0 +1,77 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::env; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +use self::bot::WorkspaceBot; + +const WORKSPACE_CONFIG_FILENAME: &str = "pw_workspace.toml"; + +pub mod bot; + +pub struct Workspace { + root_path: PathBuf, + config: WorkspaceConfig, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct WorkspaceConfig { + paths: WorkspacePaths, + bots: HashMap, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct WorkspacePaths { + maps_dir: PathBuf, + matches_dir: PathBuf, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BotEntry { + path: PathBuf, +} + +impl Workspace { + pub fn open(root_path: &Path) -> io::Result { + let config_path = root_path.join(WORKSPACE_CONFIG_FILENAME); + let config_str = fs::read_to_string(config_path)?; + let workspace_config: WorkspaceConfig = toml::from_str(&config_str)?; + + Ok(Workspace { + root_path: root_path.to_path_buf(), + config: workspace_config, + }) + } + + pub fn open_current_dir() -> io::Result { + Workspace::open(&env::current_dir()?) + } + + pub fn maps_dir(&self) -> PathBuf { + self.root_path.join(&self.config.paths.maps_dir) + } + + pub fn map_path(&self, map_name: &str) -> PathBuf { + self.maps_dir().join(format!("{}.json", map_name)) + } + + pub fn matches_dir(&self) -> PathBuf { + self.root_path.join(&self.config.paths.matches_dir) + } + + pub fn match_path(&self, match_name: &str) -> PathBuf { + self.matches_dir().join(format!("{}.log", match_name)) + } + + pub fn get_bot(&self, bot_name: &str) -> io::Result { + let bot_entry = self.config.bots.get(bot_name).ok_or_else(|| { + io::Error::new( + io::ErrorKind::NotFound, + format!("no such bot: {}", bot_name), + ) + })?; + WorkspaceBot::open(&self.root_path.join(&bot_entry.path)) + } +}