From 99da3c71507aad17bffe84476834a6b7fcad211a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Tue, 20 Oct 2020 18:31:19 +0200 Subject: [PATCH] add metrics for physics sys and implement concurrent physics --- .cargo/config | 4 +- common/src/metrics.rs | 7 ++ common/src/state.rs | 3 +- common/src/sys/phys.rs | 228 ++++++++++++++++++++++------------------- server/src/lib.rs | 24 ++++- server/src/metrics.rs | 44 ++++++++ 6 files changed, 203 insertions(+), 107 deletions(-) diff --git a/.cargo/config b/.cargo/config index 745130059d..7e18aed220 100644 --- a/.cargo/config +++ b/.cargo/config @@ -6,8 +6,8 @@ rustflags = [ [alias] generate = "run --package tools --" test-server = "-Zpackage-features run --bin veloren-server-cli --no-default-features" -tracy-server = "-Zunstable-options -Zpackage-features run --bin veloren-server-cli --no-default-features --features tracy --profile no_overflow" +tracy-server = "-Zunstable-options -Zpackage-features run --bin veloren-server-cli --no-default-features --features tracy --profile releasedebuginfo" test-voxygen = "-Zpackage-features run --bin veloren-voxygen --no-default-features --features gl" -tracy-voxygen = "-Zunstable-options -Zpackage-features run --bin veloren-voxygen --no-default-features --features tracy,gl --profile no_overflow" +tracy-voxygen = "-Zunstable-options -Zpackage-features run --bin veloren-voxygen --no-default-features --features tracy,gl --profile releasedebuginfo" server = "run --bin veloren-server-cli" diff --git a/common/src/metrics.rs b/common/src/metrics.rs index e2ec3ec27e..bbcfc9044d 100644 --- a/common/src/metrics.rs +++ b/common/src/metrics.rs @@ -11,3 +11,10 @@ pub struct SysMetrics { pub projectile_ns: AtomicI64, pub melee_ns: AtomicI64, } + +#[derive(Default)] +pub struct PhysicsMetrics { + pub velocities_cache_len: i64, + pub entity_entity_collision_checks: i64, + pub entity_entity_collisions: i64, +} diff --git a/common/src/state.rs b/common/src/state.rs index cbf00daaf2..809292cd33 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -1,7 +1,7 @@ use crate::{ comp, event::{EventBus, LocalEvent, ServerEvent}, - metrics::SysMetrics, + metrics::{PhysicsMetrics, SysMetrics}, region::RegionMap, sync::WorldSyncExt, sys, @@ -183,6 +183,7 @@ impl State { ecs.insert(comp::group::GroupManager::default()); ecs.insert(RegionMap::new()); ecs.insert(SysMetrics::default()); + ecs.insert(PhysicsMetrics::default()); ecs } diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index 876f1c209f..c2068d11bd 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -4,7 +4,7 @@ use crate::{ Shockwave, Sticky, Vel, }, event::{EventBus, ServerEvent}, - metrics::SysMetrics, + metrics::{PhysicsMetrics, SysMetrics}, span, state::DeltaTime, sync::Uid, @@ -12,7 +12,9 @@ use crate::{ vol::ReadVol, }; use rayon::iter::ParallelIterator; -use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, System, WriteStorage}; +use specs::{ + Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, System, WriteExpect, WriteStorage, +}; use std::ops::Range; use vek::*; @@ -53,6 +55,7 @@ impl<'a> System<'a> for Sys { ReadExpect<'a, TerrainGrid>, Read<'a, DeltaTime>, ReadExpect<'a, SysMetrics>, + WriteExpect<'a, PhysicsMetrics>, Read<'a, EventBus>, ReadStorage<'a, Scale>, ReadStorage<'a, Sticky>, @@ -79,6 +82,7 @@ impl<'a> System<'a> for Sys { terrain, dt, sys_metrics, + mut physics_metrics, event_bus, scales, stickies, @@ -143,7 +147,7 @@ impl<'a> System<'a> for Sys { .join() { let id = entity.id() as usize; - let vel_dt = vel.0.clone() * dt.0; + let vel_dt = vel.0 * dt.0; if id >= old_velocities_times_dt.len() { old_velocities_times_dt.resize(id + 1, Vec3::zero()); } @@ -151,7 +155,7 @@ impl<'a> System<'a> for Sys { } drop(guard); span!(guard, "Apply pushback"); - for (entity, pos, vel, scale, mass, collider, _, _, physics, projectile) in ( + let metrics = ( &entities, &positions, &mut velocities, @@ -165,120 +169,138 @@ impl<'a> System<'a> for Sys { // should interact into the collider component or into a separate component projectiles.maybe(), ) - .join() + .par_join() .filter(|(_, _, _, _, _, _, _, sticky, physics, _)| { sticky.is_none() || (physics.on_wall.is_none() && !physics.on_ground) }) - { - let scale = scale.map(|s| s.0).unwrap_or(1.0); - let radius = collider.map(|c| c.get_radius()).unwrap_or(0.5); - let z_limits = collider.map(|c| c.get_z_limits()).unwrap_or((-0.5, 0.5)); - let mass = mass.map(|m| m.0).unwrap_or(scale); - let vel_dt = old_velocities_times_dt[entity.id() as usize]; + .fold(PhysicsMetrics::default, + |mut metrics,(entity, pos, vel, scale, mass, collider, _, _, physics, projectile)| { + let scale = scale.map(|s| s.0).unwrap_or(1.0); + let radius = collider.map(|c| c.get_radius()).unwrap_or(0.5); + let z_limits = collider.map(|c| c.get_z_limits()).unwrap_or((-0.5, 0.5)); + let mass = mass.map(|m| m.0).unwrap_or(scale); + let vel_dt = old_velocities_times_dt[entity.id() as usize]; - // Resets touch_entities in physics - physics.touch_entities.clear(); + // Resets touch_entities in physics + physics.touch_entities.clear(); - let is_projectile = projectile.is_some(); + let is_projectile = projectile.is_some(); - let mut vel_delta = Vec3::zero(); + let mut vel_delta = Vec3::zero(); - for ( - entity_other, - other, - pos_other, - scale_other, - mass_other, - collider_other, - _, - _, - _, - _, - ) in ( - &entities, - &uids, - &positions, - scales.maybe(), - masses.maybe(), - colliders.maybe(), - !&projectiles, - !&mountings, - !&beams, - !&shockwaves, - ) - .join() - { - if entity == entity_other { - continue; - } - - let scale_other = scale_other.map(|s| s.0).unwrap_or(1.0); - let radius_other = collider_other.map(|c| c.get_radius()).unwrap_or(0.5); - - let collision_dist = scale * radius + scale_other * radius_other; - let collision_dist_sqr = collision_dist * collision_dist; - let vel_dt_other = old_velocities_times_dt[entity_other.id() as usize]; - - let pos_diff_squared = (pos.0 - pos_other.0).magnitude_squared(); - let vel_diff_squared = (vel_dt - vel_dt_other).magnitude_squared(); - - // Sanity check: don't try colliding entities that are too far from each other - // Note: I think this catches all cases. If you get entity collision problems, - // try removing this! - if pos_diff_squared > vel_diff_squared + collision_dist_sqr { - continue; - } - - let z_limits_other = collider_other - .map(|c| c.get_z_limits()) - .unwrap_or((-0.5, 0.5)); - let mass_other = mass_other.map(|m| m.0).unwrap_or(scale_other); - //This check after the pos check, as we currently don't have that many massless - // entites [citation needed] - if mass_other == 0.0 { - continue; - } - - const MIN_COLLISION_DIST: f32 = 0.3; - let increments = (vel_diff_squared.sqrt() / MIN_COLLISION_DIST) - .max(1.0) - .ceil() as usize; - let step_delta = 1.0 / increments as f32; - let mut collided = false; - - for i in 0..increments { - let factor = i as f32 * step_delta; - let pos = pos.0 + vel_dt * factor; - let pos_other = pos_other.0 + vel_dt_other * factor; - - let diff = pos.xy() - pos_other.xy(); - - if diff.magnitude_squared() <= collision_dist_sqr - && pos.z + z_limits.1 * scale - >= pos_other.z + z_limits_other.0 * scale_other - && pos.z + z_limits.0 * scale - <= pos_other.z + z_limits_other.1 * scale_other + for ( + entity_other, + other, + pos_other, + scale_other, + mass_other, + collider_other, + _, + _, + _, + _, + ) in ( + &entities, + &uids, + &positions, + scales.maybe(), + masses.maybe(), + colliders.maybe(), + !&projectiles, + !&mountings, + !&beams, + !&shockwaves, + ) + .join() { - if !collided { - physics.touch_entities.push(*other); + if entity == entity_other { + continue; } - // Don't apply repulsive force to projectiles - if diff.magnitude_squared() > 0.0 && !is_projectile { - let force = 400.0 * (collision_dist - diff.magnitude()) * mass_other - / (mass + mass_other); + let scale_other = scale_other.map(|s| s.0).unwrap_or(1.0); + let radius_other = collider_other.map(|c| c.get_radius()).unwrap_or(0.5); - vel_delta += Vec3::from(diff.normalized()) * force * step_delta; + let collision_dist = scale * radius + scale_other * radius_other; + let collision_dist_sqr = collision_dist * collision_dist; + let vel_dt_other = old_velocities_times_dt[entity_other.id() as usize]; + + let pos_diff_squared = (pos.0 - pos_other.0).magnitude_squared(); + let vel_diff_squared = (vel_dt - vel_dt_other).magnitude_squared(); + + // Sanity check: don't try colliding entities that are too far from each + // other Note: I think this catches all cases. If + // you get entity collision problems, try removing + // this! + if pos_diff_squared > vel_diff_squared + collision_dist_sqr { + continue; } - collided = true; + let z_limits_other = collider_other + .map(|c| c.get_z_limits()) + .unwrap_or((-0.5, 0.5)); + let mass_other = mass_other.map(|m| m.0).unwrap_or(scale_other); + //This check after the pos check, as we currently don't have that many + // massless entites [citation needed] + if mass_other == 0.0 { + continue; + } + + metrics.entity_entity_collision_checks += 1; + + const MIN_COLLISION_DIST: f32 = 0.3; + let increments = (vel_diff_squared.sqrt() / MIN_COLLISION_DIST) + .max(1.0) + .ceil() as usize; + let step_delta = 1.0 / increments as f32; + let mut collided = false; + + for i in 0..increments { + let factor = i as f32 * step_delta; + let pos = pos.0 + vel_dt * factor; + let pos_other = pos_other.0 + vel_dt_other * factor; + + let diff = pos.xy() - pos_other.xy(); + + if diff.magnitude_squared() <= collision_dist_sqr + && pos.z + z_limits.1 * scale + >= pos_other.z + z_limits_other.0 * scale_other + && pos.z + z_limits.0 * scale + <= pos_other.z + z_limits_other.1 * scale_other + { + if !collided { + physics.touch_entities.push(*other); + metrics.entity_entity_collisions += 1; + } + + // Don't apply repulsive force to projectiles + if diff.magnitude_squared() > 0.0 && !is_projectile { + let force = + 400.0 * (collision_dist - diff.magnitude()) * mass_other + / (mass + mass_other); + + vel_delta += Vec3::from(diff.normalized()) * force * step_delta; + } + + collided = true; + } + } } - } - } - // Change velocity - vel.0 += vel_delta * dt.0; - } + // Change velocity + vel.0 += vel_delta * dt.0; + metrics + }, + ) + .reduce(PhysicsMetrics::default, |old, new| { + PhysicsMetrics { + velocities_cache_len: 0, + entity_entity_collision_checks: old.entity_entity_collision_checks + new.entity_entity_collision_checks, + entity_entity_collisions: old.entity_entity_collisions + new.entity_entity_collisions, + } + }); + physics_metrics.velocities_cache_len = old_velocities_times_dt.len() as i64; + physics_metrics.entity_entity_collision_checks = metrics.entity_entity_collision_checks; + physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions; drop(guard); // Apply movement inputs diff --git a/server/src/lib.rs b/server/src/lib.rs index 8db6c0532f..036e67f8cc 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -59,7 +59,7 @@ use common::{ vol::{ReadVol, RectVolSize}, }; use futures_executor::block_on; -use metrics::{ServerMetrics, StateTickMetrics, TickMetrics}; +use metrics::{PhysicsMetrics, ServerMetrics, StateTickMetrics, TickMetrics}; use network::{Network, Pid, ProtocolAddr}; use persistence::{ character_loader::{CharacterLoader, CharacterLoaderResponseType}, @@ -108,6 +108,7 @@ pub struct Server { metrics: ServerMetrics, tick_metrics: TickMetrics, state_tick_metrics: StateTickMetrics, + physics_metrics: PhysicsMetrics, } impl Server { @@ -327,12 +328,14 @@ impl Server { let (tick_metrics, registry_tick) = TickMetrics::new(metrics.tick_clone()) .expect("Failed to initialize server tick metrics submodule."); let (state_tick_metrics, registry_state) = StateTickMetrics::new().unwrap(); + let (physics_metrics, registry_physics) = PhysicsMetrics::new().unwrap(); registry_chunk(&metrics.registry()).expect("failed to register chunk gen metrics"); registry_network(&metrics.registry()).expect("failed to register network request metrics"); registry_player(&metrics.registry()).expect("failed to register player metrics"); registry_tick(&metrics.registry()).expect("failed to register tick metrics"); registry_state(&metrics.registry()).expect("failed to register state metrics"); + registry_physics(&metrics.registry()).expect("failed to register state metrics"); let thread_pool = ThreadPoolBuilder::new() .name("veloren-worker".to_string()) @@ -358,6 +361,7 @@ impl Server { metrics, tick_metrics, state_tick_metrics, + physics_metrics, }; debug!(?settings, "created veloren server with"); @@ -777,6 +781,24 @@ impl Server { .observe(melee_ns as f64 / NANOSEC_PER_SEC); } + //detailed physics metrics + { + let res = self + .state + .ecs() + .read_resource::(); + + self.physics_metrics + .velocities_cache_len + .set(res.velocities_cache_len); + self.physics_metrics + .entity_entity_collision_checks_count + .inc_by(res.entity_entity_collision_checks); + self.physics_metrics + .entity_entity_collisions_count + .inc_by(res.entity_entity_collisions); + } + // Report other info self.tick_metrics .time_of_day diff --git a/server/src/metrics.rs b/server/src/metrics.rs index bdb1a52dc8..6148293c25 100644 --- a/server/src/metrics.rs +++ b/server/src/metrics.rs @@ -17,6 +17,12 @@ use tracing::{debug, error}; type RegistryFn = Box Result<(), prometheus::Error>>; +pub struct PhysicsMetrics { + pub velocities_cache_len: IntGauge, + pub entity_entity_collision_checks_count: IntCounter, + pub entity_entity_collisions_count: IntCounter, +} + pub struct StateTickMetrics { // Counter will only give us granularity on pool speed (2s?) for actuall spike detection we // need the Historgram @@ -62,6 +68,44 @@ pub struct ServerMetrics { tick: Arc, } +impl PhysicsMetrics { + pub fn new() -> Result<(Self, RegistryFn), prometheus::Error> { + let velocities_cache_len = IntGauge::with_opts(Opts::new( + "velocities_cache_len", + "shows the size of the velocities_cache in entries", + ))?; + let entity_entity_collision_checks_count = IntCounter::with_opts(Opts::new( + "entity_entity_collision_checks_count", + "shows the number of collision checks", + ))?; + let entity_entity_collisions_count = IntCounter::with_opts(Opts::new( + "entity_entity_collisions_count", + "shows the number of actual collisions detected", + ))?; + + let velocities_cache_len_clone = velocities_cache_len.clone(); + let entity_entity_collision_checks_count_clone = + entity_entity_collision_checks_count.clone(); + let entity_entity_collisions_count_clone = entity_entity_collisions_count.clone(); + + let f = |registry: &Registry| { + registry.register(Box::new(velocities_cache_len_clone))?; + registry.register(Box::new(entity_entity_collision_checks_count_clone))?; + registry.register(Box::new(entity_entity_collisions_count_clone))?; + Ok(()) + }; + + Ok(( + Self { + velocities_cache_len, + entity_entity_collision_checks_count, + entity_entity_collisions_count, + }, + Box::new(f), + )) + } +} + impl StateTickMetrics { pub fn new() -> Result<(Self, RegistryFn), prometheus::Error> { let bucket = vec![