Move server-cli commands to separate file, tracy profiling par_join improvements, misc improvements

- remove overwritten logging setting in server-cli
- add server-cli command to load a random area for testing without a client
- make admin add/remove commands modify ingame players instead of needing to reconnect
- add spans to par_join jobs
- added test command that loads up an area of the world
- add tracy-world-server alias
- set debug directives to info for logging
This commit is contained in:
Imbris 2021-03-13 01:48:30 -05:00
parent 4022937da7
commit 8d0b776f18
15 changed files with 374 additions and 181 deletions

View File

@ -8,6 +8,7 @@ csv-export = "run --manifest-path common/Cargo.toml --features=bin_csv --bin csv
csv-import = "run --manifest-path common/Cargo.toml --features=bin_csv --bin csv_import" csv-import = "run --manifest-path common/Cargo.toml --features=bin_csv --bin csv_import"
test-server = "-Zpackage-features run --bin veloren-server-cli --no-default-features -- -b" test-server = "-Zpackage-features run --bin veloren-server-cli --no-default-features -- -b"
tracy-server = "-Zunstable-options -Zpackage-features run --bin veloren-server-cli --no-default-features --features tracy,simd --profile no_overflow" tracy-server = "-Zunstable-options -Zpackage-features run --bin veloren-server-cli --no-default-features --features tracy,simd --profile no_overflow"
tracy-world-server = "-Zunstable-options -Zpackage-features run --bin veloren-server-cli --features tracy,simd --profile no_overflow -- -bi"
test-voxygen = "-Zpackage-features run --bin veloren-voxygen --no-default-features --features gl,simd" test-voxygen = "-Zpackage-features run --bin veloren-voxygen --no-default-features --features gl,simd"
tracy-voxygen = "-Zunstable-options -Zpackage-features run --bin veloren-voxygen --no-default-features --features tracy,gl,simd --profile no_overflow" tracy-voxygen = "-Zunstable-options -Zpackage-features run --bin veloren-voxygen --no-default-features --features tracy,gl,simd --profile no_overflow"
server = "run --bin veloren-server-cli" server = "run --bin veloren-server-cli"

View File

@ -41,6 +41,25 @@ macro_rules! span {
}; };
} }
/// Like the span macro but only used when profiling and not in regular tracing
/// operations
#[macro_export]
macro_rules! prof_span {
($guard_name:tt, $name:expr) => {
#[cfg(feature = "tracy")]
let $guard_name = $crate::tracy_client::Span::new(
$name,
"",
module_path!(),
line!(),
// No callstack since this has significant overhead
0,
);
#[cfg(not(feature = "tracy"))]
let $guard_name = ();
};
}
/// There's no guard, but really this is actually the guard /// There's no guard, but really this is actually the guard
pub struct GuardlessSpan { pub struct GuardlessSpan {
span: tracing::Span, span: tracing::Span,

View File

@ -253,7 +253,10 @@ where
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
common_base::span!(_guard, "run", &format!("{}::Sys::run", T::NAME)); common_base::span!(_guard, "run", &format!("{}::Sys::run", T::NAME));
self.cpu_stats.reset(); self.cpu_stats.reset();
T::run(self, data.0); {
common_base::span!(_guard, "run inner", &format!("{}::Sys::run inner", T::NAME));
T::run(self, data.0);
}
self.cpu_stats.end(); self.cpu_stats.end();
data.1 data.1
.stats .stats

View File

@ -18,6 +18,11 @@
trait_alias, trait_alias,
type_alias_impl_trait type_alias_impl_trait
)] )]
/// Re-exported crates
pub use uuid;
// modules
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub mod assets; pub mod assets;
#[cfg(not(target_arch = "wasm32"))] pub mod astar; #[cfg(not(target_arch = "wasm32"))] pub mod astar;

View File

@ -49,6 +49,27 @@ impl RectVolSize for TerrainChunkSize {
}; };
} }
impl TerrainChunkSize {
#[inline(always)]
/// Convert dimensions in terms of chunks into dimensions in terms of blocks
/// ```
/// assert_eq!(TerrainChunkSize::blocks(Vec2::new(3, 2)), Vec2::new(96, 64));
/// ```
pub fn blocks(chunks: Vec2<u32>) -> Vec2<u32> { chunks * Self::RECT_SIZE }
/// Calculate the world position (i.e. in blocks) at the center of this
/// chunk
/// ```
/// assert_eq!(
/// TerrainChunkSize::center_wpos(Vec2::new(0, 2)),
/// Vec2::new(16, 80)
/// );
/// ```
pub fn center_wpos(chunk_pos: Vec2<i32>) -> Vec2<i32> {
chunk_pos * Self::RECT_SIZE.as_::<i32>() + Self::RECT_SIZE.as_::<i32>() / 2
}
}
// TerrainChunkMeta // TerrainChunkMeta
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]

