Actually send deletion messages

This commit is contained in:
Imbris 2019-11-29 01:04:37 -05:00 committed by Imbris
parent 71cce03f29
commit e49cafafbf
17 changed files with 440 additions and 318 deletions

View File

@ -17,7 +17,7 @@ opt-level = 2
overflow-checks = true overflow-checks = true
debug-assertions = true debug-assertions = true
panic = "abort" panic = "abort"
debug = false # debug = false
codegen-units = 8 codegen-units = 8
lto = false lto = false
incremental = true incremental = true

View File

@ -562,10 +562,15 @@ impl Client {
self.state.ecs_mut().apply_entity_package(entity_package) self.state.ecs_mut().apply_entity_package(entity_package)
} }
ServerMsg::DeleteEntity(entity) => { ServerMsg::DeleteEntity(entity) => {
if let Some(entity) = self.state.ecs().entity_from_uid(entity) { if self
if entity != self.entity { .state
let _ = self.state.ecs_mut().delete_entity(entity); .read_component_cloned::<Uid>(self.entity)
} .map(|u| u.into())
!= Some(entity)
{
self.state
.ecs_mut()
.delete_entity_and_clear_from_uid_allocator(entity);
} }
} }
ServerMsg::EntityPos { entity, pos } => { ServerMsg::EntityPos { entity, pos } => {

View File

@ -25,9 +25,6 @@ impl sync::ResPacket for EcsResPacket {
sum_type! { sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsCompPacket { pub enum EcsCompPacket {
Pos(comp::Pos),
Vel(comp::Vel),
Ori(comp::Ori),
Body(comp::Body), Body(comp::Body),
Player(comp::Player), Player(comp::Player),
CanBuild(comp::CanBuild), CanBuild(comp::CanBuild),
@ -48,9 +45,6 @@ sum_type! {
sum_type! { sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsCompPhantom { pub enum EcsCompPhantom {
Pos(PhantomData<comp::Pos>),
Vel(PhantomData<comp::Vel>),
Ori(PhantomData<comp::Ori>),
Body(PhantomData<comp::Body>), Body(PhantomData<comp::Body>),
Player(PhantomData<comp::Player>), Player(PhantomData<comp::Player>),
CanBuild(PhantomData<comp::CanBuild>), CanBuild(PhantomData<comp::CanBuild>),
@ -70,9 +64,6 @@ impl sync::CompPacket for EcsCompPacket {
type Phantom = EcsCompPhantom; type Phantom = EcsCompPhantom;
fn apply_insert(self, entity: specs::Entity, world: &specs::World) { fn apply_insert(self, entity: specs::Entity, world: &specs::World) {
match self { match self {
EcsCompPacket::Pos(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Vel(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Ori(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Body(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Body(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Player(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Player(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::CanBuild(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::CanBuild(comp) => sync::handle_insert(comp, entity, world),
@ -90,9 +81,6 @@ impl sync::CompPacket for EcsCompPacket {
} }
fn apply_modify(self, entity: specs::Entity, world: &specs::World) { fn apply_modify(self, entity: specs::Entity, world: &specs::World) {
match self { match self {
EcsCompPacket::Pos(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Vel(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Ori(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Body(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Body(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Player(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Player(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::CanBuild(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::CanBuild(comp) => sync::handle_modify(comp, entity, world),
@ -110,9 +98,6 @@ impl sync::CompPacket for EcsCompPacket {
} }
fn apply_remove(phantom: Self::Phantom, entity: specs::Entity, world: &specs::World) { fn apply_remove(phantom: Self::Phantom, entity: specs::Entity, world: &specs::World) {
match phantom { match phantom {
EcsCompPhantom::Pos(_) => sync::handle_remove::<comp::Pos>(entity, world),
EcsCompPhantom::Vel(_) => sync::handle_remove::<comp::Vel>(entity, world),
EcsCompPhantom::Ori(_) => sync::handle_remove::<comp::Ori>(entity, world),
EcsCompPhantom::Body(_) => sync::handle_remove::<comp::Body>(entity, world), EcsCompPhantom::Body(_) => sync::handle_remove::<comp::Body>(entity, world),
EcsCompPhantom::Player(_) => sync::handle_remove::<comp::Player>(entity, world), EcsCompPhantom::Player(_) => sync::handle_remove::<comp::Player>(entity, world),
EcsCompPhantom::CanBuild(_) => sync::handle_remove::<comp::CanBuild>(entity, world), EcsCompPhantom::CanBuild(_) => sync::handle_remove::<comp::CanBuild>(entity, world),

View File

@ -104,6 +104,16 @@ impl RegionMap {
// TODO special case large entities // TODO special case large entities
pub fn tick(&mut self, pos: ReadStorage<Pos>, vel: ReadStorage<Vel>, entities: Entities) { pub fn tick(&mut self, pos: ReadStorage<Pos>, vel: ReadStorage<Vel>, entities: Entities) {
self.tick += 1; self.tick += 1;
// Clear events within each region
for i in 0..self.regions.len() {
self.regions
.get_index_mut(i)
.map(|(_, v)| v)
.unwrap()
.events
.clear();
}
// Add any untracked entites // Add any untracked entites
for (pos, id) in (&pos, &entities, !&self.tracked_entities) for (pos, id) in (&pos, &entities, !&self.tracked_entities)
.join() .join()
@ -118,14 +128,6 @@ impl RegionMap {
let mut regions_to_remove = Vec::new(); let mut regions_to_remove = Vec::new();
for i in 0..self.regions.len() { for i in 0..self.regions.len() {
// Clear events within each region
self.regions
.get_index_mut(i)
.map(|(_, v)| v)
.unwrap()
.events
.clear();
for (maybe_pos, _maybe_vel, id) in ( for (maybe_pos, _maybe_vel, id) in (
pos.maybe(), pos.maybe(),
vel.maybe(), vel.maybe(),
@ -215,6 +217,47 @@ impl RegionMap {
pub fn key_pos(key: Vec2<i32>) -> Vec2<i32> { pub fn key_pos(key: Vec2<i32>) -> Vec2<i32> {
key.map(|e| e << REGION_LOG2) key.map(|e| e << REGION_LOG2)
} }
/// Finds the region where a given entity is located using a given position to speed up the search
pub fn find_region(&self, entity: specs::Entity, pos: Vec3<f32>) -> Option<Vec2<i32>> {
let id = entity.id();
// Compute key for most likely region
let key = Self::pos_key(pos.map(|e| e as i32));
// Get region
if let Some(region) = self.regions.get(&key) {
if region.entities().contains(id) {
return Some(key);
} else {
// Check neighbors
for i in 0..8 {
if let Some(idx) = region.neighbors[i] {
let (key, region) = self.regions.get_index(idx).unwrap();
if region.entities().contains(id) {
return Some(*key);
}
}
}
}
} else {
// Check neighbors
for i in 0..8 {
let key = key + NEIGHBOR_OFFSETS[i];
if let Some(region) = self.regions.get(&key) {
if region.entities().contains(id) {
return Some(key);
}
}
}
}
// Scan though all regions
for (key, region) in self.iter() {
if region.entities().contains(id) {
return Some(key);
}
}
None
}
fn key_index(&self, key: Vec2<i32>) -> Option<usize> { fn key_index(&self, key: Vec2<i32>) -> Option<usize> {
self.regions.get_full(&key).map(|(i, _, _)| i) self.regions.get_full(&key).map(|(i, _, _)| i)
} }

View File

@ -148,10 +148,10 @@ impl State {
ecs.register::<comp::Waypoint>(); ecs.register::<comp::Waypoint>();
// Register synced resources used by the ECS. // Register synced resources used by the ECS.
ecs.add_resource(Time(0.0));
ecs.add_resource(TimeOfDay(0.0)); ecs.add_resource(TimeOfDay(0.0));
// Register unsynced resources used by the ECS. // Register unsynced resources used by the ECS.
ecs.add_resource(Time(0.0));
ecs.add_resource(DeltaTime(0.0)); ecs.add_resource(DeltaTime(0.0));
ecs.add_resource(TerrainGrid::new().unwrap()); ecs.add_resource(TerrainGrid::new().unwrap());
ecs.add_resource(BlockChange::default()); ecs.add_resource(BlockChange::default());

View File

@ -2,6 +2,7 @@ use super::{
track::{Tracker, UpdateTracker}, track::{Tracker, UpdateTracker},
uid::Uid, uid::Uid,
}; };
use log::error;
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use specs::{shred::Resource, Component, Entity, Join, ReadStorage, World}; use specs::{shred::Resource, Component, Entity, Join, ReadStorage, World};
use std::{ use std::{
@ -24,7 +25,9 @@ pub trait ResPacket: Clone + Debug + Send + 'static {
/// Useful for implementing CompPacket trait /// Useful for implementing CompPacket trait
pub fn handle_insert<C: Component>(comp: C, entity: Entity, world: &World) { pub fn handle_insert<C: Component>(comp: C, entity: Entity, world: &World) {
let _ = world.write_storage::<C>().insert(entity, comp); if let Err(err) = world.write_storage::<C>().insert(entity, comp) {
error!("Error inserting component: {:?}", err);
};
} }
/// Useful for implementing CompPacket trait /// Useful for implementing CompPacket trait
pub fn handle_modify<C: Component>(comp: C, entity: Entity, world: &World) { pub fn handle_modify<C: Component>(comp: C, entity: Entity, world: &World) {
@ -102,6 +105,7 @@ impl<P: CompPacket> SyncPackage<P> {
uids: &ReadStorage<'a, Uid>, uids: &ReadStorage<'a, Uid>,
uid_tracker: &UpdateTracker<Uid>, uid_tracker: &UpdateTracker<Uid>,
filter: impl Join + Copy, filter: impl Join + Copy,
deleted_entities: Vec<u64>,
) -> Self { ) -> Self {
// Add created and deleted entities // Add created and deleted entities
let created_entities = (uids, filter, uid_tracker.inserted()) let created_entities = (uids, filter, uid_tracker.inserted())
@ -110,10 +114,15 @@ impl<P: CompPacket> SyncPackage<P> {
.collect(); .collect();
// TODO: handle modified uid? // TODO: handle modified uid?
//created_entities.append(&mut (uids, filter, uid_tracker.inserted()).join().map(|(uid, _, _)| uid).collect()); //created_entities.append(&mut (uids, filter, uid_tracker.inserted()).join().map(|(uid, _, _)| uid).collect());
let deleted_entities = (uids, filter, uid_tracker.removed()) // let deleted_entities = (uids.maybe(), filter, uid_tracker.removed())
.join() // .join()
.map(|(uid, _, _)| (*uid).into()) // Why doesn't this panic??
.collect(); // .map(|(uid, _, _)| Into::<u64>::into(*uid.unwrap()))
// .collect::<Vec<_>>();
//let len = deleted_entities.len();
//if len > 0 {
// println!("deleted {} in sync message", len);
// }
Self { Self {
comp_updates: Vec::new(), comp_updates: Vec::new(),
@ -124,7 +133,6 @@ impl<P: CompPacket> SyncPackage<P> {
pub fn with_component<'a, C: Component + Clone + Send + Sync>( pub fn with_component<'a, C: Component + Clone + Send + Sync>(
mut self, mut self,
uids: &ReadStorage<'a, Uid>, uids: &ReadStorage<'a, Uid>,
uid_tracker: &UpdateTracker<Uid>,
tracker: &impl Tracker<C, P>, tracker: &impl Tracker<C, P>,
storage: &ReadStorage<'a, C>, storage: &ReadStorage<'a, C>,
filter: impl Join + Copy, filter: impl Join + Copy,
@ -136,13 +144,7 @@ impl<P: CompPacket> SyncPackage<P> {
P::Phantom: TryInto<PhantomData<C>>, P::Phantom: TryInto<PhantomData<C>>,
C::Storage: specs::storage::Tracked, C::Storage: specs::storage::Tracked,
{ {
tracker.get_updates_for( tracker.get_updates_for(uids, storage, filter, &mut self.comp_updates);
uids,
storage,
// Don't include updates for deleted entities
(filter, &!uid_tracker.removed()),
&mut self.comp_updates,
);
self self
} }
} }

View File

@ -6,6 +6,7 @@ use super::{
track::UpdateTracker, track::UpdateTracker,
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
}; };
use log::error;
use specs::{ use specs::{
saveload::{MarkedBuilder, MarkerAllocator}, saveload::{MarkedBuilder, MarkerAllocator},
world::Builder, world::Builder,
@ -20,6 +21,7 @@ pub trait WorldSyncExt {
where where
C::Storage: Default + specs::storage::Tracked; C::Storage: Default + specs::storage::Tracked;
fn create_entity_synced(&mut self) -> specs::EntityBuilder; fn create_entity_synced(&mut self) -> specs::EntityBuilder;
fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: u64);
fn uid_from_entity(&self, entity: specs::Entity) -> Option<Uid>; fn uid_from_entity(&self, entity: specs::Entity) -> Option<Uid>;
fn entity_from_uid(&self, uid: u64) -> Option<specs::Entity>; fn entity_from_uid(&self, uid: u64) -> Option<specs::Entity>;
fn apply_entity_package<P: CompPacket>(&mut self, entity_package: EntityPackage<P>); fn apply_entity_package<P: CompPacket>(&mut self, entity_package: EntityPackage<P>);
@ -35,14 +37,11 @@ impl WorldSyncExt for specs::World {
fn register_sync_marker(&mut self) { fn register_sync_marker(&mut self) {
self.register_synced::<Uid>(); self.register_synced::<Uid>();
// TODO: Consider only having allocator server side for now
self.add_resource(UidAllocator::new()); self.add_resource(UidAllocator::new());
} }
fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self) fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self)
where where
// P: From<C>,
// C: TryFrom<P, Error = InvalidType>,
// P::Phantom: From<PhantomData<C>>,
// P::Phantom: TryInto<PhantomData<C>>,
C::Storage: Default + specs::storage::Tracked, C::Storage: Default + specs::storage::Tracked,
{ {
self.register::<C>(); self.register::<C>();
@ -50,26 +49,12 @@ impl WorldSyncExt for specs::World {
} }
fn register_tracker<C: specs::Component + Clone + Send + Sync>(&mut self) fn register_tracker<C: specs::Component + Clone + Send + Sync>(&mut self)
where where
// P: From<C>,
// C: TryFrom<P, Error = InvalidType>,
// P::Phantom: From<PhantomData<C>>,
// P::Phantom: TryInto<PhantomData<C>>,
C::Storage: Default + specs::storage::Tracked, C::Storage: Default + specs::storage::Tracked,
{ {
let tracker = UpdateTracker::<C>::new(self); let tracker = UpdateTracker::<C>::new(self);
self.add_resource(tracker); self.add_resource(tracker);
} }
/*fn insert_synced<C: specs::shred::Resource + Clone + Send + Sync>(&mut self, res: C)
//where
// R: From<C>,
// C: TryFrom<R>,
{
self.add_resource::<C>(res);
self.res_trackers.insert(ResUpdateTracker::<C>::new());
}*/
fn create_entity_synced(&mut self) -> specs::EntityBuilder { fn create_entity_synced(&mut self) -> specs::EntityBuilder {
self.create_entity().marked::<super::Uid>() self.create_entity().marked::<super::Uid>()
} }
@ -94,6 +79,16 @@ impl WorldSyncExt for specs::World {
} }
} }
fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: u64) {
// Clear from uid allocator
let maybe_entity = self.write_resource::<UidAllocator>().remove_entity(uid);
if let Some(entity) = maybe_entity {
if let Err(err) = self.delete_entity(entity) {
error!("Failed to delete entity: {:?}", err);
}
}
}
fn apply_state_package<P: CompPacket, R: ResPacket>( fn apply_state_package<P: CompPacket, R: ResPacket>(
&mut self, &mut self,
state_package: StatePackage<P, R>, state_package: StatePackage<P, R>,
@ -114,7 +109,7 @@ impl WorldSyncExt for specs::World {
} }
// Initialize entities // Initialize entities
//specs_world.maintain(); //self.maintain();
} }
fn apply_sync_package<P: CompPacket>(&mut self, package: SyncPackage<P>) { fn apply_sync_package<P: CompPacket>(&mut self, package: SyncPackage<P>) {
@ -152,12 +147,7 @@ impl WorldSyncExt for specs::World {
// Attempt to delete entities that were marked for deletion // Attempt to delete entities that were marked for deletion
for entity_uid in deleted_entities { for entity_uid in deleted_entities {
let entity = self self.delete_entity_and_clear_from_uid_allocator(entity_uid);
.read_resource::<UidAllocator>()
.retrieve_entity_internal(entity_uid);
if let Some(entity) = entity {
let _ = self.delete_entity(entity);
}
} }
} }
fn apply_res_sync_package<R: ResPacket>(&mut self, package: ResSyncPackage<R>) { fn apply_res_sync_package<R: ResPacket>(&mut self, package: ResSyncPackage<R>) {
@ -174,5 +164,15 @@ fn create_entity_with_uid(specs_world: &mut specs::World, entity_uid: u64) -> sp
.read_resource::<UidAllocator>() .read_resource::<UidAllocator>()
.retrieve_entity_internal(entity_uid); .retrieve_entity_internal(entity_uid);
existing_entity.unwrap_or_else(|| specs_world.create_entity_synced().build()) match existing_entity {
Some(entity) => entity,
None => {
let entity_builder = specs_world.create_entity();
let uid = entity_builder
.world
.write_resource::<UidAllocator>()
.allocate(entity_builder.entity, Some(entity_uid));
entity_builder.with(uid).build()
}
}
} }

View File

@ -76,6 +76,7 @@ where
specs::storage::ComponentEvent::Modified(id) => { specs::storage::ComponentEvent::Modified(id) => {
// We don't care about modification if the component was just added or was // We don't care about modification if the component was just added or was
// removed // removed
// Could potentially remove since this should theoretically never occur...
if !self.removed.contains(*id) && !self.inserted.contains(*id) { if !self.removed.contains(*id) && !self.inserted.contains(*id) {
self.modified.add(*id); self.modified.add(*id);
} }

View File

@ -56,6 +56,10 @@ impl UidAllocator {
mapping: HashMap::new(), mapping: HashMap::new(),
} }
} }
// Useful for when a single entity is deleted because it doesn't reconstruct the entire hashmap
pub fn remove_entity(&mut self, id: u64) -> Option<Entity> {
self.mapping.remove(&id)
}
} }
impl Default for UidAllocator { impl Default for UidAllocator {
@ -69,14 +73,14 @@ impl MarkerAllocator<Uid> for UidAllocator {
let id = id.unwrap_or_else(|| { let id = id.unwrap_or_else(|| {
let id = self.index; let id = self.index;
self.index += 1; self.index += 1;
self.mapping.insert(id, entity);
id id
}); });
self.mapping.insert(id, entity);
Uid(id) Uid(id)
} }
fn retrieve_entity_internal(&self, id: u64) -> Option<Entity> { fn retrieve_entity_internal(&self, id: u64) -> Option<Entity> {
self.mapping.get(&id).cloned() self.mapping.get(&id).copied()
} }
fn maintain(&mut self, entities: &EntitiesRes, storage: &ReadStorage<Uid>) { fn maintain(&mut self, entities: &EntitiesRes, storage: &ReadStorage<Uid>) {

View File

@ -21,6 +21,7 @@ use vek::*;
use world::util::Sampler; use world::util::Sampler;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::error;
use scan_fmt::{scan_fmt, scan_fmt_some}; use scan_fmt::{scan_fmt, scan_fmt_some};
/// Struct representing a command that a user can run from server chat. /// Struct representing a command that a user can run from server chat.
@ -1118,7 +1119,9 @@ fn handle_remove_lights(
let size = to_delete.len(); let size = to_delete.len();
for entity in to_delete { for entity in to_delete {
let _ = server.state.ecs_mut().delete_entity(entity); if let Err(err) = server.state.delete_entity_recorded(entity) {
error!("Failed to delete light: {:?}", err);
}
} }
server.notify_client( server.notify_client(

View File

@ -19,7 +19,7 @@ use crate::{
chunk_generator::ChunkGenerator, chunk_generator::ChunkGenerator,
client::{Client, RegionSubscription}, client::{Client, RegionSubscription},
cmd::CHAT_COMMANDS, cmd::CHAT_COMMANDS,
sys::sentinel::{TrackedComps, TrackedResources}, sys::sentinel::{DeletedEntities, TrackedComps, TrackedResources},
}; };
use common::{ use common::{
assets, comp, assets, comp,
@ -32,7 +32,7 @@ use common::{
terrain::{block::Block, TerrainChunkSize, TerrainGrid}, terrain::{block::Block, TerrainChunkSize, TerrainGrid},
vol::{ReadVol, RectVolSize, Vox}, vol::{ReadVol, RectVolSize, Vox},
}; };
use log::debug; use log::{debug, error};
use metrics::ServerMetrics; use metrics::ServerMetrics;
use rand::Rng; use rand::Rng;
use specs::{ use specs::{
@ -166,6 +166,8 @@ impl Server {
// Register trackers // Register trackers
sys::sentinel::register_trackers(&mut state.ecs_mut()); sys::sentinel::register_trackers(&mut state.ecs_mut());
state.ecs_mut().add_resource(DeletedEntities::default());
let this = Self { let this = Self {
state, state,
world: Arc::new(world), world: Arc::new(world),
@ -309,8 +311,6 @@ impl Server {
let server_settings = &self.server_settings; let server_settings = &self.server_settings;
let mut todo_remove = None;
match event { match event {
ServerEvent::Explosion { pos, radius } => { ServerEvent::Explosion { pos, radius } => {
const RAYS: usize = 500; const RAYS: usize = 500;
@ -380,19 +380,20 @@ impl Server {
} }
ServerEvent::Destroy { entity, cause } => { ServerEvent::Destroy { entity, cause } => {
let ecs = state.ecs();
// Chat message // Chat message
if let Some(player) = ecs.read_storage::<comp::Player>().get(entity) { if let Some(player) = state.ecs().read_storage::<comp::Player>().get(entity) {
let msg = if let comp::HealthSource::Attack { by } = cause { let msg = if let comp::HealthSource::Attack { by } = cause {
ecs.entity_from_uid(by.into()).and_then(|attacker| { state.ecs().entity_from_uid(by.into()).and_then(|attacker| {
ecs.read_storage::<comp::Player>().get(attacker).map( state
|attacker_alias| { .ecs()
.read_storage::<comp::Player>()
.get(attacker)
.map(|attacker_alias| {
format!( format!(
"{} was killed by {}", "{} was killed by {}",
&player.alias, &attacker_alias.alias &player.alias, &attacker_alias.alias
) )
}, })
)
}) })
} else { } else {
None None
@ -402,28 +403,44 @@ impl Server {
state.notify_registered_clients(ServerMsg::kill(msg)); state.notify_registered_clients(ServerMsg::kill(msg));
} }
// Give EXP to the killer if entity had stats {
let mut stats = ecs.write_storage::<comp::Stats>(); // Give EXP to the killer if entity had stats
let mut stats = state.ecs().write_storage::<comp::Stats>();
if let Some(entity_stats) = stats.get(entity).cloned() { if let Some(entity_stats) = stats.get(entity).cloned() {
if let comp::HealthSource::Attack { by } = cause { if let comp::HealthSource::Attack { by } = cause {
ecs.entity_from_uid(by.into()).map(|attacker| { state.ecs().entity_from_uid(by.into()).map(|attacker| {
if let Some(attacker_stats) = stats.get_mut(attacker) { if let Some(attacker_stats) = stats.get_mut(attacker) {
// TODO: Discuss whether we should give EXP by Player Killing or not. // TODO: Discuss whether we should give EXP by Player Killing or not.
attacker_stats attacker_stats
.exp .exp
.change_by((entity_stats.level.level() * 10) as i64); .change_by((entity_stats.level.level() * 10) as i64);
} }
}); });
}
} }
} }
if let Some(client) = ecs.write_storage::<Client>().get_mut(entity) { // This sucks
let _ = ecs.write_storage().insert(entity, comp::Vel(Vec3::zero())); let mut remove = false;
let _ = ecs.write_storage().insert(entity, comp::ForceUpdate);
if let Some(client) = state.ecs().write_storage::<Client>().get_mut(entity) {
let _ = state
.ecs()
.write_storage()
.insert(entity, comp::Vel(Vec3::zero()));
let _ = state
.ecs()
.write_storage()
.insert(entity, comp::ForceUpdate);
client.force_state(ClientState::Dead); client.force_state(ClientState::Dead);
} else { } else {
todo_remove = Some(entity.clone()); remove = true;
}
if remove {
if let Err(err) = state.delete_entity_recorded(entity) {
error!("Failed to delete destroyed entity: {:?}", err);
}
} }
} }
@ -457,7 +474,9 @@ impl Server {
}; };
if let Some(item_entity) = item_entity { if let Some(item_entity) = item_entity {
let _ = state.ecs_mut().delete_entity(item_entity); if let Err(err) = state.delete_entity_recorded(item_entity) {
error!("Failed to delete picked up item entity: {:?}", err);
}
} }
state.write_component(entity, comp::InventoryUpdate); state.write_component(entity, comp::InventoryUpdate);
@ -728,7 +747,7 @@ impl Server {
main, main,
&server_settings, &server_settings,
); );
Self::initialize_region_subscription(state, entity); sys::subscription::initialize_region_subscription(state.ecs(), entity);
} }
ServerEvent::CreateNpc { ServerEvent::CreateNpc {
@ -746,8 +765,8 @@ impl Server {
} }
ServerEvent::ClientDisconnect(entity) => { ServerEvent::ClientDisconnect(entity) => {
if let Err(err) = state.ecs_mut().delete_entity(entity) { if let Err(err) = state.delete_entity_recorded(entity) {
debug!("Failed to delete disconnected client: {:?}", err); error!("Failed to delete disconnected client: {:?}", err);
} }
frontend_events.push(Event::ClientDisconnected { entity }); frontend_events.push(Event::ClientDisconnected { entity });
@ -761,11 +780,6 @@ impl Server {
chat_commands.push((entity, cmd)); chat_commands.push((entity, cmd));
} }
} }
// TODO: is this needed?
if let Some(entity) = todo_remove {
let _ = state.ecs_mut().delete_entity(entity);
}
} }
// Generate requested chunks. // Generate requested chunks.
@ -825,7 +839,7 @@ impl Server {
frontend_events.append(&mut self.handle_new_connections()?); frontend_events.append(&mut self.handle_new_connections()?);
let before_tick_4 = Instant::now(); let before_tick_4 = Instant::now();
// 4) Tick the client's LocalState. // 4) Tick the server's LocalState.
self.state.tick(dt, sys::add_server_systems); self.state.tick(dt, sys::add_server_systems);
let before_handle_events = Instant::now(); let before_handle_events = Instant::now();
@ -856,7 +870,9 @@ impl Server {
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
for entity in to_delete { for entity in to_delete {
let _ = self.state.ecs_mut().delete_entity(entity); if let Err(err) = self.state.delete_entity_recorded(entity) {
error!("Failed to delete agent outside the terrain: {:?}", err);
}
} }
let before_tick_7 = Instant::now(); let before_tick_7 = Instant::now();
@ -1004,83 +1020,6 @@ impl Server {
Ok(frontend_events) Ok(frontend_events)
} }
/// Initialize region subscription
fn initialize_region_subscription(state: &mut State, entity: specs::Entity) {
let mut subscription = None;
if let (Some(client_pos), Some(client_vd), Some(client)) = (
state.ecs().read_storage::<comp::Pos>().get(entity),
state
.ecs()
.read_storage::<comp::Player>()
.get(entity)
.map(|pl| pl.view_distance)
.and_then(|v| v),
state.ecs().write_storage::<Client>().get_mut(entity),
) {
use common::region::RegionMap;
let fuzzy_chunk = (Vec2::<f32>::from(client_pos.0))
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32;
let regions = common::region::regions_in_vd(
client_pos.0,
(client_vd as f32 * chunk_size) as f32
+ (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(),
);
for (_, region) in state
.ecs()
.read_resource::<RegionMap>()
.iter()
.filter(|(key, _)| regions.contains(key))
{
// Sync physics of all entities in this region
for (&uid, &pos, vel, ori, character_state, _) in (
&state.ecs().read_storage::<Uid>(),
&state.ecs().read_storage::<comp::Pos>(), // We assume all these entities have a position
state.ecs().read_storage::<comp::Vel>().maybe(),
state.ecs().read_storage::<comp::Ori>().maybe(),
state.ecs().read_storage::<comp::CharacterState>().maybe(),
region.entities(),
)
.join()
{
client.notify(ServerMsg::EntityPos {
entity: uid.into(),
pos,
});
if let Some(vel) = vel.copied() {
client.notify(ServerMsg::EntityVel {
entity: uid.into(),
vel,
});
}
if let Some(ori) = ori.copied() {
client.notify(ServerMsg::EntityOri {
entity: uid.into(),
ori,
});
}
if let Some(character_state) = character_state.copied() {
client.notify(ServerMsg::EntityCharacterState {
entity: uid.into(),
character_state,
});
}
}
}
subscription = Some(RegionSubscription {
fuzzy_chunk,
regions,
});
}
if let Some(subscription) = subscription {
state.write_component(entity, subscription);
}
}
pub fn notify_client(&self, entity: EcsEntity, msg: ServerMsg) { pub fn notify_client(&self, entity: EcsEntity, msg: ServerMsg) {
if let Some(client) = self.state.ecs().write_storage::<Client>().get_mut(entity) { if let Some(client) = self.state.ecs().write_storage::<Client>().get_mut(entity) {
client.notify(msg) client.notify(msg)
@ -1142,6 +1081,10 @@ trait StateExt {
stats: comp::Stats, stats: comp::Stats,
body: comp::Body, body: comp::Body,
) -> EcsEntityBuilder; ) -> EcsEntityBuilder;
fn delete_entity_recorded(
&mut self,
entity: EcsEntity,
) -> Result<(), specs::error::WrongGeneration>;
} }
impl StateExt for State { impl StateExt for State {
@ -1202,4 +1145,28 @@ impl StateExt for State {
client.notify(msg.clone()) client.notify(msg.clone())
} }
} }
fn delete_entity_recorded(
&mut self,
entity: EcsEntity,
) -> Result<(), specs::error::WrongGeneration> {
let (maybe_uid, maybe_pos) = (
self.ecs().read_storage::<Uid>().get(entity).copied(),
self.ecs().read_storage::<comp::Pos>().get(entity).copied(),
);
let res = self.ecs_mut().delete_entity(entity);
if res.is_ok() {
if let (Some(uid), Some(pos)) = (maybe_uid, maybe_pos) {
let region_key = self
.ecs()
.read_resource::<common::region::RegionMap>()
.find_region(entity, pos.0)
.expect("Failed to find region containing entity during entity deletion");
self.ecs()
.write_resource::<DeletedEntities>()
.record_deleted_entity(uid, region_key);
}
}
res
}
} }

View File

@ -1,5 +1,5 @@
use super::{ use super::{
sentinel::{ReadTrackers, TrackedComps, TrackedResources}, sentinel::{DeletedEntities, ReadTrackers, TrackedComps, TrackedResources},
SysTimer, SysTimer,
}; };
use crate::{ use crate::{
@ -38,6 +38,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Client>, WriteStorage<'a, Client>,
WriteStorage<'a, ForceUpdate>, WriteStorage<'a, ForceUpdate>,
WriteStorage<'a, InventoryUpdate>, WriteStorage<'a, InventoryUpdate>,
Write<'a, DeletedEntities>,
TrackedComps<'a>, TrackedComps<'a>,
ReadTrackers<'a>, ReadTrackers<'a>,
TrackedResources<'a>, TrackedResources<'a>,
@ -64,8 +65,9 @@ impl<'a> System<'a> for Sys {
mut clients, mut clients,
mut force_updates, mut force_updates,
mut inventory_updates, mut inventory_updates,
mut deleted_entities,
tracked_comps, tracked_comps,
read_trackers, trackers,
tracked_resources, tracked_resources,
): Self::SystemData, ): Self::SystemData,
) { ) {
@ -86,6 +88,8 @@ impl<'a> System<'a> for Sys {
// Sync physics // Sync physics
// via iterating through regions // via iterating through regions
for (key, region) in region_map.iter() { for (key, region) in region_map.iter() {
// Assemble subscriber list for this region by iterating through clients and checking
// if they are subscribed to this region
let mut subscribers = (&mut clients, &entities, &subscriptions, &positions) let mut subscribers = (&mut clients, &entities, &subscriptions, &positions)
.join() .join()
.filter_map(|(client, entity, subscription, pos)| { .filter_map(|(client, entity, subscription, pos)| {
@ -100,6 +104,10 @@ impl<'a> System<'a> for Sys {
for event in region.events() { for event in region.events() {
match event { match event {
RegionEvent::Entered(id, maybe_key) => { RegionEvent::Entered(id, maybe_key) => {
// Don't process newly created entities here (redundant network messages)
if trackers.uid.inserted().contains(*id) {
continue;
}
let entity = entities.entity(*id); let entity = entities.entity(*id);
if let Some((uid, pos, vel, ori, character_state)) = if let Some((uid, pos, vel, ori, character_state)) =
uids.get(entity).and_then(|uid| { uids.get(entity).and_then(|uid| {
@ -156,8 +164,15 @@ impl<'a> System<'a> for Sys {
} }
// Sync tracked components // Sync tracked components
// Get deleted entities in this region from DeletedEntities
let sync_msg = ServerMsg::EcsSync( let sync_msg = ServerMsg::EcsSync(
read_trackers.create_sync_package(&tracked_comps, region.entities()), trackers.create_sync_package(
&tracked_comps,
region.entities(),
deleted_entities
.take_deleted_in_region(key)
.unwrap_or_else(|| Vec::new()),
),
); );
for (client, _, _, _) in &mut subscribers { for (client, _, _, _) in &mut subscribers {
client.notify(sync_msg.clone()); client.notify(sync_msg.clone());
@ -169,9 +184,9 @@ impl<'a> System<'a> for Sys {
force_update: Option<&ForceUpdate>, force_update: Option<&ForceUpdate>,
throttle: bool| { throttle: bool| {
for (client, _, client_entity, client_pos) in &mut subscribers { for (client, _, client_entity, client_pos) in &mut subscribers {
let update = if client_entity == &entity && force_update.is_none() { let update = if client_entity == &entity {
// Don't send client physics update about itself // Don't send client physics updates about itself unless force update is set
false force_update.is_some()
} else if !throttle { } else if !throttle {
// Update rate not thottled by distance // Update rate not thottled by distance
true true
@ -283,6 +298,25 @@ impl<'a> System<'a> for Sys {
} }
} }
// Handle entity deletion in regions that don't exist in RegionMap (theoretically none)
for (region_key, deleted) in deleted_entities.take_remaining_deleted() {
for client in
(&mut clients, &subscriptions)
.join()
.filter_map(|(client, subscription)| {
if client.is_ingame() && subscription.regions.contains(&region_key) {
Some(client)
} else {
None
}
})
{
for uid in &deleted {
client.notify(ServerMsg::DeleteEntity(*uid));
}
}
}
// TODO: Sync clients that don't have a position? // TODO: Sync clients that don't have a position?
// Sync inventories // Sync inventories

View File

@ -1,7 +1,7 @@
use super::SysTimer; use super::SysTimer;
use crate::{auth_provider::AuthProvider, client::Client, CLIENT_TIMEOUT}; use crate::{auth_provider::AuthProvider, client::Client, CLIENT_TIMEOUT};
use common::{ use common::{
comp::{Admin, Body, CanBuild, Controller, Ori, Player, Pos, Vel}, comp::{Admin, Body, CanBuild, Controller, ForceUpdate, Ori, Player, Pos, Vel},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
msg::{validate_chat_msg, ChatMsgValidationError, MAX_BYTES_CHAT_MSG}, msg::{validate_chat_msg, ChatMsgValidationError, MAX_BYTES_CHAT_MSG},
msg::{ClientMsg, ClientState, RequestStateError, ServerMsg}, msg::{ClientMsg, ClientState, RequestStateError, ServerMsg},
@ -25,6 +25,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Body>, ReadStorage<'a, Body>,
ReadStorage<'a, CanBuild>, ReadStorage<'a, CanBuild>,
ReadStorage<'a, Admin>, ReadStorage<'a, Admin>,
ReadStorage<'a, ForceUpdate>,
WriteExpect<'a, AuthProvider>, WriteExpect<'a, AuthProvider>,
Write<'a, BlockChange>, Write<'a, BlockChange>,
WriteStorage<'a, Pos>, WriteStorage<'a, Pos>,
@ -46,6 +47,7 @@ impl<'a> System<'a> for Sys {
bodies, bodies,
can_build, can_build,
admins, admins,
force_updates,
mut accounts, mut accounts,
mut block_changes, mut block_changes,
mut positions, mut positions,
@ -218,9 +220,11 @@ impl<'a> System<'a> for Sys {
}, },
ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state { ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state {
ClientState::Character => { ClientState::Character => {
let _ = positions.insert(entity, pos); if force_updates.get(entity).is_none() {
let _ = velocities.insert(entity, vel); let _ = positions.insert(entity, pos);
let _ = orientations.insert(entity, ori); let _ = velocities.insert(entity, vel);
let _ = orientations.insert(entity, ori);
}
} }
// Only characters can send positions. // Only characters can send positions.
_ => client.error_state(RequestStateError::Impossible), _ => client.error_state(RequestStateError::Impossible),

View File

@ -5,17 +5,19 @@ use common::{
Projectile, Scale, Stats, Sticky, Projectile, Scale, Stats, Sticky,
}, },
msg::{EcsCompPacket, EcsResPacket}, msg::{EcsCompPacket, EcsResPacket},
state::{Time, TimeOfDay}, state::TimeOfDay,
sync::{ sync::{
CompPacket, EntityPackage, ResSyncPackage, StatePackage, SyncPackage, Uid, UpdateTracker, CompPacket, EntityPackage, ResSyncPackage, StatePackage, SyncPackage, Uid, UpdateTracker,
WorldSyncExt, WorldSyncExt,
}, },
}; };
use hashbrown::HashMap;
use shred_derive::SystemData; use shred_derive::SystemData;
use specs::{ use specs::{
Entity as EcsEntity, Join, ReadExpect, ReadStorage, System, World, Write, WriteExpect, Entity as EcsEntity, Join, ReadExpect, ReadStorage, System, World, Write, WriteExpect,
}; };
use std::ops::Deref; use std::ops::Deref;
use vek::*;
/// Always watching /// Always watching
/// This system will monitor specific components for insertion, removal, and modification /// This system will monitor specific components for insertion, removal, and modification
@ -39,20 +41,20 @@ impl<'a> System<'a> for Sys {
// Probably more difficult than it needs to be :p // Probably more difficult than it needs to be :p
#[derive(SystemData)] #[derive(SystemData)]
pub struct TrackedComps<'a> { pub struct TrackedComps<'a> {
uid: ReadStorage<'a, Uid>, pub uid: ReadStorage<'a, Uid>,
body: ReadStorage<'a, Body>, pub body: ReadStorage<'a, Body>,
player: ReadStorage<'a, Player>, pub player: ReadStorage<'a, Player>,
stats: ReadStorage<'a, Stats>, pub stats: ReadStorage<'a, Stats>,
can_build: ReadStorage<'a, CanBuild>, pub can_build: ReadStorage<'a, CanBuild>,
light_emitter: ReadStorage<'a, LightEmitter>, pub light_emitter: ReadStorage<'a, LightEmitter>,
item: ReadStorage<'a, Item>, pub item: ReadStorage<'a, Item>,
scale: ReadStorage<'a, Scale>, pub scale: ReadStorage<'a, Scale>,
mounting: ReadStorage<'a, Mounting>, pub mounting: ReadStorage<'a, Mounting>,
mount_state: ReadStorage<'a, MountState>, pub mount_state: ReadStorage<'a, MountState>,
mass: ReadStorage<'a, Mass>, pub mass: ReadStorage<'a, Mass>,
sticky: ReadStorage<'a, Sticky>, pub sticky: ReadStorage<'a, Sticky>,
gravity: ReadStorage<'a, Gravity>, pub gravity: ReadStorage<'a, Gravity>,
projectile: ReadStorage<'a, Projectile>, pub projectile: ReadStorage<'a, Projectile>,
} }
impl<'a> TrackedComps<'a> { impl<'a> TrackedComps<'a> {
pub fn create_entity_package(&self, entity: EcsEntity) -> EntityPackage<EcsCompPacket> { pub fn create_entity_package(&self, entity: EcsEntity) -> EntityPackage<EcsCompPacket> {
@ -121,115 +123,53 @@ impl<'a> TrackedComps<'a> {
} }
#[derive(SystemData)] #[derive(SystemData)]
pub struct ReadTrackers<'a> { pub struct ReadTrackers<'a> {
uid: ReadExpect<'a, UpdateTracker<Uid>>, pub uid: ReadExpect<'a, UpdateTracker<Uid>>,
body: ReadExpect<'a, UpdateTracker<Body>>, pub body: ReadExpect<'a, UpdateTracker<Body>>,
player: ReadExpect<'a, UpdateTracker<Player>>, pub player: ReadExpect<'a, UpdateTracker<Player>>,
stats: ReadExpect<'a, UpdateTracker<Stats>>, pub stats: ReadExpect<'a, UpdateTracker<Stats>>,
can_build: ReadExpect<'a, UpdateTracker<CanBuild>>, pub can_build: ReadExpect<'a, UpdateTracker<CanBuild>>,
light_emitter: ReadExpect<'a, UpdateTracker<LightEmitter>>, pub light_emitter: ReadExpect<'a, UpdateTracker<LightEmitter>>,
item: ReadExpect<'a, UpdateTracker<Item>>, pub item: ReadExpect<'a, UpdateTracker<Item>>,
scale: ReadExpect<'a, UpdateTracker<Scale>>, pub scale: ReadExpect<'a, UpdateTracker<Scale>>,
mounting: ReadExpect<'a, UpdateTracker<Mounting>>, pub mounting: ReadExpect<'a, UpdateTracker<Mounting>>,
mount_state: ReadExpect<'a, UpdateTracker<MountState>>, pub mount_state: ReadExpect<'a, UpdateTracker<MountState>>,
mass: ReadExpect<'a, UpdateTracker<Mass>>, pub mass: ReadExpect<'a, UpdateTracker<Mass>>,
sticky: ReadExpect<'a, UpdateTracker<Sticky>>, pub sticky: ReadExpect<'a, UpdateTracker<Sticky>>,
gravity: ReadExpect<'a, UpdateTracker<Gravity>>, pub gravity: ReadExpect<'a, UpdateTracker<Gravity>>,
projectile: ReadExpect<'a, UpdateTracker<Projectile>>, pub projectile: ReadExpect<'a, UpdateTracker<Projectile>>,
} }
impl<'a> ReadTrackers<'a> { impl<'a> ReadTrackers<'a> {
pub fn create_sync_package( pub fn create_sync_package(
&self, &self,
comps: &TrackedComps, comps: &TrackedComps,
filter: impl Join + Copy, filter: impl Join + Copy,
deleted_entities: Vec<u64>,
) -> SyncPackage<EcsCompPacket> { ) -> SyncPackage<EcsCompPacket> {
SyncPackage::new(&comps.uid, &self.uid, filter) SyncPackage::new(&comps.uid, &self.uid, filter, deleted_entities)
.with_component(&comps.uid, self.body.deref(), &comps.body, filter)
.with_component(&comps.uid, self.player.deref(), &comps.player, filter)
.with_component(&comps.uid, self.stats.deref(), &comps.stats, filter)
.with_component(&comps.uid, self.can_build.deref(), &comps.can_build, filter)
.with_component( .with_component(
&comps.uid, &comps.uid,
&self.uid,
self.body.deref(),
&comps.body,
filter,
)
.with_component(
&comps.uid,
&self.uid,
self.player.deref(),
&comps.player,
filter,
)
.with_component(
&comps.uid,
&self.uid,
self.stats.deref(),
&comps.stats,
filter,
)
.with_component(
&comps.uid,
&self.uid,
self.can_build.deref(),
&comps.can_build,
filter,
)
.with_component(
&comps.uid,
&self.uid,
self.light_emitter.deref(), self.light_emitter.deref(),
&comps.light_emitter, &comps.light_emitter,
filter, filter,
) )
.with_component(&comps.uid, self.item.deref(), &comps.item, filter)
.with_component(&comps.uid, self.scale.deref(), &comps.scale, filter)
.with_component(&comps.uid, self.mounting.deref(), &comps.mounting, filter)
.with_component( .with_component(
&comps.uid, &comps.uid,
&self.uid,
self.item.deref(),
&comps.item,
filter,
)
.with_component(
&comps.uid,
&self.uid,
self.scale.deref(),
&comps.scale,
filter,
)
.with_component(
&comps.uid,
&self.uid,
self.mounting.deref(),
&comps.mounting,
filter,
)
.with_component(
&comps.uid,
&self.uid,
self.mount_state.deref(), self.mount_state.deref(),
&comps.mount_state, &comps.mount_state,
filter, filter,
) )
.with_component(&comps.uid, self.mass.deref(), &comps.mass, filter)
.with_component(&comps.uid, self.sticky.deref(), &comps.sticky, filter)
.with_component(&comps.uid, self.gravity.deref(), &comps.gravity, filter)
.with_component( .with_component(
&comps.uid, &comps.uid,
&self.uid,
self.mass.deref(),
&comps.mass,
filter,
)
.with_component(
&comps.uid,
&self.uid,
self.sticky.deref(),
&comps.sticky,
filter,
)
.with_component(
&comps.uid,
&self.uid,
self.gravity.deref(),
&comps.gravity,
filter,
)
.with_component(
&comps.uid,
&self.uid,
self.projectile.deref(), self.projectile.deref(),
&comps.projectile, &comps.projectile,
filter, filter,
@ -292,19 +232,46 @@ pub fn register_trackers(world: &mut World) {
#[derive(SystemData)] #[derive(SystemData)]
pub struct TrackedResources<'a> { pub struct TrackedResources<'a> {
time: ReadExpect<'a, Time>,
time_of_day: ReadExpect<'a, TimeOfDay>, time_of_day: ReadExpect<'a, TimeOfDay>,
} }
impl<'a> TrackedResources<'a> { impl<'a> TrackedResources<'a> {
pub fn create_res_sync_package(&self) -> ResSyncPackage<EcsResPacket> { pub fn create_res_sync_package(&self) -> ResSyncPackage<EcsResPacket> {
ResSyncPackage::new() ResSyncPackage::new().with_res(self.time_of_day.deref())
.with_res(self.time.deref())
.with_res(self.time_of_day.deref())
} }
/// Create state package with resources included /// Create state package with resources included
pub fn state_package<C: CompPacket>(&self) -> StatePackage<C, EcsResPacket> { pub fn state_package<C: CompPacket>(&self) -> StatePackage<C, EcsResPacket> {
StatePackage::new() StatePackage::new().with_res(self.time_of_day.deref())
.with_res(self.time.deref()) }
.with_res(self.time_of_day.deref()) }
/// Deleted entities grouped by region
pub struct DeletedEntities {
map: HashMap<Vec2<i32>, Vec<u64>>,
}
impl Default for DeletedEntities {
fn default() -> Self {
Self {
map: HashMap::new(),
}
}
}
impl DeletedEntities {
pub fn record_deleted_entity(&mut self, uid: Uid, region_key: Vec2<i32>) {
self.map
.entry(region_key)
.or_insert(Vec::new())
.push(uid.into());
}
pub fn take_deleted_in_region(&mut self, key: Vec2<i32>) -> Option<Vec<u64>> {
self.map.remove(&key)
}
pub fn get_deleted_in_region(&mut self, key: Vec2<i32>) -> Option<&Vec<u64>> {
self.map.get(&key)
}
pub fn take_remaining_deleted(&mut self) -> Vec<(Vec2<i32>, Vec<u64>)> {
// TODO: don't allocate
self.map.drain().collect()
} }
} }

View File

@ -1,4 +1,7 @@
use super::{sentinel::TrackedComps, SysTimer}; use super::{
sentinel::{DeletedEntities, TrackedComps},
SysTimer,
};
use crate::client::{self, Client, RegionSubscription}; use crate::client::{self, Client, RegionSubscription};
use common::{ use common::{
comp::{CharacterState, Ori, Player, Pos, Vel}, comp::{CharacterState, Ori, Player, Pos, Vel},
@ -8,7 +11,10 @@ use common::{
terrain::TerrainChunkSize, terrain::TerrainChunkSize,
vol::RectVolSize, vol::RectVolSize,
}; };
use specs::{Entities, Join, ReadExpect, ReadStorage, System, Write, WriteStorage}; use log::{debug, error};
use specs::{
Entities, Join, ReadExpect, ReadStorage, System, SystemData, World, Write, WriteStorage,
};
use vek::*; use vek::*;
/// This system will update region subscriptions based on client positions /// This system will update region subscriptions based on client positions
@ -26,6 +32,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Player>, ReadStorage<'a, Player>,
WriteStorage<'a, Client>, WriteStorage<'a, Client>,
WriteStorage<'a, RegionSubscription>, WriteStorage<'a, RegionSubscription>,
Write<'a, DeletedEntities>,
TrackedComps<'a>, TrackedComps<'a>,
); );
@ -43,6 +50,7 @@ impl<'a> System<'a> for Sys {
players, players,
mut clients, mut clients,
mut subscriptions, mut subscriptions,
mut deleted_entities,
tracked_comps, tracked_comps,
): Self::SystemData, ): Self::SystemData,
) { ) {
@ -68,8 +76,15 @@ impl<'a> System<'a> for Sys {
&entities, &entities,
) )
.join() .join()
.filter_map(|(c, s, pos, player, e)| player.view_distance.map(|v| (c, s, pos, v, e))) .filter_map(|(client, s, pos, player, e)| {
if client.is_ingame() {
player.view_distance.map(|v| (client, s, pos, v, e))
} else {
None
}
})
{ {
// Calculate current chunk
let chunk = (Vec2::<f32>::from(pos.0)) let chunk = (Vec2::<f32>::from(pos.0))
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32); .map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
// Only update regions when moving to a new chunk // Only update regions when moving to a new chunk
@ -87,7 +102,7 @@ impl<'a> System<'a> for Sys {
.reduce_or() .reduce_or()
{ {
// Update current chunk // Update current chunk
subscription.fuzzy_chunk = (Vec2::<f32>::from(pos.0)) subscription.fuzzy_chunk = dbg!(Vec2::<f32>::from(pos.0))
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32); .map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
// Use the largest side length as our chunk size // Use the largest side length as our chunk size
let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32; let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32;
@ -108,19 +123,24 @@ impl<'a> System<'a> for Sys {
// Iterate through regions to remove // Iterate through regions to remove
for key in regions_to_remove.drain(..) { for key in regions_to_remove.drain(..) {
// Remove region from this clients set of subscribed regions // Remove region from this client's set of subscribed regions
subscription.regions.remove(&key); subscription.regions.remove(&key);
// Tell the client to delete the entities in that region if it exists in the RegionMap // Tell the client to delete the entities in that region if it exists in the RegionMap
if let Some(region) = region_map.get(key) { if let Some(region) = region_map.get(key) {
// Process entity left events since they won't be processed during phsyics sync because this region is no longer subscribed to // Process entity left events since they won't be processed during entity sync because this region is no longer subscribed to
// TODO: consider changing system ordering??
for event in region.events() { for event in region.events() {
match event { match event {
RegionEvent::Entered(_, _) => {} // These don't need to be processed because this region is being thrown out anyway RegionEvent::Entered(_, _) => {} // These don't need to be processed because this region is being thrown out anyway
RegionEvent::Left(id, maybe_key) => { RegionEvent::Left(id, maybe_key) => {
// Lookup UID for entity // Lookup UID for entity
// Doesn't overlap with entity deletion in sync packages
// because the uid would not be available if the entity was
// deleted
if let Some(&uid) = uids.get(entities.entity(*id)) { if let Some(&uid) = uids.get(entities.entity(*id)) {
if !maybe_key if !maybe_key
.as_ref() .as_ref()
// Don't need to check that this isn't also in the regions to remove since the entity will be removed when we get to that one
.map(|key| subscription.regions.contains(key)) .map(|key| subscription.regions.contains(key))
.unwrap_or(false) .unwrap_or(false)
{ {
@ -130,10 +150,19 @@ impl<'a> System<'a> for Sys {
} }
} }
} }
// Tell client to delete entities in the region
for (&uid, _) in (&uids, region.entities()).join() { for (&uid, _) in (&uids, region.entities()).join() {
client.notify(ServerMsg::DeleteEntity(uid.into())) client.notify(ServerMsg::DeleteEntity(uid.into()));
} }
} }
// Send deleted entities since they won't be processed for this client in entity sync
for uid in deleted_entities
.get_deleted_in_region(key)
.iter()
.flat_map(|v| v.iter())
{
client.notify(ServerMsg::DeleteEntity(*uid));
}
} }
for key in regions_in_vd( for key in regions_in_vd(
@ -141,8 +170,10 @@ impl<'a> System<'a> for Sys {
(vd as f32 * chunk_size) (vd as f32 * chunk_size)
+ (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(), + (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(),
) { ) {
// Send client intial info about the entities in this region // Send client intial info about the entities in this region if it was not
if subscription.regions.insert(key) { // already within the set of subscribed regions
if subscription.regions.insert(key.clone()) {
let mut counter = 0;
if let Some(region) = region_map.get(key) { if let Some(region) = region_map.get(key) {
for (uid, pos, vel, ori, character_state, _, entity) in ( for (uid, pos, vel, ori, character_state, _, entity) in (
&uids, &uids,
@ -156,6 +187,7 @@ impl<'a> System<'a> for Sys {
.join() .join()
.filter(|(_, _, _, _, _, _, e)| *e != client_entity) .filter(|(_, _, _, _, _, _, e)| *e != client_entity)
{ {
counter += 1;
// Send message to create entity and tracked components // Send message to create entity and tracked components
client.notify(ServerMsg::CreateEntity( client.notify(ServerMsg::CreateEntity(
tracked_comps.create_entity_package(entity), tracked_comps.create_entity_package(entity),
@ -179,3 +211,69 @@ impl<'a> System<'a> for Sys {
timer.end(); timer.end();
} }
} }
/// Initialize region subscription
pub fn initialize_region_subscription(world: &World, entity: specs::Entity) {
if let (Some(client_pos), Some(client_vd), Some(client)) = (
world.read_storage::<Pos>().get(entity),
world
.read_storage::<Player>()
.get(entity)
.map(|pl| pl.view_distance)
.and_then(|v| v),
world.write_storage::<Client>().get_mut(entity),
) {
let fuzzy_chunk = (Vec2::<f32>::from(client_pos.0))
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32;
let regions = common::region::regions_in_vd(
client_pos.0,
(client_vd as f32 * chunk_size) as f32
+ (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(),
);
let region_map = world.read_resource::<RegionMap>();
let tracked_comps = TrackedComps::fetch(&world.res);
for key in &regions {
if let Some(region) = region_map.get(*key) {
for (uid, pos, vel, ori, character_state, _, entity) in (
&tracked_comps.uid,
&world.read_storage::<Pos>(), // We assume all these entities have a position
world.read_storage::<Vel>().maybe(),
world.read_storage::<Ori>().maybe(),
world.read_storage::<CharacterState>().maybe(),
region.entities(),
&world.entities(),
)
.join()
{
// Send message to create entity and tracked components
client.notify(ServerMsg::CreateEntity(
tracked_comps.create_entity_package(entity),
));
// Send message to create physics components
super::entity_sync::send_initial_unsynced_components(
client,
uid,
pos,
vel,
ori,
character_state,
);
}
}
}
if let Err(err) = world.write_storage().insert(
entity,
RegionSubscription {
fuzzy_chunk,
regions,
},
) {
error!("Failed to insert region subscription component: {:?}", err);
}
} else {
debug!("Failed to initialize region subcription. Couldn't retrieve all the neccesary components on the provided entity");
}
}

View File

@ -112,6 +112,7 @@ widget_ids! {
velocity, velocity,
loaded_distance, loaded_distance,
time, time,
entity_count,
// Game Version // Game Version
version, version,
@ -872,11 +873,19 @@ impl Hud {
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.font_size(14) .font_size(14)
.set(self.ids.time, ui_widgets); .set(self.ids.time, ui_widgets);
// Number of entities
let entity_count = client.state().ecs().entities().join().count();
Text::new(&format!("Entity count: {}", entity_count))
.color(TEXT_COLOR)
.down_from(self.ids.time, 5.0)
.font_id(self.fonts.cyri)
.font_size(14)
.set(self.ids.entity_count, ui_widgets);
// Help Window // Help Window
Text::new("Press 'F1' to show Keybindings") Text::new("Press 'F1' to show Keybindings")
.color(TEXT_COLOR) .color(TEXT_COLOR)
.down_from(self.ids.time, 5.0) .down_from(self.ids.entity_count, 5.0)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.font_size(14) .font_size(14)
.set(self.ids.help_info, ui_widgets); .set(self.ids.help_info, ui_widgets);

View File

@ -324,7 +324,7 @@ impl WorldSim {
let logistic_cdf = |x: f64| (x / logistic_2_base).tanh() * 0.5 + 0.5; let logistic_cdf = |x: f64| (x / logistic_2_base).tanh() * 0.5 + 0.5;
let erosion_pow = 2.0; let erosion_pow = 2.0;
let n_steps = 100; let n_steps = 7;
let erosion_factor = |x: f64| logistic_cdf(erosion_pow * logit(x)); let erosion_factor = |x: f64| logistic_cdf(erosion_pow * logit(x));
let alt = do_erosion( let alt = do_erosion(
0.0, 0.0,