mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'imbris/sync' into 'master'
Fix sync issues Closes #359 and #377 See merge request veloren/veloren!675
This commit is contained in:
commit
ddfa2b4600
431
Cargo.lock
generated
431
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,6 @@ uvth = "3.1.1"
|
||||
image = "0.22.3"
|
||||
num_cpus = "1.10.1"
|
||||
log = "0.4.8"
|
||||
specs = "0.14.2"
|
||||
specs = "0.15.1"
|
||||
vek = { version = "0.9.9", features = ["serde"] }
|
||||
hashbrown = { version = "0.6.2", features = ["serde", "nightly"] }
|
||||
|
@ -5,7 +5,7 @@ pub mod error;
|
||||
|
||||
// Reexports
|
||||
pub use crate::error::Error;
|
||||
pub use specs::{join::Join, saveload::Marker, Entity as EcsEntity, ReadStorage};
|
||||
pub use specs::{join::Join, saveload::Marker, Entity as EcsEntity, ReadStorage, WorldExt};
|
||||
|
||||
use common::{
|
||||
comp::{self, ControlEvent, Controller, ControllerInputs, InventoryManip},
|
||||
@ -14,7 +14,8 @@ use common::{
|
||||
ServerError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG,
|
||||
},
|
||||
net::PostBox,
|
||||
state::{State, Uid},
|
||||
state::State,
|
||||
sync::{Uid, WorldSyncExt},
|
||||
terrain::{block::Block, TerrainChunk, TerrainChunkSize},
|
||||
vol::RectVolSize,
|
||||
ChatType,
|
||||
@ -78,9 +79,9 @@ impl Client {
|
||||
// Wait for initial sync
|
||||
let (state, entity, server_info, world_map) = match postbox.next_message() {
|
||||
Some(ServerMsg::InitialSync {
|
||||
ecs_state,
|
||||
entity_uid,
|
||||
entity_package,
|
||||
server_info,
|
||||
time_of_day,
|
||||
// world_map: /*(map_size, world_map)*/map_size,
|
||||
}) => {
|
||||
// TODO: Voxygen should display this.
|
||||
@ -94,11 +95,10 @@ impl Client {
|
||||
);
|
||||
}
|
||||
|
||||
let state = State::from_state_package(ecs_state);
|
||||
let entity = state
|
||||
.ecs()
|
||||
.entity_from_uid(entity_uid)
|
||||
.ok_or(Error::ServerWentMad)?;
|
||||
// Initialize `State`
|
||||
let mut state = State::default();
|
||||
let entity = state.ecs_mut().apply_entity_package(entity_package);
|
||||
*state.ecs_mut().write_resource() = time_of_day;
|
||||
|
||||
// assert_eq!(world_map.len(), map_size.x * map_size.y);
|
||||
let map_size = Vec2::new(1024, 1024);
|
||||
@ -537,7 +537,7 @@ impl Client {
|
||||
|
||||
self.last_ping_delta = Instant::now()
|
||||
.duration_since(self.last_server_ping)
|
||||
.as_secs_f64()
|
||||
.as_secs_f64();
|
||||
}
|
||||
ServerMsg::ChatMsg { chat_type, message } => {
|
||||
frontend_events.push(Event::Chat { chat_type, message })
|
||||
@ -549,14 +549,25 @@ impl Client {
|
||||
return Err(Error::Other("Failed to find entity from uid.".to_owned()));
|
||||
}
|
||||
}
|
||||
ServerMsg::TimeOfDay(time_of_day) => {
|
||||
*self.state.ecs_mut().write_resource() = time_of_day;
|
||||
}
|
||||
ServerMsg::EcsSync(sync_package) => {
|
||||
self.state.ecs_mut().sync_with_package(sync_package)
|
||||
self.state.ecs_mut().apply_sync_package(sync_package);
|
||||
}
|
||||
ServerMsg::CreateEntity(entity_package) => {
|
||||
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 } => {
|
||||
|
@ -5,10 +5,9 @@ authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>", "Maciej Ćwięka <mc
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
sphynx = { git = "https://gitlab.com/veloren/sphynx.git", features = ["serde1"], rev = "ac4adf54d181339a789907acd27f61fe81daa455" }
|
||||
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" }
|
||||
|
||||
specs = { version = "0.14.2", features = ["serde", "nightly"] }
|
||||
specs = { version = "0.15.1", features = ["serde", "nightly", "storage-event-control"] }
|
||||
vek = { version = "0.9.9", features = ["serde"] }
|
||||
dot_vox = "4.0.0"
|
||||
image = "0.22.3"
|
||||
@ -30,8 +29,7 @@ parking_lot = "0.9.0"
|
||||
crossbeam = "=0.7.2"
|
||||
notify = "5.0.0-pre.1"
|
||||
indexmap = "1.3.0"
|
||||
# TODO: remove when upgrading to specs 0.15
|
||||
hibitset = "0.5.3"
|
||||
sum_type = "0.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
|
@ -33,6 +33,23 @@ impl Body {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
// Note: this might need to be refined to something more complex for realistic
|
||||
// behavior with less cylindrical bodies (e.g. wolfs)
|
||||
pub fn radius(&self) -> f32 {
|
||||
// TODO: Improve these values (some might be reliant on more info in inner type)
|
||||
match self {
|
||||
Body::Humanoid(_) => 0.5,
|
||||
Body::QuadrupedSmall(_) => 0.6,
|
||||
Body::QuadrupedMedium(_) => 0.9,
|
||||
Body::BirdMedium(_) => 0.5,
|
||||
Body::FishMedium(_) => 0.5,
|
||||
Body::Dragon(_) => 2.5,
|
||||
Body::BirdSmall(_) => 0.2,
|
||||
Body::FishSmall(_) => 0.2,
|
||||
Body::BipedLarge(_) => 1.0,
|
||||
Body::Object(_) => 0.3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Body {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::sync::Uid;
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IDVStorage;
|
||||
use sphynx::Uid;
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::state::Uid;
|
||||
use crate::sync::Uid;
|
||||
use specs::{Component, FlaggedStorage, NullStorage};
|
||||
use specs_idvs::IDVStorage;
|
||||
use vek::*;
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::comp;
|
||||
use crate::state::Uid;
|
||||
use crate::{comp, sync::Uid};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IDVStorage;
|
||||
use std::time::Duration;
|
||||
@ -15,6 +14,7 @@ pub enum Effect {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Projectile {
|
||||
pub owner: Uid,
|
||||
// TODO: use SmallVec for these effects
|
||||
pub hit_ground: Vec<Effect>,
|
||||
pub hit_wall: Vec<Effect>,
|
||||
pub hit_entity: Vec<Effect>,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{comp, state::Uid};
|
||||
use crate::{comp, sync::Uid};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IDVStorage;
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
use crate::comp;
|
||||
use crate::{comp, sync::Uid};
|
||||
use comp::item::Tool;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use specs::Entity as EcsEntity;
|
||||
use sphynx::Uid;
|
||||
use std::{collections::VecDeque, ops::DerefMut};
|
||||
use vek::*;
|
||||
|
||||
|
@ -20,6 +20,7 @@ pub mod pathfinding;
|
||||
pub mod ray;
|
||||
pub mod region;
|
||||
pub mod state;
|
||||
pub mod sync;
|
||||
pub mod sys;
|
||||
pub mod terrain;
|
||||
pub mod util;
|
||||
|
@ -1,25 +1,13 @@
|
||||
use crate::{comp, state};
|
||||
use crate::{comp, sync};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::marker::PhantomData;
|
||||
use sum_type::sum_type;
|
||||
|
||||
// Automatically derive From<T> for EcsResPacket
|
||||
// for each variant EcsResPacket::T(T).
|
||||
sphynx::sum_type! {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum EcsResPacket {
|
||||
Time(state::Time),
|
||||
TimeOfDay(state::TimeOfDay),
|
||||
}
|
||||
}
|
||||
impl sphynx::ResPacket for EcsResPacket {}
|
||||
// Automatically derive From<T> for EcsCompPacket
|
||||
// for each variant EcsCompPacket::T(T.)
|
||||
sphynx::sum_type! {
|
||||
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),
|
||||
@ -30,19 +18,15 @@ sphynx::sum_type! {
|
||||
MountState(comp::MountState),
|
||||
Mounting(comp::Mounting),
|
||||
Mass(comp::Mass),
|
||||
Projectile(comp::Projectile),
|
||||
Gravity(comp::Gravity),
|
||||
Sticky(comp::Sticky),
|
||||
}
|
||||
}
|
||||
// Automatically derive From<T> for EcsCompPhantom
|
||||
// for each variant EcsCompPhantom::T(PhantomData<T>).
|
||||
sphynx::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>),
|
||||
@ -53,11 +37,60 @@ sphynx::sum_type! {
|
||||
MountState(PhantomData<comp::MountState>),
|
||||
Mounting(PhantomData<comp::Mounting>),
|
||||
Mass(PhantomData<comp::Mass>),
|
||||
Projectile(PhantomData<comp::Projectile>),
|
||||
Gravity(PhantomData<comp::Gravity>),
|
||||
Sticky(PhantomData<comp::Sticky>),
|
||||
}
|
||||
}
|
||||
impl sphynx::CompPacket for EcsCompPacket {
|
||||
impl sync::CompPacket for EcsCompPacket {
|
||||
type Phantom = EcsCompPhantom;
|
||||
fn apply_insert(self, entity: specs::Entity, world: &specs::World) {
|
||||
match self {
|
||||
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),
|
||||
EcsCompPacket::Stats(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::MountState(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Mounting(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Gravity(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world),
|
||||
}
|
||||
}
|
||||
fn apply_modify(self, entity: specs::Entity, world: &specs::World) {
|
||||
match self {
|
||||
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),
|
||||
EcsCompPacket::Stats(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::MountState(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Mounting(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Gravity(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world),
|
||||
}
|
||||
}
|
||||
fn apply_remove(phantom: Self::Phantom, entity: specs::Entity, world: &specs::World) {
|
||||
match phantom {
|
||||
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),
|
||||
EcsCompPhantom::Stats(_) => sync::handle_remove::<comp::Stats>(entity, world),
|
||||
EcsCompPhantom::LightEmitter(_) => {
|
||||
sync::handle_remove::<comp::LightEmitter>(entity, world)
|
||||
}
|
||||
EcsCompPhantom::Item(_) => sync::handle_remove::<comp::Item>(entity, world),
|
||||
EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(entity, world),
|
||||
EcsCompPhantom::MountState(_) => sync::handle_remove::<comp::MountState>(entity, world),
|
||||
EcsCompPhantom::Mounting(_) => sync::handle_remove::<comp::Mounting>(entity, world),
|
||||
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
|
||||
EcsCompPhantom::Gravity(_) => sync::handle_remove::<comp::Gravity>(entity, world),
|
||||
EcsCompPhantom::Sticky(_) => sync::handle_remove::<comp::Sticky>(entity, world),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ pub mod server;
|
||||
|
||||
// Reexports
|
||||
pub use self::client::ClientMsg;
|
||||
pub use self::ecs_packet::{EcsCompPacket, EcsResPacket};
|
||||
pub use self::ecs_packet::EcsCompPacket;
|
||||
pub use self::server::{RequestStateError, ServerError, ServerInfo, ServerMsg};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::{ClientState, EcsCompPacket, EcsResPacket};
|
||||
use super::{ClientState, EcsCompPacket};
|
||||
use crate::{
|
||||
comp,
|
||||
comp, state, sync,
|
||||
terrain::{Block, TerrainChunk},
|
||||
ChatType,
|
||||
};
|
||||
@ -26,9 +26,9 @@ pub struct ServerInfo {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ServerMsg {
|
||||
InitialSync {
|
||||
ecs_state: sphynx::StatePackage<EcsCompPacket, EcsResPacket>,
|
||||
entity_uid: u64,
|
||||
entity_package: sync::EntityPackage<EcsCompPacket>,
|
||||
server_info: ServerInfo,
|
||||
time_of_day: state::TimeOfDay,
|
||||
// world_map: Vec2<usize>, /*, Vec<u32>)*/
|
||||
},
|
||||
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
|
||||
@ -40,7 +40,9 @@ pub enum ServerMsg {
|
||||
message: String,
|
||||
},
|
||||
SetPlayerEntity(u64),
|
||||
EcsSync(sphynx::SyncPackage<EcsCompPacket, EcsResPacket>),
|
||||
TimeOfDay(state::TimeOfDay),
|
||||
EcsSync(sync::SyncPackage<EcsCompPacket>),
|
||||
CreateEntity(sync::EntityPackage<EcsCompPacket>),
|
||||
DeleteEntity(u64),
|
||||
EntityPos {
|
||||
entity: u64,
|
||||
|
@ -1,8 +1,7 @@
|
||||
use crate::comp::{Pos, Vel};
|
||||
use hashbrown::{hash_map::DefaultHashBuilder, HashSet};
|
||||
use hibitset::BitSetLike;
|
||||
use indexmap::IndexMap;
|
||||
use specs::{BitSet, Entities, Join, ReadStorage};
|
||||
use specs::{hibitset::BitSetLike, BitSet, Entities, Join, ReadStorage};
|
||||
use vek::*;
|
||||
|
||||
pub enum Event {
|
||||
@ -104,6 +103,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 +127,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 +216,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)
|
||||
}
|
||||
|
@ -1,11 +1,8 @@
|
||||
// Reexports
|
||||
pub use sphynx::Uid;
|
||||
|
||||
use crate::{
|
||||
comp,
|
||||
event::{EventBus, LocalEvent, ServerEvent, SfxEventItem},
|
||||
msg::{EcsCompPacket, EcsResPacket},
|
||||
region::RegionMap,
|
||||
sync::WorldSyncExt,
|
||||
sys,
|
||||
terrain::{Block, TerrainChunk, TerrainGrid},
|
||||
vol::WriteVol,
|
||||
@ -14,12 +11,10 @@ use hashbrown::{HashMap, HashSet};
|
||||
use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use specs::{
|
||||
saveload::Marker,
|
||||
shred::{Fetch, FetchMut},
|
||||
storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage},
|
||||
Component, DispatcherBuilder, Entity as EcsEntity, Join,
|
||||
Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
|
||||
};
|
||||
use sphynx;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use vek::*;
|
||||
|
||||
@ -28,7 +23,7 @@ use vek::*;
|
||||
const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0;
|
||||
|
||||
/// A resource that stores the time of day.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct TimeOfDay(pub f64);
|
||||
|
||||
/// A resource that stores the tick (i.e: physics) time.
|
||||
@ -89,7 +84,7 @@ impl TerrainChanges {
|
||||
/// A type used to represent game state stored on both the client and the server. This includes
|
||||
/// things like entity components, terrain data, and global states like weather, time of day, etc.
|
||||
pub struct State {
|
||||
ecs: sphynx::World<EcsCompPacket, EcsResPacket>,
|
||||
ecs: specs::World,
|
||||
// Avoid lifetime annotation by storing a thread pool instead of the whole dispatcher
|
||||
thread_pool: Arc<ThreadPool>,
|
||||
}
|
||||
@ -98,44 +93,32 @@ impl Default for State {
|
||||
/// Create a new `State`.
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ecs: sphynx::World::new(specs::World::new(), Self::setup_sphynx_world),
|
||||
ecs: Self::setup_ecs_world(),
|
||||
thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Create a new `State` from an ECS state package.
|
||||
pub fn from_state_package(
|
||||
state_package: sphynx::StatePackage<EcsCompPacket, EcsResPacket>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ecs: sphynx::World::from_state_package(
|
||||
specs::World::new(),
|
||||
Self::setup_sphynx_world,
|
||||
state_package,
|
||||
),
|
||||
thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new Sphynx ECS world.
|
||||
/// Creates ecs world and registers all the common components and resources
|
||||
// TODO: Split up registering into server and client (e.g. move EventBus<ServerEvent> to the server)
|
||||
fn setup_sphynx_world(ecs: &mut sphynx::World<EcsCompPacket, EcsResPacket>) {
|
||||
fn setup_ecs_world() -> specs::World {
|
||||
let mut ecs = specs::World::new();
|
||||
// Uids for sync
|
||||
ecs.register_sync_marker();
|
||||
// Register server -> all clients synced components.
|
||||
ecs.register_synced::<comp::Body>();
|
||||
ecs.register_synced::<comp::Player>();
|
||||
ecs.register_synced::<comp::Stats>();
|
||||
ecs.register_synced::<comp::CanBuild>();
|
||||
ecs.register_synced::<comp::LightEmitter>();
|
||||
ecs.register_synced::<comp::Item>();
|
||||
ecs.register_synced::<comp::Scale>();
|
||||
ecs.register_synced::<comp::Mounting>();
|
||||
ecs.register_synced::<comp::MountState>();
|
||||
ecs.register_synced::<comp::Mass>();
|
||||
ecs.register_synced::<comp::Sticky>();
|
||||
ecs.register_synced::<comp::Gravity>();
|
||||
ecs.register_synced::<comp::Projectile>();
|
||||
ecs.register::<comp::Body>();
|
||||
ecs.register::<comp::Player>();
|
||||
ecs.register::<comp::Stats>();
|
||||
ecs.register::<comp::CanBuild>();
|
||||
ecs.register::<comp::LightEmitter>();
|
||||
ecs.register::<comp::Item>();
|
||||
ecs.register::<comp::Scale>();
|
||||
ecs.register::<comp::Mounting>();
|
||||
ecs.register::<comp::MountState>();
|
||||
ecs.register::<comp::Mass>();
|
||||
ecs.register::<comp::Sticky>();
|
||||
ecs.register::<comp::Gravity>();
|
||||
|
||||
// Register components send from clients -> server
|
||||
ecs.register::<comp::Controller>();
|
||||
@ -151,6 +134,7 @@ impl State {
|
||||
ecs.register::<comp::Inventory>();
|
||||
|
||||
// Register server-local components
|
||||
// TODO: only register on the server
|
||||
ecs.register::<comp::Last<comp::Pos>>();
|
||||
ecs.register::<comp::Last<comp::Vel>>();
|
||||
ecs.register::<comp::Last<comp::Ori>>();
|
||||
@ -158,23 +142,26 @@ impl State {
|
||||
ecs.register::<comp::Agent>();
|
||||
ecs.register::<comp::ForceUpdate>();
|
||||
ecs.register::<comp::InventoryUpdate>();
|
||||
ecs.register::<comp::Inventory>();
|
||||
ecs.register::<comp::Admin>();
|
||||
ecs.register::<comp::Waypoint>();
|
||||
ecs.register::<comp::Projectile>();
|
||||
|
||||
// Register synced resources used by the ECS.
|
||||
ecs.insert_synced(TimeOfDay(0.0));
|
||||
ecs.insert(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());
|
||||
ecs.add_resource(TerrainChanges::default());
|
||||
ecs.add_resource(EventBus::<ServerEvent>::default());
|
||||
ecs.add_resource(EventBus::<LocalEvent>::default());
|
||||
ecs.add_resource(EventBus::<SfxEventItem>::default());
|
||||
ecs.add_resource(RegionMap::new());
|
||||
ecs.insert(Time(0.0));
|
||||
ecs.insert(DeltaTime(0.0));
|
||||
ecs.insert(TerrainGrid::new().unwrap());
|
||||
ecs.insert(BlockChange::default());
|
||||
ecs.insert(TerrainChanges::default());
|
||||
// TODO: only register on the server
|
||||
ecs.insert(EventBus::<ServerEvent>::default());
|
||||
ecs.insert(EventBus::<LocalEvent>::default());
|
||||
ecs.insert(EventBus::<SfxEventItem>::default());
|
||||
ecs.insert(RegionMap::new());
|
||||
|
||||
ecs
|
||||
}
|
||||
|
||||
/// Register a component with the state's ECS.
|
||||
@ -207,12 +194,12 @@ impl State {
|
||||
}
|
||||
|
||||
/// Get a reference to the internal ECS world.
|
||||
pub fn ecs(&self) -> &sphynx::World<EcsCompPacket, EcsResPacket> {
|
||||
pub fn ecs(&self) -> &specs::World {
|
||||
&self.ecs
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the internal ECS world.
|
||||
pub fn ecs_mut(&mut self) -> &mut sphynx::World<EcsCompPacket, EcsResPacket> {
|
||||
pub fn ecs_mut(&mut self) -> &mut specs::World {
|
||||
&mut self.ecs
|
||||
}
|
||||
|
||||
@ -319,68 +306,6 @@ impl State {
|
||||
// Beyond a delta time of MAX_DELTA_TIME, start lagging to avoid skipping important physics events.
|
||||
self.ecs.write_resource::<DeltaTime>().0 = dt.as_secs_f32().min(MAX_DELTA_TIME);
|
||||
|
||||
// Mounted entities. We handle this here because we need access to the Uid registry and I
|
||||
// forgot how to access that within a system. Anyhow, here goes.
|
||||
for (entity, mount_state) in (
|
||||
&self.ecs.entities(),
|
||||
&mut self.ecs.write_storage::<comp::MountState>(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
match mount_state {
|
||||
comp::MountState::Unmounted => {}
|
||||
comp::MountState::MountedBy(mounter) => {
|
||||
if let Some((controller, mounter)) =
|
||||
self.ecs.entity_from_uid(mounter.id()).and_then(|mounter| {
|
||||
self.ecs
|
||||
.read_storage::<comp::Controller>()
|
||||
.get(mounter)
|
||||
.cloned()
|
||||
.map(|x| (x, mounter))
|
||||
})
|
||||
{
|
||||
let pos = self.ecs.read_storage::<comp::Pos>().get(entity).copied();
|
||||
let ori = self.ecs.read_storage::<comp::Ori>().get(entity).copied();
|
||||
let vel = self.ecs.read_storage::<comp::Vel>().get(entity).copied();
|
||||
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
|
||||
let _ = self
|
||||
.ecs
|
||||
.write_storage()
|
||||
.insert(mounter, comp::Pos(pos.0 + Vec3::unit_z() * 1.0));
|
||||
let _ = self.ecs.write_storage().insert(mounter, ori);
|
||||
let _ = self.ecs.write_storage().insert(mounter, vel);
|
||||
}
|
||||
let _ = self
|
||||
.ecs
|
||||
.write_storage::<comp::Controller>()
|
||||
.insert(entity, controller);
|
||||
} else {
|
||||
*mount_state = comp::MountState::Unmounted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut to_unmount = Vec::new();
|
||||
for (entity, comp::Mounting(mountee)) in (
|
||||
&self.ecs.entities(),
|
||||
&self.ecs.read_storage::<comp::Mounting>(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
if self
|
||||
.ecs
|
||||
.entity_from_uid(mountee.id())
|
||||
.filter(|mountee| self.ecs.is_alive(*mountee))
|
||||
.is_none()
|
||||
{
|
||||
to_unmount.push(entity);
|
||||
}
|
||||
}
|
||||
for entity in to_unmount {
|
||||
self.ecs.write_storage::<comp::Mounting>().remove(entity);
|
||||
}
|
||||
|
||||
// Run RegionMap tick to update entity region occupancy
|
||||
self.ecs.write_resource::<RegionMap>().tick(
|
||||
self.ecs.read_storage::<comp::Pos>(),
|
||||
@ -395,7 +320,7 @@ impl State {
|
||||
// TODO: Consider alternative ways to do this
|
||||
add_foreign_systems(&mut dispatch_builder);
|
||||
// This dispatches all the systems in parallel.
|
||||
dispatch_builder.build().dispatch(&self.ecs.res);
|
||||
dispatch_builder.build().dispatch(&self.ecs);
|
||||
|
||||
self.ecs.maintain();
|
||||
|
||||
|
14
common/src/sync/mod.rs
Normal file
14
common/src/sync/mod.rs
Normal file
@ -0,0 +1,14 @@
|
||||
// Note: Currently only one-way sync is supported until a usecase for two-way sync arises
|
||||
mod packet;
|
||||
mod sync_ext;
|
||||
mod track;
|
||||
mod uid;
|
||||
|
||||
// Reexports
|
||||
pub use packet::{
|
||||
handle_insert, handle_modify, handle_remove, CompPacket, EntityPackage, StatePackage,
|
||||
SyncPackage,
|
||||
};
|
||||
pub use sync_ext::WorldSyncExt;
|
||||
pub use track::UpdateTracker;
|
||||
pub use uid::{Uid, UidAllocator};
|
125
common/src/sync/packet.rs
Normal file
125
common/src/sync/packet.rs
Normal file
@ -0,0 +1,125 @@
|
||||
use super::{track::UpdateTracker, uid::Uid};
|
||||
use log::error;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use specs::{Component, Entity, Join, ReadStorage, World, WorldExt};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
/// Implemented by type that carries component data for insertion and modification
|
||||
/// The assocatied `Phantom` type only carries information about which component type is of
|
||||
/// interest and is used to transmit deletion events
|
||||
pub trait CompPacket: Clone + Debug + Send + 'static {
|
||||
type Phantom: Clone + Debug + Serialize + DeserializeOwned;
|
||||
|
||||
fn apply_insert(self, entity: Entity, world: &World);
|
||||
fn apply_modify(self, entity: Entity, world: &World);
|
||||
fn apply_remove(phantom: Self::Phantom, entity: Entity, world: &World);
|
||||
}
|
||||
|
||||
/// Useful for implementing CompPacket trait
|
||||
pub fn handle_insert<C: Component>(comp: C, entity: Entity, world: &World) {
|
||||
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) {
|
||||
let _ = world
|
||||
.write_storage::<C>()
|
||||
.get_mut(entity)
|
||||
.map(|c| *c = comp);
|
||||
}
|
||||
/// Useful for implementing CompPacket trait
|
||||
pub fn handle_remove<C: Component>(entity: Entity, world: &World) {
|
||||
let _ = world.write_storage::<C>().remove(entity);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum CompUpdateKind<P: CompPacket> {
|
||||
Inserted(P),
|
||||
Modified(P),
|
||||
Removed(P::Phantom),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct EntityPackage<P: CompPacket> {
|
||||
pub uid: u64,
|
||||
pub comps: Vec<P>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct StatePackage<P: CompPacket> {
|
||||
pub entities: Vec<EntityPackage<P>>,
|
||||
}
|
||||
|
||||
impl<P: CompPacket> Default for StatePackage<P> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entities: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: CompPacket> StatePackage<P> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn with_entities<C: Component + Clone + Send + Sync>(
|
||||
mut self,
|
||||
mut entities: Vec<EntityPackage<P>>,
|
||||
) -> Self {
|
||||
self.entities.append(&mut entities);
|
||||
self
|
||||
}
|
||||
pub fn with_entity(mut self, entry: EntityPackage<P>) -> Self {
|
||||
self.entities.push(entry);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SyncPackage<P: CompPacket> {
|
||||
pub comp_updates: Vec<(u64, CompUpdateKind<P>)>,
|
||||
pub created_entities: Vec<u64>,
|
||||
pub deleted_entities: Vec<u64>,
|
||||
}
|
||||
impl<P: CompPacket> SyncPackage<P> {
|
||||
pub fn new<'a>(
|
||||
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())
|
||||
.join()
|
||||
.map(|(uid, _, _)| (*uid).into())
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
comp_updates: Vec::new(),
|
||||
created_entities,
|
||||
deleted_entities,
|
||||
}
|
||||
}
|
||||
pub fn with_component<'a, C: Component + Clone + Send + Sync>(
|
||||
mut self,
|
||||
uids: &ReadStorage<'a, Uid>,
|
||||
tracker: &UpdateTracker<C>,
|
||||
storage: &ReadStorage<'a, C>,
|
||||
filter: impl Join + Copy,
|
||||
) -> Self
|
||||
where
|
||||
P: From<C>,
|
||||
C: TryFrom<P>,
|
||||
P::Phantom: From<PhantomData<C>>,
|
||||
P::Phantom: TryInto<PhantomData<C>>,
|
||||
C::Storage: specs::storage::Tracked,
|
||||
{
|
||||
tracker.get_updates_for(uids, storage, filter, &mut self.comp_updates);
|
||||
self
|
||||
}
|
||||
}
|
164
common/src/sync/sync_ext.rs
Normal file
164
common/src/sync/sync_ext.rs
Normal file
@ -0,0 +1,164 @@
|
||||
use super::{
|
||||
packet::{CompPacket, CompUpdateKind, EntityPackage, StatePackage, SyncPackage},
|
||||
track::UpdateTracker,
|
||||
uid::{Uid, UidAllocator},
|
||||
};
|
||||
use log::error;
|
||||
use specs::{
|
||||
saveload::{MarkedBuilder, MarkerAllocator},
|
||||
world::Builder,
|
||||
WorldExt,
|
||||
};
|
||||
|
||||
pub trait WorldSyncExt {
|
||||
fn register_sync_marker(&mut self);
|
||||
fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self)
|
||||
where
|
||||
C::Storage: Default + specs::storage::Tracked;
|
||||
fn register_tracker<C: specs::Component + Clone + Send + Sync>(&mut self)
|
||||
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>,
|
||||
) -> specs::Entity;
|
||||
fn apply_state_package<P: CompPacket>(&mut self, state_package: StatePackage<P>);
|
||||
fn apply_sync_package<P: CompPacket>(&mut self, package: SyncPackage<P>);
|
||||
}
|
||||
|
||||
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.insert(UidAllocator::new());
|
||||
}
|
||||
fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self)
|
||||
where
|
||||
C::Storage: Default + specs::storage::Tracked,
|
||||
{
|
||||
self.register::<C>();
|
||||
self.register_tracker::<C>();
|
||||
}
|
||||
fn register_tracker<C: specs::Component + Clone + Send + Sync>(&mut self)
|
||||
where
|
||||
C::Storage: Default + specs::storage::Tracked,
|
||||
{
|
||||
let tracker = UpdateTracker::<C>::new(self);
|
||||
self.insert(tracker);
|
||||
}
|
||||
|
||||
fn create_entity_synced(&mut self) -> specs::EntityBuilder {
|
||||
self.create_entity().marked::<super::Uid>()
|
||||
}
|
||||
|
||||
/// Get the UID of an entity
|
||||
fn uid_from_entity(&self, entity: specs::Entity) -> Option<Uid> {
|
||||
self.read_storage::<Uid>().get(entity).copied()
|
||||
}
|
||||
|
||||
/// Get the UID of an entity
|
||||
fn entity_from_uid(&self, uid: u64) -> Option<specs::Entity> {
|
||||
self.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal(uid)
|
||||
}
|
||||
|
||||
fn apply_entity_package<P: CompPacket>(
|
||||
&mut self,
|
||||
entity_package: EntityPackage<P>,
|
||||
) -> specs::Entity {
|
||||
let EntityPackage { uid, comps } = entity_package;
|
||||
|
||||
let entity = create_entity_with_uid(self, uid);
|
||||
for packet in comps {
|
||||
packet.apply_insert(entity, self)
|
||||
}
|
||||
|
||||
entity
|
||||
}
|
||||
|
||||
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>(&mut self, state_package: StatePackage<P>) {
|
||||
let StatePackage { entities } = state_package;
|
||||
|
||||
// Apply state package entities
|
||||
for entity_package in entities {
|
||||
self.apply_entity_package(entity_package);
|
||||
}
|
||||
|
||||
// TODO: determine if this is needed
|
||||
// Initialize entities
|
||||
//self.maintain();
|
||||
}
|
||||
|
||||
fn apply_sync_package<P: CompPacket>(&mut self, package: SyncPackage<P>) {
|
||||
// Take ownership of the fields
|
||||
let SyncPackage {
|
||||
comp_updates,
|
||||
created_entities,
|
||||
deleted_entities,
|
||||
} = package;
|
||||
|
||||
// Attempt to create entities
|
||||
for entity_uid in created_entities {
|
||||
create_entity_with_uid(self, entity_uid);
|
||||
}
|
||||
|
||||
// Update components
|
||||
for (entity_uid, update) in comp_updates {
|
||||
if let Some(entity) = self
|
||||
.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal(entity_uid)
|
||||
{
|
||||
match update {
|
||||
CompUpdateKind::Inserted(packet) => {
|
||||
packet.apply_insert(entity, self);
|
||||
}
|
||||
CompUpdateKind::Modified(packet) => {
|
||||
packet.apply_modify(entity, self);
|
||||
}
|
||||
CompUpdateKind::Removed(phantom) => {
|
||||
P::apply_remove(phantom, entity, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to delete entities that were marked for deletion
|
||||
for entity_uid in deleted_entities {
|
||||
self.delete_entity_and_clear_from_uid_allocator(entity_uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Private utilities
|
||||
fn create_entity_with_uid(specs_world: &mut specs::World, entity_uid: u64) -> specs::Entity {
|
||||
let existing_entity = specs_world
|
||||
.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal(entity_uid);
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
128
common/src/sync/track.rs
Normal file
128
common/src/sync/track.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use super::{
|
||||
packet::{CompPacket, CompUpdateKind},
|
||||
uid::Uid,
|
||||
};
|
||||
use specs::{BitSet, Component, Entity, Join, ReadStorage, World, WorldExt};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
pub struct UpdateTracker<C: Component> {
|
||||
reader_id: specs::ReaderId<specs::storage::ComponentEvent>,
|
||||
inserted: BitSet,
|
||||
modified: BitSet,
|
||||
removed: BitSet,
|
||||
phantom: PhantomData<C>,
|
||||
}
|
||||
impl<C: Component> UpdateTracker<C>
|
||||
where
|
||||
C::Storage: specs::storage::Tracked,
|
||||
{
|
||||
pub fn new(specs_world: &mut World) -> Self {
|
||||
Self {
|
||||
reader_id: specs_world.write_storage::<C>().register_reader(),
|
||||
inserted: BitSet::new(),
|
||||
modified: BitSet::new(),
|
||||
removed: BitSet::new(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
pub fn inserted(&self) -> &BitSet {
|
||||
&self.inserted
|
||||
}
|
||||
pub fn modified(&self) -> &BitSet {
|
||||
&self.modified
|
||||
}
|
||||
pub fn removed(&self) -> &BitSet {
|
||||
&self.removed
|
||||
}
|
||||
pub fn record_changes<'a>(&mut self, storage: &specs::ReadStorage<'a, C>) {
|
||||
self.inserted.clear();
|
||||
self.modified.clear();
|
||||
self.removed.clear();
|
||||
|
||||
for event in storage.channel().read(&mut self.reader_id) {
|
||||
match event {
|
||||
specs::storage::ComponentEvent::Inserted(id) => {
|
||||
// If previously removed/modified we don't need to know that anymore
|
||||
self.removed.remove(*id);
|
||||
self.modified.remove(*id);
|
||||
self.inserted.add(*id);
|
||||
}
|
||||
specs::storage::ComponentEvent::Modified(id) => {
|
||||
// We don't care about modification if the component was just added
|
||||
if !self.inserted.contains(*id) {
|
||||
debug_assert!(!self.removed.contains(*id)); // Theoretically impossible
|
||||
self.modified.add(*id);
|
||||
}
|
||||
}
|
||||
specs::storage::ComponentEvent::Removed(id) => {
|
||||
// Don't need to know that it was inserted/modified if it was subsequently removed
|
||||
self.inserted.remove(*id);
|
||||
self.modified.remove(*id);
|
||||
self.removed.add(*id);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component + Clone + Send + Sync> UpdateTracker<C> {
|
||||
pub fn add_packet_for<'a, P>(
|
||||
&self,
|
||||
storage: &ReadStorage<'a, C>,
|
||||
entity: Entity,
|
||||
packets: &mut Vec<P>,
|
||||
) where
|
||||
P: CompPacket,
|
||||
P: From<C>,
|
||||
C: TryFrom<P>,
|
||||
P::Phantom: From<PhantomData<C>>,
|
||||
P::Phantom: TryInto<PhantomData<C>>,
|
||||
C::Storage: specs::storage::Tracked,
|
||||
{
|
||||
if let Some(comp) = storage.get(entity) {
|
||||
packets.push(P::from(comp.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_updates_for<'a, P>(
|
||||
&self,
|
||||
uids: &specs::ReadStorage<'a, Uid>,
|
||||
storage: &specs::ReadStorage<'a, C>,
|
||||
entity_filter: impl Join + Copy,
|
||||
buf: &mut Vec<(u64, CompUpdateKind<P>)>,
|
||||
) where
|
||||
P: CompPacket,
|
||||
P: From<C>,
|
||||
C: TryFrom<P>,
|
||||
P::Phantom: From<PhantomData<C>>,
|
||||
P::Phantom: TryInto<PhantomData<C>>,
|
||||
C::Storage: specs::storage::Tracked,
|
||||
{
|
||||
// Generate inserted updates
|
||||
for (uid, comp, _, _) in (uids, storage, &self.inserted, entity_filter).join() {
|
||||
buf.push((
|
||||
(*uid).into(),
|
||||
CompUpdateKind::Inserted(P::from(comp.clone())),
|
||||
));
|
||||
}
|
||||
|
||||
// Generate modified updates
|
||||
for (uid, comp, _, _) in (uids, storage, &self.modified, entity_filter).join() {
|
||||
buf.push((
|
||||
(*uid).into(),
|
||||
CompUpdateKind::Modified(P::from(comp.clone())),
|
||||
));
|
||||
}
|
||||
|
||||
// Generate removed updates
|
||||
for (uid, _, _) in (uids, &self.removed, entity_filter).join() {
|
||||
buf.push((
|
||||
(*uid).into(),
|
||||
CompUpdateKind::Removed(P::Phantom::from(PhantomData::<C>)),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
92
common/src/sync/uid.rs
Normal file
92
common/src/sync/uid.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
world::EntitiesRes,
|
||||
Component, Entity, FlaggedStorage, Join, ReadStorage, VecStorage,
|
||||
};
|
||||
use std::{collections::HashMap, fmt, u64};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Uid(pub u64);
|
||||
|
||||
impl Into<u64> for Uid {
|
||||
fn into(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Uid {
|
||||
fn from(uid: u64) -> Self {
|
||||
Self(uid)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Uid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Uid {
|
||||
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
|
||||
}
|
||||
|
||||
impl Marker for Uid {
|
||||
type Identifier = u64;
|
||||
type Allocator = UidAllocator;
|
||||
|
||||
fn id(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn update(&mut self, update: Self) {
|
||||
assert_eq!(self.0, update.0);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UidAllocator {
|
||||
index: u64,
|
||||
mapping: HashMap<u64, Entity>,
|
||||
}
|
||||
|
||||
impl UidAllocator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
index: 0,
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MarkerAllocator<Uid> for UidAllocator {
|
||||
fn allocate(&mut self, entity: Entity, id: Option<u64>) -> Uid {
|
||||
let id = id.unwrap_or_else(|| {
|
||||
let id = self.index;
|
||||
self.index += 1;
|
||||
id
|
||||
});
|
||||
self.mapping.insert(id, entity);
|
||||
Uid(id)
|
||||
}
|
||||
|
||||
fn retrieve_entity_internal(&self, id: u64) -> Option<Entity> {
|
||||
self.mapping.get(&id).copied()
|
||||
}
|
||||
|
||||
fn maintain(&mut self, entities: &EntitiesRes, storage: &ReadStorage<Uid>) {
|
||||
self.mapping = (entities, storage)
|
||||
.join()
|
||||
.map(|(e, m)| (m.id(), e))
|
||||
.collect();
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
use crate::comp::{
|
||||
Agent, CharacterState, Controller, ControllerInputs, MountState, MovementState::Glide, Pos,
|
||||
Stats,
|
||||
Agent, CharacterState, Controller, MountState, MovementState::Glide, Pos, Stats,
|
||||
};
|
||||
use crate::pathfinding::WorldPath;
|
||||
use crate::terrain::TerrainGrid;
|
||||
@ -60,7 +59,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
controller.reset();
|
||||
|
||||
let mut inputs = ControllerInputs::default();
|
||||
let mut inputs = &mut controller.inputs;
|
||||
|
||||
match agent {
|
||||
Agent::Traveler { path } => {
|
||||
@ -147,10 +146,13 @@ impl<'a> System<'a> for Sys {
|
||||
let dist = Vec2::<f32>::from(target_pos.0 - pos.0).magnitude();
|
||||
if target_stats.is_dead {
|
||||
choose_new = true;
|
||||
} else if dist < MIN_ATTACK_DIST
|
||||
&& dist > 0.001
|
||||
&& rand::random::<f32>() < 0.3
|
||||
{
|
||||
} else if dist < 0.001 {
|
||||
// Probably can only happen when entities are at a different z-level
|
||||
// since at the same level repulsion would keep them apart.
|
||||
// Distinct from the first if block since we may want to change the
|
||||
// behavior for this case.
|
||||
choose_new = true;
|
||||
} else if dist < MIN_ATTACK_DIST {
|
||||
// Fight (and slowly move closer)
|
||||
inputs.move_dir =
|
||||
Vec2::<f32>::from(target_pos.0 - pos.0).normalized() * 0.01;
|
||||
@ -203,7 +205,8 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
|
||||
controller.inputs = inputs;
|
||||
debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
|
||||
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
ActionState::*, CharacterState, Controller, HealthChange, HealthSource, Item, ItemKind,
|
||||
Ori, Pos, Stats,
|
||||
ActionState::*, Body, CharacterState, Controller, HealthChange, HealthSource, Item,
|
||||
ItemKind, Ori, Pos, Scale, Stats,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
state::{DeltaTime, Uid},
|
||||
state::DeltaTime,
|
||||
sync::Uid,
|
||||
};
|
||||
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
|
||||
use std::time::Duration;
|
||||
@ -12,7 +13,8 @@ use vek::*;
|
||||
|
||||
const BLOCK_EFFICIENCY: f32 = 0.9;
|
||||
|
||||
const ATTACK_RANGE: f32 = 4.0;
|
||||
const ATTACK_RANGE: f32 = 3.5;
|
||||
const ATTACK_ANGLE: f32 = 45.0;
|
||||
const BLOCK_ANGLE: f32 = 180.0;
|
||||
|
||||
/// This system is responsible for handling accepted inputs like moving or attacking
|
||||
@ -26,9 +28,11 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Pos>,
|
||||
ReadStorage<'a, Ori>,
|
||||
ReadStorage<'a, Scale>,
|
||||
ReadStorage<'a, Controller>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, Stats>,
|
||||
WriteStorage<'a, CharacterState>,
|
||||
WriteStorage<'a, Stats>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
@ -41,20 +45,23 @@ impl<'a> System<'a> for Sys {
|
||||
uids,
|
||||
positions,
|
||||
orientations,
|
||||
scales,
|
||||
controllers,
|
||||
mut character_states,
|
||||
bodies,
|
||||
stats,
|
||||
mut character_states,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
let mut _local_emitter = local_bus.emitter();
|
||||
|
||||
// Attacks
|
||||
for (entity, uid, pos, ori, _, stat) in (
|
||||
for (entity, uid, pos, ori, scale_maybe, _, attacker_stats) in (
|
||||
&entities,
|
||||
&uids,
|
||||
&positions,
|
||||
&orientations,
|
||||
scales.maybe(),
|
||||
&controllers,
|
||||
&stats,
|
||||
)
|
||||
@ -63,7 +70,7 @@ impl<'a> System<'a> for Sys {
|
||||
let recover_duration = if let Some(Item {
|
||||
kind: ItemKind::Tool { kind, .. },
|
||||
..
|
||||
}) = stat.equipment.main
|
||||
}) = attacker_stats.equipment.main
|
||||
{
|
||||
kind.attack_recover_duration()
|
||||
} else {
|
||||
@ -91,13 +98,15 @@ impl<'a> System<'a> for Sys {
|
||||
if deal_damage {
|
||||
if let Some(Attack { .. }) = &character_states.get(entity).map(|c| c.action) {
|
||||
// Go through all other entities
|
||||
for (b, uid_b, pos_b, ori_b, character_b, stat_b) in (
|
||||
for (b, uid_b, pos_b, ori_b, scale_b_maybe, character_b, stats_b, body_b) in (
|
||||
&entities,
|
||||
&uids,
|
||||
&positions,
|
||||
&orientations,
|
||||
scales.maybe(),
|
||||
&character_states,
|
||||
&stats,
|
||||
&bodies,
|
||||
)
|
||||
.join()
|
||||
{
|
||||
@ -106,16 +115,21 @@ impl<'a> System<'a> for Sys {
|
||||
let pos_b2: Vec2<f32> = Vec2::from(pos_b.0);
|
||||
let ori2 = Vec2::from(ori.0);
|
||||
|
||||
// Scales
|
||||
let scale = scale_maybe.map_or(1.0, |s| s.0);
|
||||
let scale_b = scale_b_maybe.map_or(1.0, |s| s.0);
|
||||
let rad_b = body_b.radius() * scale_b;
|
||||
|
||||
// Check if it is a hit
|
||||
if entity != b
|
||||
&& !stat_b.is_dead
|
||||
&& pos.0.distance_squared(pos_b.0) < ATTACK_RANGE.powi(2)
|
||||
// TODO: Use size instead of 1.0
|
||||
&& ori2.angle_between(pos_b2 - pos2) < (2.0 / pos2.distance(pos_b2)).atan()
|
||||
&& !stats_b.is_dead
|
||||
// Spherical wedge shaped attack field
|
||||
&& pos.0.distance_squared(pos_b.0) < (rad_b + scale * ATTACK_RANGE).powi(2)
|
||||
&& ori2.angle_between(pos_b2 - pos2) < ATTACK_ANGLE.to_radians() / 2.0 + (rad_b / pos2.distance(pos_b2)).atan()
|
||||
{
|
||||
// Weapon gives base damage
|
||||
let mut dmg = if let Some(ItemKind::Tool { power, .. }) =
|
||||
stat.equipment.main.as_ref().map(|i| &i.kind)
|
||||
attacker_stats.equipment.main.as_ref().map(|i| &i.kind)
|
||||
{
|
||||
*power as i32
|
||||
} else {
|
||||
@ -124,8 +138,8 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// Block
|
||||
if character_b.action.is_block()
|
||||
&& ori_b.0.angle_between(pos.0 - pos_b.0).to_degrees()
|
||||
< BLOCK_ANGLE / 2.0
|
||||
&& ori_b.0.angle_between(pos.0 - pos_b.0)
|
||||
< BLOCK_ANGLE.to_radians() / 2.0
|
||||
{
|
||||
dmg = (dmg as f32 * (1.0 - BLOCK_EFFICIENCY)) as i32
|
||||
}
|
||||
|
@ -7,12 +7,12 @@ use crate::{
|
||||
},
|
||||
event::{Emitter, EventBus, LocalEvent, ServerEvent},
|
||||
state::DeltaTime,
|
||||
sync::{Uid, UidAllocator},
|
||||
};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
Entities, Entity, Join, Read, ReadStorage, System, WriteStorage,
|
||||
};
|
||||
use sphynx::{Uid, UidAllocator};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
|
@ -2,6 +2,7 @@ pub mod agent;
|
||||
mod cleanup;
|
||||
pub mod combat;
|
||||
pub mod controller;
|
||||
mod mount;
|
||||
pub mod movement;
|
||||
pub mod phys;
|
||||
mod projectile;
|
||||
@ -13,6 +14,7 @@ use specs::DispatcherBuilder;
|
||||
// System names
|
||||
const AGENT_SYS: &str = "agent_sys";
|
||||
const CONTROLLER_SYS: &str = "controller_sys";
|
||||
const MOUNT_SYS: &str = "mount_sys";
|
||||
const PHYS_SYS: &str = "phys_sys";
|
||||
const MOVEMENT_SYS: &str = "movement_sys";
|
||||
const PROJECTILE_SYS: &str = "projectile_sys";
|
||||
@ -23,13 +25,20 @@ const CLEANUP_SYS: &str = "cleanup_sys";
|
||||
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);
|
||||
dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[AGENT_SYS]);
|
||||
dispatch_builder.add(mount::Sys, MOUNT_SYS, &[CONTROLLER_SYS]);
|
||||
dispatch_builder.add(movement::Sys, MOVEMENT_SYS, &[]);
|
||||
dispatch_builder.add(combat::Sys, COMBAT_SYS, &[CONTROLLER_SYS]);
|
||||
dispatch_builder.add(stats::Sys, STATS_SYS, &[COMBAT_SYS]);
|
||||
dispatch_builder.add(
|
||||
phys::Sys,
|
||||
PHYS_SYS,
|
||||
&[CONTROLLER_SYS, MOVEMENT_SYS, COMBAT_SYS, STATS_SYS],
|
||||
&[
|
||||
CONTROLLER_SYS,
|
||||
MOUNT_SYS,
|
||||
MOVEMENT_SYS,
|
||||
COMBAT_SYS,
|
||||
STATS_SYS,
|
||||
],
|
||||
);
|
||||
dispatch_builder.add(projectile::Sys, PROJECTILE_SYS, &[PHYS_SYS]);
|
||||
dispatch_builder.add(cleanup::Sys, CLEANUP_SYS, &[PHYS_SYS]);
|
||||
|
78
common/src/sys/mount.rs
Normal file
78
common/src/sys/mount.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use crate::{
|
||||
comp::{Controller, MountState, Mounting, Ori, Pos, Vel},
|
||||
sync::UidAllocator,
|
||||
};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
Entities, Join, Read, System, WriteStorage,
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
/// This system is responsible for controlling mounts
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Read<'a, UidAllocator>,
|
||||
Entities<'a>,
|
||||
WriteStorage<'a, Controller>,
|
||||
WriteStorage<'a, MountState>,
|
||||
WriteStorage<'a, Mounting>,
|
||||
WriteStorage<'a, Pos>,
|
||||
WriteStorage<'a, Vel>,
|
||||
WriteStorage<'a, Ori>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
(
|
||||
uid_allocator,
|
||||
entities,
|
||||
mut controllers,
|
||||
mut mount_state,
|
||||
mut mountings,
|
||||
mut positions,
|
||||
mut velocities,
|
||||
mut orientations,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
// Mounted entities.
|
||||
for (entity, mut mount_states) in (&entities, &mut mount_state.restrict_mut()).join() {
|
||||
match mount_states.get_unchecked() {
|
||||
MountState::Unmounted => {}
|
||||
MountState::MountedBy(mounter_uid) => {
|
||||
if let Some((controller, mounter)) = uid_allocator
|
||||
.retrieve_entity_internal(mounter_uid.id())
|
||||
.and_then(|mounter| controllers.get(mounter).cloned().map(|x| (x, mounter)))
|
||||
{
|
||||
// TODO: consider joining on these? (remember we can use .maybe())
|
||||
let pos = positions.get(entity).copied();
|
||||
let ori = orientations.get(entity).copied();
|
||||
let vel = velocities.get(entity).copied();
|
||||
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
|
||||
let _ = positions.insert(mounter, Pos(pos.0 + Vec3::unit_z() * 1.0));
|
||||
let _ = orientations.insert(mounter, ori);
|
||||
let _ = velocities.insert(mounter, vel);
|
||||
}
|
||||
let _ = controllers.insert(entity, controller);
|
||||
} else {
|
||||
*(mount_states.get_mut_unchecked()) = MountState::Unmounted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut to_unmount = Vec::new();
|
||||
for (entity, Mounting(mountee_uid)) in (&entities, &mountings).join() {
|
||||
if uid_allocator
|
||||
.retrieve_entity_internal(mountee_uid.id())
|
||||
.filter(|mountee| entities.is_alive(*mountee))
|
||||
.is_none()
|
||||
{
|
||||
to_unmount.push(entity);
|
||||
}
|
||||
}
|
||||
for entity in to_unmount {
|
||||
mountings.remove(entity);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,10 +5,10 @@ use crate::{
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
state::DeltaTime,
|
||||
sync::Uid,
|
||||
terrain::TerrainGrid,
|
||||
};
|
||||
use specs::prelude::*;
|
||||
use sphynx::Uid;
|
||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
@ -173,10 +173,14 @@ impl<'a> System<'a> for Sys {
|
||||
if Vec2::<f32>::from(wall_dir).magnitude_squared() > 0.001 {
|
||||
Vec2::from(wall_dir).normalized()
|
||||
} else {
|
||||
Vec2::from(vel.0)
|
||||
Vec2::from(inputs.move_dir)
|
||||
}
|
||||
} else {
|
||||
} else if let Glide = character.movement {
|
||||
// Note: non-gliding forces will also affect velocity and thus orientation
|
||||
// producing potentially unexpected changes in direction
|
||||
Vec2::from(vel.0)
|
||||
} else {
|
||||
Vec2::from(inputs.move_dir)
|
||||
};
|
||||
|
||||
if ori_dir.magnitude_squared() > 0.0001
|
||||
|
@ -3,11 +3,11 @@ use {
|
||||
comp::{Body, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, Scale, Sticky, Vel},
|
||||
event::{EventBus, ServerEvent},
|
||||
state::DeltaTime,
|
||||
sync::Uid,
|
||||
terrain::{Block, TerrainGrid},
|
||||
vol::ReadVol,
|
||||
},
|
||||
specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage},
|
||||
sphynx::Uid,
|
||||
vek::*,
|
||||
};
|
||||
|
||||
@ -102,6 +102,7 @@ impl<'a> System<'a> for Sys {
|
||||
let scale = scale.map(|s| s.0).unwrap_or(1.0);
|
||||
|
||||
// Basic collision with terrain
|
||||
// TODO: rename this, not just the player entity
|
||||
let player_rad = 0.3 * scale; // half-width of the player's AABB
|
||||
let player_height = 1.5 * scale;
|
||||
|
||||
@ -342,16 +343,20 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
// Apply pushback
|
||||
for (pos, scale, mass, vel, _, _, physics) in (
|
||||
for (pos, scale, mass, vel, _, _, _, physics) in (
|
||||
&positions,
|
||||
scales.maybe(),
|
||||
masses.maybe(),
|
||||
&mut velocities,
|
||||
&bodies,
|
||||
!&mountings,
|
||||
stickies.maybe(),
|
||||
&mut physics_states,
|
||||
)
|
||||
.join()
|
||||
.filter(|(_, _, _, _, _, _, sticky, physics)| {
|
||||
sticky.is_none() || (physics.on_wall.is_none() && !physics.on_ground)
|
||||
})
|
||||
{
|
||||
physics.touch_entity = None;
|
||||
|
||||
|
@ -23,8 +23,25 @@ impl<'a> System<'a> for Sys {
|
||||
) {
|
||||
let mut server_event_emitter = server_event_bus.emitter();
|
||||
|
||||
for (entity, mut stat) in (&entities, &mut stats).join() {
|
||||
if stat.should_die() && !stat.is_dead {
|
||||
// Increment last change timer
|
||||
stats.set_event_emission(false); // avoid unnecessary syncing
|
||||
for stat in (&mut stats).join() {
|
||||
stat.health.last_change.0 += f64::from(dt.0);
|
||||
}
|
||||
stats.set_event_emission(true);
|
||||
|
||||
// Mutates all stats every tick causing the server to resend this component for every entity every tick
|
||||
for (entity, mut stats) in (&entities, &mut stats.restrict_mut()).join() {
|
||||
let (set_dead, level_up) = {
|
||||
let stat = stats.get_unchecked();
|
||||
(
|
||||
stat.should_die() && !stat.is_dead,
|
||||
stat.exp.current() >= stat.exp.maximum(),
|
||||
)
|
||||
};
|
||||
|
||||
if set_dead {
|
||||
let stat = stats.get_mut_unchecked();
|
||||
server_event_emitter.emit(ServerEvent::Destroy {
|
||||
entity,
|
||||
cause: stat.health.last_change.1.cause,
|
||||
@ -33,9 +50,8 @@ impl<'a> System<'a> for Sys {
|
||||
stat.is_dead = true;
|
||||
}
|
||||
|
||||
stat.health.last_change.0 += f64::from(dt.0);
|
||||
|
||||
if stat.exp.current() >= stat.exp.maximum() {
|
||||
if level_up {
|
||||
let stat = stats.get_mut_unchecked();
|
||||
while stat.exp.current() >= stat.exp.maximum() {
|
||||
stat.exp.change_by(-(stat.exp.maximum() as i64));
|
||||
stat.exp.change_maximum_by(25);
|
||||
|
@ -11,7 +11,7 @@ world = { package = "veloren-world", path = "../world" }
|
||||
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" }
|
||||
|
||||
log = "0.4.8"
|
||||
specs = "0.14.2"
|
||||
specs = { version = "0.15.1", features = ["shred-derive"] }
|
||||
vek = "0.9.9"
|
||||
uvth = "3.1.1"
|
||||
lazy_static = "1.4.0"
|
||||
@ -27,5 +27,3 @@ prometheus = "0.7"
|
||||
prometheus-static-metric = "0.2"
|
||||
rouille = "3.0.0"
|
||||
portpicker = { git = "https://github.com/wusyong/portpicker-rs", branch = "fix_ipv6" }
|
||||
# TODO: remove when upgrading to specs 0.15
|
||||
hibitset = "0.5.3"
|
||||
|
@ -11,15 +11,17 @@ use common::{
|
||||
npc::{get_npc_name, NpcKind},
|
||||
pathfinding::WorldPath,
|
||||
state::TimeOfDay,
|
||||
sync::WorldSyncExt,
|
||||
terrain::{Block, BlockKind, TerrainChunkSize},
|
||||
vol::RectVolSize,
|
||||
};
|
||||
use rand::Rng;
|
||||
use specs::{Builder, Entity as EcsEntity, Join};
|
||||
use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
|
||||
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.
|
||||
@ -1117,7 +1119,9 @@ fn handle_remove_lights(
|
||||
let size = to_delete.len();
|
||||
|
||||
for entity in to_delete {
|
||||
let _ = server.state.ecs_mut().delete_entity_synced(entity);
|
||||
if let Err(err) = server.state.delete_entity_recorded(entity) {
|
||||
error!("Failed to delete light: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
server.notify_client(
|
||||
|
@ -19,6 +19,7 @@ use crate::{
|
||||
chunk_generator::ChunkGenerator,
|
||||
client::{Client, RegionSubscription},
|
||||
cmd::CHAT_COMMANDS,
|
||||
sys::sentinel::{DeletedEntities, TrackedComps},
|
||||
};
|
||||
use common::{
|
||||
assets, comp,
|
||||
@ -26,14 +27,18 @@ use common::{
|
||||
event::{EventBus, ServerEvent},
|
||||
msg::{ClientMsg, ClientState, ServerError, ServerInfo, ServerMsg},
|
||||
net::PostOffice,
|
||||
state::{BlockChange, State, TimeOfDay, Uid},
|
||||
state::{BlockChange, State, TimeOfDay},
|
||||
sync::{Uid, WorldSyncExt},
|
||||
terrain::{block::Block, TerrainChunkSize, TerrainGrid},
|
||||
vol::{ReadVol, RectVolSize, Vox},
|
||||
};
|
||||
use log::debug;
|
||||
use log::{debug, error};
|
||||
use metrics::ServerMetrics;
|
||||
use rand::Rng;
|
||||
use specs::{join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entity as EcsEntity};
|
||||
use specs::{
|
||||
join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entity as EcsEntity, RunNow,
|
||||
SystemData, WorldExt,
|
||||
};
|
||||
use std::{
|
||||
i32,
|
||||
sync::Arc,
|
||||
@ -83,25 +88,18 @@ impl Server {
|
||||
/// Create a new `Server`
|
||||
pub fn new(settings: ServerSettings) -> Result<Self, Error> {
|
||||
let mut state = State::default();
|
||||
state
|
||||
.ecs_mut()
|
||||
.add_resource(EventBus::<ServerEvent>::default());
|
||||
state.ecs_mut().insert(EventBus::<ServerEvent>::default());
|
||||
// TODO: anything but this
|
||||
state.ecs_mut().add_resource(AuthProvider::new());
|
||||
state.ecs_mut().add_resource(Tick(0));
|
||||
state.ecs_mut().add_resource(ChunkGenerator::new());
|
||||
// System timers
|
||||
state
|
||||
.ecs_mut()
|
||||
.add_resource(sys::EntitySyncTimer::default());
|
||||
state.ecs_mut().add_resource(sys::MessageTimer::default());
|
||||
state
|
||||
.ecs_mut()
|
||||
.add_resource(sys::SubscriptionTimer::default());
|
||||
state
|
||||
.ecs_mut()
|
||||
.add_resource(sys::TerrainSyncTimer::default());
|
||||
state.ecs_mut().add_resource(sys::TerrainTimer::default());
|
||||
state.ecs_mut().insert(AuthProvider::new());
|
||||
state.ecs_mut().insert(Tick(0));
|
||||
state.ecs_mut().insert(ChunkGenerator::new());
|
||||
// System timers for performance monitoring
|
||||
state.ecs_mut().insert(sys::EntitySyncTimer::default());
|
||||
state.ecs_mut().insert(sys::MessageTimer::default());
|
||||
state.ecs_mut().insert(sys::SentinelTimer::default());
|
||||
state.ecs_mut().insert(sys::SubscriptionTimer::default());
|
||||
state.ecs_mut().insert(sys::TerrainSyncTimer::default());
|
||||
state.ecs_mut().insert(sys::TerrainTimer::default());
|
||||
// Server-only components
|
||||
state.ecs_mut().register::<RegionSubscription>();
|
||||
state.ecs_mut().register::<Client>();
|
||||
@ -153,11 +151,16 @@ impl Server {
|
||||
};
|
||||
|
||||
// set the spawn point we calculated above
|
||||
state.ecs_mut().add_resource(SpawnPoint(spawn_point));
|
||||
state.ecs_mut().insert(SpawnPoint(spawn_point));
|
||||
|
||||
// Set starting time for the server.
|
||||
state.ecs_mut().write_resource::<TimeOfDay>().0 = settings.start_time;
|
||||
|
||||
// Register trackers
|
||||
sys::sentinel::register_trackers(&mut state.ecs_mut());
|
||||
|
||||
state.ecs_mut().insert(DeletedEntities::default());
|
||||
|
||||
let this = Self {
|
||||
state,
|
||||
world: Arc::new(world),
|
||||
@ -301,8 +304,6 @@ impl Server {
|
||||
|
||||
let server_settings = &self.server_settings;
|
||||
|
||||
let mut todo_remove = None;
|
||||
|
||||
match event {
|
||||
ServerEvent::Explosion { pos, radius } => {
|
||||
const RAYS: usize = 500;
|
||||
@ -372,19 +373,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
|
||||
@ -394,28 +396,48 @@ 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>();
|
||||
|
||||
if let Some(entity_stats) = stats.get(entity).cloned() {
|
||||
if let comp::HealthSource::Attack { by } = cause {
|
||||
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
|
||||
.exp
|
||||
.change_by((entity_stats.level.level() * 10) as i64);
|
||||
}
|
||||
});
|
||||
{
|
||||
// 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 comp::HealthSource::Attack { by } = cause {
|
||||
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
|
||||
.exp
|
||||
.change_by((entity_stats.level.level() * 10) as i64);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
let mut remove = true;
|
||||
|
||||
if let Some(client) = state.ecs().write_storage::<Client>().get_mut(entity) {
|
||||
remove = false;
|
||||
state
|
||||
.ecs()
|
||||
.write_storage()
|
||||
.insert(entity, comp::Vel(Vec3::zero()))
|
||||
.err()
|
||||
.map(|err| error!("Failed to set zero vel on dead client: {:?}", err));
|
||||
state
|
||||
.ecs()
|
||||
.write_storage()
|
||||
.insert(entity, comp::ForceUpdate)
|
||||
.err()
|
||||
.map(|err| {
|
||||
error!("Failed to insert ForceUpdate on dead client: {:?}", err)
|
||||
});
|
||||
client.force_state(ClientState::Dead);
|
||||
} else {
|
||||
todo_remove = Some(entity.clone());
|
||||
}
|
||||
|
||||
if remove {
|
||||
if let Err(err) = state.delete_entity_recorded(entity) {
|
||||
error!("Failed to delete destroyed entity: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,7 +471,9 @@ impl Server {
|
||||
};
|
||||
|
||||
if let Some(item_entity) = item_entity {
|
||||
let _ = state.ecs_mut().delete_entity_synced(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);
|
||||
@ -605,8 +629,8 @@ impl Server {
|
||||
{
|
||||
let not_mounting_yet = if let Some(comp::MountState::Unmounted) = state
|
||||
.ecs()
|
||||
.write_storage::<comp::MountState>()
|
||||
.get_mut(mountee)
|
||||
.read_storage::<comp::MountState>()
|
||||
.get(mountee)
|
||||
.cloned()
|
||||
{
|
||||
true
|
||||
@ -720,7 +744,7 @@ impl Server {
|
||||
main,
|
||||
&server_settings,
|
||||
);
|
||||
Self::initialize_region_subscription(state, entity);
|
||||
sys::subscription::initialize_region_subscription(state.ecs(), entity);
|
||||
}
|
||||
|
||||
ServerEvent::CreateNpc {
|
||||
@ -738,8 +762,8 @@ impl Server {
|
||||
}
|
||||
|
||||
ServerEvent::ClientDisconnect(entity) => {
|
||||
if let Err(err) = state.ecs_mut().delete_entity_synced(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 });
|
||||
@ -753,11 +777,6 @@ impl Server {
|
||||
chat_commands.push((entity, cmd));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: is this needed?
|
||||
if let Some(entity) = todo_remove {
|
||||
let _ = state.ecs_mut().delete_entity_synced(entity);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate requested chunks.
|
||||
@ -816,8 +835,12 @@ impl Server {
|
||||
// 3) Handle inputs from clients
|
||||
frontend_events.append(&mut self.handle_new_connections()?);
|
||||
|
||||
// Run message recieving sys before the systems in common for decreased latency (e.g. run before controller system)
|
||||
sys::message::Sys.run_now(&self.state.ecs());
|
||||
|
||||
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();
|
||||
@ -832,11 +855,6 @@ impl Server {
|
||||
|
||||
let before_tick_6 = Instant::now();
|
||||
// 6) Synchronise clients with the new state of the world.
|
||||
// TODO: Remove sphynx
|
||||
// Sync 'logical' state using Sphynx.
|
||||
let sync_package = self.state.ecs_mut().next_sync_package();
|
||||
self.state
|
||||
.notify_registered_clients(ServerMsg::EcsSync(sync_package));
|
||||
|
||||
// Remove NPCs that are outside the view distances of all players
|
||||
// This is done by removing NPCs in unloaded chunks
|
||||
@ -853,7 +871,9 @@ impl Server {
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
for entity in to_delete {
|
||||
let _ = self.state.ecs_mut().delete_entity_synced(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();
|
||||
@ -864,6 +884,7 @@ impl Server {
|
||||
.read_resource::<sys::EntitySyncTimer>()
|
||||
.nanos as i64;
|
||||
let message_nanos = self.state.ecs().read_resource::<sys::MessageTimer>().nanos as i64;
|
||||
let sentinel_nanos = self.state.ecs().read_resource::<sys::SentinelTimer>().nanos as i64;
|
||||
let subscription_nanos = self
|
||||
.state
|
||||
.ecs()
|
||||
@ -877,24 +898,28 @@ impl Server {
|
||||
let terrain_nanos = self.state.ecs().read_resource::<sys::TerrainTimer>().nanos as i64;
|
||||
let total_sys_nanos = entity_sync_nanos
|
||||
+ message_nanos
|
||||
+ sentinel_nanos
|
||||
+ subscription_nanos
|
||||
+ terrain_sync_nanos
|
||||
+ terrain_nanos;
|
||||
self.metrics
|
||||
.tick_time
|
||||
.with_label_values(&["input"])
|
||||
.set((before_tick_4 - before_tick_1).as_nanos() as i64);
|
||||
.set((before_tick_4 - before_tick_1).as_nanos() as i64 - message_nanos);
|
||||
self.metrics
|
||||
.tick_time
|
||||
.with_label_values(&["state tick"])
|
||||
.set((before_handle_events - before_tick_4).as_nanos() as i64 - total_sys_nanos);
|
||||
.set(
|
||||
(before_handle_events - before_tick_4).as_nanos() as i64
|
||||
- (total_sys_nanos - message_nanos),
|
||||
);
|
||||
self.metrics
|
||||
.tick_time
|
||||
.with_label_values(&["handle server events"])
|
||||
.set((before_tick_6 - before_handle_events).as_nanos() as i64);
|
||||
self.metrics
|
||||
.tick_time
|
||||
.with_label_values(&["sphynx sync"])
|
||||
.with_label_values(&["entity deletion"])
|
||||
.set((before_tick_7 - before_tick_6).as_nanos() as i64);
|
||||
self.metrics
|
||||
.tick_time
|
||||
@ -971,7 +996,8 @@ impl Server {
|
||||
.create_entity_synced()
|
||||
.with(client)
|
||||
.build();
|
||||
// Return the state of the current world (all of the components that Sphynx tracks).
|
||||
// Send client all the tracked components currently attached to its entity as well
|
||||
// as synced resources (currently only `TimeOfDay`)
|
||||
log::debug!("Starting initial sync with client.");
|
||||
self.state
|
||||
.ecs()
|
||||
@ -979,9 +1005,11 @@ impl Server {
|
||||
.get_mut(entity)
|
||||
.unwrap()
|
||||
.notify(ServerMsg::InitialSync {
|
||||
ecs_state: self.state.ecs().gen_state_package(),
|
||||
entity_uid: self.state.ecs().uid_from_entity(entity).unwrap().into(), // Can't fail.
|
||||
// Send client their entity
|
||||
entity_package: TrackedComps::fetch(&self.state.ecs())
|
||||
.create_entity_package(entity),
|
||||
server_info: self.server_info.clone(),
|
||||
time_of_day: *self.state.ecs().read_resource(),
|
||||
// world_map: (WORLD_SIZE/*, self.world.sim().get_map()*/),
|
||||
});
|
||||
log::debug!("Done initial sync with client.");
|
||||
@ -993,83 +1021,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)
|
||||
@ -1131,6 +1082,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 {
|
||||
@ -1191,4 +1146,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
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
use super::SysTimer;
|
||||
use super::{
|
||||
sentinel::{DeletedEntities, ReadTrackers, TrackedComps},
|
||||
SysTimer,
|
||||
};
|
||||
use crate::{
|
||||
client::{Client, RegionSubscription},
|
||||
Tick,
|
||||
@ -7,7 +10,8 @@ use common::{
|
||||
comp::{CharacterState, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Pos, Vel},
|
||||
msg::ServerMsg,
|
||||
region::{Event as RegionEvent, RegionMap},
|
||||
state::Uid,
|
||||
state::TimeOfDay,
|
||||
sync::Uid,
|
||||
};
|
||||
use specs::{
|
||||
Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage,
|
||||
@ -19,6 +23,7 @@ impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
Read<'a, Tick>,
|
||||
ReadExpect<'a, TimeOfDay>,
|
||||
ReadExpect<'a, RegionMap>,
|
||||
Write<'a, SysTimer<Self>>,
|
||||
ReadStorage<'a, Uid>,
|
||||
@ -35,6 +40,9 @@ impl<'a> System<'a> for Sys {
|
||||
WriteStorage<'a, Client>,
|
||||
WriteStorage<'a, ForceUpdate>,
|
||||
WriteStorage<'a, InventoryUpdate>,
|
||||
Write<'a, DeletedEntities>,
|
||||
TrackedComps<'a>,
|
||||
ReadTrackers<'a>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
@ -42,6 +50,7 @@ impl<'a> System<'a> for Sys {
|
||||
(
|
||||
entities,
|
||||
tick,
|
||||
time_of_day,
|
||||
region_map,
|
||||
mut timer,
|
||||
uids,
|
||||
@ -58,6 +67,9 @@ impl<'a> System<'a> for Sys {
|
||||
mut clients,
|
||||
mut force_updates,
|
||||
mut inventory_updates,
|
||||
mut deleted_entities,
|
||||
tracked_comps,
|
||||
trackers,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
timer.start();
|
||||
@ -68,8 +80,8 @@ impl<'a> System<'a> for Sys {
|
||||
// 2. Iterate through region subscribers (ie clients)
|
||||
// - Collect a list of entity ids for clients who are subscribed to this region (hash calc to check each)
|
||||
// 3. Iterate through events from that region
|
||||
// - For each entity left event, iterate through the client list and check if they are subscribed to the destination (hash calc per subscribed client per entity left event)
|
||||
// - Do something with entity entered events when sphynx is removed??
|
||||
// - For each entity entered event, iterate through the client list and check if they are subscribed to the source (hash calc per subscribed client per entity event), if not subscribed to the source send a entity creation message to that client
|
||||
// - For each entity left event, iterate through the client list and check if they are subscribed to the destination (hash calc per subscribed client per entity event)
|
||||
// 4. Iterate through entities in that region
|
||||
// 5. Inform clients of the component changes for that entity
|
||||
// - Throttle update rate base on distance to each client
|
||||
@ -77,6 +89,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)| {
|
||||
@ -91,6 +105,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| {
|
||||
@ -105,14 +123,18 @@ impl<'a> System<'a> for Sys {
|
||||
})
|
||||
})
|
||||
{
|
||||
let create_msg = ServerMsg::CreateEntity(
|
||||
tracked_comps.create_entity_package(entity),
|
||||
);
|
||||
for (client, regions, client_entity, _) in &mut subscribers {
|
||||
if maybe_key
|
||||
.as_ref()
|
||||
.map(|key| !regions.contains(key))
|
||||
.unwrap_or(true)
|
||||
// Client doesn't need to know about itself
|
||||
&& *client_entity != entity
|
||||
// Client doesn't need to know about itself
|
||||
{
|
||||
client.notify(create_msg.clone());
|
||||
send_initial_unsynced_components(
|
||||
client,
|
||||
uid,
|
||||
@ -142,39 +164,59 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
|
||||
let mut send_msg =
|
||||
|msg: ServerMsg, entity: EcsEntity, pos: Pos, force_update, throttle: bool| {
|
||||
for (client, _, client_entity, client_pos) in &mut subscribers {
|
||||
match force_update {
|
||||
None if client_entity == &entity => {}
|
||||
_ => {
|
||||
let distance_sq = client_pos.0.distance_squared(pos.0);
|
||||
// Sync tracked components
|
||||
// Get deleted entities in this region from DeletedEntities
|
||||
let sync_msg = ServerMsg::EcsSync(
|
||||
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());
|
||||
}
|
||||
|
||||
// Throttle update rate based on distance to player
|
||||
// TODO: more entities will be farther away so it could be more
|
||||
// efficient to reverse the order of these checks
|
||||
let update = if !throttle || distance_sq < 100.0f32.powi(2) {
|
||||
true // Closer than 100.0 blocks
|
||||
} else if distance_sq < 150.0f32.powi(2) {
|
||||
(tick + entity.id() as u64) % 2 == 0
|
||||
} else if distance_sq < 200.0f32.powi(2) {
|
||||
(tick + entity.id() as u64) % 4 == 0
|
||||
} else if distance_sq < 250.0f32.powi(2) {
|
||||
(tick + entity.id() as u64) % 8 == 0
|
||||
} else if distance_sq < 300.0f32.powi(2) {
|
||||
(tick + entity.id() as u64) % 16 == 0
|
||||
} else {
|
||||
(tick + entity.id() as u64) % 32 == 0
|
||||
};
|
||||
|
||||
if update {
|
||||
client.notify(msg.clone());
|
||||
}
|
||||
}
|
||||
let mut send_msg = |msg: ServerMsg,
|
||||
entity: EcsEntity,
|
||||
pos: Pos,
|
||||
force_update: Option<&ForceUpdate>,
|
||||
throttle: bool| {
|
||||
for (client, _, client_entity, client_pos) in &mut subscribers {
|
||||
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
|
||||
} else {
|
||||
// Throttle update rate based on distance to client
|
||||
let distance_sq = client_pos.0.distance_squared(pos.0);
|
||||
// More entities farther away so checks start there
|
||||
if distance_sq > 300.0f32.powi(2) {
|
||||
(tick + entity.id() as u64) % 32 == 0
|
||||
} else if distance_sq > 250.0f32.powi(2) {
|
||||
(tick + entity.id() as u64) % 16 == 0
|
||||
} else if distance_sq > 200.0f32.powi(2) {
|
||||
(tick + entity.id() as u64) % 8 == 0
|
||||
} else if distance_sq > 150.0f32.powi(2) {
|
||||
(tick + entity.id() as u64) % 4 == 0
|
||||
} else if distance_sq > 100.0f32.powi(2) {
|
||||
(tick + entity.id() as u64) % 2 == 0
|
||||
} else {
|
||||
true // Closer than 100 blocks
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if update {
|
||||
client.notify(msg.clone());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Sync physics components
|
||||
for (_, entity, &uid, &pos, maybe_vel, maybe_ori, character_state, force_update) in (
|
||||
region.entities(),
|
||||
&entities,
|
||||
@ -257,6 +299,27 @@ 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(®ion_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
|
||||
for (client, inventory, _) in (&mut clients, &inventories, &inventory_updates).join() {
|
||||
client.notify(ServerMsg::InventoryUpdate(inventory.clone()));
|
||||
@ -266,6 +329,13 @@ impl<'a> System<'a> for Sys {
|
||||
force_updates.clear();
|
||||
inventory_updates.clear();
|
||||
|
||||
// Sync resources
|
||||
// TODO: doesn't really belong in this system (rename system or create another system?)
|
||||
let tof_msg = ServerMsg::TimeOfDay(*time_of_day);
|
||||
for client in (&mut clients).join() {
|
||||
client.notify(tof_msg.clone());
|
||||
}
|
||||
|
||||
timer.end();
|
||||
}
|
||||
}
|
||||
|
@ -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,9 +220,11 @@ impl<'a> System<'a> for Sys {
|
||||
},
|
||||
ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state {
|
||||
ClientState::Character => {
|
||||
let _ = positions.insert(entity, pos);
|
||||
let _ = velocities.insert(entity, vel);
|
||||
let _ = orientations.insert(entity, ori);
|
||||
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),
|
||||
|
@ -1,5 +1,6 @@
|
||||
pub mod entity_sync;
|
||||
pub mod message;
|
||||
pub mod sentinel;
|
||||
pub mod subscription;
|
||||
pub mod terrain;
|
||||
pub mod terrain_sync;
|
||||
@ -9,23 +10,29 @@ use std::{marker::PhantomData, time::Instant};
|
||||
|
||||
pub type EntitySyncTimer = SysTimer<entity_sync::Sys>;
|
||||
pub type MessageTimer = SysTimer<message::Sys>;
|
||||
pub type SentinelTimer = SysTimer<sentinel::Sys>;
|
||||
pub type SubscriptionTimer = SysTimer<subscription::Sys>;
|
||||
pub type TerrainTimer = SysTimer<terrain::Sys>;
|
||||
pub type TerrainSyncTimer = SysTimer<terrain_sync::Sys>;
|
||||
|
||||
// System names
|
||||
const ENTITY_SYNC_SYS: &str = "server_entity_sync_sys";
|
||||
const SENTINEL_SYS: &str = "sentinel_sys";
|
||||
const SUBSCRIPTION_SYS: &str = "server_subscription_sys";
|
||||
const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys";
|
||||
const TERRAIN_SYS: &str = "server_terrain_sys";
|
||||
const MESSAGE_SYS: &str = "server_message_sys";
|
||||
|
||||
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
// TODO: makes some of these dependent on systems in common like the phys system
|
||||
dispatch_builder.add(sentinel::Sys, SENTINEL_SYS, &[]);
|
||||
dispatch_builder.add(subscription::Sys, SUBSCRIPTION_SYS, &[]);
|
||||
dispatch_builder.add(entity_sync::Sys, ENTITY_SYNC_SYS, &[SUBSCRIPTION_SYS]);
|
||||
dispatch_builder.add(
|
||||
entity_sync::Sys,
|
||||
ENTITY_SYNC_SYS,
|
||||
&[SUBSCRIPTION_SYS, SENTINEL_SYS],
|
||||
);
|
||||
dispatch_builder.add(terrain_sync::Sys, TERRAIN_SYS, &[]);
|
||||
dispatch_builder.add(terrain::Sys, TERRAIN_SYNC_SYS, &[TERRAIN_SYS]);
|
||||
dispatch_builder.add(message::Sys, MESSAGE_SYS, &[]);
|
||||
}
|
||||
|
||||
/// Used to keep track of how much time each system takes
|
||||
|
229
server/src/sys/sentinel.rs
Normal file
229
server/src/sys/sentinel.rs
Normal file
@ -0,0 +1,229 @@
|
||||
use super::SysTimer;
|
||||
use common::{
|
||||
comp::{
|
||||
Body, CanBuild, Gravity, Item, LightEmitter, Mass, MountState, Mounting, Player, Scale,
|
||||
Stats, Sticky,
|
||||
},
|
||||
msg::EcsCompPacket,
|
||||
sync::{EntityPackage, SyncPackage, Uid, UpdateTracker, WorldSyncExt},
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use specs::{
|
||||
shred::ResourceId, Entity as EcsEntity, Join, ReadExpect, ReadStorage, System, SystemData,
|
||||
World, Write, WriteExpect,
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
/// Always watching
|
||||
/// This system will monitor specific components for insertion, removal, and modification
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Write<'a, SysTimer<Self>>,
|
||||
TrackedComps<'a>,
|
||||
WriteTrackers<'a>,
|
||||
);
|
||||
|
||||
fn run(&mut self, (mut timer, comps, mut trackers): Self::SystemData) {
|
||||
timer.start();
|
||||
|
||||
record_changes(&comps, &mut trackers);
|
||||
|
||||
timer.end();
|
||||
}
|
||||
}
|
||||
|
||||
// Probably more difficult than it needs to be :p
|
||||
#[derive(SystemData)]
|
||||
pub struct TrackedComps<'a> {
|
||||
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>,
|
||||
}
|
||||
impl<'a> TrackedComps<'a> {
|
||||
pub fn create_entity_package(&self, entity: EcsEntity) -> EntityPackage<EcsCompPacket> {
|
||||
let uid = self
|
||||
.uid
|
||||
.get(entity)
|
||||
.copied()
|
||||
.expect("No uid to create an entity package")
|
||||
.0;
|
||||
let mut comps = Vec::new();
|
||||
self.body.get(entity).copied().map(|c| comps.push(c.into()));
|
||||
self.player
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.stats
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.can_build
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.light_emitter
|
||||
.get(entity)
|
||||
.copied()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.item.get(entity).cloned().map(|c| comps.push(c.into()));
|
||||
self.scale
|
||||
.get(entity)
|
||||
.copied()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.mounting
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.mount_state
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.mass.get(entity).copied().map(|c| comps.push(c.into()));
|
||||
self.sticky
|
||||
.get(entity)
|
||||
.copied()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.gravity
|
||||
.get(entity)
|
||||
.copied()
|
||||
.map(|c| comps.push(c.into()));
|
||||
|
||||
EntityPackage { uid, comps }
|
||||
}
|
||||
}
|
||||
#[derive(SystemData)]
|
||||
pub struct ReadTrackers<'a> {
|
||||
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>>,
|
||||
}
|
||||
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, deleted_entities)
|
||||
.with_component(&comps.uid, &*self.body, &comps.body, filter)
|
||||
.with_component(&comps.uid, &*self.player, &comps.player, filter)
|
||||
.with_component(&comps.uid, &*self.stats, &comps.stats, filter)
|
||||
.with_component(&comps.uid, &*self.can_build, &comps.can_build, filter)
|
||||
.with_component(
|
||||
&comps.uid,
|
||||
&*self.light_emitter,
|
||||
&comps.light_emitter,
|
||||
filter,
|
||||
)
|
||||
.with_component(&comps.uid, &*self.item, &comps.item, filter)
|
||||
.with_component(&comps.uid, &*self.scale, &comps.scale, filter)
|
||||
.with_component(&comps.uid, &*self.mounting, &comps.mounting, filter)
|
||||
.with_component(&comps.uid, &*self.mount_state, &comps.mount_state, filter)
|
||||
.with_component(&comps.uid, &*self.mass, &comps.mass, filter)
|
||||
.with_component(&comps.uid, &*self.sticky, &comps.sticky, filter)
|
||||
.with_component(&comps.uid, &*self.gravity, &comps.gravity, filter)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(SystemData)]
|
||||
pub struct WriteTrackers<'a> {
|
||||
uid: WriteExpect<'a, UpdateTracker<Uid>>,
|
||||
body: WriteExpect<'a, UpdateTracker<Body>>,
|
||||
player: WriteExpect<'a, UpdateTracker<Player>>,
|
||||
stats: WriteExpect<'a, UpdateTracker<Stats>>,
|
||||
can_build: WriteExpect<'a, UpdateTracker<CanBuild>>,
|
||||
light_emitter: WriteExpect<'a, UpdateTracker<LightEmitter>>,
|
||||
item: WriteExpect<'a, UpdateTracker<Item>>,
|
||||
scale: WriteExpect<'a, UpdateTracker<Scale>>,
|
||||
mounting: WriteExpect<'a, UpdateTracker<Mounting>>,
|
||||
mount_state: WriteExpect<'a, UpdateTracker<MountState>>,
|
||||
mass: WriteExpect<'a, UpdateTracker<Mass>>,
|
||||
sticky: WriteExpect<'a, UpdateTracker<Sticky>>,
|
||||
gravity: WriteExpect<'a, UpdateTracker<Gravity>>,
|
||||
}
|
||||
|
||||
fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
// Update trackers
|
||||
trackers.uid.record_changes(&comps.uid);
|
||||
trackers.body.record_changes(&comps.body);
|
||||
trackers.player.record_changes(&comps.player);
|
||||
trackers.stats.record_changes(&comps.stats);
|
||||
trackers.can_build.record_changes(&comps.can_build);
|
||||
trackers.light_emitter.record_changes(&comps.light_emitter);
|
||||
trackers.item.record_changes(&comps.item);
|
||||
trackers.scale.record_changes(&comps.scale);
|
||||
trackers.mounting.record_changes(&comps.mounting);
|
||||
trackers.mount_state.record_changes(&comps.mount_state);
|
||||
trackers.mass.record_changes(&comps.mass);
|
||||
trackers.sticky.record_changes(&comps.sticky);
|
||||
trackers.gravity.record_changes(&comps.gravity);
|
||||
}
|
||||
|
||||
pub fn register_trackers(world: &mut World) {
|
||||
world.register_tracker::<Uid>();
|
||||
world.register_tracker::<Body>();
|
||||
world.register_tracker::<Player>();
|
||||
world.register_tracker::<Stats>();
|
||||
world.register_tracker::<CanBuild>();
|
||||
world.register_tracker::<LightEmitter>();
|
||||
world.register_tracker::<Item>();
|
||||
world.register_tracker::<Scale>();
|
||||
world.register_tracker::<Mounting>();
|
||||
world.register_tracker::<MountState>();
|
||||
world.register_tracker::<Mass>();
|
||||
world.register_tracker::<Sticky>();
|
||||
world.register_tracker::<Gravity>();
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
@ -1,14 +1,21 @@
|
||||
use super::SysTimer;
|
||||
use super::{
|
||||
sentinel::{DeletedEntities, TrackedComps},
|
||||
SysTimer,
|
||||
};
|
||||
use crate::client::{self, Client, RegionSubscription};
|
||||
use common::{
|
||||
comp::{CharacterState, Ori, Player, Pos, Vel},
|
||||
msg::ServerMsg,
|
||||
region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap},
|
||||
state::Uid,
|
||||
sync::Uid,
|
||||
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, WorldExt, Write,
|
||||
WriteStorage,
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
/// This system will update region subscriptions based on client positions
|
||||
@ -26,6 +33,8 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Player>,
|
||||
WriteStorage<'a, Client>,
|
||||
WriteStorage<'a, RegionSubscription>,
|
||||
Write<'a, DeletedEntities>,
|
||||
TrackedComps<'a>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
@ -42,6 +51,8 @@ impl<'a> System<'a> for Sys {
|
||||
players,
|
||||
mut clients,
|
||||
mut subscriptions,
|
||||
mut deleted_entities,
|
||||
tracked_comps,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
timer.start();
|
||||
@ -66,8 +77,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
|
||||
@ -85,7 +103,7 @@ impl<'a> System<'a> for Sys {
|
||||
.reduce_or()
|
||||
{
|
||||
// Update current chunk
|
||||
subscription.fuzzy_chunk = (Vec2::<f32>::from(pos.0))
|
||||
subscription.fuzzy_chunk = 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;
|
||||
@ -106,19 +124,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)
|
||||
{
|
||||
@ -128,10 +151,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(
|
||||
@ -139,10 +171,11 @@ 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()) {
|
||||
if let Some(region) = region_map.get(key) {
|
||||
for (uid, pos, vel, ori, character_state, _, _) in (
|
||||
for (uid, pos, vel, ori, character_state, _, entity) in (
|
||||
&uids,
|
||||
&positions,
|
||||
velocities.maybe(),
|
||||
@ -154,6 +187,11 @@ impl<'a> System<'a> for Sys {
|
||||
.join()
|
||||
.filter(|(_, _, _, _, _, _, e)| *e != client_entity)
|
||||
{
|
||||
// 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,
|
||||
@ -172,3 +210,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);
|
||||
for key in ®ions {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ conrod_winit = { git = "https://gitlab.com/veloren/conrod.git" }
|
||||
euc = "0.3.0"
|
||||
|
||||
# ECS
|
||||
specs = "0.14.2"
|
||||
specs = "0.15.1"
|
||||
|
||||
# Mathematics
|
||||
vek = { version = "0.9.9", features = ["serde"] }
|
||||
|
@ -8,7 +8,7 @@ use common::{
|
||||
event::{EventBus, SfxEvent, SfxEventItem},
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use specs::{Entity as EcsEntity, Join};
|
||||
use specs::{Entity as EcsEntity, Join, WorldExt};
|
||||
use std::time::{Duration, Instant};
|
||||
use vek::*;
|
||||
|
||||
|
@ -9,6 +9,7 @@ use common::{
|
||||
event::{EventBus, SfxEvent, SfxEventItem},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use specs::WorldExt;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -7,6 +7,7 @@ use conrod_core::{
|
||||
widget::{self, Button, Image, Rectangle, Text},
|
||||
widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
use specs::WorldExt;
|
||||
use vek::*;
|
||||
|
||||
widget_ids! {
|
||||
|
@ -7,6 +7,7 @@ use conrod_core::{
|
||||
widget::{self, Button, Image, Rectangle, Text},
|
||||
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
use specs::WorldExt;
|
||||
use std::time::{Duration, Instant};
|
||||
use vek::*;
|
||||
|
||||
|
@ -49,7 +49,7 @@ use conrod_core::{
|
||||
widget::{self, Button, Image, Rectangle, Text},
|
||||
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
|
||||
};
|
||||
use specs::Join;
|
||||
use specs::{Join, WorldExt};
|
||||
use std::collections::VecDeque;
|
||||
use vek::*;
|
||||
|
||||
@ -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);
|
||||
|
@ -7,7 +7,7 @@ use conrod_core::{
|
||||
widget_ids, /*, Color*/
|
||||
Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
use specs::Join;
|
||||
use specs::{Join, WorldExt};
|
||||
|
||||
use client::{self, Client};
|
||||
|
||||
|
@ -21,6 +21,7 @@ use common::{
|
||||
terrain::BlockKind,
|
||||
};
|
||||
use log::error;
|
||||
use specs::WorldExt;
|
||||
use vek::*;
|
||||
|
||||
struct Skybox {
|
||||
|
@ -26,7 +26,7 @@ use common::{
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use log::trace;
|
||||
use specs::{Entity as EcsEntity, Join};
|
||||
use specs::{Entity as EcsEntity, Join, WorldExt};
|
||||
use vek::*;
|
||||
|
||||
const DAMAGE_FADE_COEFFICIENT: f64 = 5.0;
|
||||
|
@ -22,7 +22,7 @@ use common::{
|
||||
terrain::{BlockKind, TerrainChunk},
|
||||
vol::ReadVol,
|
||||
};
|
||||
use specs::Join;
|
||||
use specs::{Join, WorldExt};
|
||||
use vek::*;
|
||||
|
||||
// TODO: Don't hard-code this.
|
||||
|
@ -17,7 +17,7 @@ use common::{
|
||||
ChatType,
|
||||
};
|
||||
use log::error;
|
||||
use specs::Join;
|
||||
use specs::{Join, WorldExt};
|
||||
use std::{cell::RefCell, rc::Rc, time::Duration};
|
||||
use vek::*;
|
||||
|
||||
|
@ -342,7 +342,7 @@ impl Ui {
|
||||
// moving origin to top-left corner (from middle).
|
||||
let min_x = self.ui.win_w / 2.0 + l;
|
||||
let min_y = self.ui.win_h / 2.0 - b - h;
|
||||
Aabr {
|
||||
let intersection = Aabr {
|
||||
min: Vec2 {
|
||||
x: (min_x * scale_factor) as u16,
|
||||
y: (min_y * scale_factor) as u16,
|
||||
@ -352,7 +352,13 @@ impl Ui {
|
||||
y: ((min_y + h) * scale_factor) as u16,
|
||||
},
|
||||
}
|
||||
.intersection(window_scissor)
|
||||
.intersection(window_scissor);
|
||||
|
||||
if intersection.is_valid() {
|
||||
intersection
|
||||
} else {
|
||||
Aabr::new_empty(Vec2::zero())
|
||||
}
|
||||
};
|
||||
if new_scissor != current_scissor {
|
||||
// Finish the current command.
|
||||
@ -750,7 +756,7 @@ impl Ui {
|
||||
}
|
||||
|
||||
fn default_scissor(renderer: &Renderer) -> Aabr<u16> {
|
||||
let (screen_w, screen_h) = renderer.get_resolution().map(|e| e as u16).into_tuple();
|
||||
let (screen_w, screen_h) = renderer.get_resolution().into_tuple();
|
||||
Aabr {
|
||||
min: Vec2 { x: 0, y: 0 },
|
||||
max: Vec2 {
|
||||
|
Loading…
Reference in New Issue
Block a user