View File

@ -10,7 +10,7 @@ use common::{
uid::Uid, uid::Uid,
vol::ReadVol, vol::ReadVol,
}; };
use common_base::span; use common_base::{prof_span, span};
use common_ecs::{Job, Origin, ParMode, Phase, PhysicsMetrics, System}; use common_ecs::{Job, Origin, ParMode, Phase, PhysicsMetrics, System};
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage}; use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
@ -232,9 +232,12 @@ impl<'a> System<'a> for Sys {
sticky.is_none() || (physics.on_wall.is_none() && !physics.on_ground) sticky.is_none() || (physics.on_wall.is_none() && !physics.on_ground)
}) })
.map(|(e, p, v, vd, m, c, _, _, ph, pr, c_s)| (e, p, v, vd, m, c, ph, pr, c_s)) .map(|(e, p, v, vd, m, c, _, _, ph, pr, c_s)| (e, p, v, vd, m, c, ph, pr, c_s))
.fold( .map_init(
PhysicsMetrics::default, || {
|mut metrics, prof_span!(guard, "physics e<>e rayon job");
guard
},
|_guard,
( (
entity, entity,
pos, pos,
@ -256,6 +259,9 @@ impl<'a> System<'a> for Sys {
let mut vel_delta = Vec3::zero(); let mut vel_delta = Vec3::zero();
let mut entity_entity_collision_checks = 0;
let mut entity_entity_collisions = 0;
for ( for (
entity_other, entity_other,
other, other,
@ -307,7 +313,7 @@ impl<'a> System<'a> for Sys {
continue; continue;
} }
metrics.entity_entity_collision_checks += 1; entity_entity_collision_checks += 1;
const MIN_COLLISION_DIST: f32 = 0.3; const MIN_COLLISION_DIST: f32 = 0.3;
let increments = ((previous_cache.velocity_dt let increments = ((previous_cache.velocity_dt
@ -334,7 +340,7 @@ impl<'a> System<'a> for Sys {
{ {
if !collided { if !collided {
physics.touch_entities.push(*other); physics.touch_entities.push(*other);
metrics.entity_entity_collisions += 1; entity_entity_collisions += 1;
} }
// Don't apply repulsive force to projectiles // Don't apply repulsive force to projectiles
@ -353,7 +359,12 @@ impl<'a> System<'a> for Sys {
// Change velocity // Change velocity
vel.0 += vel_delta * dt.0; vel.0 += vel_delta * dt.0;
metrics
// Metrics
PhysicsMetrics {
entity_entity_collision_checks,
entity_entity_collisions,
}
}, },
) )
.reduce(PhysicsMetrics::default, |old, new| PhysicsMetrics { .reduce(PhysicsMetrics::default, |old, new| PhysicsMetrics {
@ -380,13 +391,19 @@ impl<'a> System<'a> for Sys {
!&mountings, !&mountings,
) )
.par_join() .par_join()
.fold(Vec::new, | .map_init(
mut land_on_grounds, || {
prof_span!(guard, "physics e<>t rayon job");
guard
},
|_guard,
(entity, _scale, sticky, collider, mut pos, mut vel, _ori, mut physics_state, _), (entity, _scale, sticky, collider, mut pos, mut vel, _ori, mut physics_state, _),
| { | {
let mut landed_on_ground = None;
if sticky.is_some() && physics_state.on_surface().is_some() { if sticky.is_some() && physics_state.on_surface().is_some() {
vel.0 = Vec3::zero(); vel.0 = Vec3::zero();
return land_on_grounds; return landed_on_ground;
} }
// TODO: Use this // TODO: Use this
@ -593,7 +610,7 @@ impl<'a> System<'a> for Sys {
on_ground = true; on_ground = true;
if !was_on_ground { if !was_on_ground {
land_on_grounds.push((entity, *vel)); landed_on_ground = Some((entity, *vel));
} }
} else if resolve_dir.z < 0.0 && vel.0.z >= 0.0 { } else if resolve_dir.z < 0.0 && vel.0.z >= 0.0 {
on_ceiling = true; on_ceiling = true;
@ -770,7 +787,12 @@ impl<'a> System<'a> for Sys {
}, },
} }
land_on_grounds landed_on_ground
}).fold(Vec::new, |mut lands_on_grounds, landed_on_ground| {
if let Some(land_on_ground) = landed_on_ground {
lands_on_grounds.push(land_on_ground);
}
lands_on_grounds
}).reduce(Vec::new, |mut land_on_grounds_a, mut land_on_grounds_b| { }).reduce(Vec::new, |mut land_on_grounds_a, mut land_on_grounds_b| {
land_on_grounds_a.append(&mut land_on_grounds_b); land_on_grounds_a.append(&mut land_on_grounds_b);
land_on_grounds_a land_on_grounds_a

View File

@ -16,12 +16,12 @@ pub fn admin_subcommand(
match sub_m.subcommand() { match sub_m.subcommand() {
("add", Some(sub_m)) => { ("add", Some(sub_m)) => {
if let Some(username) = sub_m.value_of("username") { if let Some(username) = sub_m.value_of("username") {
server::add_admin(username, &login_provider, editable_settings, data_dir) server::add_admin(username, &login_provider, editable_settings, data_dir);
} }
}, },
("remove", Some(sub_m)) => { ("remove", Some(sub_m)) => {
if let Some(username) = sub_m.value_of("username") { if let Some(username) = sub_m.value_of("username") {
server::remove_admin(username, &login_provider, editable_settings, data_dir) server::remove_admin(username, &login_provider, editable_settings, data_dir);
} }
}, },
// TODO: can clap enforce this? // TODO: can clap enforce this?

140
server-cli/src/cmd.rs Normal file
View File

@ -0,0 +1,140 @@
use core::time::Duration;
use std::sync::mpsc::Sender;
use tracing::{error, info, warn};
#[derive(Debug, Clone)]
pub enum Message {
AbortShutdown,
Shutdown { grace_period: Duration },
Quit,
AddAdmin(String),
RemoveAdmin(String),
LoadArea(u32),
}
struct Command<'a> {
name: &'a str,
description: &'a str,
// Whether or not the command splits the arguments on whitespace
split_spaces: bool,
args: usize,
cmd: fn(Vec<String>, &mut Sender<Message>),
}
// TODO: maybe we could be using clap here?
const COMMANDS: [Command; 6] = [
Command {
name: "quit",
description: "Closes the server",
split_spaces: true,
args: 0,
cmd: |_, sender| sender.send(Message::Quit).unwrap(),
},
Command {
name: "shutdown",
description: "Initiates a graceful shutdown of the server, waiting the specified number \
of seconds before shutting down",
split_spaces: true,
args: 1,
cmd: |args, sender| {
if let Ok(grace_period) = args.first().unwrap().parse::<u64>() {
sender
.send(Message::Shutdown {
grace_period: Duration::from_secs(grace_period),
})
.unwrap()
} else {
error!("Grace period must an integer")
}
},
},
Command {
name: "loadarea",
description: "Loads up the chunks in a random area and adds a entity that mimics a player \
to keep them from despawning",
split_spaces: true,
args: 1,
cmd: |args, sender| {
if let Ok(view_distance) = args.first().unwrap().parse::<u32>() {
sender.send(Message::LoadArea(view_distance)).unwrap();
} else {
error!("View distance must be an integer");
}
},
},
Command {
name: "abortshutdown",
description: "Aborts a shutdown if one is in progress",
split_spaces: false,
args: 0,
cmd: |_, sender| sender.send(Message::AbortShutdown).unwrap(),
},
Command {
name: "admin",
description: "Add or remove an admin via \'admin add/remove <username>\'",
split_spaces: true,
args: 2,
cmd: |args, sender| match args.get(..2) {
Some([op, username]) if op == "add" => {
sender.send(Message::AddAdmin(username.clone())).unwrap()
},
Some([op, username]) if op == "remove" => {
sender.send(Message::RemoveAdmin(username.clone())).unwrap()
},
Some(_) => error!("First arg must be add or remove"),
_ => error!("Not enough args, should be unreachable"),
},
},
Command {
name: "help",
description: "List all command available",
split_spaces: true,
args: 0,
cmd: |_, _| {
info!("===== Help =====");
for command in COMMANDS.iter() {
info!("{} - {}", command.name, command.description)
}
info!("================");
},
},
];
pub fn parse_command(input: &str, msg_s: &mut Sender<Message>) {
let mut args = input.split_whitespace();
if let Some(cmd_name) = args.next() {
if let Some(cmd) = COMMANDS.iter().find(|cmd| cmd.name == cmd_name) {
let args = args.collect::<Vec<_>>();
let (arg_len, args) = if cmd.split_spaces {
(
args.len(),
args.into_iter()
.map(|s| s.to_string())
.collect::<Vec<String>>(),
)
} else {
(0, vec![args.into_iter().collect::<String>()])
};
use core::cmp::Ordering;
match arg_len.cmp(&cmd.args) {
Ordering::Less => error!("{} takes {} arguments", cmd_name, cmd.args),
Ordering::Greater => {
warn!("{} only takes {} arguments", cmd_name, cmd.args);
let cmd = cmd.cmd;
cmd(args, msg_s)
},
Ordering::Equal => {
let cmd = cmd.cmd;
cmd(args, msg_s)
},
}
} else {
error!("{} not found", cmd_name);
}
}
}

View File

@ -1,6 +1,5 @@
use crate::tuilog::TuiLog; use crate::tuilog::TuiLog;
use termcolor::{ColorChoice, StandardStream}; use termcolor::{ColorChoice, StandardStream};
use tracing::Level;
use tracing_subscriber::{filter::LevelFilter, EnvFilter, FmtSubscriber}; use tracing_subscriber::{filter::LevelFilter, EnvFilter, FmtSubscriber};
#[cfg(feature = "tracy")] #[cfg(feature = "tracy")]
use tracing_subscriber::{layer::SubscriberExt, prelude::*}; use tracing_subscriber::{layer::SubscriberExt, prelude::*};
@ -19,10 +18,10 @@ pub fn init(basic: bool) {
.add_directive("hyper=info".parse().unwrap()) .add_directive("hyper=info".parse().unwrap())
.add_directive("prometheus_hyper=info".parse().unwrap()) .add_directive("prometheus_hyper=info".parse().unwrap())
.add_directive("mio::pool=info".parse().unwrap()) .add_directive("mio::pool=info".parse().unwrap())
.add_directive("mio::sys::windows=debug".parse().unwrap()) .add_directive("mio::sys::windows=info".parse().unwrap())
.add_directive("h2=info".parse().unwrap()) .add_directive("h2=info".parse().unwrap())
.add_directive("tokio_util=info".parse().unwrap()) .add_directive("tokio_util=info".parse().unwrap())
.add_directive("rustls=debug".parse().unwrap()) .add_directive("rustls=info".parse().unwrap())
.add_directive("veloren_network_protocol=info".parse().unwrap()) .add_directive("veloren_network_protocol=info".parse().unwrap())
.add_directive( .add_directive(
"veloren_server::persistence::character=info" "veloren_server::persistence::character=info"
@ -32,7 +31,6 @@ pub fn init(basic: bool) {
.add_directive(LevelFilter::INFO.into()) .add_directive(LevelFilter::INFO.into())
}; };
#[cfg(not(feature = "tracy"))]
let filter = match std::env::var_os(RUST_LOG_ENV).map(|s| s.into_string()) { let filter = match std::env::var_os(RUST_LOG_ENV).map(|s| s.into_string()) {
Some(Ok(env)) => { Some(Ok(env)) => {
let mut filter = base_exceptions(EnvFilter::new("")); let mut filter = base_exceptions(EnvFilter::new(""));
@ -49,6 +47,7 @@ pub fn init(basic: bool) {
#[cfg(feature = "tracy")] #[cfg(feature = "tracy")]
tracing_subscriber::registry() tracing_subscriber::registry()
.with(filter)
.with(tracing_tracy::TracyLayer::new().with_stackdepth(0)) .with(tracing_tracy::TracyLayer::new().with_stackdepth(0))
.init(); .init();
@ -56,9 +55,7 @@ pub fn init(basic: bool) {
// TODO: when tracing gets per Layer filters re-enable this when the tracy feature is being // TODO: when tracing gets per Layer filters re-enable this when the tracy feature is being
// used (and do the same in voxygen) // used (and do the same in voxygen)
{ {
let subscriber = FmtSubscriber::builder() let subscriber = FmtSubscriber::builder().with_env_filter(filter);
.with_max_level(Level::ERROR)
.with_env_filter(filter);
if basic { if basic {
subscriber subscriber

View File

@ -3,16 +3,16 @@
#![feature(bool_to_option)] #![feature(bool_to_option)]
mod admin; mod admin;
/// `server-cli` interface commands not to be confused with the commands sent
/// from the client to the server
mod cmd;
mod logging; mod logging;
mod settings; mod settings;
mod shutdown_coordinator; mod shutdown_coordinator;
mod tui_runner; mod tui_runner;
mod tuilog; mod tuilog;
use crate::{ use crate::{cmd::Message, shutdown_coordinator::ShutdownCoordinator, tui_runner::Tui};
shutdown_coordinator::ShutdownCoordinator,
tui_runner::{Message, Tui},
};
use clap::{App, Arg, SubCommand}; use clap::{App, Arg, SubCommand};
use common::clock::Clock; use common::clock::Clock;
use common_base::span; use common_base::span;
@ -201,6 +201,9 @@ fn main() -> io::Result<()> {
Message::RemoveAdmin(username) => { Message::RemoveAdmin(username) => {
server.remove_admin(&username); server.remove_admin(&username);
}, },
Message::LoadArea(view_distance) => {
server.create_centered_persister(view_distance);
},
}, },
Err(mpsc::TryRecvError::Empty) | Err(mpsc::TryRecvError::Disconnected) => {}, Err(mpsc::TryRecvError::Empty) | Err(mpsc::TryRecvError::Disconnected) => {},
} }

View File

@ -1,4 +1,4 @@
use crate::logging::LOG; use crate::{cmd, logging::LOG, Message};
use crossterm::{ use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture}, event::{DisableMouseCapture, EnableMouseCapture},
execute, execute,
@ -12,7 +12,7 @@ use std::{
}, },
time::Duration, time::Duration,
}; };
use tracing::{debug, error, info, warn}; use tracing::{debug, error, warn};
use tui::{ use tui::{
backend::CrosstermBackend, backend::CrosstermBackend,
layout::Rect, layout::Rect,
@ -21,89 +21,6 @@ use tui::{
Terminal, Terminal,
}; };
#[derive(Debug, Clone)]
pub enum Message {
AbortShutdown,
Shutdown { grace_period: Duration },
Quit,
AddAdmin(String),
RemoveAdmin(String),
}
pub struct Command<'a> {
pub name: &'a str,
pub description: &'a str,
// Whether or not the command splits the arguments on whitespace
pub split_spaces: bool,
pub args: usize,
pub cmd: fn(Vec<String>, &mut mpsc::Sender<Message>),
}
// TODO: mabye we could be using clap here?
pub const COMMANDS: [Command; 5] = [
Command {
name: "quit",
description: "Closes the server",
split_spaces: true,
args: 0,
cmd: |_, sender| sender.send(Message::Quit).unwrap(),
},
Command {
name: "shutdown",
description: "Initiates a graceful shutdown of the server, waiting the specified number \
of seconds before shutting down",
split_spaces: true,
args: 1,
cmd: |args, sender| {
if let Ok(grace_period) = args.first().unwrap().parse::<u64>() {
sender
.send(Message::Shutdown {
grace_period: Duration::from_secs(grace_period),
})
.unwrap()
} else {
error!("Grace period must an integer")
}
},
},
Command {
name: "abortshutdown",
description: "Aborts a shutdown if one is in progress",
split_spaces: false,
args: 0,
cmd: |_, sender| sender.send(Message::AbortShutdown).unwrap(),
},
Command {
name: "admin",
description: "Add or remove an admin via \'admin add/remove <username>\'",
split_spaces: true,
args: 2,
cmd: |args, sender| match args.get(..2) {
Some([op, username]) if op == "add" => {
sender.send(Message::AddAdmin(username.clone())).unwrap()
},
Some([op, username]) if op == "remove" => {
sender.send(Message::RemoveAdmin(username.clone())).unwrap()
},
Some(_) => error!("First arg must be add or remove"),
_ => error!("Not enough args, should be unreachable"),
},
},
Command {
name: "help",
description: "List all command available",
split_spaces: true,
args: 0,
cmd: |_, _| {
info!("===== Help =====");
for command in COMMANDS.iter() {
info!("{} - {}", command.name, command.description)
}
info!("================");
},
},
];
pub struct Tui { pub struct Tui {
pub msg_r: mpsc::Receiver<Message>, pub msg_r: mpsc::Receiver<Message>,
background: Option<std::thread::JoinHandle<()>>, background: Option<std::thread::JoinHandle<()>>,
@ -129,7 +46,7 @@ impl Tui {
}, },
KeyCode::Enter => { KeyCode::Enter => {
debug!(?input, "tui mode: command entered"); debug!(?input, "tui mode: command entered");
parse_command(input, msg_s); cmd::parse_command(input, msg_s);
*input = String::new(); *input = String::new();
}, },
@ -163,7 +80,7 @@ impl Tui {
}, },
Ok(_) => { Ok(_) => {
debug!(?line, "basic mode: command entered"); debug!(?line, "basic mode: command entered");
parse_command(&line, &mut msg_s); crate::cmd::parse_command(&line, &mut msg_s);
}, },
} }
} }
@ -263,41 +180,3 @@ impl Drop for Tui {
Tui::shutdown(self.basic); Tui::shutdown(self.basic);
} }
} }
fn parse_command(input: &str, msg_s: &mut mpsc::Sender<Message>) {
let mut args = input.split_whitespace();
if let Some(cmd_name) = args.next() {
if let Some(cmd) = COMMANDS.iter().find(|cmd| cmd.name == cmd_name) {
let args = args.collect::<Vec<_>>();
let (arg_len, args) = if cmd.split_spaces {
(
args.len(),
args.into_iter()
.map(|s| s.to_string())
.collect::<Vec<String>>(),
)
} else {
(0, vec![args.into_iter().collect::<String>()])
};
match arg_len.cmp(&cmd.args) {
std::cmp::Ordering::Less => error!("{} takes {} arguments", cmd_name, cmd.args),
std::cmp::Ordering::Greater => {
warn!("{} only takes {} arguments", cmd_name, cmd.args);
let cmd = cmd.cmd;
cmd(args, msg_s)
},
std::cmp::Ordering::Equal => {
let cmd = cmd.cmd;
cmd(args, msg_s)
},
}
} else {
error!("{} not found", cmd_name);
}
}
}

View File

@ -305,14 +305,10 @@ impl Server {
.min_by_key(|site_pos| site_pos.distance_squared(center_chunk)) .min_by_key(|site_pos| site_pos.distance_squared(center_chunk))
.unwrap_or(center_chunk); .unwrap_or(center_chunk);
// calculate the absolute position of the chunk in the world // Calculate the middle of the chunk in the world
// (we could add TerrainChunkSize::RECT_SIZE / 2 here, to spawn in the middle of let spawn_wpos = TerrainChunkSize::center_wpos(spawn_chunk);
// the chunk)
let spawn_wpos = spawn_chunk.map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
e as i32 * sz as i32 + sz as i32 / 2
});
// unwrapping because generate_chunk only returns err when should_continue evals // Unwrapping because generate_chunk only returns err when should_continue evals
// to true // to true
let (tc, _cs) = world.generate_chunk(index, spawn_chunk, || false).unwrap(); let (tc, _cs) = world.generate_chunk(index, spawn_chunk, || false).unwrap();
let min_z = tc.get_min_z(); let min_z = tc.get_min_z();
@ -345,7 +341,7 @@ impl Server {
#[cfg(not(feature = "worldgen"))] #[cfg(not(feature = "worldgen"))]
let spawn_point = Vec3::new(0.0, 0.0, 256.0); let spawn_point = Vec3::new(0.0, 0.0, 256.0);
// set the spawn point we calculated above // Set the spawn point we calculated above
state.ecs_mut().insert(SpawnPoint(spawn_point)); state.ecs_mut().insert(SpawnPoint(spawn_point));
// Insert the world into the ECS (todo: Maybe not an Arc?) // Insert the world into the ECS (todo: Maybe not an Arc?)
@ -931,30 +927,63 @@ impl Server {
self.state.ecs().read_storage::<Client>().join().count() as i64 self.state.ecs().read_storage::<Client>().join().count() as i64
} }
// TODO: add Admin comp if ingame
pub fn add_admin(&self, username: &str) { pub fn add_admin(&self, username: &str) {
let mut editable_settings = self.editable_settings_mut(); let mut editable_settings = self.editable_settings_mut();
let login_provider = self.state.ecs().fetch::<LoginProvider>(); let login_provider = self.state.ecs().fetch::<LoginProvider>();
let data_dir = self.data_dir(); let data_dir = self.data_dir();
add_admin( if let Some(entity) = add_admin(
username, username,
&login_provider, &login_provider,
&mut editable_settings, &mut editable_settings,
&data_dir.path, &data_dir.path,
); ).and_then(|uuid| {
let state = &self.state;
(&state.ecs().entities(), &state.read_storage::<comp::Player>())
.join()
.find(|(_, player)| player.uuid() == uuid)
.map(|(e, _)| e)
}) {
// Add admin component if the player is ingame
let _ = self.state.ecs().write_storage().insert(entity, comp::Admin);
};
} }
// TODO: remove Admin comp if ingame
pub fn remove_admin(&self, username: &str) { pub fn remove_admin(&self, username: &str) {
let mut editable_settings = self.editable_settings_mut(); let mut editable_settings = self.editable_settings_mut();
let login_provider = self.state.ecs().fetch::<LoginProvider>(); let login_provider = self.state.ecs().fetch::<LoginProvider>();
let data_dir = self.data_dir(); let data_dir = self.data_dir();
remove_admin( if let Some(entity) = remove_admin(
username, username,
&login_provider, &login_provider,
&mut editable_settings, &mut editable_settings,
&data_dir.path, &data_dir.path,
); ).and_then(|uuid| {
let state = &self.state;
(&state.ecs().entities(), &state.read_storage::<comp::Player>())
.join()
.find(|(_, player)| player.uuid() == uuid)
.map(|(e, _)| e)
}) {
// Remove admin component if the player is ingame
let _ = self.state.ecs().write_storage::<comp::Admin>().remove(entity);
};
}
/// Useful for testing without a client
/// view_distance: distance in chunks that are persisted, this acts like the player view
/// distance so it is actually a bit farther due to a buffer zone
pub fn create_centered_persister(&mut self, view_distance: u32) {
let world_dims_chunks = self.world.sim().get_size();
let world_dims_blocks = TerrainChunkSize::blocks(world_dims_chunks);
// NOTE: origin is in the corner of the map
// TODO: extend this function to have picking a random position or specifiying a position
// as options
//let mut rng = rand::thread_rng();
// // Pick a random position but not to close to the edge
// let rand_pos = world_dims_blocks.map(|e| e as i32).map(|e| e / 2 + rng.gen_range(-e/2..e/2 + 1));
let pos = comp::Pos(Vec3::from(world_dims_blocks.map(|e| e as f32 / 2.0)));
self.state.create_persister(pos, view_distance, &self.world, &self.index, &self.runtime).build();
} }
} }
@ -966,35 +995,42 @@ impl Drop for Server {
} }
} }
/// If successful returns the Some(uuid) of the added admin
pub fn add_admin( pub fn add_admin(
username: &str, username: &str,
login_provider: &LoginProvider, login_provider: &LoginProvider,
editable_settings: &mut EditableSettings, editable_settings: &mut EditableSettings,
data_dir: &std::path::Path, data_dir: &std::path::Path,
) { ) -> Option<common::uuid::Uuid> {
use crate::settings::EditableSetting; use crate::settings::EditableSetting;
match login_provider.username_to_uuid(username) { match login_provider.username_to_uuid(username) {
Ok(uuid) => editable_settings.admins.edit(data_dir, |admins| { Ok(uuid) => editable_settings.admins.edit(data_dir, |admins| {
if admins.insert(uuid) { if admins.insert(uuid) {
info!("Successfully added {} ({}) as an admin!", username, uuid); info!("Successfully added {} ({}) as an admin!", username, uuid);
Some(uuid)
} else { } else {
info!("{} ({}) is already an admin!", username, uuid); info!("{} ({}) is already an admin!", username, uuid);
None
} }
}), }),
Err(err) => error!( Err(err) => {
?err, error!(
"Could not find uuid for this name either the user does not exist or there was an \ ?err,
error communicating with the auth server." "Could not find uuid for this name either the user does not exist or there was an \
), error communicating with the auth server."
);
None
},
} }
} }
/// If successful returns the Some(uuid) of the removed admin
pub fn remove_admin( pub fn remove_admin(
username: &str, username: &str,
login_provider: &LoginProvider, login_provider: &LoginProvider,
editable_settings: &mut EditableSettings, editable_settings: &mut EditableSettings,
data_dir: &std::path::Path, data_dir: &std::path::Path,
) { ) -> Option<common::uuid::Uuid> {
use crate::settings::EditableSetting; use crate::settings::EditableSetting;
match login_provider.username_to_uuid(username) { match login_provider.username_to_uuid(username) {
Ok(uuid) => editable_settings.admins.edit(data_dir, |admins| { Ok(uuid) => editable_settings.admins.edit(data_dir, |admins| {
@ -1003,14 +1039,19 @@ pub fn remove_admin(
"Successfully removed {} ({}) from the admins", "Successfully removed {} ({}) from the admins",
username, uuid username, uuid
); );
Some(uuid)
} else { } else {
info!("{} ({}) is not an admin!", username, uuid); info!("{} ({}) is not an admin!", username, uuid);
None
} }
}), }),
Err(err) => error!( Err(err) => {
?err, error!(
"Could not find uuid for this name either the user does not exist or there was an \ ?err,
error communicating with the auth server." "Could not find uuid for this name either the user does not exist or there was an \
), error communicating with the auth server."
);
None
},
} }
} }

View File

@ -63,6 +63,18 @@ pub trait StateExt {
pos: comp::Pos, pos: comp::Pos,
ori: comp::Ori, ori: comp::Ori,
) -> EcsEntityBuilder; ) -> EcsEntityBuilder;
// NOTE: currently only used for testing
/// Queues chunk generation in the view distance of the persister, this
/// entity must be built before those chunks are received (the builder
/// borrows the ecs world so that is kind of impossible in practice)
fn create_persister(
&mut self,
pos: comp::Pos,
view_distance: u32,
world: &std::sync::Arc<world::World>,
index: &world::IndexOwned,
runtime: &tokio::runtime::Runtime,
) -> EcsEntityBuilder;
/// Insert common/default components for a new character joining the server /// Insert common/default components for a new character joining the server
fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId); fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId);
/// Update the components associated with the entity's current character. /// Update the components associated with the entity's current character.
@ -257,6 +269,50 @@ impl StateExt for State {
}) })
} }
// NOTE: currently only used for testing
/// Queues chunk generation in the view distance of the persister, this
/// entity must be built before those chunks are received (the builder
/// borrows the ecs world so that is kind of impossible in practice)
fn create_persister(
&mut self,
pos: comp::Pos,
view_distance: u32,
world: &std::sync::Arc<world::World>,
index: &world::IndexOwned,
runtime: &tokio::runtime::Runtime,
) -> EcsEntityBuilder {
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
use std::sync::Arc;
// Request chunks
{
let mut chunk_generator = self
.ecs()
.write_resource::<crate::chunk_generator::ChunkGenerator>();
let chunk_pos = self.terrain().pos_key(pos.0.map(|e| e as i32));
(-(view_distance as i32)..view_distance as i32 + 1)
.flat_map(|x| {
(-(view_distance as i32)..view_distance as i32 + 1).map(move |y| Vec2::new(x, y))
})
.map(|offset| offset + chunk_pos)
// Filter chunks outside the view distance
// Note: calculation from client chunk request filtering
.filter(|chunk_key| {
pos.0.xy().map(|e| e as f64).distance(
chunk_key.map(|e| e as f64 + 0.5) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64),
) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt())
* TerrainChunkSize::RECT_SIZE.x as f64
})
.for_each(|chunk_key| {
chunk_generator.generate_chunk(None, chunk_key, runtime, Arc::clone(world), index.clone());
});
}
self.ecs_mut()
.create_entity_synced()
.with(pos)
.with(Presence::new(view_distance, PresenceKind::Spectator))
}
fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId) { fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId) {
let spawn_point = self.ecs().read_resource::<SpawnPoint>().0; let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;

View File

@ -23,6 +23,7 @@ use common::{
util::Dir, util::Dir,
vol::ReadVol, vol::ReadVol,
}; };
use common_base::prof_span;
use common_ecs::{Job, Origin, ParMode, Phase, System}; use common_ecs::{Job, Origin, ParMode, Phase, System};
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
@ -141,8 +142,13 @@ impl<'a> System<'a> for Sys {
.map(|ms| *ms == MountState::Unmounted) .map(|ms| *ms == MountState::Unmounted)
.unwrap_or(true) .unwrap_or(true)
}) })
.for_each( .for_each_init(
|( || {
prof_span!(guard, "agent rayon job");
guard
},
|_guard,
(
entity, entity,
(energy, health), (energy, health),
pos, pos,

View File

@ -43,10 +43,10 @@ pub fn init(settings: &Settings) -> Vec<impl Drop> {
env.add_directive("dot_vox::parser=warn".parse().unwrap()) env.add_directive("dot_vox::parser=warn".parse().unwrap())
.add_directive("gfx_device_gl=warn".parse().unwrap()) .add_directive("gfx_device_gl=warn".parse().unwrap())
.add_directive("prometheus_hyper=warn".parse().unwrap()) .add_directive("prometheus_hyper=warn".parse().unwrap())
.add_directive("mio::sys::windows=debug".parse().unwrap()) .add_directive("mio::sys::windows=info".parse().unwrap())
.add_directive("h2=info".parse().unwrap()) .add_directive("h2=info".parse().unwrap())
.add_directive("tokio_util=info".parse().unwrap()) .add_directive("tokio_util=info".parse().unwrap())
.add_directive("rustls=debug".parse().unwrap()) .add_directive("rustls=info".parse().unwrap())
.add_directive("veloren_network_protocol=info".parse().unwrap()) .add_directive("veloren_network_protocol=info".parse().unwrap())
.add_directive( .add_directive(
"veloren_server::persistence::character=info" "veloren_server::persistence::character=info"