handle ticks in a seperate thread that tui on botclient

This commit is contained in:
Marcel Märtens 2021-03-12 11:08:24 +01:00
parent e001b2cac2
commit c1d6b1ac75
4 changed files with 164 additions and 111 deletions

3
Cargo.lock generated
View File

@ -5526,6 +5526,7 @@ dependencies = [
name = "veloren-client"
version = "0.8.0"
dependencies = [
"async-channel",
"authc",
"byteorder",
"clap",
@ -5541,8 +5542,6 @@ dependencies = [
"termcolor",
"tokio",
"tracing",
"tracing-appender",
"tracing-log",
"tracing-subscriber",
"vek 0.14.1",
"veloren-common",

View File

@ -7,7 +7,7 @@ edition = "2018"
[features]
simd = ["vek/platform_intrinsics"]
plugins = ["common-sys/plugins"]
bin_bot = ["common-ecs", "serde", "ron", "clap", "rustyline", "termcolor", "tracing-appender", "tracing-log", "tracing-subscriber"]
bin_bot = ["common-ecs", "serde", "ron", "clap", "rustyline", "termcolor", "tracing-subscriber", "async-channel"]
default = ["simd"]
@ -31,6 +31,7 @@ hashbrown = { version = "0.9", features = ["rayon", "serde", "nightly"] }
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "fb3dcbc4962b367253f8f2f92760ef44d2679c9a" }
#bot only
async-channel = { version = "1.6", optional = true }
common-ecs = { package = "veloren-common-ecs", path = "../common/ecs", optional = true }
serde = { version = "1.0", features = [ "rc", "derive" ], optional = true }
ron = { version = "0.6", default-features = false, optional = true }
@ -38,8 +39,6 @@ clap = { version = "2.33", optional = true }
rustyline = { version = "8.0.0", optional = true }
## logging
termcolor = { version = "1.1", optional = true }
tracing-appender = { version = "0.1", optional = true }
tracing-log = { version = "0.1.1", optional = true }
tracing-subscriber = {version = "0.2.3", default-features = false, features = ["env-filter", "fmt", "chrono", "ansi", "smallvec", "tracing-log"], optional = true }
[dev-dependencies]

View File

@ -3,33 +3,19 @@
#[macro_use] extern crate serde;
use authc::AuthClient;
use clap::{App, AppSettings, Arg, SubCommand};
use common::{clock::Clock, comp};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use std::{sync::Arc, time::Duration};
use tokio::runtime::Runtime;
use tracing::{error, info, warn};
use tracing::{info, trace, warn};
use veloren_client::{addr::ConnectionArgs, Client};
mod settings;
mod tui;
use settings::Settings;
pub fn init_logging() {
use termcolor::{ColorChoice, StandardStream};
use tracing::Level;
use tracing_subscriber::{filter::LevelFilter, EnvFilter, FmtSubscriber};
const RUST_LOG_ENV: &str = "RUST_LOG";
let filter = EnvFilter::from_env(RUST_LOG_ENV).add_directive(LevelFilter::INFO.into());
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::ERROR)
.with_env_filter(filter);
subscriber
.with_writer(|| StandardStream::stdout(ColorChoice::Auto))
.init();
}
use tui::Cmd;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BotCreds {
@ -38,18 +24,28 @@ pub struct BotCreds {
}
pub fn main() {
init_logging();
tui::init_logging();
let settings = Settings::load();
info!("Settings: {:?}", settings);
let (_tui, cmds) = tui::Tui::new();
let mut bc = BotClient::new(settings);
bc.repl();
'outer: loop {
loop {
match cmds.try_recv() {
Ok(cmd) => bc.cmd(cmd),
Err(async_channel::TryRecvError::Empty) => break,
Err(async_channel::TryRecvError::Closed) => break 'outer,
}
}
bc.tick();
}
info!("shutdown complete");
}
pub struct BotClient {
settings: Settings,
readline: rustyline::Editor<()>,
runtime: Arc<Runtime>,
menu_client: Client,
bot_clients: HashMap<String, Client>,
@ -71,13 +67,11 @@ pub fn make_client(runtime: &Arc<Runtime>, server: &str) -> Client {
impl BotClient {
pub fn new(settings: Settings) -> BotClient {
let readline = rustyline::Editor::<()>::new();
let runtime = Arc::new(Runtime::new().unwrap());
let menu_client: Client = make_client(&runtime, &settings.server);
let clock = Clock::new(Duration::from_secs_f64(1.0 / 60.0));
BotClient {
settings,
readline,
runtime,
menu_client,
bot_clients: HashMap::new(),
@ -85,75 +79,38 @@ impl BotClient {
}
}
pub fn repl(&mut self) {
loop {
match self.readline.readline("\n\nbotclient> ") {
Ok(cmd) => {
let keep_going = self.process_command(&cmd);
self.readline.add_history_entry(cmd);
if !keep_going {
break;
}
},
Err(_) => break,
}
pub fn tick(&mut self) {
self.clock.tick();
for (username, client) in self.bot_clients.iter_mut() {
//trace!("cl {:?}: {:?}", username, client.character_list());
trace!(?username, "tick");
let msgs: Result<Vec<veloren_client::Event>, veloren_client::Error> =
client.tick(comp::ControllerInputs::default(), self.clock.dt(), |_| {});
/*trace!(
"msgs {:?}: {:?} {:?}",
username,
msgs,
client.character_list()
);*/
}
}
pub fn process_command(&mut self, cmd: &str) -> bool {
let matches = App::new("veloren-botclient")
.version(common::util::DISPLAY_VERSION_LONG.as_str())
.author("The veloren devs <https://gitlab.com/veloren/veloren>")
.about("The veloren bot client allows logging in as a horde of bots for load-testing")
.setting(AppSettings::NoBinaryName)
.subcommand(
SubCommand::with_name("register")
.about("Register more bots with the auth server")
.args(&[
Arg::with_name("prefix").required(true),
Arg::with_name("password").required(true),
Arg::with_name("count"),
]),
)
.subcommand(
SubCommand::with_name("login")
.about("Login all registered bots whose username starts with a prefix")
.args(&[Arg::with_name("prefix").required(true)]),
)
.subcommand(SubCommand::with_name("tick").about("Handle ticks for all logged in bots"))
.get_matches_from_safe(cmd.split(" "));
use clap::ErrorKind::*;
match matches {
Ok(matches) => match matches.subcommand() {
("register", Some(matches)) => self.handle_register(
matches.value_of("prefix").unwrap(),
matches.value_of("password").unwrap(),
matches
.value_of("count")
.and_then(|x| x.parse::<usize>().ok()),
),
("login", Some(matches)) => self.handle_login(matches.value_of("prefix").unwrap()),
("tick", _) => self.handle_tick(),
_ => {},
},
Err(e)
if [HelpDisplayed, MissingRequiredArgument, UnknownArgument].contains(&e.kind) =>
{
println!("{}", e.message);
}
Err(e) => {
error!("{:?}", e);
return false;
},
pub fn cmd(&mut self, cmd: Cmd) {
match cmd {
Cmd::Register {
prefix,
password,
count,
} => self.handle_register(&prefix, &password, count),
Cmd::Login { prefix } => self.handle_login(&prefix),
}
true
}
pub fn handle_register(&mut self, prefix: &str, password: &str, count: Option<usize>) {
let usernames = match count {
Some(n) => (0..n)
.into_iter()
.map(|i| format!("{}{}", prefix, i))
.map(|i| format!("{}{:03}", prefix, i))
.collect::<Vec<String>>(),
None => vec![prefix.to_string()],
};
@ -194,14 +151,7 @@ impl BotClient {
} else {
warn!("Server's auth_provider is None");
}
}
pub fn client_for_bot(&mut self, username: &str) -> &mut Client {
let runtime = Arc::clone(&self.runtime);
let server = self.settings.server.clone();
self.bot_clients
.entry(username.to_string())
.or_insert_with(|| make_client(&runtime, &server))
info!("register done");
}
pub fn handle_login(&mut self, prefix: &str) {
@ -214,7 +164,13 @@ impl BotClient {
.collect();
for cred in creds.iter() {
let runtime = Arc::clone(&self.runtime);
let client = self.client_for_bot(&cred.username);
let server = self.settings.server.clone();
let client = self
.bot_clients
.entry(cred.username.clone())
.or_insert_with(|| make_client(&runtime, &server));
// TODO: log the clients in in parallel instead of in series
if let Err(e) = runtime.block_on(client.register(
cred.username.clone(),
@ -246,21 +202,6 @@ impl BotClient {
//client.create_character(cred.username.clone(),
// Some("common.items.debug.admin_stick".to_string()), body.into());
}
}
// TODO: maybe do this automatically in a threadpool instead of as a command
pub fn handle_tick(&mut self) {
self.clock.tick();
for (username, client) in self.bot_clients.iter_mut() {
info!("cl {:?}: {:?}", username, client.character_list());
let msgs: Result<Vec<veloren_client::Event>, veloren_client::Error> =
client.tick(comp::ControllerInputs::default(), self.clock.dt(), |_| {});
info!(
"msgs {:?}: {:?} {:?}",
username,
msgs,
client.character_list()
);
}
info!("login done");
}
}

114
client/src/bin/bot/tui.rs Normal file
View File

@ -0,0 +1,114 @@
use clap::{App, AppSettings, Arg, SubCommand};
use std::{thread, time::Duration};
use tracing::error;
pub fn init_logging() {
use termcolor::{ColorChoice, StandardStream};
use tracing::Level;
use tracing_subscriber::{filter::LevelFilter, EnvFilter, FmtSubscriber};
const RUST_LOG_ENV: &str = "RUST_LOG";
let filter = EnvFilter::from_env(RUST_LOG_ENV).add_directive(LevelFilter::INFO.into());
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::ERROR)
.with_env_filter(filter);
subscriber
.with_writer(|| StandardStream::stdout(ColorChoice::Auto))
.init();
}
pub enum Cmd {
Register {
prefix: String,
password: String,
count: Option<usize>,
},
Login {
prefix: String,
},
}
pub struct Tui {
_handle: thread::JoinHandle<()>,
}
impl Tui {
pub fn new() -> (Self, async_channel::Receiver<Cmd>) {
let (mut commands_s, commands_r) = async_channel::unbounded();
let handle = thread::spawn(move || {
thread::sleep(Duration::from_millis(20));
let mut readline = rustyline::Editor::<()>::new();
loop {
match readline.readline("\n\nbotclient> ") {
Ok(cmd) => {
let keep_going = Self::process_command(&cmd, &mut commands_s);
readline.add_history_entry(cmd);
if !keep_going {
break;
}
},
Err(_) => break,
}
}
});
(Self { _handle: handle }, commands_r)
}
pub fn process_command(cmd: &str, command_s: &mut async_channel::Sender<Cmd>) -> bool {
let matches = App::new("veloren-botclient")
.version(common::util::DISPLAY_VERSION_LONG.as_str())
.author("The veloren devs <https://gitlab.com/veloren/veloren>")
.about("The veloren bot client allows logging in as a horde of bots for load-testing")
.setting(AppSettings::NoBinaryName)
.subcommand(
SubCommand::with_name("register")
.about("Register more bots with the auth server")
.args(&[
Arg::with_name("prefix").required(true),
Arg::with_name("password").required(true),
Arg::with_name("count"),
]),
)
.subcommand(
SubCommand::with_name("login")
.about("Login all registered bots whose username starts with a prefix")
.args(&[Arg::with_name("prefix").required(true)]),
)
.subcommand(SubCommand::with_name("tick").about("Handle ticks for all logged in bots"))
.get_matches_from_safe(cmd.split(" "));
use clap::ErrorKind::*;
match matches {
Ok(matches) => {
if match matches.subcommand() {
("register", Some(matches)) => command_s.try_send(Cmd::Register {
prefix: matches.value_of("prefix").unwrap().to_string(),
password: matches.value_of("password").unwrap().to_string(),
count: matches
.value_of("count")
.and_then(|x| x.parse::<usize>().ok()),
}),
("login", Some(matches)) => command_s.try_send(Cmd::Login {
prefix: matches.value_of("prefix").unwrap().to_string(),
}),
_ => Ok(()),
}
.is_err()
{
return false;
}
},
Err(e)
if [HelpDisplayed, MissingRequiredArgument, UnknownArgument].contains(&e.kind) =>
{
println!("{}", e.message);
}
Err(e) => {
error!("{:?}", e);
return false;
},
}
true
}
}