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" }
|
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"] }
|
||||||
|
|
|
@ -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"]
|
|
||||||
|
|
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
|
// 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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, ×tamp));
|
||||||
|
|
||||||
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,
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>)
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
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