refactor workspace code
This commit is contained in:
parent
5ca8dd4c84
commit
dacc05a41b
13 changed files with 161 additions and 84 deletions
|
@ -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"] }
|
||||
|
|
|
@ -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"]
|
||||
path = "bots/simplebot"
|
||||
|
|
2
planetwars-cli/assets/simplebot/botconfig.toml
Normal file
2
planetwars-cli/assets/simplebot/botconfig.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
name = "simplebot"
|
||||
run_command = "python3 simplebot.py"
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
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,
|
||||
});
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let match_config = MatchConfig {
|
||||
map_name: self.map,
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, BotConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct BotConfig {
|
||||
path: String,
|
||||
argv: Vec<String>,
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String>,
|
||||
}
|
||||
|
||||
|
|
|
@ -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<MatchBot>,
|
||||
pub players: Vec<MatchPlayer>,
|
||||
}
|
||||
|
||||
#[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<dyn PlayerHandle>)
|
||||
|
|
|
@ -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<Arc<State>>) -> Json<Vec<MatchData>> {
|
||||
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<MatchMeta> {
|
|||
}
|
||||
|
||||
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");
|
||||
fs::read_to_string(match_path).unwrap()
|
||||
}
|
||||
|
|
40
planetwars-cli/src/workspace/bot.rs
Normal file
40
planetwars-cli/src/workspace/bot.rs
Normal 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.")
|
||||
}
|
||||
}
|
77
planetwars-cli/src/workspace/mod.rs
Normal file
77
planetwars-cli/src/workspace/mod.rs
Normal 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))
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue