From a76fdbc325e2db8423675212465f81b26bc3222c Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 28 Mar 2021 23:54:49 -0400 Subject: [PATCH] Expose CachedSpatialGrid resource that is updated by the physics system, move BuildAreas into its own module, removed unused ExecMode --- common/src/cached_spatial_grid.rs | 21 +++++++ common/src/lib.rs | 4 ++ common/src/util/mod.rs | 10 ++- .../src/phys => src/util}/spatial_grid.rs | 7 +++ common/state/src/build_areas.rs | 54 ++++++++++++++++ common/state/src/lib.rs | 7 ++- common/state/src/state.rs | 63 +------------------ common/systems/src/phys.rs | 50 +++++++++++---- 8 files changed, 137 insertions(+), 79 deletions(-) create mode 100644 common/src/cached_spatial_grid.rs rename common/{systems/src/phys => src/util}/spatial_grid.rs (95%) create mode 100644 common/state/src/build_areas.rs diff --git a/common/src/cached_spatial_grid.rs b/common/src/cached_spatial_grid.rs new file mode 100644 index 0000000000..92b453868f --- /dev/null +++ b/common/src/cached_spatial_grid.rs @@ -0,0 +1,21 @@ +use crate::util::SpatialGrid; + +/// Cached [`SpatialGrid`] for reuse within different ecs systems during a tick. +/// This is used to accelerate queries on entities within a specific area. +/// Updated within the physics system [`crate::sys::phys::Sys`] after new entity +/// positions are calculated for the tick. So any position modifications outside +/// the physics system will not be reflected here until the next tick when the +/// physics system runs. +pub struct CachedSpatialGrid(pub SpatialGrid); + +impl Default for CachedSpatialGrid { + fn default() -> Self { + let lg2_cell_size = 5; // 32 + let lg2_large_cell_size = 6; // 64 + let radius_cutoff = 8; + + let spatial_grid = SpatialGrid::new(lg2_cell_size, lg2_large_cell_size, radius_cutoff); + + Self(spatial_grid) + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index c78e861453..d4d8f6a094 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -26,6 +26,8 @@ pub use uuid; pub mod assets; #[cfg(not(target_arch = "wasm32"))] pub mod astar; #[cfg(not(target_arch = "wasm32"))] +mod cached_spatial_grid; +#[cfg(not(target_arch = "wasm32"))] pub mod character; #[cfg(not(target_arch = "wasm32"))] pub mod clock; #[cfg(not(target_arch = "wasm32"))] pub mod cmd; @@ -78,6 +80,8 @@ pub mod uid; #[cfg(not(target_arch = "wasm32"))] pub mod volumes; +#[cfg(not(target_arch = "wasm32"))] +pub use cached_spatial_grid::CachedSpatialGrid; pub use combat::DamageSource; #[cfg(not(target_arch = "wasm32"))] pub use combat::{Damage, GroupTarget, Knockback, KnockbackDir}; diff --git a/common/src/util/mod.rs b/common/src/util/mod.rs index 2dbbc44a4f..bfad8f01e9 100644 --- a/common/src/util/mod.rs +++ b/common/src/util/mod.rs @@ -4,6 +4,9 @@ pub mod find_dist; mod option; pub mod plane; pub mod projection; +/// Contains [`SpatialGrid`] which is useful for accelerating queries of nearby +/// entities +mod spatial_grid; pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash")); pub const GIT_TAG: &str = include_str!(concat!(env!("OUT_DIR"), "/gittag")); @@ -28,6 +31,7 @@ lazy_static::lazy_static! { pub use color::*; pub use dir::*; -pub use option::*; -pub use plane::*; -pub use projection::*; +pub use option::either_with; +pub use plane::Plane; +pub use projection::Projection; +pub use spatial_grid::SpatialGrid; diff --git a/common/systems/src/phys/spatial_grid.rs b/common/src/util/spatial_grid.rs similarity index 95% rename from common/systems/src/phys/spatial_grid.rs rename to common/src/util/spatial_grid.rs index 0e5cfd716a..3f7caa5e4d 100644 --- a/common/systems/src/phys/spatial_grid.rs +++ b/common/src/util/spatial_grid.rs @@ -1,5 +1,6 @@ use vek::*; +#[derive(Debug)] pub struct SpatialGrid { // Uses two scales of grids so that we can have a hard limit on how far to search in the // smaller grid @@ -74,4 +75,10 @@ impl SpatialGrid { self.lg2_large_cell_size, )) } + + pub fn clear(&mut self) { + self.grid.clear(); + self.large_grid.clear(); + self.largest_large_radius = self.radius_cutoff; + } } diff --git a/common/state/src/build_areas.rs b/common/state/src/build_areas.rs new file mode 100644 index 0000000000..8d1e273e77 --- /dev/null +++ b/common/state/src/build_areas.rs @@ -0,0 +1,54 @@ +use common::depot::{Depot, Id}; +use hashbrown::{hash_map, HashMap}; +use vek::*; + +/// NOTE: Please don't add `Deserialize` without checking to make sure we +/// can guarantee the invariant that every entry in `area_names` points to a +/// valid id in `areas`. +#[derive(Default)] +pub struct BuildAreas { + areas: Depot>, + area_names: HashMap>>, +} + +pub enum BuildAreaError { + /// This build area name is reserved by the system. + Reserved, + /// The build area name was not found. + NotFound, +} + +/// Build area names that can only be inserted, not removed. +const RESERVED_BUILD_AREA_NAMES: &[&str] = &["world"]; + +impl BuildAreas { + pub fn areas(&self) -> &Depot> { &self.areas } + + pub fn area_names(&self) -> &HashMap>> { &self.area_names } + + /// If the area_name is already in the map, returns Err(area_name). + pub fn insert(&mut self, area_name: String, area: Aabb) -> Result>, String> { + let area_name_entry = match self.area_names.entry(area_name) { + hash_map::Entry::Occupied(o) => return Err(o.replace_key()), + hash_map::Entry::Vacant(v) => v, + }; + let bb_id = self.areas.insert(area.made_valid()); + area_name_entry.insert(bb_id); + Ok(bb_id) + } + + pub fn remove(&mut self, area_name: &str) -> Result, BuildAreaError> { + if RESERVED_BUILD_AREA_NAMES.contains(&area_name) { + return Err(BuildAreaError::Reserved); + } + let bb_id = self + .area_names + .remove(area_name) + .ok_or(BuildAreaError::NotFound)?; + let area = self.areas.remove(bb_id).expect( + "Entries in `areas` are added before entries in `area_names` in `insert`, and that is \ + the only exposed way to add elements to `area_names`.", + ); + Ok(area) + } +} diff --git a/common/state/src/lib.rs b/common/state/src/lib.rs index 8c1ce6bbcb..d0047fe6de 100644 --- a/common/state/src/lib.rs +++ b/common/state/src/lib.rs @@ -1,8 +1,9 @@ //! This crate contains the [`State`] and shared between //! server (`veloren-server`) and the client (`veloren-client`) +mod build_areas; #[cfg(feature = "plugins")] pub mod plugin; mod state; - -// TODO: breakup state module and remove globs -pub use state::*; +// TODO: breakup state module and remove glob +pub use build_areas::{BuildAreaError, BuildAreas}; +pub use state::{BlockChange, State, TerrainChanges}; diff --git a/common/state/src/state.rs b/common/state/src/state.rs index 27d9e1d8d7..b51797098e 100644 --- a/common/state/src/state.rs +++ b/common/state/src/state.rs @@ -6,7 +6,6 @@ use crate::plugin::PluginMgr; use common::uid::UidAllocator; use common::{ comp, - depot::{Depot, Id}, event::{EventBus, LocalEvent, ServerEvent}, outcome::Outcome, region::RegionMap, @@ -21,7 +20,7 @@ use common_base::span; use common_ecs::{PhysicsMetrics, SysMetrics}; use common_net::sync::{interpolation as sync_interp, WorldSyncExt}; use core::{convert::identity, time::Duration}; -use hashbrown::{hash_map, HashMap, HashSet}; +use hashbrown::{HashMap, HashSet}; use rayon::{ThreadPool, ThreadPoolBuilder}; use specs::{ prelude::Resource, @@ -43,57 +42,6 @@ const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0; /// avoid such a situation. const MAX_DELTA_TIME: f32 = 1.0; -/// NOTE: Please don't add `Deserialize` without checking to make sure we -/// can guarantee the invariant that every entry in `area_names` points to a -/// valid id in `areas`. -#[derive(Default)] -pub struct BuildAreas { - areas: Depot>, - area_names: HashMap>>, -} - -pub enum BuildAreaError { - /// This build area name is reserved by the system. - Reserved, - /// The build area name was not found. - NotFound, -} - -/// Build area names that can only be inserted, not removed. -const RESERVED_BUILD_AREA_NAMES: &[&str] = &["world"]; - -impl BuildAreas { - pub fn areas(&self) -> &Depot> { &self.areas } - - pub fn area_names(&self) -> &HashMap>> { &self.area_names } - - /// If the area_name is already in the map, returns Err(area_name). - pub fn insert(&mut self, area_name: String, area: Aabb) -> Result>, String> { - let area_name_entry = match self.area_names.entry(area_name) { - hash_map::Entry::Occupied(o) => return Err(o.replace_key()), - hash_map::Entry::Vacant(v) => v, - }; - let bb_id = self.areas.insert(area.made_valid()); - area_name_entry.insert(bb_id); - Ok(bb_id) - } - - pub fn remove(&mut self, area_name: &str) -> Result, BuildAreaError> { - if RESERVED_BUILD_AREA_NAMES.contains(&area_name) { - return Err(BuildAreaError::Reserved); - } - let bb_id = self - .area_names - .remove(area_name) - .ok_or(BuildAreaError::NotFound)?; - let area = self.areas.remove(bb_id).expect( - "Entries in `areas` are added before entries in `area_names` in `insert`, and that is \ - the only exposed way to add elements to `area_names`.", - ); - Ok(area) - } -} - #[derive(Default)] pub struct BlockChange { blocks: HashMap, Block>, @@ -130,13 +78,6 @@ impl TerrainChanges { } } -#[derive(Copy, Clone)] -pub enum ExecMode { - Server, - Client, - Singleplayer, -} - /// A type used to represent game state stored on both the client and the /// server. This includes things like entity components, terrain data, and /// global states like weather, time of day, etc. @@ -260,7 +201,7 @@ impl State { ecs.insert(PlayerEntity(None)); ecs.insert(TerrainGrid::new().unwrap()); ecs.insert(BlockChange::default()); - ecs.insert(BuildAreas::default()); + ecs.insert(crate::build_areas::BuildAreas::default()); ecs.insert(TerrainChanges::default()); ecs.insert(EventBus::::default()); ecs.insert(game_mode); diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 6d766b77fe..b20115ec39 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -1,7 +1,3 @@ -mod spatial_grid; - -use spatial_grid::SpatialGrid; - use common::{ comp::{ body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST}, @@ -15,7 +11,7 @@ use common::{ resources::DeltaTime, terrain::{Block, TerrainGrid}, uid::Uid, - util::Projection, + util::{Projection, SpatialGrid}, vol::{BaseVol, ReadVol}, }; use common_base::{prof_span, span}; @@ -148,6 +144,7 @@ pub struct PhysicsRead<'a> { #[derive(SystemData)] pub struct PhysicsWrite<'a> { physics_metrics: WriteExpect<'a, PhysicsMetrics>, + cached_spatial_grid: WriteExpect<'a, common::CachedSpatialGrid>, physics_states: WriteStorage<'a, PhysicsState>, positions: WriteStorage<'a, Pos>, velocities: WriteStorage<'a, Vel>, @@ -1119,6 +1116,32 @@ impl<'a> PhysicsData<'a> { event_emitter.emit(ServerEvent::LandOnGround { entity, vel: vel.0 }); }); } + + fn update_cached_spatial_grid(&mut self) { + span!(_guard, "Update cached spatial grid"); + let PhysicsData { + ref read, + ref mut write, + } = self; + + let spatial_grid = &mut write.cached_spatial_grid.0; + spatial_grid.clear(); + ( + &read.entities, + &write.positions, + read.scales.maybe(), + read.colliders.maybe(), + ) + .join() + .for_each(|(entity, pos, scale, collider)| { + let scale = scale.map(|s| s.0).unwrap_or(1.0); + let radius_2d = + (collider.map(|c| c.get_radius()).unwrap_or(0.5) * scale).ceil() as u32; + let pos_2d = pos.0.xy().map(|e| e as i32); + const POS_TRUNCATION_ERROR: u32 = 1; + spatial_grid.insert(pos_2d, radius_2d + POS_TRUNCATION_ERROR, entity); + }); + } } impl<'a> System<'a> for Sys { @@ -1128,8 +1151,8 @@ impl<'a> System<'a> for Sys { const ORIGIN: Origin = Origin::Common; const PHASE: Phase = Phase::Create; - fn run(job: &mut Job, mut psd: Self::SystemData) { - psd.reset(); + fn run(job: &mut Job, mut physics_data: Self::SystemData) { + physics_data.reset(); // Apply pushback // @@ -1144,13 +1167,16 @@ impl<'a> System<'a> for Sys { // terrain collision code below, although that's not trivial to do since // it means the step needs to take into account the speeds of both // entities. - psd.maintain_pushback_cache(); + physics_data.maintain_pushback_cache(); - let spatial_grid = psd.construct_spatial_grid(); - psd.apply_pushback(job, &spatial_grid); + let spatial_grid = physics_data.construct_spatial_grid(); + physics_data.apply_pushback(job, &spatial_grid); - let voxel_collider_spatial_grid = psd.construct_voxel_collider_spatial_grid(); - psd.handle_movement_and_terrain(job, &voxel_collider_spatial_grid); + let voxel_collider_spatial_grid = physics_data.construct_voxel_collider_spatial_grid(); + physics_data.handle_movement_and_terrain(job, &voxel_collider_spatial_grid); + + // Spatial grid used by other systems + physics_data.update_cached_spatial_grid(); } }