refactor workspace code

This commit is contained in:
Ilion Beyst 2021-12-28 14:57:41 +01:00
parent 5ca8dd4c84
commit dacc05a41b
13 changed files with 161 additions and 84 deletions

View file

@ -18,6 +18,7 @@ toml = "0.5"
planetwars-rules = { path = "../planetwars-rules" } planetwars-rules = { path = "../planetwars-rules" }
clap = { version = "3.0.0-rc.8", features = ["derive"] } clap = { version = "3.0.0-rc.8", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
shlex = "1.1"
rust-embed = "6.3.0" rust-embed = "6.3.0"
axum = { version = "0.4", features = ["ws"] } axum = { version = "0.4", features = ["ws"] }

View file

@ -1,10 +1,6 @@
[bots] [paths]
maps_dir = "maps"
matches_dir = "matches"
# define a bot called simplebot
[bots.simplebot] [bots.simplebot]
path = "bots/simplebot"
# The working directory for the bot.
path = "./bots/simplebot"
# What command to use for running the bot
argv = ["python", "simplebot.py"]

View file

@ -0,0 +1,2 @@
name = "simplebot"
run_command = "python3 simplebot.py"

View file

@ -31,8 +31,8 @@ impl InitCommand {
// create files // create files
copy_asset!(path, "pw_workspace.toml"); copy_asset!(path, "pw_workspace.toml");
copy_asset!(path.join("maps"), "hex.json"); 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(()) Ok(())
} }
} }

View file

