mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Don't parallelize spatial grid construction.
Instead, let the single threaded systems run more in parallel. This is the beginning of an effort to make physics run concurrently with almost all other systems.
This commit is contained in:
parent
cc6d904c75
commit
cead27989b
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -6718,6 +6718,7 @@ dependencies = [
|
||||
"sha2",
|
||||
"slab",
|
||||
"slotmap 1.0.6",
|
||||
"smallvec",
|
||||
"specs",
|
||||
"spin_sleep",
|
||||
"structopt",
|
||||
@ -6861,7 +6862,7 @@ dependencies = [
|
||||
"crossbeam-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"hashbrown 0.9.1",
|
||||
"hashbrown 0.12.3",
|
||||
"lazy_static",
|
||||
"lz-fear",
|
||||
"prometheus",
|
||||
|
@ -71,9 +71,9 @@ petgraph = { version = "0.6", optional = true }
|
||||
kiddo = { version = "0.1", optional = true }
|
||||
|
||||
# Data structures
|
||||
dashmap = { version = "5.4.0", features = ["rayon"] }
|
||||
hashbrown = { version = "0.12", features = ["rayon", "serde", "nightly"] }
|
||||
slotmap = { version = "1.0", features = ["serde"] }
|
||||
smallvec = { version = "1.9.0", features = ["union", "const_generics", "const_new", "specialization", "may_dangle"] }
|
||||
indexmap = { version = "1.3.0", features = ["rayon"] }
|
||||
slab = "0.4.2"
|
||||
|
||||
|
@ -14,7 +14,7 @@ impl Default for CachedSpatialGrid {
|
||||
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).into_read_only();
|
||||
let spatial_grid = SpatialGrid::new(lg2_cell_size, lg2_large_cell_size, radius_cutoff, (0, 0)).into_read_only();
|
||||
|
||||
Self(spatial_grid)
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ pub use self::{
|
||||
player::DisconnectReason,
|
||||
player::{AliasError, Player, MAX_ALIAS_LEN},
|
||||
poise::{Poise, PoiseChange, PoiseState},
|
||||
projectile::{Projectile, ProjectileConstructor},
|
||||
projectile::{Projectile, ProjectileConstructor, ProjectileOwned},
|
||||
shockwave::{Shockwave, ShockwaveHitEntities},
|
||||
skillset::{
|
||||
skills::{self, Skill},
|
||||
|
@ -23,11 +23,6 @@ pub enum Effect {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Projectile {
|
||||
// TODO: use SmallVec for these effects
|
||||
pub hit_solid: Vec<Effect>,
|
||||
pub hit_entity: Vec<Effect>,
|
||||
/// Time left until the projectile will despawn
|
||||
pub time_left: Duration,
|
||||
pub owner: Option<Uid>,
|
||||
/// Whether projectile collides with entities in the same group as its
|
||||
/// owner
|
||||
@ -38,10 +33,26 @@ pub struct Projectile {
|
||||
pub is_point: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ProjectileOwned {
|
||||
/// TODO: use SmallVec for these effects
|
||||
pub hit_solid: Vec<Effect>,
|
||||
pub hit_entity: Vec<Effect>,
|
||||
/// Time left until the projectile will despawn
|
||||
///
|
||||
/// TODO: Remove this, we can calculate this from an initial time which we
|
||||
/// can store in the regular component.
|
||||
pub time_left: Duration,
|
||||
}
|
||||
|
||||
impl Component for Projectile {
|
||||
type Storage = specs::DenseVecStorage<Self>;
|
||||
}
|
||||
|
||||
impl Component for ProjectileOwned {
|
||||
type Storage = specs::DenseVecStorage<Self>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ProjectileConstructor {
|
||||
Arrow {
|
||||
@ -103,7 +114,7 @@ impl ProjectileConstructor {
|
||||
crit_chance: f32,
|
||||
crit_mult: f32,
|
||||
buff_strength: f32,
|
||||
) -> Projectile {
|
||||
) -> (ProjectileOwned, Projectile) {
|
||||
let instance = rand::random();
|
||||
use ProjectileConstructor::*;
|
||||
match self {
|
||||
@ -145,15 +156,19 @@ impl ProjectileConstructor {
|
||||
.with_effect(knockback)
|
||||
.with_combo_increment();
|
||||
|
||||
Projectile {
|
||||
(
|
||||
ProjectileOwned {
|
||||
hit_solid: vec![Effect::Stick, Effect::Bonk],
|
||||
hit_entity: vec![Effect::Attack(attack), Effect::Vanish],
|
||||
time_left: Duration::from_secs(15),
|
||||
},
|
||||
Projectile {
|
||||
owner,
|
||||
ignore_group: true,
|
||||
is_sticky: true,
|
||||
is_point: true,
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
Fireball {
|
||||
damage,
|
||||
@ -193,15 +208,19 @@ impl ProjectileConstructor {
|
||||
reagent: Some(Reagent::Red),
|
||||
min_falloff,
|
||||
};
|
||||
Projectile {
|
||||
(
|
||||
ProjectileOwned {
|
||||
hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish],
|
||||
hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
|
||||
time_left: Duration::from_secs(10),
|
||||
},
|
||||
Projectile {
|
||||
owner,
|
||||
ignore_group: true,
|
||||
is_sticky: true,
|
||||
is_point: true,
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
Frostball {
|
||||
damage,
|
||||
@ -227,15 +246,19 @@ impl ProjectileConstructor {
|
||||
reagent: Some(Reagent::White),
|
||||
min_falloff,
|
||||
};
|
||||
Projectile {
|
||||
(
|
||||
ProjectileOwned {
|
||||
hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish],
|
||||
hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
|
||||
time_left: Duration::from_secs(10),
|
||||
},
|
||||
Projectile {
|
||||
owner,
|
||||
ignore_group: true,
|
||||
is_sticky: true,
|
||||
is_point: true,
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
Poisonball {
|
||||
damage,
|
||||
@ -274,15 +297,19 @@ impl ProjectileConstructor {
|
||||
reagent: Some(Reagent::Purple),
|
||||
min_falloff,
|
||||
};
|
||||
Projectile {
|
||||
(
|
||||
ProjectileOwned {
|
||||
hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish],
|
||||
hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
|
||||
time_left: Duration::from_secs(10),
|
||||
},
|
||||
Projectile {
|
||||
owner,
|
||||
ignore_group: true,
|
||||
is_sticky: true,
|
||||
is_point: true,
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
NecroticSphere {
|
||||
damage,
|
||||
@ -308,25 +335,33 @@ impl ProjectileConstructor {
|
||||
reagent: Some(Reagent::Purple),
|
||||
min_falloff,
|
||||
};
|
||||
Projectile {
|
||||
(
|
||||
ProjectileOwned {
|
||||
hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish],
|
||||
hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
|
||||
time_left: Duration::from_secs(10),
|
||||
},
|
||||
Projectile {
|
||||
owner,
|
||||
ignore_group: true,
|
||||
is_sticky: true,
|
||||
is_point: true,
|
||||
}
|
||||
},
|
||||
Possess => Projectile {
|
||||
)
|
||||
},
|
||||
Possess => (
|
||||
ProjectileOwned {
|
||||
hit_solid: vec![Effect::Stick],
|
||||
hit_entity: vec![Effect::Stick, Effect::Possess],
|
||||
time_left: Duration::from_secs(10),
|
||||
},
|
||||
Projectile {
|
||||
owner,
|
||||
ignore_group: false,
|
||||
is_sticky: true,
|
||||
is_point: true,
|
||||
},
|
||||
),
|
||||
ClayRocket {
|
||||
damage,
|
||||
radius,
|
||||
@ -363,15 +398,19 @@ impl ProjectileConstructor {
|
||||
reagent: Some(Reagent::Red),
|
||||
min_falloff,
|
||||
};
|
||||
Projectile {
|
||||
(
|
||||
ProjectileOwned {
|
||||
hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish],
|
||||
hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
|
||||
time_left: Duration::from_secs(10),
|
||||
},
|
||||
Projectile {
|
||||
owner,
|
||||
ignore_group: true,
|
||||
is_sticky: true,
|
||||
is_point: true,
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
Snowball {
|
||||
damage,
|
||||
@ -396,15 +435,19 @@ impl ProjectileConstructor {
|
||||
reagent: Some(Reagent::White),
|
||||
min_falloff,
|
||||
};
|
||||
Projectile {
|
||||
(
|
||||
ProjectileOwned {
|
||||
hit_solid: vec![],
|
||||
hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
|
||||
time_left: Duration::from_secs(120),
|
||||
},
|
||||
Projectile {
|
||||
owner,
|
||||
ignore_group: true,
|
||||
is_sticky: false,
|
||||
is_point: false,
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
ExplodingPumpkin {
|
||||
damage,
|
||||
@ -453,15 +496,19 @@ impl ProjectileConstructor {
|
||||
reagent: Some(Reagent::Red),
|
||||
min_falloff,
|
||||
};
|
||||
Projectile {
|
||||
(
|
||||
ProjectileOwned {
|
||||
hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish],
|
||||
hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
|
||||
time_left: Duration::from_secs(10),
|
||||
},
|
||||
Projectile {
|
||||
owner,
|
||||
ignore_group: true,
|
||||
is_sticky: true,
|
||||
is_point: true,
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
DagonBomb {
|
||||
damage,
|
||||
@ -510,15 +557,19 @@ impl ProjectileConstructor {
|
||||
reagent: Some(Reagent::Blue),
|
||||
min_falloff,
|
||||
};
|
||||
Projectile {
|
||||
(
|
||||
ProjectileOwned {
|
||||
hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish],
|
||||
hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
|
||||
time_left: Duration::from_secs(10),
|
||||
},
|
||||
Projectile {
|
||||
owner,
|
||||
ignore_group: true,
|
||||
is_sticky: true,
|
||||
is_point: true,
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ pub enum ServerEvent {
|
||||
dir: Dir,
|
||||
body: comp::Body,
|
||||
light: Option<comp::LightEmitter>,
|
||||
projectile: comp::Projectile,
|
||||
projectile: (comp::ProjectileOwned, comp::Projectile),
|
||||
speed: f32,
|
||||
object: Option<comp::Object>,
|
||||
},
|
||||
@ -148,7 +148,7 @@ pub enum ServerEvent {
|
||||
anchor: Option<comp::Anchor>,
|
||||
loot: LootSpec<String>,
|
||||
rtsim_entity: Option<RtSimEntity>,
|
||||
projectile: Option<comp::Projectile>,
|
||||
projectile: Option<(comp::ProjectileOwned, comp::Projectile)>,
|
||||
},
|
||||
CreateShip {
|
||||
pos: Pos,
|
||||
|
@ -4,7 +4,7 @@ use crate::{
|
||||
character_state::OutputEvents,
|
||||
inventory::loadout_builder::{self, LoadoutBuilder},
|
||||
skillset::skills,
|
||||
Behavior, BehaviorCapability, CharacterState, Projectile, StateUpdate,
|
||||
Behavior, BehaviorCapability, CharacterState, Projectile, ProjectileOwned, StateUpdate,
|
||||
},
|
||||
event::{LocalEvent, ServerEvent},
|
||||
outcome::Outcome,
|
||||
@ -162,15 +162,17 @@ impl CharacterBehavior for Data {
|
||||
.0;
|
||||
|
||||
// If a duration is specified, create a projectile component for the npc
|
||||
let projectile = self.static_data.duration.map(|duration| Projectile {
|
||||
let projectile = self.static_data.duration.map(|duration| (ProjectileOwned {
|
||||
hit_solid: Vec::new(),
|
||||
hit_entity: Vec::new(),
|
||||
time_left: duration,
|
||||
},
|
||||
Projectile {
|
||||
owner: Some(*data.uid),
|
||||
ignore_group: true,
|
||||
is_sticky: false,
|
||||
is_point: false,
|
||||
});
|
||||
}));
|
||||
|
||||
// Send server event to create npc
|
||||
output_events.emit_server(ServerEvent::CreateNpc {
|
||||
|
@ -1,8 +1,13 @@
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
use core::sync::atomic::AtomicU32;
|
||||
use smallvec::SmallVec;
|
||||
use vek::*;
|
||||
|
||||
pub type MapMut = dashmap::DashMap<Vec2<i32>, Vec<specs::Entity>>;
|
||||
pub type MapRef = dashmap::ReadOnlyView<Vec2<i32>, Vec<specs::Entity>>;
|
||||
// NOTE: (Vec2<i32>, [specs::Entity; 6]) should fit in a cacheline, reducing false sharing if we
|
||||
// ever decide to directly update the SmallVecs.
|
||||
type EntityVec = SmallVec<[specs::Entity; 6]>;
|
||||
|
||||
pub type MapMut = /*dashmap::DashMap*/hashbrown::HashMap<Vec2<i32>, EntityVec>;
|
||||
pub type MapRef = /*dashmap::ReadOnlyView*/hashbrown::HashMap<Vec2<i32>, EntityVec>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SpatialGridInner<Map, Radius> {
|
||||
@ -32,10 +37,10 @@ pub type SpatialGrid = SpatialGridInner<MapMut, AtomicU32>;
|
||||
pub type SpatialGridRef = SpatialGridInner<MapRef, u32>;
|
||||
|
||||
impl SpatialGrid {
|
||||
pub fn new(lg2_cell_size: usize, lg2_large_cell_size: usize, radius_cutoff: u32) -> Self {
|
||||
pub fn new(lg2_cell_size: usize, lg2_large_cell_size: usize, radius_cutoff: u32, (capacity, large_capacity): (usize, usize)) -> Self {
|
||||
Self {
|
||||
grid: Default::default(),
|
||||
large_grid: Default::default(),
|
||||
grid: MapMut::with_capacity(capacity),
|
||||
large_grid: MapMut::with_capacity(large_capacity),
|
||||
lg2_cell_size,
|
||||
lg2_large_cell_size,
|
||||
radius_cutoff,
|
||||
@ -43,8 +48,12 @@ impl SpatialGrid {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> (usize, usize) {
|
||||
(self.grid.len(), self.large_grid.len())
|
||||
}
|
||||
|
||||
/// Add an entity at the provided 2d pos into the spatial grid
|
||||
pub fn insert(&self, pos: Vec2<i32>, radius: u32, entity: specs::Entity) {
|
||||
pub fn insert(&mut self, pos: Vec2<i32>, radius: u32, entity: specs::Entity) {
|
||||
if radius <= self.radius_cutoff {
|
||||
let cell = pos.map(|e| e >> self.lg2_cell_size);
|
||||
self.grid.entry(cell).or_default().push(entity);
|
||||
@ -59,14 +68,16 @@ impl SpatialGrid {
|
||||
//
|
||||
// TODO: Verify that intrinsics lower intelligently to a priority update on CPUs (since
|
||||
// the intrinsic seems targeted at GPUs).
|
||||
self.largest_large_radius.fetch_max(radius, Ordering::Relaxed);
|
||||
/* self.largest_large_radius.fetch_max(radius, Ordering::Relaxed); */
|
||||
let largest_radius = self.largest_large_radius.get_mut();
|
||||
*largest_radius = (*largest_radius).max(radius);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_read_only(self) -> SpatialGridRef {
|
||||
SpatialGridInner {
|
||||
grid: self.grid.into_read_only(),
|
||||
large_grid: self.large_grid.into_read_only(),
|
||||
grid: self.grid/*.into_read_only()*/,
|
||||
large_grid: self.large_grid/*.into_read_only()*/,
|
||||
lg2_cell_size: self.lg2_cell_size,
|
||||
lg2_large_cell_size: self.lg2_large_cell_size,
|
||||
radius_cutoff: self.radius_cutoff,
|
||||
@ -136,8 +147,8 @@ impl SpatialGridRef {
|
||||
|
||||
pub fn into_inner(self) -> SpatialGrid {
|
||||
SpatialGridInner {
|
||||
grid: self.grid.into_inner(),
|
||||
large_grid: self.large_grid.into_inner(),
|
||||
grid: self.grid/*.into_inner()*/,
|
||||
large_grid: self.large_grid/*.into_inner()*/,
|
||||
lg2_cell_size: self.lg2_cell_size,
|
||||
lg2_large_cell_size: self.lg2_large_cell_size,
|
||||
radius_cutoff: self.radius_cutoff,
|
||||
|
@ -214,6 +214,7 @@ impl State {
|
||||
ecs.register::<comp::Admin>();
|
||||
ecs.register::<comp::Waypoint>();
|
||||
ecs.register::<comp::MapMarker>();
|
||||
ecs.register::<comp::ProjectileOwned>();
|
||||
ecs.register::<comp::Projectile>();
|
||||
ecs.register::<comp::Melee>();
|
||||
ecs.register::<comp::ItemDrop>();
|
||||
|
@ -20,10 +20,9 @@ use common::{
|
||||
use common_base::prof_span;
|
||||
use common_ecs::{Job, Origin, ParMode, Phase, System};
|
||||
use hashbrown::HashMap;
|
||||
use rayon::iter::ParallelIterator;
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity, Join, ParJoin, Read,
|
||||
ReadExpect, ReadStorage, SystemData, World, WriteStorage,
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity, Join, Read, ReadExpect,
|
||||
ReadStorage, SystemData, World, WriteStorage,
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
@ -71,20 +70,17 @@ impl<'a> System<'a> for Sys {
|
||||
// removes burning, but campfires don't have healths/stats/energies/buffs, so
|
||||
// this needs a separate loop.
|
||||
job.cpu_stats.measure(ParMode::Rayon);
|
||||
let to_put_out_campfires = (
|
||||
let light_emitters_mask = light_emitters.mask().clone();
|
||||
prof_span!(guard_, "buff campfire deactivate");
|
||||
(
|
||||
&read_data.entities,
|
||||
&bodies,
|
||||
&mut bodies,
|
||||
&read_data.physics_states,
|
||||
&light_emitters, //to improve iteration speed
|
||||
light_emitters_mask, //to improve iteration speed
|
||||
)
|
||||
.par_join()
|
||||
.map_init(
|
||||
|| {
|
||||
prof_span!(guard, "buff campfire deactivate");
|
||||
guard
|
||||
},
|
||||
|_guard, (entity, body, physics_state, _)| {
|
||||
if matches!(*body, Body::Object(object::Body::CampfireLit))
|
||||
.join()
|
||||
.filter(|(_, body, physics_state, _)| {
|
||||
matches!(&**body, Body::Object(object::Body::CampfireLit))
|
||||
&& matches!(
|
||||
physics_state.in_fluid,
|
||||
Some(Fluid::Liquid {
|
||||
@ -92,38 +88,12 @@ impl<'a> System<'a> for Sys {
|
||||
..
|
||||
})
|
||||
)
|
||||
{
|
||||
Some(entity)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.fold(Vec::new, |mut to_put_out_campfires, put_out_campfire| {
|
||||
put_out_campfire.map(|put| to_put_out_campfires.push(put));
|
||||
to_put_out_campfires
|
||||
})
|
||||
.reduce(
|
||||
Vec::new,
|
||||
|mut to_put_out_campfires_a, mut to_put_out_campfires_b| {
|
||||
to_put_out_campfires_a.append(&mut to_put_out_campfires_b);
|
||||
to_put_out_campfires_a
|
||||
},
|
||||
);
|
||||
job.cpu_stats.measure(ParMode::Single);
|
||||
{
|
||||
prof_span!(_guard, "write deferred campfire deletion");
|
||||
// Assume that to_put_out_campfires is near to zero always, so this access isn't
|
||||
// slower than parallel checking above
|
||||
for e in to_put_out_campfires {
|
||||
{
|
||||
bodies
|
||||
.get_mut(e)
|
||||
.map(|mut body| *body = Body::Object(object::Body::Campfire));
|
||||
.for_each(|(e, mut body, _, _)| {
|
||||
*body = Body::Object(object::Body::Campfire);
|
||||
light_emitters.remove(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
drop(guard_);
|
||||
|
||||
for (entity, mut buff_comp, mut stat, health, energy, physics_state) in (
|
||||
&read_data.entities,
|
||||
|
@ -1,4 +1,4 @@
|
||||
#![feature(btree_drain_filter)]
|
||||
#![feature(let_chains, btree_drain_filter)]
|
||||
#![allow(clippy::option_map_unit_fn)]
|
||||
|
||||
mod aura;
|
||||
|
@ -12,6 +12,7 @@ use common::{
|
||||
mounting::Rider,
|
||||
outcome::Outcome,
|
||||
resources::DeltaTime,
|
||||
slowjob::SlowJobPool,
|
||||
states,
|
||||
terrain::{Block, BlockKind, TerrainGrid},
|
||||
uid::Uid,
|
||||
@ -107,6 +108,7 @@ pub struct PhysicsRead<'a> {
|
||||
entities: Entities<'a>,
|
||||
uids: ReadStorage<'a, Uid>,
|
||||
terrain: ReadExpect<'a, TerrainGrid>,
|
||||
slowjob: ReadExpect<'a, SlowJobPool>,
|
||||
dt: Read<'a, DeltaTime>,
|
||||
event_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
scales: ReadStorage<'a, Scale>,
|
||||
@ -121,6 +123,7 @@ pub struct PhysicsRead<'a> {
|
||||
character_states: ReadStorage<'a, CharacterState>,
|
||||
densities: ReadStorage<'a, Density>,
|
||||
stats: ReadStorage<'a, Stats>,
|
||||
outcomes: Read<'a, EventBus<Outcome>>,
|
||||
}
|
||||
|
||||
#[derive(SystemData)]
|
||||
@ -133,7 +136,6 @@ pub struct PhysicsWrite<'a> {
|
||||
pos_vel_ori_defers: WriteStorage<'a, PosVelOriDefer>,
|
||||
orientations: WriteStorage<'a, Ori>,
|
||||
previous_phys_cache: WriteStorage<'a, PreviousPhysCache>,
|
||||
outcomes: Read<'a, EventBus<Outcome>>,
|
||||
}
|
||||
|
||||
#[derive(SystemData)]
|
||||
@ -142,46 +144,53 @@ pub struct PhysicsData<'a> {
|
||||
write: PhysicsWrite<'a>,
|
||||
}
|
||||
|
||||
impl<'a> PhysicsData<'a> {
|
||||
impl<'a> PhysicsRead<'a> {
|
||||
/// Add/reset physics state components
|
||||
fn reset(&mut self) {
|
||||
fn reset(
|
||||
&self,
|
||||
positions: &WriteStorage<'a, Pos>,
|
||||
velocities: &WriteStorage<'a, Vel>,
|
||||
orientations: &WriteStorage<'a, Ori>,
|
||||
physics_states: &mut WriteStorage<'a, PhysicsState>,
|
||||
) {
|
||||
span!(_guard, "Add/reset physics state components");
|
||||
(
|
||||
&self.read.entities,
|
||||
self.read.colliders.mask(),
|
||||
self.write.positions.mask(),
|
||||
self.write.velocities.mask(),
|
||||
self.write.orientations.mask(),
|
||||
&self.entities,
|
||||
self.colliders.mask(),
|
||||
positions.mask(),
|
||||
velocities.mask(),
|
||||
orientations.mask(),
|
||||
)
|
||||
.join()
|
||||
.for_each(|(entity, _, _, _, _)| {
|
||||
let _ = self
|
||||
.write
|
||||
.physics_states
|
||||
let _ = physics_states
|
||||
.entry(entity)
|
||||
.map(|e| e.or_insert_with(Default::default));
|
||||
});
|
||||
}
|
||||
|
||||
fn maintain_pushback_cache(&mut self) {
|
||||
fn maintain_pushback_cache(
|
||||
&self,
|
||||
positions: &WriteStorage<'a, Pos>,
|
||||
velocities: &WriteStorage<'a, Vel>,
|
||||
orientations: &WriteStorage<'a, Ori>,
|
||||
previous_phys_cache: &mut WriteStorage<'a, PreviousPhysCache>,
|
||||
) {
|
||||
span!(_guard, "Maintain pushback cache");
|
||||
// Add PreviousPhysCache for all relevant entities
|
||||
(
|
||||
&self.read.entities,
|
||||
self.read.colliders.mask(),
|
||||
self.write.velocities.mask(),
|
||||
self.write.positions.mask(),
|
||||
!self.write.previous_phys_cache.mask(),
|
||||
&self.entities,
|
||||
self.colliders.mask(),
|
||||
velocities.mask(),
|
||||
positions.mask(),
|
||||
!previous_phys_cache.mask(),
|
||||
)
|
||||
.join()
|
||||
.map(|(e, _, _, _, _)| e)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.for_each(|entity| {
|
||||
let _ = self
|
||||
.write
|
||||
.previous_phys_cache
|
||||
.insert(entity, PreviousPhysCache {
|
||||
let _ = previous_phys_cache.insert(entity, PreviousPhysCache {
|
||||
velocity_dt: Vec3::zero(),
|
||||
center: Vec3::zero(),
|
||||
collision_boundary: 0.0,
|
||||
@ -197,13 +206,13 @@ impl<'a> PhysicsData<'a> {
|
||||
// Update PreviousPhysCache
|
||||
(
|
||||
/* &self.read.entities, */
|
||||
&self.write.velocities,
|
||||
&self.write.positions,
|
||||
&self.write.orientations,
|
||||
&mut self.write.previous_phys_cache,
|
||||
&self.read.colliders,
|
||||
self.read.scales.maybe(),
|
||||
self.read.char_states.maybe(),
|
||||
velocities,
|
||||
positions,
|
||||
orientations,
|
||||
previous_phys_cache,
|
||||
&self.colliders,
|
||||
self.scales.maybe(),
|
||||
self.char_states.maybe(),
|
||||
)
|
||||
.par_join()
|
||||
.for_each(
|
||||
@ -214,7 +223,7 @@ impl<'a> PhysicsData<'a> {
|
||||
let (z_min, z_max) = (z_min * scale, z_max * scale);
|
||||
let half_height = (z_max - z_min) / 2.0;
|
||||
|
||||
phys_cache.velocity_dt = vel.0 * self.read.dt.0;
|
||||
phys_cache.velocity_dt = vel.0 * self.dt.0;
|
||||
let entity_center = position.0 + Vec3::new(0.0, 0.0, z_min + half_height);
|
||||
let flat_radius = collider.bounding_radius() * scale;
|
||||
let radius = (flat_radius.powi(2) + half_height.powi(2)).sqrt();
|
||||
@ -279,12 +288,14 @@ impl<'a> PhysicsData<'a> {
|
||||
);
|
||||
}
|
||||
|
||||
fn construct_spatial_grid(&mut self) -> SpatialGrid {
|
||||
fn construct_spatial_grid(
|
||||
&self,
|
||||
positions: &WriteStorage<'a, Pos>,
|
||||
velocities: &WriteStorage<'a, Vel>,
|
||||
capacity: (usize, usize),
|
||||
/* previous_phys_cache: &WriteStorage<'a, PreviousPhysCache>, */
|
||||
) -> SpatialGrid {
|
||||
span!(_guard, "Construct spatial grid");
|
||||
let PhysicsData {
|
||||
ref read,
|
||||
ref write,
|
||||
} = self;
|
||||
// NOTE: i32 places certain constraints on how far out collision works
|
||||
// NOTE: uses the radius of the entity and their current position rather than
|
||||
// the radius of their bounding sphere for the current frame of movement
|
||||
@ -298,19 +309,24 @@ impl<'a> PhysicsData<'a> {
|
||||
let lg2_cell_size = 5;
|
||||
let lg2_large_cell_size = 6;
|
||||
let radius_cutoff = 8;
|
||||
let spatial_grid = SpatialGrid::new(lg2_cell_size, lg2_large_cell_size, radius_cutoff);
|
||||
let mut spatial_grid =
|
||||
SpatialGrid::new(lg2_cell_size, lg2_large_cell_size, radius_cutoff, capacity);
|
||||
(
|
||||
&read.entities,
|
||||
&write.positions,
|
||||
&write.previous_phys_cache,
|
||||
write.velocities.mask(),
|
||||
!read.projectiles.mask(), /* Not needed because they are skipped in the inner loop
|
||||
&self.entities,
|
||||
positions,
|
||||
/* previous_phys_cache, */
|
||||
velocities.mask(),
|
||||
&self.colliders,
|
||||
self.scales.maybe(),
|
||||
!self.projectiles.mask(), /* Not needed because they are skipped in the inner loop
|
||||
* below */
|
||||
)
|
||||
.par_join()
|
||||
.for_each(|(entity, pos, phys_cache, _, _)| {
|
||||
./*par_join*/join()
|
||||
.for_each(|(entity, pos, /*phys_cache, */_, collider, scale, _)| {
|
||||
let scale = scale.map(|s| s.0).unwrap_or(1.0);
|
||||
let scaled_radius = collider.bounding_radius() * scale;
|
||||
// Note: to not get too fine grained we use a 2D grid for now
|
||||
let radius_2d = phys_cache.scaled_radius.ceil() as u32;
|
||||
let radius_2d = /*phys_cache.*/scaled_radius.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);
|
||||
@ -319,30 +335,34 @@ impl<'a> PhysicsData<'a> {
|
||||
spatial_grid
|
||||
}
|
||||
|
||||
fn apply_pushback(&mut self, job: &mut Job<Sys>, spatial_grid: &SpatialGridRef) {
|
||||
fn apply_pushback(
|
||||
&self,
|
||||
job: &mut Job<Sys>,
|
||||
spatial_grid: &SpatialGridRef,
|
||||
physics_metrics: &mut WriteExpect<'a, PhysicsMetrics>,
|
||||
physics_states: &mut WriteStorage<'a, PhysicsState>,
|
||||
positions: &WriteStorage<'a, Pos>,
|
||||
velocities: &mut WriteStorage<'a, Vel>,
|
||||
previous_phys_cache: &WriteStorage<'a, PreviousPhysCache>,
|
||||
) {
|
||||
span!(_guard, "Apply pushback");
|
||||
job.cpu_stats.measure(ParMode::Rayon);
|
||||
let PhysicsData {
|
||||
ref read,
|
||||
ref mut write,
|
||||
} = self;
|
||||
let (positions, previous_phys_cache) = (&write.positions, &write.previous_phys_cache);
|
||||
let metrics = (
|
||||
&read.entities,
|
||||
&self.entities,
|
||||
positions,
|
||||
&mut write.velocities,
|
||||
velocities,
|
||||
previous_phys_cache,
|
||||
&read.masses,
|
||||
&read.colliders,
|
||||
read.is_ridings.maybe(),
|
||||
read.stickies.maybe(),
|
||||
read.immovables.maybe(),
|
||||
&mut write.physics_states,
|
||||
&self.masses,
|
||||
&self.colliders,
|
||||
self.is_ridings.maybe(),
|
||||
self.stickies.maybe(),
|
||||
self.immovables.maybe(),
|
||||
physics_states,
|
||||
// TODO: if we need to avoid collisions for other things consider
|
||||
// moving whether it should interact into the collider component
|
||||
// or into a separate component.
|
||||
read.projectiles.maybe(),
|
||||
read.char_states.maybe(),
|
||||
self.projectiles.maybe(),
|
||||
self.char_states.maybe(),
|
||||
)
|
||||
.par_join()
|
||||
.map_init(
|
||||
@ -397,11 +417,11 @@ impl<'a> PhysicsData<'a> {
|
||||
spatial_grid
|
||||
.in_circle_aabr(query_center, query_radius)
|
||||
.filter_map(|entity| {
|
||||
let uid = read.uids.get(entity)?;
|
||||
let uid = self.uids.get(entity)?;
|
||||
let pos = positions.get(entity)?;
|
||||
let previous_cache = previous_phys_cache.get(entity)?;
|
||||
let mass = read.masses.get(entity)?;
|
||||
let collider = read.colliders.get(entity)?;
|
||||
let mass = self.masses.get(entity)?;
|
||||
let collider = self.colliders.get(entity)?;
|
||||
|
||||
Some((
|
||||
entity,
|
||||
@ -410,8 +430,8 @@ impl<'a> PhysicsData<'a> {
|
||||
previous_cache,
|
||||
mass,
|
||||
collider,
|
||||
read.char_states.get(entity),
|
||||
read.is_ridings.get(entity),
|
||||
self.char_states.get(entity),
|
||||
self.is_ridings.get(entity),
|
||||
))
|
||||
})
|
||||
.for_each(
|
||||
@ -498,7 +518,7 @@ impl<'a> PhysicsData<'a> {
|
||||
);
|
||||
|
||||
// Change velocity
|
||||
vel.0 += vel_delta * read.dt.0;
|
||||
vel.0 += vel_delta * self.dt.0;
|
||||
|
||||
// Metrics
|
||||
PhysicsMetrics {
|
||||
@ -513,18 +533,17 @@ impl<'a> PhysicsData<'a> {
|
||||
entity_entity_collisions: old.entity_entity_collisions
|
||||
+ new.entity_entity_collisions,
|
||||
});
|
||||
write.physics_metrics.entity_entity_collision_checks =
|
||||
metrics.entity_entity_collision_checks;
|
||||
write.physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions;
|
||||
physics_metrics.entity_entity_collision_checks = metrics.entity_entity_collision_checks;
|
||||
physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions;
|
||||
}
|
||||
|
||||
fn construct_voxel_collider_spatial_grid(&mut self) -> SpatialGrid {
|
||||
fn construct_voxel_collider_spatial_grid(
|
||||
&self,
|
||||
positions: &WriteStorage<'a, Pos>,
|
||||
orientations: &WriteStorage<'a, Ori>,
|
||||
capacity: (usize, usize),
|
||||
) -> SpatialGrid {
|
||||
span!(_guard, "Construct voxel collider spatial grid");
|
||||
let PhysicsData {
|
||||
ref read,
|
||||
ref write,
|
||||
} = self;
|
||||
|
||||
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
|
||||
|
||||
// NOTE: i32 places certain constraints on how far out collision works
|
||||
@ -536,15 +555,16 @@ impl<'a> PhysicsData<'a> {
|
||||
let lg2_cell_size = 7; // 128
|
||||
let lg2_large_cell_size = 8; // 256
|
||||
let radius_cutoff = 64;
|
||||
let spatial_grid = SpatialGrid::new(lg2_cell_size, lg2_large_cell_size, radius_cutoff);
|
||||
let mut spatial_grid =
|
||||
SpatialGrid::new(lg2_cell_size, lg2_large_cell_size, radius_cutoff, capacity);
|
||||
// TODO: give voxel colliders their own component type
|
||||
(
|
||||
&read.entities,
|
||||
&write.positions,
|
||||
&read.colliders,
|
||||
&write.orientations,
|
||||
&self.entities,
|
||||
positions,
|
||||
&self.colliders,
|
||||
orientations,
|
||||
)
|
||||
.par_join()
|
||||
./*par_join*/join()
|
||||
.for_each(|(entity, pos, collider, ori)| {
|
||||
let vol = match collider {
|
||||
Collider::Voxel { id } => voxel_colliders_manifest.colliders.get(id),
|
||||
@ -563,7 +583,9 @@ impl<'a> PhysicsData<'a> {
|
||||
|
||||
spatial_grid
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PhysicsData<'a> {
|
||||
fn handle_movement_and_terrain(
|
||||
&mut self,
|
||||
job: &mut Job<Sys>,
|
||||
@ -707,7 +729,7 @@ impl<'a> PhysicsData<'a> {
|
||||
&mut write.previous_phys_cache,
|
||||
read.colliders.mask(),
|
||||
)
|
||||
.par_join()
|
||||
.join()
|
||||
.for_each(|(pos, ori, previous_phys_cache, _)| {
|
||||
// Note: updating ori with the rest of the cache values above was attempted but
|
||||
// it did not work (investigate root cause?)
|
||||
@ -746,6 +768,7 @@ impl<'a> PhysicsData<'a> {
|
||||
&mut write.pos_vel_ori_defers,
|
||||
previous_phys_cache,
|
||||
!&read.is_ridings,
|
||||
read.projectiles.maybe(),
|
||||
)
|
||||
.par_join()
|
||||
.filter(|tuple| tuple.3.is_voxel() == terrain_like_entities)
|
||||
@ -769,6 +792,7 @@ impl<'a> PhysicsData<'a> {
|
||||
pos_vel_ori_defer,
|
||||
previous_cache,
|
||||
_,
|
||||
projectile,
|
||||
)| {
|
||||
let mut land_on_ground = None;
|
||||
let mut outcomes = Vec::new();
|
||||
@ -931,11 +955,9 @@ impl<'a> PhysicsData<'a> {
|
||||
|
||||
// TODO: Not all projectiles should count as sticky!
|
||||
if sticky.is_some() {
|
||||
if let Some((projectile, body)) = read
|
||||
.projectiles
|
||||
.get(entity)
|
||||
if let Some((projectile, body)) = projectile
|
||||
.filter(|_| vel.0.magnitude_squared() > 1.0 && block.is_some())
|
||||
.zip(read.bodies.get(entity).copied())
|
||||
.zip(body.copied())
|
||||
{
|
||||
outcomes.push(Outcome::ProjectileHit {
|
||||
pos: pos.0 + pos_delta * dist,
|
||||
@ -1212,6 +1234,11 @@ impl<'a> PhysicsData<'a> {
|
||||
}
|
||||
if vel != old_vel {
|
||||
pos_vel_ori_defer.vel = Some(vel);
|
||||
// Moving this logic here instead of the projectile system allows it to
|
||||
// avoid writing to ori.
|
||||
if projectile.is_some() && let Some(dir) = Ori::from_unnormalized_vec(vel.0) {
|
||||
ori = dir;
|
||||
}
|
||||
} else {
|
||||
pos_vel_ori_defer.vel = None;
|
||||
}
|
||||
@ -1244,7 +1271,7 @@ impl<'a> PhysicsData<'a> {
|
||||
drop(guard);
|
||||
job.cpu_stats.measure(ParMode::Single);
|
||||
|
||||
write.outcomes.emitter().emit_many(outcomes);
|
||||
read.outcomes.emitter().emit_many(outcomes);
|
||||
|
||||
prof_span!(guard, "write deferred pos and vel");
|
||||
(
|
||||
@ -1255,7 +1282,7 @@ impl<'a> PhysicsData<'a> {
|
||||
&mut write.pos_vel_ori_defers,
|
||||
&read.colliders,
|
||||
)
|
||||
.par_join()
|
||||
.join()
|
||||
.filter(|tuple| tuple./*5*/4.is_voxel() == terrain_like_entities)
|
||||
.for_each(|(/* _, */ pos, vel, ori, pos_vel_ori_defer, _)| {
|
||||
if let Some(new_pos) = pos_vel_ori_defer.pos.take() {
|
||||
@ -1276,26 +1303,20 @@ impl<'a> PhysicsData<'a> {
|
||||
});
|
||||
}
|
||||
|
||||
fn update_cached_spatial_grid(&mut self) {
|
||||
fn update_cached_spatial_grid(&mut self, mut spatial_grid: SpatialGrid) {
|
||||
span!(_guard, "Update cached spatial grid");
|
||||
let PhysicsData {
|
||||
ref read,
|
||||
ref mut write,
|
||||
} = self;
|
||||
|
||||
// Borrow checker dance, since transferring away from a read only view requires
|
||||
// &mut self.
|
||||
let mut spatial_grid = core::mem::take(&mut *write.cached_spatial_grid)
|
||||
.0
|
||||
.into_inner();
|
||||
spatial_grid.clear();
|
||||
(
|
||||
&read.entities,
|
||||
&write.positions,
|
||||
read.scales.maybe(),
|
||||
read.colliders.maybe(),
|
||||
)
|
||||
.par_join()
|
||||
.join()
|
||||
.for_each(|(entity, pos, scale, collider)| {
|
||||
let scale = scale.map(|s| s.0).unwrap_or(1.0);
|
||||
let radius_2d =
|
||||
@ -1317,33 +1338,112 @@ impl<'a> System<'a> for Sys {
|
||||
const PHASE: Phase = Phase::Create;
|
||||
|
||||
fn run(job: &mut Job<Self>, mut physics_data: Self::SystemData) {
|
||||
physics_data.reset();
|
||||
// Borrow checker dance, since transferring away from a read only view requires
|
||||
// &mut self.
|
||||
let mut cached_spatial_grid = core::mem::take(&mut *physics_data.write.cached_spatial_grid)
|
||||
.0
|
||||
.into_inner();
|
||||
// Initialize grids to twice the cached grid's capacity, since entity
|
||||
// distribution will rarely change much from tick to tick.
|
||||
let grid_capacity = cached_spatial_grid.len();
|
||||
let grid_capacity = (
|
||||
grid_capacity.0.saturating_mul(2),
|
||||
grid_capacity.1.saturating_mul(2),
|
||||
);
|
||||
rayon::join(
|
||||
|| {
|
||||
let PhysicsData {
|
||||
ref read,
|
||||
ref mut write,
|
||||
} = physics_data;
|
||||
|
||||
let (spatial_grid, voxel_collider_spatial_grid) = rayon::join(
|
||||
|| {
|
||||
let (spatial_grid, ()) = rayon::join(
|
||||
|| {
|
||||
read.construct_spatial_grid(
|
||||
&write.positions,
|
||||
&write.velocities,
|
||||
grid_capacity,
|
||||
)
|
||||
.into_read_only()
|
||||
},
|
||||
|| {
|
||||
rayon::join(
|
||||
|| {
|
||||
read.reset(
|
||||
&write.positions,
|
||||
&write.velocities,
|
||||
&write.orientations,
|
||||
&mut write.physics_states,
|
||||
);
|
||||
},
|
||||
|| {
|
||||
read.maintain_pushback_cache(
|
||||
&write.positions,
|
||||
&write.velocities,
|
||||
&write.orientations,
|
||||
&mut write.previous_phys_cache,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Apply pushback
|
||||
//
|
||||
// Note: We now do this first because we project velocity ahead. This is slighty
|
||||
// imperfect and implies that we might get edge-cases where entities
|
||||
// standing right next to the edge of a wall may get hit by projectiles
|
||||
// Note: We now do this first because we project velocity ahead. This is
|
||||
// slighty imperfect and implies that we might get
|
||||
// edge-cases where entities standing right next to
|
||||
// the edge of a wall may get hit by projectiles
|
||||
// fired into the wall very close to them. However, this sort of thing is
|
||||
// already possible with poorly-defined hitboxes anyway so it's not too
|
||||
// much of a concern.
|
||||
//
|
||||
// If this situation becomes a problem, this code should be integrated with the
|
||||
// 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.
|
||||
physics_data.maintain_pushback_cache();
|
||||
// If this situation becomes a problem, this code should be integrated with
|
||||
// the 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.
|
||||
read.apply_pushback(
|
||||
job,
|
||||
&spatial_grid,
|
||||
&mut write.physics_metrics,
|
||||
&mut write.physics_states,
|
||||
&write.positions,
|
||||
&mut write.velocities,
|
||||
&write.previous_phys_cache,
|
||||
);
|
||||
spatial_grid
|
||||
},
|
||||
|| {
|
||||
read.construct_voxel_collider_spatial_grid(
|
||||
&write.positions,
|
||||
&write.orientations,
|
||||
// Almost certainly overkill since most chunks won't contain a
|
||||
// collider, but probably not worth altering.
|
||||
grid_capacity,
|
||||
)
|
||||
.into_read_only()
|
||||
},
|
||||
);
|
||||
|
||||
let spatial_grid = physics_data.construct_spatial_grid().into_read_only();
|
||||
physics_data.apply_pushback(job, &spatial_grid);
|
||||
|
||||
let voxel_collider_spatial_grid = physics_data
|
||||
.construct_voxel_collider_spatial_grid()
|
||||
.into_read_only();
|
||||
physics_data.handle_movement_and_terrain(job, &voxel_collider_spatial_grid);
|
||||
|
||||
physics_data.read.slowjob.spawn("CHUNK_DROP", move || {
|
||||
drop(spatial_grid);
|
||||
drop(voxel_collider_spatial_grid);
|
||||
});
|
||||
},
|
||||
|| {
|
||||
cached_spatial_grid.clear();
|
||||
},
|
||||
);
|
||||
|
||||
// Spatial grid used by other systems
|
||||
physics_data.update_cached_spatial_grid();
|
||||
//
|
||||
// TODO: Consider inserting only the difference so we can update in parallel,
|
||||
// rather than clearing and reinserting everything.
|
||||
physics_data.update_cached_spatial_grid(cached_spatial_grid);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,20 +3,21 @@ use common::{
|
||||
comp::{
|
||||
agent::{Sound, SoundKind},
|
||||
projectile, Alignment, Body, CharacterState, Combo, Energy, Group, Health, Inventory, Ori,
|
||||
PhysicsState, Player, Pos, Projectile, Stats, Vel,
|
||||
PhysicsState, Player, Pos, Projectile, ProjectileOwned, Stats, Vel,
|
||||
},
|
||||
event::{Emitter, EventBus, ServerEvent},
|
||||
outcome::Outcome,
|
||||
resources::{DeltaTime, Time},
|
||||
uid::{Uid, UidAllocator},
|
||||
util::Dir,
|
||||
GroupTarget,
|
||||
};
|
||||
use common_base::prof_span;
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::iter::ParallelIterator;
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity as EcsEntity, Join, Read,
|
||||
ReadStorage, SystemData, World, WriteStorage,
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity as EcsEntity, Join, ParJoin,
|
||||
Read, ReadStorage, SystemData, World, WriteStorage,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
@ -25,6 +26,8 @@ use vek::*;
|
||||
pub struct ReadData<'a> {
|
||||
time: Read<'a, Time>,
|
||||
entities: Entities<'a>,
|
||||
projectiles: ReadStorage<'a, Projectile>,
|
||||
orientations: ReadStorage<'a, Ori>,
|
||||
players: ReadStorage<'a, Player>,
|
||||
dt: Read<'a, DeltaTime>,
|
||||
uid_allocator: Read<'a, UidAllocator>,
|
||||
@ -50,8 +53,7 @@ pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
ReadData<'a>,
|
||||
WriteStorage<'a, Ori>,
|
||||
WriteStorage<'a, Projectile>,
|
||||
WriteStorage<'a, ProjectileOwned>,
|
||||
Read<'a, EventBus<Outcome>>,
|
||||
);
|
||||
|
||||
@ -59,23 +61,26 @@ impl<'a> System<'a> for Sys {
|
||||
const ORIGIN: Origin = Origin::Common;
|
||||
const PHASE: Phase = Phase::Create;
|
||||
|
||||
fn run(
|
||||
_job: &mut Job<Self>,
|
||||
(read_data, mut orientations, mut projectiles, outcomes): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = read_data.server_bus.emitter();
|
||||
let mut outcomes_emitter = outcomes.emitter();
|
||||
|
||||
fn run(_job: &mut Job<Self>, (read_data, mut projectiles, outcomes): Self::SystemData) {
|
||||
// Attacks
|
||||
'projectile_loop: for (entity, pos, physics, vel, mut projectile) in (
|
||||
(
|
||||
&read_data.entities,
|
||||
&read_data.positions,
|
||||
&read_data.physics_states,
|
||||
&read_data.velocities,
|
||||
&read_data.projectiles,
|
||||
// TODO: Investigate whether the `maybe` are actually necessary here.
|
||||
(&read_data.orientations).maybe(),
|
||||
(&read_data.bodies).maybe(),
|
||||
&mut projectiles,
|
||||
)
|
||||
.join()
|
||||
{
|
||||
.par_join()
|
||||
.for_each_init(
|
||||
|| {
|
||||
prof_span!(guard, "projectile rayon job");
|
||||
(read_data.server_bus.emitter(), outcomes.emitter(), guard)
|
||||
},
|
||||
|(server_emitter, outcomes_emitter, _guard), (entity, pos, physics, vel, projectile, ori, body, projectile_write)| {
|
||||
let projectile_owner = projectile
|
||||
.owner
|
||||
.and_then(|uid| read_data.uid_allocator.retrieve_entity_internal(uid.into()));
|
||||
@ -117,19 +122,21 @@ impl<'a> System<'a> for Sys {
|
||||
continue;
|
||||
}
|
||||
|
||||
let projectile = &mut *projectile;
|
||||
let projectile_write = &mut *projectile_write;
|
||||
|
||||
let entity_of =
|
||||
|uid: Uid| read_data.uid_allocator.retrieve_entity_internal(uid.into());
|
||||
for effect in projectile.hit_entity.drain(..) {
|
||||
for effect in projectile_write.hit_entity.drain(..) {
|
||||
let owner = projectile.owner.and_then(entity_of);
|
||||
let projectile_info = ProjectileInfo {
|
||||
entity,
|
||||
effect,
|
||||
owner_uid: projectile.owner,
|
||||
owner,
|
||||
ori: orientations.get(entity),
|
||||
ori,
|
||||
pos,
|
||||
vel,
|
||||
body,
|
||||
};
|
||||
|
||||
let target = entity_of(other);
|
||||
@ -137,7 +144,7 @@ impl<'a> System<'a> for Sys {
|
||||
uid: other,
|
||||
entity: target,
|
||||
target_group,
|
||||
ori: target.and_then(|target| orientations.get(target)),
|
||||
ori: target.and_then(|target| read_data.orientations.get(target)),
|
||||
};
|
||||
|
||||
dispatch_hit(
|
||||
@ -145,19 +152,19 @@ impl<'a> System<'a> for Sys {
|
||||
projectile_target_info,
|
||||
&read_data,
|
||||
&mut projectile_vanished,
|
||||
&mut outcomes_emitter,
|
||||
&mut server_emitter,
|
||||
outcomes_emitter,
|
||||
server_emitter,
|
||||
);
|
||||
}
|
||||
|
||||
if projectile_vanished {
|
||||
continue 'projectile_loop;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if physics.on_surface().is_some() {
|
||||
let projectile = &mut *projectile;
|
||||
for effect in projectile.hit_solid.drain(..) {
|
||||
let projectile_write = &mut *projectile_write;
|
||||
for effect in projectile_write.hit_solid.drain(..) {
|
||||
match effect {
|
||||
projectile::Effect::Explode(e) => {
|
||||
// We offset position a little back on the way,
|
||||
@ -167,8 +174,7 @@ impl<'a> System<'a> for Sys {
|
||||
// TODO: orientation of fallen projectile is
|
||||
// fragile heuristic for direction, find more
|
||||
// robust method.
|
||||
let projectile_direction = orientations
|
||||
.get(entity)
|
||||
let projectile_direction = ori
|
||||
.map_or_else(Vec3::zero, |ori| ori.look_vec());
|
||||
let offset = -0.2 * projectile_direction;
|
||||
server_emitter.emit(ServerEvent::Explosion {
|
||||
@ -193,22 +199,18 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
if projectile_vanished {
|
||||
continue 'projectile_loop;
|
||||
}
|
||||
} else if let Some(ori) = orientations.get_mut(entity) {
|
||||
if let Some(dir) = Dir::from_unnormalized(vel.0) {
|
||||
*ori = dir.into();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if projectile.time_left == Duration::default() {
|
||||
if projectile_write.time_left == Duration::default() {
|
||||
server_emitter.emit(ServerEvent::Delete(entity));
|
||||
}
|
||||
projectile.time_left = projectile
|
||||
projectile_write.time_left = projectile_write
|
||||
.time_left
|
||||
.checked_sub(Duration::from_secs_f32(read_data.dt.0))
|
||||
.unwrap_or_default();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,7 +220,9 @@ struct ProjectileInfo<'a> {
|
||||
owner_uid: Option<Uid>,
|
||||
owner: Option<EcsEntity>,
|
||||
ori: Option<&'a Ori>,
|
||||
body: Option<&'a Body>,
|
||||
pos: &'a Pos,
|
||||
vel: &'a Vel,
|
||||
}
|
||||
|
||||
struct ProjectileTargetInfo<'a> {
|
||||
@ -258,7 +262,6 @@ fn dispatch_hit(
|
||||
};
|
||||
|
||||
let owner = projectile_info.owner;
|
||||
let projectile_entity = projectile_info.entity;
|
||||
|
||||
let attacker_info =
|
||||
owner
|
||||
@ -285,14 +288,11 @@ fn dispatch_hit(
|
||||
};
|
||||
|
||||
// TODO: Is it possible to have projectile without body??
|
||||
if let Some(&body) = read_data.bodies.get(projectile_entity) {
|
||||
if let Some(&body) = projectile_info.body {
|
||||
outcomes_emitter.emit(Outcome::ProjectileHit {
|
||||
pos: target_pos,
|
||||
body,
|
||||
vel: read_data
|
||||
.velocities
|
||||
.get(projectile_entity)
|
||||
.map_or(Vec3::zero(), |v| v.0),
|
||||
vel: projectile_info.vel.0,
|
||||
source: projectile_info.owner_uid,
|
||||
target: read_data.uids.get(target).copied(),
|
||||
});
|
||||
|
@ -8,8 +8,8 @@ use common::{
|
||||
beam,
|
||||
buff::{BuffCategory, BuffData, BuffKind, BuffSource},
|
||||
shockwave, Agent, Alignment, Anchor, Body, Health, Inventory, ItemDrop, LightEmitter,
|
||||
Object, Ori, PidController, Poise, Pos, Projectile, Scale, SkillSet, Stats, Vel,
|
||||
WaypointArea,
|
||||
Object, Ori, PidController, Poise, Pos, Projectile, ProjectileOwned, Scale, SkillSet,
|
||||
Stats, Vel, WaypointArea,
|
||||
},
|
||||
event::{EventBus, UpdateCharacterMetadata},
|
||||
lottery::LootSpec,
|
||||
@ -93,7 +93,7 @@ pub fn handle_create_npc(
|
||||
loot: LootSpec<String>,
|
||||
home_chunk: Option<Anchor>,
|
||||
rtsim_entity: Option<RtSimEntity>,
|
||||
projectile: Option<Projectile>,
|
||||
projectile: Option<(ProjectileOwned, Projectile)>,
|
||||
) {
|
||||
let entity = server
|
||||
.state
|
||||
@ -125,8 +125,8 @@ pub fn handle_create_npc(
|
||||
entity
|
||||
};
|
||||
|
||||
let entity = if let Some(projectile) = projectile {
|
||||
entity.with(projectile)
|
||||
let entity = if let Some((projectile_owned, projectile)) = projectile {
|
||||
entity.with(projectile_owned).with(projectile)
|
||||
} else {
|
||||
entity
|
||||
};
|
||||
@ -207,7 +207,7 @@ pub fn handle_shoot(
|
||||
dir: Dir,
|
||||
body: Body,
|
||||
light: Option<LightEmitter>,
|
||||
projectile: Projectile,
|
||||
projectile: (ProjectileOwned, Projectile),
|
||||
speed: f32,
|
||||
object: Option<Object>,
|
||||
) {
|
||||
|
@ -72,7 +72,7 @@ pub trait StateExt {
|
||||
pos: comp::Pos,
|
||||
vel: comp::Vel,
|
||||
body: comp::Body,
|
||||
projectile: comp::Projectile,
|
||||
projectile: (comp::ProjectileOwned, comp::Projectile),
|
||||
) -> EcsEntityBuilder;
|
||||
/// Build a shockwave entity
|
||||
fn create_shockwave(
|
||||
@ -343,7 +343,7 @@ impl StateExt for State {
|
||||
pos: comp::Pos,
|
||||
vel: comp::Vel,
|
||||
body: comp::Body,
|
||||
projectile: comp::Projectile,
|
||||
(projectile_owned, projectile): (comp::ProjectileOwned, comp::Projectile),
|
||||
) -> EcsEntityBuilder {
|
||||
let mut projectile_base = self
|
||||
.ecs_mut()
|
||||
@ -363,7 +363,10 @@ impl StateExt for State {
|
||||
projectile_base = projectile_base.with(body.collider())
|
||||
}
|
||||
|
||||
projectile_base.with(projectile).with(body)
|
||||
projectile_base
|
||||
.with(projectile_owned)
|
||||
.with(projectile)
|
||||
.with(body)
|
||||
}
|
||||
|
||||
fn create_shockwave(
|
||||
|
@ -71,7 +71,7 @@ impl<'a> System<'a> for Sys {
|
||||
const ENABLE_RECURSIVE_FIREWORKS: bool = true;
|
||||
if ENABLE_RECURSIVE_FIREWORKS {
|
||||
use common::{
|
||||
comp::{object, Body, LightEmitter, Projectile},
|
||||
comp::{object, Body, LightEmitter, Projectile, ProjectileOwned},
|
||||
util::Dir,
|
||||
};
|
||||
use rand::Rng;
|
||||
@ -121,15 +121,19 @@ impl<'a> System<'a> for Sys {
|
||||
strength: 2.0,
|
||||
col: Rgb::new(1.0, 1.0, 0.0),
|
||||
}),
|
||||
projectile: Projectile {
|
||||
projectile: (
|
||||
ProjectileOwned {
|
||||
hit_solid: Vec::new(),
|
||||
hit_entity: Vec::new(),
|
||||
time_left: Duration::from_secs(60),
|
||||
},
|
||||
Projectile {
|
||||
owner: *owner,
|
||||
ignore_group: true,
|
||||
is_sticky: true,
|
||||
is_point: true,
|
||||
},
|
||||
),
|
||||
speed,
|
||||
object: Some(Object::Firework {
|
||||
owner: *owner,
|
||||
|
Loading…
Reference in New Issue
Block a user