mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
handle ticks in a seperate thread that tui on botclient
This commit is contained in:
parent
e001b2cac2
commit
c1d6b1ac75
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -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",
|
||||
|
@ -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]
|
||||
|
@ -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
114
client/src/bin/bot/tui.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user