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
debug-assertions = true
panic = "abort"
debug = false
# debug = false
codegen-units = 8
lto = false
incremental = true

View File

@ -562,10 +562,15 @@ impl Client {
self.state.ecs_mut().apply_entity_package(entity_package)
}
ServerMsg::DeleteEntity(entity) => {
if let Some(entity) = self.state.ecs().entity_from_uid(entity) {
if entity != self.entity {
let _ = self.state.ecs_mut().delete_entity(entity);
}
if self
.state
.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 } => {

View File

@ -25,9 +25,6 @@ impl sync::ResPacket for EcsResPacket {
sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsCompPacket {
Pos(comp::Pos),
Vel(comp::Vel),
Ori(comp::Ori),
Body(comp::Body),
Player(comp::Player),
CanBuild(comp::CanBuild),
@ -48,9 +45,6 @@ sum_type! {
sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsCompPhantom {
Pos(PhantomData<comp::Pos>),
Vel(PhantomData<comp::Vel>),
Ori(PhantomData<comp::Ori>),
Body(PhantomData<comp::Body>),
Player(PhantomData<comp::Player>),
CanBuild(PhantomData<comp::CanBuild>),
@ -70,9 +64,6 @@ impl sync::CompPacket for EcsCompPacket {
type Phantom = EcsCompPhantom;
fn apply_insert(self, entity: specs::Entity, world: &specs::World) {
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::Player(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) {
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::Player(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) {
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::Player(_) => sync::handle_remove::<comp::Player>(entity, world),
EcsCompPhantom::CanBuild(_) => sync::handle_remove::<comp::CanBuild>(entity, world),

View File

@ -104,6 +104,16 @@ impl RegionMap {
// TODO special case large entities
pub fn tick(&mut self, pos: ReadStorage<Pos>, vel: ReadStorage<Vel>, entities: Entities) {
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
for (pos, id) in (&pos, &entities, !&self.tracked_entities)
.join()
@ -118,14 +128,6 @@ impl RegionMap {
let mut regions_to_remove = Vec::new();
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 (
pos.maybe(),
vel.maybe(),
@ -215,6 +217,47 @@ impl RegionMap {
pub fn key_pos(key: Vec2<i32>) -> Vec2<i32> {
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> {
self.regions.get_full(&key).map(|(i, _, _)| i)
}

View File

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

View File

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

View File

@ -6,6 +6,7 @@ use super::{
track::UpdateTracker,
uid::{Uid, UidAllocator},
};
use log::error;
use specs::{
saveload::{MarkedBuilder, MarkerAllocator},
world::Builder,
@ -20,6 +21,7 @@ pub trait WorldSyncExt {
where
C::Storage: Default + specs::storage::Tracked;
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 entity_from_uid(&self, uid: u64) -> Option<specs::Entity>;
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) {
self.register_synced::<Uid>();
// TODO: Consider only having allocator server side for now
self.add_resource(UidAllocator::new());
}
fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self)
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,
{
self.register::<C>();
@ -50,26 +49,12 @@ impl WorldSyncExt for specs::World {
}
fn register_tracker<C: specs::Component + Clone + Send + Sync>(&mut self)
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,
{
let tracker = UpdateTracker::<C>::new(self);
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 {
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>(
&mut self,
state_package: StatePackage<P, R>,
@ -114,7 +109,7 @@ impl WorldSyncExt for specs::World {
}
// Initialize entities
//specs_world.maintain();
//self.maintain();
}
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
for entity_uid in deleted_entities {
let entity = self
.read_resource::<UidAllocator>()
.retrieve_entity_internal(entity_uid);
if let Some(entity) = entity {
let _ = self.delete_entity(entity);
}
self.delete_entity_and_clear_from_uid_allocator(entity_uid);
}
}
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>()
.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) => {
// We don't care about modification if the component was just added or was
// removed
// Could potentially remove since this should theoretically never occur...
if !self.removed.contains(*id) && !self.inserted.contains(*id) {
self.modified.add(*id);
}

View File

@ -56,6 +56,10 @@ impl UidAllocator {
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 {
@ -69,14 +73,14 @@ impl MarkerAllocator<Uid> for UidAllocator {
let id = id.unwrap_or_else(|| {
let id = self.index;
self.index += 1;
self.mapping.insert(id, entity);
id
});
self.mapping.insert(id, entity);
Uid(id)
}
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>) {

View File

@ -21,6 +21,7 @@ use vek::*;
use world::util::Sampler;
use lazy_static::lazy_static;
use log::error;
use scan_fmt::{scan_fmt, scan_fmt_some};
/// 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();
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(

View File

@ -19,7 +19,7 @@ use crate::{
chunk_generator::ChunkGenerator,
client::{Client, RegionSubscription},
cmd::CHAT_COMMANDS,
sys::sentinel::{TrackedComps, TrackedResources},
sys::sentinel::{DeletedEntities, TrackedComps, TrackedResources},
};
use common::{
assets, comp,
@ -32,7 +32,7 @@ use common::{
terrain::{block::Block, TerrainChunkSize, TerrainGrid},
vol::{ReadVol, RectVolSize, Vox},
};
use log::debug;
use log::{debug, error};
use metrics::ServerMetrics;
use rand::Rng;
use specs::{
@ -166,6 +166,8 @@ impl Server {
// Register trackers
sys::sentinel::register_trackers(&mut state.ecs_mut());
state.ecs_mut().add_resource(DeletedEntities::default());
let this = Self {
state,
world: Arc::new(world),
@ -309,8 +311,6 @@ impl Server {
let server_settings = &self.server_settings;
let mut todo_remove = None;
match event {
ServerEvent::Explosion { pos, radius } => {
const RAYS: usize = 500;
@ -380,19 +380,20 @@ impl Server {
}
ServerEvent::Destroy { entity, cause } => {
let ecs = state.ecs();
// 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 {
ecs.entity_from_uid(by.into()).and_then(|attacker| {
ecs.read_storage::<comp::Player>().get(attacker).map(
|attacker_alias| {
state.ecs().entity_from_uid(by.into()).and_then(|attacker| {
state
.ecs()
.read_storage::<comp::Player>()
.get(attacker)
.map(|attacker_alias| {
format!(
"{} was killed by {}",
&player.alias, &attacker_alias.alias
)
},
)
})
})
} else {
None
@ -402,12 +403,12 @@ impl Server {
state.notify_registered_clients(ServerMsg::kill(msg));
}
{
// Give EXP to the killer if entity had stats
let mut stats = ecs.write_storage::<comp::Stats>();
let mut stats = state.ecs().write_storage::<comp::Stats>();
if let Some(entity_stats) = stats.get(entity).cloned() {
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) {
// TODO: Discuss whether we should give EXP by Player Killing or not.
attacker_stats
@ -417,13 +418,29 @@ impl Server {
});
}
}
}
if let Some(client) = ecs.write_storage::<Client>().get_mut(entity) {
let _ = ecs.write_storage().insert(entity, comp::Vel(Vec3::zero()));
let _ = ecs.write_storage().insert(entity, comp::ForceUpdate);
// This sucks
let mut remove = false;
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);
} 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 {
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);
@ -728,7 +747,7 @@ impl Server {
main,
&server_settings,
);
Self::initialize_region_subscription(state, entity);
sys::subscription::initialize_region_subscription(state.ecs(), entity);
}
ServerEvent::CreateNpc {
@ -746,8 +765,8 @@ impl Server {
}
ServerEvent::ClientDisconnect(entity) => {
if let Err(err) = state.ecs_mut().delete_entity(entity) {
debug!("Failed to delete disconnected client: {:?}", err);
if let Err(err) = state.delete_entity_recorded(entity) {
error!("Failed to delete disconnected client: {:?}", err);
}
frontend_events.push(Event::ClientDisconnected { entity });
@ -761,11 +780,6 @@ impl Server {
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.
@ -825,7 +839,7 @@ impl Server {
frontend_events.append(&mut self.handle_new_connections()?);
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);
let before_handle_events = Instant::now();
@ -856,7 +870,9 @@ impl Server {
.collect::<Vec<_>>()
};
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();
@ -1004,83 +1020,6 @@ impl Server {
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) {
if let Some(client) = self.state.ecs().write_storage::<Client>().get_mut(entity) {
client.notify(msg)
@ -1142,6 +1081,10 @@ trait StateExt {
stats: comp::Stats,
body: comp::Body,
) -> EcsEntityBuilder;
fn delete_entity_recorded(
&mut self,
entity: EcsEntity,
) -> Result<(), specs::error::WrongGeneration>;
}
impl StateExt for State {
@ -1202,4 +1145,28 @@ impl StateExt for State {
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::{
sentinel::{ReadTrackers, TrackedComps, TrackedResources},
sentinel::{DeletedEntities, ReadTrackers, TrackedComps, TrackedResources},
SysTimer,
};
use crate::{
@ -38,6 +38,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Client>,
WriteStorage<'a, ForceUpdate>,
WriteStorage<'a, InventoryUpdate>,
Write<'a, DeletedEntities>,
TrackedComps<'a>,
ReadTrackers<'a>,
TrackedResources<'a>,
@ -64,8 +65,9 @@ impl<'a> System<'a> for Sys {
mut clients,
mut force_updates,
mut inventory_updates,
mut deleted_entities,
tracked_comps,
read_trackers,
trackers,
tracked_resources,
): Self::SystemData,
) {
@ -86,6 +88,8 @@ impl<'a> System<'a> for Sys {
// Sync physics
// via iterating through regions
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)
.join()
.filter_map(|(client, entity, subscription, pos)| {
@ -100,6 +104,10 @@ impl<'a> System<'a> for Sys {
for event in region.events() {
match event {
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);
if let Some((uid, pos, vel, ori, character_state)) =
uids.get(entity).and_then(|uid| {
@ -156,8 +164,15 @@ impl<'a> System<'a> for Sys {
}
// Sync tracked components
// Get deleted entities in this region from DeletedEntities
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 {
client.notify(sync_msg.clone());
@ -169,9 +184,9 @@ impl<'a> System<'a> for Sys {
force_update: Option<&ForceUpdate>,
throttle: bool| {
for (client, _, client_entity, client_pos) in &mut subscribers {
let update = if client_entity == &entity && force_update.is_none() {
// Don't send client physics update about itself
false
let update = if client_entity == &entity {
// Don't send client physics updates about itself unless force update is set
force_update.is_some()
} else if !throttle {
// Update rate not thottled by distance
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?
// Sync inventories

View File

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

View File

@ -5,17 +5,19 @@ use common::{
Projectile, Scale, Stats, Sticky,
},
msg::{EcsCompPacket, EcsResPacket},
state::{Time, TimeOfDay},
state::TimeOfDay,
sync::{
CompPacket, EntityPackage, ResSyncPackage, StatePackage, SyncPackage, Uid, UpdateTracker,
WorldSyncExt,
},
};
use hashbrown::HashMap;
use shred_derive::SystemData;
use specs::{
Entity as EcsEntity, Join, ReadExpect, ReadStorage, System, World, Write, WriteExpect,
};
use std::ops::Deref;
use vek::*;
/// Always watching
/// 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
#[derive(SystemData)]
pub struct TrackedComps<'a> {
uid: ReadStorage<'a, Uid>,
body: ReadStorage<'a, Body>,
player: ReadStorage<'a, Player>,
stats: ReadStorage<'a, Stats>,
can_build: ReadStorage<'a, CanBuild>,
light_emitter: ReadStorage<'a, LightEmitter>,
item: ReadStorage<'a, Item>,
scale: ReadStorage<'a, Scale>,
mounting: ReadStorage<'a, Mounting>,
mount_state: ReadStorage<'a, MountState>,
mass: ReadStorage<'a, Mass>,
sticky: ReadStorage<'a, Sticky>,
gravity: ReadStorage<'a, Gravity>,
projectile: ReadStorage<'a, Projectile>,
pub uid: ReadStorage<'a, Uid>,
pub body: ReadStorage<'a, Body>,
pub player: ReadStorage<'a, Player>,
pub stats: ReadStorage<'a, Stats>,
pub can_build: ReadStorage<'a, CanBuild>,
pub light_emitter: ReadStorage<'a, LightEmitter>,
pub item: ReadStorage<'a, Item>,
pub scale: ReadStorage<'a, Scale>,
pub mounting: ReadStorage<'a, Mounting>,
pub mount_state: ReadStorage<'a, MountState>,
pub mass: ReadStorage<'a, Mass>,
pub sticky: ReadStorage<'a, Sticky>,
pub gravity: ReadStorage<'a, Gravity>,
pub projectile: ReadStorage<'a, Projectile>,
}
impl<'a> TrackedComps<'a> {
pub fn create_entity_package(&self, entity: EcsEntity) -> EntityPackage<EcsCompPacket> {
@ -121,115 +123,53 @@ impl<'a> TrackedComps<'a> {
}
#[derive(SystemData)]
pub struct ReadTrackers<'a> {
uid: ReadExpect<'a, UpdateTracker<Uid>>,
body: ReadExpect<'a, UpdateTracker<Body>>,
player: ReadExpect<'a, UpdateTracker<Player>>,
stats: ReadExpect<'a, UpdateTracker<Stats>>,
can_build: ReadExpect<'a, UpdateTracker<CanBuild>>,
light_emitter: ReadExpect<'a, UpdateTracker<LightEmitter>>,
item: ReadExpect<'a, UpdateTracker<Item>>,
scale: ReadExpect<'a, UpdateTracker<Scale>>,
mounting: ReadExpect<'a, UpdateTracker<Mounting>>,
mount_state: ReadExpect<'a, UpdateTracker<MountState>>,
mass: ReadExpect<'a, UpdateTracker<Mass>>,
sticky: ReadExpect<'a, UpdateTracker<Sticky>>,
gravity: ReadExpect<'a, UpdateTracker<Gravity>>,
projectile: ReadExpect<'a, UpdateTracker<Projectile>>,
pub uid: ReadExpect<'a, UpdateTracker<Uid>>,
pub body: ReadExpect<'a, UpdateTracker<Body>>,
pub player: ReadExpect<'a, UpdateTracker<Player>>,
pub stats: ReadExpect<'a, UpdateTracker<Stats>>,
pub can_build: ReadExpect<'a, UpdateTracker<CanBuild>>,
pub light_emitter: ReadExpect<'a, UpdateTracker<LightEmitter>>,
pub item: ReadExpect<'a, UpdateTracker<Item>>,
pub scale: ReadExpect<'a, UpdateTracker<Scale>>,
pub mounting: ReadExpect<'a, UpdateTracker<Mounting>>,
pub mount_state: ReadExpect<'a, UpdateTracker<MountState>>,
pub mass: ReadExpect<'a, UpdateTracker<Mass>>,
pub sticky: ReadExpect<'a, UpdateTracker<Sticky>>,
pub gravity: ReadExpect<'a, UpdateTracker<Gravity>>,
pub projectile: ReadExpect<'a, UpdateTracker<Projectile>>,
}
impl<'a> ReadTrackers<'a> {
pub fn create_sync_package(
&self,
comps: &TrackedComps,
filter: impl Join + Copy,
deleted_entities: Vec<u64>,
) -> 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(
&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(),
&comps.light_emitter,
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(
&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(),
&comps.mount_state,
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(
&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(),
&comps.projectile,
filter,
@ -292,19 +232,46 @@ pub fn register_trackers(world: &mut World) {
#[derive(SystemData)]
pub struct TrackedResources<'a> {
time: ReadExpect<'a, Time>,
time_of_day: ReadExpect<'a, TimeOfDay>,
}
impl<'a> TrackedResources<'a> {
pub fn create_res_sync_package(&self) -> ResSyncPackage<EcsResPacket> {
ResSyncPackage::new()
.with_res(self.time.deref())
.with_res(self.time_of_day.deref())
ResSyncPackage::new().with_res(self.time_of_day.deref())
}
/// Create state package with resources included
pub fn state_package<C: CompPacket>(&self) -> StatePackage<C, EcsResPacket> {
StatePackage::new()
.with_res(self.time.deref())
.with_res(self.time_of_day.deref())
StatePackage::new().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 common::{
comp::{CharacterState, Ori, Player, Pos, Vel},
@ -8,7 +11,10 @@ use common::{
terrain::TerrainChunkSize,
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::*;
/// This system will update region subscriptions based on client positions
@ -26,6 +32,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Player>,
WriteStorage<'a, Client>,
WriteStorage<'a, RegionSubscription>,
Write<'a, DeletedEntities>,
TrackedComps<'a>,
);
@ -43,6 +50,7 @@ impl<'a> System<'a> for Sys {
players,
mut clients,
mut subscriptions,
mut deleted_entities,
tracked_comps,
): Self::SystemData,
) {
@ -68,8 +76,15 @@ impl<'a> System<'a> for Sys {
&entities,
)
.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))
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
// Only update regions when moving to a new chunk
@ -87,7 +102,7 @@ impl<'a> System<'a> for Sys {
.reduce_or()
{
// 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);
// Use the largest side length as our chunk size
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
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);
// Tell the client to delete the entities in that region if it exists in the RegionMap
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() {
match event {
RegionEvent::Entered(_, _) => {} // These don't need to be processed because this region is being thrown out anyway
RegionEvent::Left(id, maybe_key) => {
// 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 !maybe_key
.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))
.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() {
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(
@ -141,8 +170,10 @@ impl<'a> System<'a> for Sys {
(vd as f32 * chunk_size)
+ (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(),
) {
// Send client intial info about the entities in this region
if subscription.regions.insert(key) {
// Send client intial info about the entities in this region if it was not
// 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) {
for (uid, pos, vel, ori, character_state, _, entity) in (
&uids,
@ -156,6 +187,7 @@ impl<'a> System<'a> for Sys {
.join()
.filter(|(_, _, _, _, _, _, e)| *e != client_entity)
{
counter += 1;
// Send message to create entity and tracked components
client.notify(ServerMsg::CreateEntity(
tracked_comps.create_entity_package(entity),
@ -179,3 +211,69 @@ impl<'a> System<'a> for Sys {
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,
loaded_distance,
time,
entity_count,
// Game Version
version,
@ -872,11 +873,19 @@ impl Hud {
.font_id(self.fonts.cyri)
.font_size(14)
.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
Text::new("Press 'F1' to show Keybindings")
.color(TEXT_COLOR)
.down_from(self.ids.time, 5.0)
.down_from(self.ids.entity_count, 5.0)
.font_id(self.fonts.cyri)
.font_size(14)
.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 erosion_pow = 2.0;
let n_steps = 100;
let n_steps = 7;
let erosion_factor = |x: f64| logistic_cdf(erosion_pow * logit(x));
let alt = do_erosion(
0.0,