@ -1,14 +1,10 @@
use std::env;
use std::io; use std::io;
use clap::Parser; use clap::Parser;
use crate::match_runner;
use crate::match_runner::MatchBot;
use crate::match_runner::MatchConfig; use crate::match_runner::MatchConfig;
use crate::resolve_bot_config; use crate::match_runner::{self, MatchPlayer};
use crate::WorkspaceConfig; use crate::workspace::Workspace;
#[derive(Parser)] #[derive(Parser)]
pub struct RunMatchCommand { pub struct RunMatchCommand {
/// map name /// map name
@ -19,30 +15,20 @@ pub struct RunMatchCommand {
impl RunMatchCommand { impl RunMatchCommand {
pub async fn run(self) -> io::Result<()> { pub async fn run(self) -> io::Result<()> {
let workspace_root = env::current_dir().unwrap(); let workspace = Workspace::open_current_dir()?;
let map_path = workspace.maps_dir().join(format!("{}.json", &self.map));
let config_path = workspace_root.join("pw_workspace.toml");
let map_path = workspace_root.join(format!("maps/{}.json", self.map));
let timestamp = chrono::Local::now().format("%Y-%m-%d-%H-%M-%S"); 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, &timestamp));
let config_str = std::fs::read_to_string(config_path).unwrap(); let mut players = Vec::new();
let workspace_config: WorkspaceConfig = toml::from_str(&config_str).unwrap(); for bot_name in &self.bots {
let bot = workspace.get_bot(&bot_name)?;
let players = self players.push(MatchPlayer {
.bots name: bot_name.clone(),
.into_iter() bot,
.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 match_config = MatchConfig { let match_config = MatchConfig {
map_name: self.map, map_name: self.map,

View file

@ -1,18 +1,17 @@
use std::env;
use std::io; use std::io;
use clap::Parser; use clap::Parser;
use crate::web; use crate::web;
use crate::workspace::Workspace;
#[derive(Parser)] #[derive(Parser)]
pub struct ServeCommand; pub struct ServeCommand;
impl ServeCommand { impl ServeCommand {
pub async fn run(self) -> io::Result<()> { pub async fn run(self) -> io::Result<()> {
let workspace_root = env::current_dir().unwrap(); let workspace = Workspace::open_current_dir()?;
web::run(workspace).await;
web::run(workspace_root).await;
Ok(()) Ok(())
} }
} }

View file

@ -1,23 +1,7 @@
use serde::Deserialize;
mod commands; mod commands;
mod match_runner; mod match_runner;
mod web; mod web;
mod workspace;
use serde::Serialize;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Serialize, Deserialize, Debug)]
struct WorkspaceConfig {
bots: HashMap<String, BotConfig>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BotConfig {
path: String,
argv: Vec<String>,
}
pub async fn run() { pub async fn run() {
let res = commands::Cli::run().await; let res = commands::Cli::run().await;
@ -26,12 +10,3 @@ pub async fn run() {
std::process::exit(1); 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,
}
}

View file

@ -1,4 +1,5 @@
use std::io; use std::io;
use std::path::PathBuf;
use std::process::Stdio; use std::process::Stdio;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
@ -75,7 +76,7 @@ impl LocalBotRunner {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Bot { pub struct Bot {
pub working_dir: String, pub working_dir: PathBuf,
pub argv: Vec<String>, pub argv: Vec<String>,
} }

View file

@ -12,7 +12,7 @@ use match_context::MatchCtx;
use planetwars_rules::PwConfig; use planetwars_rules::PwConfig;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::BotConfig; use crate::workspace::bot::WorkspaceBot;
use self::match_context::{EventBus, PlayerHandle}; use self::match_context::{EventBus, PlayerHandle};
@ -20,7 +20,7 @@ pub struct MatchConfig {
pub map_name: String, pub map_name: String,
pub map_path: PathBuf, pub map_path: PathBuf,
pub log_path: PathBuf, pub log_path: PathBuf,
pub players: Vec<MatchBot>, pub players: Vec<MatchPlayer>,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -35,9 +35,9 @@ pub struct PlayerInfo {
pub name: String, pub name: String,
} }
pub struct MatchBot { pub struct MatchPlayer {
pub name: String, pub name: String,
pub bot_config: BotConfig, pub bot: WorkspaceBot,
} }
pub async fn run_match(config: MatchConfig) { pub async fn run_match(config: MatchConfig) {
@ -53,11 +53,11 @@ pub async fn run_match(config: MatchConfig) {
.players .players
.iter() .iter()
.enumerate() .enumerate()
.map(|(player_id, bot)| { .map(|(player_id, player)| {
let player_id = (player_id + 1) as u32; let player_id = (player_id + 1) as u32;
let bot = bot_runner::Bot { let bot = bot_runner::Bot {
working_dir: bot.bot_config.path.clone(), working_dir: player.bot.path.clone(),
argv: bot.bot_config.argv.clone(), argv: player.bot.config.get_run_argv(),
}; };
let handle = bot_runner::run_local_bot(player_id, event_bus.clone(), bot); let handle = bot_runner::run_local_bot(player_id, event_bus.clone(), bot);
(player_id, Box::new(handle) as Box<dyn PlayerHandle>) (player_id, Box::new(handle) as Box<dyn PlayerHandle>)

View file

@ -18,20 +18,20 @@ use std::{
sync::Arc, sync::Arc,
}; };
use crate::match_runner::MatchMeta; use crate::{match_runner::MatchMeta, workspace::Workspace};
struct State { struct State {
workspace_root: PathBuf, workspace: Workspace,
} }
impl State { impl State {
fn new(workspace_root: PathBuf) -> Self { fn new(workspace: Workspace) -> Self {
Self { workspace_root } Self { workspace }
} }
} }
pub async fn run(workspace_root: PathBuf) { pub async fn run(workspace: Workspace) {
let shared_state = Arc::new(State::new(workspace_root)); let shared_state = Arc::new(State::new(workspace));
// build our application with a route // build our application with a route
let app = Router::new() let app = Router::new()
@ -80,8 +80,8 @@ struct MatchData {
async fn list_matches(Extension(state): Extension<Arc<State>>) -> Json<Vec<MatchData>> { async fn list_matches(Extension(state): Extension<Arc<State>>) -> Json<Vec<MatchData>> {
let matches = state let matches = state
.workspace_root .workspace
.join("matches") .matches_dir()
.read_dir() .read_dir()
.unwrap() .unwrap()
.filter_map(|entry| { .filter_map(|entry| {
@ -125,7 +125,7 @@ fn read_match_meta(path: &path::Path) -> io::Result<MatchMeta> {
} }
async fn get_match(Extension(state): Extension<Arc<State>>, Path(id): Path<String>) -> String { async fn get_match(Extension(state): Extension<Arc<State>>, Path(id): Path<String>) -> 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"); match_path.set_extension("log");
fs::read_to_string(match_path).unwrap() fs::read_to_string(match_path).unwrap()
} }

View file

@ -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<Self> {
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<String> {
// TODO: proper error handling
shlex::split(&self.run_command)
.expect("Failed to parse bot run command. Check for unterminated quotes.")
}
}

View file

@ -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<String, BotEntry>,
}
#[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<Workspace> {
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> {
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<WorkspaceBot> {
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))
}
}