mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'imbris/component-sync' into 'master'
Sync some components only from the client's own entity. See merge request veloren/veloren!3108
This commit is contained in:
commit
59d195b9bc
@ -6,6 +6,10 @@ pub use userdata_dir::userdata_dir;
|
||||
|
||||
#[cfg(feature = "tracy")] pub use tracy_client;
|
||||
|
||||
/// Allows downstream crates to conditionally do things based on whether tracy
|
||||
/// is enabled without having to expose a cargo feature themselves.
|
||||
pub const TRACY_ENABLED: bool = cfg!(feature = "tracy");
|
||||
|
||||
#[cfg(not(feature = "tracy"))]
|
||||
#[macro_export]
|
||||
macro_rules! plot {
|
||||
|
@ -2,3 +2,4 @@
|
||||
#![feature(generic_const_exprs, const_fn_floating_point_arithmetic)]
|
||||
pub mod msg;
|
||||
pub mod sync;
|
||||
pub mod synced_components;
|
||||
|
@ -1,224 +1,106 @@
|
||||
use crate::sync;
|
||||
use common::{
|
||||
comp,
|
||||
link::Is,
|
||||
mounting::{Mount, Rider},
|
||||
resources::Time,
|
||||
};
|
||||
use crate::sync::{self, NetSync};
|
||||
use common::comp;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::WorldExt;
|
||||
use std::marker::PhantomData;
|
||||
use sum_type::sum_type;
|
||||
|
||||
// Automatically derive From<T> for EcsCompPacket
|
||||
// for each variant EcsCompPacket::T(T.)
|
||||
sum_type! {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum EcsCompPacket {
|
||||
Body(comp::Body),
|
||||
Player(comp::Player),
|
||||
CanBuild(comp::CanBuild),
|
||||
Stats(comp::Stats),
|
||||
SkillSet(comp::SkillSet),
|
||||
ActiveAbilities(comp::ActiveAbilities),
|
||||
Buffs(comp::Buffs),
|
||||
Auras(comp::Auras),
|
||||
Energy(comp::Energy),
|
||||
Combo(comp::Combo),
|
||||
Health(comp::Health),
|
||||
Poise(comp::Poise),
|
||||
LightEmitter(comp::LightEmitter),
|
||||
Inventory(comp::Inventory),
|
||||
Item(comp::Item),
|
||||
Scale(comp::Scale),
|
||||
Group(comp::Group),
|
||||
IsMount(Is<Mount>),
|
||||
IsRider(Is<Rider>),
|
||||
Mass(comp::Mass),
|
||||
Density(comp::Density),
|
||||
Collider(comp::Collider),
|
||||
Sticky(comp::Sticky),
|
||||
CharacterState(comp::CharacterState),
|
||||
Pos(comp::Pos),
|
||||
Vel(comp::Vel),
|
||||
Ori(comp::Ori),
|
||||
Shockwave(comp::Shockwave),
|
||||
BeamSegment(comp::BeamSegment),
|
||||
Alignment(comp::Alignment),
|
||||
}
|
||||
}
|
||||
// Automatically derive From<T> for EcsCompPhantom
|
||||
// for each variant EcsCompPhantom::T(PhantomData<T>).
|
||||
sum_type! {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum EcsCompPhantom {
|
||||
Body(PhantomData<comp::Body>),
|
||||
Player(PhantomData<comp::Player>),
|
||||
CanBuild(PhantomData<comp::CanBuild>),
|
||||
Stats(PhantomData<comp::Stats>),
|
||||
SkillSet(PhantomData<comp::SkillSet>),
|
||||
ActiveAbilities(PhantomData<comp::ActiveAbilities>),
|
||||
Buffs(PhantomData<comp::Buffs>),
|
||||
Auras(PhantomData<comp::Auras>),
|
||||
Energy(PhantomData<comp::Energy>),
|
||||
Combo(PhantomData<comp::Combo>),
|
||||
Health(PhantomData<comp::Health>),
|
||||
Poise(PhantomData<comp::Poise>),
|
||||
LightEmitter(PhantomData<comp::LightEmitter>),
|
||||
Inventory(PhantomData<comp::Inventory>),
|
||||
Item(PhantomData<comp::Item>),
|
||||
Scale(PhantomData<comp::Scale>),
|
||||
Group(PhantomData<comp::Group>),
|
||||
IsMount(PhantomData<Is<Mount>>),
|
||||
IsRider(PhantomData<Is<Rider>>),
|
||||
Mass(PhantomData<comp::Mass>),
|
||||
Density(PhantomData<comp::Density>),
|
||||
Collider(PhantomData<comp::Collider>),
|
||||
Sticky(PhantomData<comp::Sticky>),
|
||||
CharacterState(PhantomData<comp::CharacterState>),
|
||||
Pos(PhantomData<comp::Pos>),
|
||||
Vel(PhantomData<comp::Vel>),
|
||||
Ori(PhantomData<comp::Ori>),
|
||||
Shockwave(PhantomData<comp::Shockwave>),
|
||||
BeamSegment(PhantomData<comp::BeamSegment>),
|
||||
Alignment(PhantomData<comp::Alignment>),
|
||||
}
|
||||
}
|
||||
impl sync::CompPacket for EcsCompPacket {
|
||||
type Phantom = EcsCompPhantom;
|
||||
/// This macro defines [`EcsCompPacke`]
|
||||
///
|
||||
/// It is meant to be passed to the `synced_components!` macro which will call
|
||||
/// it with a list of components.
|
||||
macro_rules! comp_packet {
|
||||
($($component_name:ident: $component_type:ident,)*) => {
|
||||
|
||||
fn apply_insert(self, entity: specs::Entity, world: &specs::World, force_update: bool) {
|
||||
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::SkillSet(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::ActiveAbilities(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Buffs(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Auras(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Energy(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Combo(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Health(mut comp) => {
|
||||
// Time isn't synced between client and server so replace the Time from the
|
||||
// server with the Client's local Time to enable accurate comparison.
|
||||
comp.last_change.time = *world.read_resource::<Time>();
|
||||
sync::handle_insert(comp, entity, world)
|
||||
},
|
||||
EcsCompPacket::Poise(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Inventory(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::Group(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::IsMount(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::IsRider(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Density(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Collider(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::CharacterState(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Pos(comp) => {
|
||||
sync::handle_interp_insert(comp, entity, world, force_update)
|
||||
},
|
||||
EcsCompPacket::Vel(comp) => {
|
||||
sync::handle_interp_insert(comp, entity, world, force_update)
|
||||
},
|
||||
EcsCompPacket::Ori(comp) => {
|
||||
sync::handle_interp_insert(comp, entity, world, force_update)
|
||||
},
|
||||
EcsCompPacket::Shockwave(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::BeamSegment(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Alignment(comp) => sync::handle_insert(comp, entity, world),
|
||||
// `sum_type!` will automatically derive From<T> for EcsCompPacket
|
||||
// for each variant EcsCompPacket::T(T).
|
||||
sum_type::sum_type! {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum EcsCompPacket {
|
||||
// Note: also use the component_type identifier
|
||||
// to name the enum variant that contains the component.
|
||||
$($component_type($component_type),)*
|
||||
// These aren't included in the "synced_components" because the way
|
||||
// we determine if they are changed and when to send them is different
|
||||
// from the other components.
|
||||
Pos(comp::Pos),
|
||||
Vel(comp::Vel),
|
||||
Ori(comp::Ori),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modify(self, entity: specs::Entity, world: &specs::World, force_update: bool) {
|
||||
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::SkillSet(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::ActiveAbilities(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Buffs(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Auras(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Energy(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Combo(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Health(mut comp) => {
|
||||
// Time isn't synced between client and server so replace the Time from the
|
||||
// server with the Client's local Time to enable accurate comparison.
|
||||
comp.last_change.time = *world.read_resource::<Time>();
|
||||
sync::handle_modify(comp, entity, world)
|
||||
},
|
||||
EcsCompPacket::Poise(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Inventory(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::Group(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::IsMount(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::IsRider(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Density(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Collider(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::CharacterState(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Pos(comp) => {
|
||||
sync::handle_interp_modify(comp, entity, world, force_update)
|
||||
},
|
||||
EcsCompPacket::Vel(comp) => {
|
||||
sync::handle_interp_modify(comp, entity, world, force_update)
|
||||
},
|
||||
EcsCompPacket::Ori(comp) => {
|
||||
sync::handle_interp_modify(comp, entity, world, force_update)
|
||||
},
|
||||
EcsCompPacket::Shockwave(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::BeamSegment(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Alignment(comp) => sync::handle_modify(comp, entity, world),
|
||||
// `sum_type!` will automatically derive From<PhantomData<T>> for EcsCompPhantom
|
||||
// for each variant EcsCompPhantom::T(PhantomData<T>).
|
||||
sum_type::sum_type! {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum EcsCompPhantom {
|
||||
$($component_type(PhantomData<$component_type>),)*
|
||||
Pos(PhantomData<comp::Pos>),
|
||||
Vel(PhantomData<comp::Vel>),
|
||||
Ori(PhantomData<comp::Ori>),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::SkillSet(_) => sync::handle_remove::<comp::SkillSet>(entity, world),
|
||||
EcsCompPhantom::ActiveAbilities(_) => {
|
||||
sync::handle_remove::<comp::ActiveAbilities>(entity, world)
|
||||
},
|
||||
EcsCompPhantom::Buffs(_) => sync::handle_remove::<comp::Buffs>(entity, world),
|
||||
EcsCompPhantom::Auras(_) => sync::handle_remove::<comp::Auras>(entity, world),
|
||||
EcsCompPhantom::Energy(_) => sync::handle_remove::<comp::Energy>(entity, world),
|
||||
EcsCompPhantom::Combo(_) => sync::handle_remove::<comp::Combo>(entity, world),
|
||||
EcsCompPhantom::Health(_) => sync::handle_remove::<comp::Health>(entity, world),
|
||||
EcsCompPhantom::Poise(_) => sync::handle_remove::<comp::Poise>(entity, world),
|
||||
EcsCompPhantom::LightEmitter(_) => {
|
||||
sync::handle_remove::<comp::LightEmitter>(entity, world)
|
||||
},
|
||||
EcsCompPhantom::Inventory(_) => sync::handle_remove::<comp::Inventory>(entity, world),
|
||||
EcsCompPhantom::Item(_) => sync::handle_remove::<comp::Item>(entity, world),
|
||||
EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(entity, world),
|
||||
EcsCompPhantom::Group(_) => sync::handle_remove::<comp::Group>(entity, world),
|
||||
EcsCompPhantom::IsMount(_) => sync::handle_remove::<Is<Mount>>(entity, world),
|
||||
EcsCompPhantom::IsRider(_) => sync::handle_remove::<Is<Rider>>(entity, world),
|
||||
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
|
||||
EcsCompPhantom::Density(_) => sync::handle_remove::<comp::Density>(entity, world),
|
||||
EcsCompPhantom::Collider(_) => sync::handle_remove::<comp::Collider>(entity, world),
|
||||
EcsCompPhantom::Sticky(_) => sync::handle_remove::<comp::Sticky>(entity, world),
|
||||
EcsCompPhantom::CharacterState(_) => {
|
||||
sync::handle_remove::<comp::CharacterState>(entity, world)
|
||||
},
|
||||
EcsCompPhantom::Pos(_) => sync::handle_interp_remove::<comp::Pos>(entity, world),
|
||||
EcsCompPhantom::Vel(_) => sync::handle_interp_remove::<comp::Vel>(entity, world),
|
||||
EcsCompPhantom::Ori(_) => sync::handle_interp_remove::<comp::Ori>(entity, world),
|
||||
EcsCompPhantom::Shockwave(_) => sync::handle_remove::<comp::Shockwave>(entity, world),
|
||||
EcsCompPhantom::BeamSegment(_) => {
|
||||
sync::handle_remove::<comp::BeamSegment>(entity, world)
|
||||
},
|
||||
EcsCompPhantom::Alignment(_) => sync::handle_remove::<comp::Alignment>(entity, world),
|
||||
impl sync::CompPacket for EcsCompPacket {
|
||||
type Phantom = EcsCompPhantom;
|
||||
|
||||
fn apply_insert(self, entity: specs::Entity, world: &specs::World, force_update: bool) {
|
||||
match self {
|
||||
$(Self::$component_type(mut comp) => {
|
||||
comp.pre_insert(world);
|
||||
sync::handle_insert(comp, entity, world);
|
||||
},)*
|
||||
Self::Pos(comp) => {
|
||||
sync::handle_interp_insert(comp, entity, world, force_update)
|
||||
},
|
||||
Self::Vel(comp) => {
|
||||
sync::handle_interp_insert(comp, entity, world, force_update)
|
||||
},
|
||||
Self::Ori(comp) => {
|
||||
sync::handle_interp_insert(comp, entity, world, force_update)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modify(self, entity: specs::Entity, world: &specs::World, force_update: bool) {
|
||||
match self {
|
||||
$(Self::$component_type(mut comp) => {
|
||||
comp.pre_modify(world);
|
||||
sync::handle_modify(comp, entity, world);
|
||||
},)*
|
||||
Self::Pos(comp) => {
|
||||
sync::handle_interp_modify(comp, entity, world, force_update)
|
||||
},
|
||||
Self::Vel(comp) => {
|
||||
sync::handle_interp_modify(comp, entity, world, force_update)
|
||||
},
|
||||
Self::Ori(comp) => {
|
||||
sync::handle_interp_modify(comp, entity, world, force_update)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_remove(phantom: Self::Phantom, entity: specs::Entity, world: &specs::World) {
|
||||
match phantom {
|
||||
$(EcsCompPhantom::$component_type(_) => {
|
||||
sync::handle_remove::<$component_type>(entity, world);
|
||||
},)*
|
||||
EcsCompPhantom::Pos(_) => {
|
||||
sync::handle_interp_remove::<comp::Pos>(entity, world)
|
||||
},
|
||||
EcsCompPhantom::Vel(_) => {
|
||||
sync::handle_interp_remove::<comp::Vel>(entity, world)
|
||||
},
|
||||
EcsCompPhantom::Ori(_) => {
|
||||
sync::handle_interp_remove::<comp::Ori>(entity, world)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import all the component types so they will be available when expanding the
|
||||
// macro below.
|
||||
use crate::synced_components::*;
|
||||
// Pass `comp_packet!` macro to this "x macro" which will invoke it with a list
|
||||
// of components. This will declare the types defined in the macro above.
|
||||
crate::synced_components!(comp_packet);
|
||||
|
@ -1,12 +1,14 @@
|
||||
// Note: Currently only one-way sync is supported until a usecase for two-way
|
||||
// sync arises
|
||||
pub mod interpolation;
|
||||
mod net_sync;
|
||||
mod packet;
|
||||
mod sync_ext;
|
||||
mod track;
|
||||
|
||||
// Reexports
|
||||
pub use common::uid::{Uid, UidAllocator};
|
||||
pub use net_sync::{NetSync, SyncFrom};
|
||||
pub use packet::{
|
||||
handle_insert, handle_interp_insert, handle_interp_modify, handle_interp_remove, handle_modify,
|
||||
handle_remove, CompPacket, CompSyncPackage, EntityPackage, EntitySyncPackage,
|
||||
|
50
common/net/src/sync/net_sync.rs
Normal file
50
common/net/src/sync/net_sync.rs
Normal file
@ -0,0 +1,50 @@
|
||||
//! Types of syncing:
|
||||
//! * synced from any entity (within range)
|
||||
//! * synced only from the client's entity
|
||||
//!
|
||||
//! Types of updating
|
||||
//! * Plain copy of the new component state
|
||||
//! * (unimplemented) Diff to update component, two variants
|
||||
//! * Keep a full copy of the component and generate diff from that
|
||||
//! * Intercept changes to the component (no need to compute diff or keep a
|
||||
//! full copy)
|
||||
//!
|
||||
//! NOTE: rapidly updated components like Pos/Vel/Ori are not covered here
|
||||
|
||||
/// Trait that must be implemented for most components that are synced over the
|
||||
/// network.
|
||||
pub trait NetSync: specs::Component + Clone + Send + Sync
|
||||
where
|
||||
Self::Storage: specs::storage::Tracked,
|
||||
{
|
||||
// TODO: this scheme theoretically supports diffing withing the
|
||||
// impl of `From<UpdateFrom> for Update` but there is no automatic
|
||||
// machinery to provide the `UpdateFrom` value yet. Might need to
|
||||
// rework this when actuall implementing though.
|
||||
//
|
||||
//type UpdateFrom = Self;
|
||||
//type Update: From<Self::UpdateFrom> = Self;
|
||||
|
||||
/// Determines what for entities this component is synced to the client.
|
||||
///
|
||||
/// For example, [`SyncFrom::ClientEntity`] can be used to only sync the
|
||||
/// components for the client's own entity.
|
||||
const SYNC_FROM: SyncFrom;
|
||||
|
||||
// sync::handle_modify(comp, entity, world)
|
||||
|
||||
/// Allows making modifications before the synced component is inserted on
|
||||
/// the client.
|
||||
fn pre_insert(&mut self, world: &specs::World) { let _world = world; }
|
||||
|
||||
/// Allows making modifications before the synced component is overwritten
|
||||
/// with this version on the client.
|
||||
fn pre_modify(&mut self, world: &specs::World) { let _world = world; }
|
||||
}
|
||||
|
||||
/// Whether a component is synced to the client for any entity or for just the
|
||||
/// client's own entity.
|
||||
pub enum SyncFrom {
|
||||
AnyEntity,
|
||||
ClientEntity,
|
||||
}
|
@ -168,15 +168,13 @@ impl<P: CompPacket> CompSyncPackage<P> {
|
||||
.push((uid.into(), CompUpdateKind::Removed(PhantomData::<C>.into())));
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_component<'a, C: Component + Clone + Send + Sync>(
|
||||
mut self,
|
||||
pub fn add_component_updates<'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
|
||||
) where
|
||||
P: From<C>,
|
||||
C: TryFrom<P>,
|
||||
P::Phantom: From<PhantomData<C>>,
|
||||
@ -184,6 +182,29 @@ impl<P: CompPacket> CompSyncPackage<P> {
|
||||
C::Storage: specs::storage::Tracked,
|
||||
{
|
||||
tracker.get_updates_for(uids, storage, filter, &mut self.comp_updates);
|
||||
self
|
||||
}
|
||||
|
||||
/// If there was an update to the component `C` on the provided entity this
|
||||
/// will add the update to this package.
|
||||
pub fn add_component_update<'a, C: Component + Clone + Send + Sync>(
|
||||
&mut self,
|
||||
tracker: &UpdateTracker<C>,
|
||||
storage: &ReadStorage<'a, C>,
|
||||
uid: u64,
|
||||
entity: Entity,
|
||||
) where
|
||||
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_update) = tracker.get_update(storage, entity) {
|
||||
self.comp_updates.push((uid, comp_update))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether this package is empty, useful for not sending an empty
|
||||
/// message.
|
||||
pub fn is_empty(&self) -> bool { self.comp_updates.is_empty() }
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ impl<C: Component> UpdateTracker<C>
|
||||
where
|
||||
C::Storage: specs::storage::Tracked,
|
||||
{
|
||||
pub fn new(specs_world: &mut World) -> Self {
|
||||
pub fn new(specs_world: &World) -> Self {
|
||||
Self {
|
||||
reader_id: specs_world.write_storage::<C>().register_reader(),
|
||||
inserted: BitSet::new(),
|
||||
@ -122,4 +122,38 @@ impl<C: Component + Clone + Send + Sync> UpdateTracker<C> {
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some(update)` if the tracked component was modified for this
|
||||
/// entity.
|
||||
pub fn get_update<'a, P>(
|
||||
&self,
|
||||
storage: &specs::ReadStorage<'a, C>,
|
||||
entity: Entity,
|
||||
) -> Option<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,
|
||||
{
|
||||
let id = entity.id();
|
||||
// Generate update if one exists.
|
||||
//
|
||||
// Note: presence of the id in these bitsets should be mutually exclusive
|
||||
if self.modified.contains(id) {
|
||||
storage
|
||||
.get(entity)
|
||||
.map(|comp| CompUpdateKind::Modified(P::from(comp.clone())))
|
||||
} else if self.inserted.contains(id) {
|
||||
storage
|
||||
.get(entity)
|
||||
.map(|comp| CompUpdateKind::Inserted(P::from(comp.clone())))
|
||||
} else if self.removed.contains(id) {
|
||||
Some(CompUpdateKind::Removed(P::Phantom::from(PhantomData::<C>)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
231
common/net/src/synced_components.rs
Normal file
231
common/net/src/synced_components.rs
Normal file
@ -0,0 +1,231 @@
|
||||
//! Contains an "x macro" for all synced components as well as [NetSync]
|
||||
//! implementations for those components.
|
||||
//!
|
||||
//!
|
||||
//! An x macro accepts another macro as input and calls it with a list of
|
||||
//! inputs. This allows adding components to the list in the x macro declaration
|
||||
//! and then writing macros that will accept this list and generate code that
|
||||
//! handles every synced component without further repitition of the component
|
||||
//! set.
|
||||
//!
|
||||
//! This module also re-exports all the component types that are synced.
|
||||
//!
|
||||
//! A glob import from this can be used so that the component types are in scope
|
||||
//! when using the x macro defined here which requires this.
|
||||
|
||||
/// This provides a lowercase name and the component type.
|
||||
///
|
||||
/// See [module](self) level docs for more details.
|
||||
#[macro_export]
|
||||
macro_rules! synced_components {
|
||||
($macro:ident) => {
|
||||
$macro! {
|
||||
body: Body,
|
||||
stats: Stats,
|
||||
buffs: Buffs,
|
||||
auras: Auras,
|
||||
energy: Energy,
|
||||
health: Health,
|
||||
poise: Poise,
|
||||
light_emitter: LightEmitter,
|
||||
item: Item,
|
||||
scale: Scale,
|
||||
group: Group,
|
||||
is_mount: IsMount,
|
||||
is_rider: IsRider,
|
||||
mass: Mass,
|
||||
density: Density,
|
||||
collider: Collider,
|
||||
sticky: Sticky,
|
||||
character_state: CharacterState,
|
||||
shockwave: Shockwave,
|
||||
beam_segment: BeamSegment,
|
||||
alignment: Alignment,
|
||||
// TODO: evaluate if this is used on the client,
|
||||
// and if so what it is used for
|
||||
player: Player,
|
||||
// TODO: change this to `SyncFrom::ClientEntity` and sync the bare minimum
|
||||
// from other entities (e.g. just keys needed to show appearance
|
||||
// based on their loadout). Also, it looks like this actually has
|
||||
// an alternative sync method implemented in entity_sync via
|
||||
// ServerGeneral::InventoryUpdate so we could use that instead
|
||||
// or remove the part where it clones the inventory.
|
||||
inventory: Inventory,
|
||||
// TODO: this is used in combat rating calculation in voxygen but we can probably
|
||||
// remove it from that and then see if it's used for anything else and try to move
|
||||
// to only being synced for the client's entity.
|
||||
skill_set: SkillSet,
|
||||
|
||||
// Synced to the client only for its own entity
|
||||
|
||||
combo: Combo,
|
||||
active_abilities: ActiveAbilities,
|
||||
can_build: CanBuild,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! reexport_comps {
|
||||
($($name:ident: $type:ident,)*) => {
|
||||
mod inner {
|
||||
pub use common::comp::*;
|
||||
use common::link::Is;
|
||||
use common::mounting::{Mount, Rider};
|
||||
|
||||
// We alias these because the identifier used for the
|
||||
// component's type is reused as an enum variant name
|
||||
// in the macro's that we pass to `synced_components!`.
|
||||
//
|
||||
// This is also the reason we need this inner module, since
|
||||
// we can't just re-export all the types directly from `common::comp`.
|
||||
pub type IsMount = Is<Mount>;
|
||||
pub type IsRider = Is<Rider>;
|
||||
}
|
||||
|
||||
// Re-export all the component types. So that uses of `synced_components!` outside this
|
||||
// module can bring them into scope with a single glob import.
|
||||
$(pub use inner::$type;)*
|
||||
}
|
||||
}
|
||||
// Pass `reexport_comps` macro to the "x macro" which will invoke it with a list
|
||||
// of components.
|
||||
//
|
||||
// Note: this brings all these components into scope for the implementations
|
||||
// below.
|
||||
synced_components!(reexport_comps);
|
||||
|
||||
// ===============================
|
||||
// === NetSync implementations ===
|
||||
// ===============================
|
||||
|
||||
use crate::sync::{NetSync, SyncFrom};
|
||||
|
||||
// These are synced from any entity within range.
|
||||
|
||||
impl NetSync for Body {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Stats {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Buffs {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Auras {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Energy {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Health {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
|
||||
fn pre_insert(&mut self, world: &specs::World) {
|
||||
use common::resources::Time;
|
||||
use specs::WorldExt;
|
||||
|
||||
// Time isn't synced between client and server so replace the Time from the
|
||||
// server with the Client's local Time to enable accurate comparison.
|
||||
self.last_change.time = *world.read_resource::<Time>();
|
||||
}
|
||||
|
||||
fn pre_modify(&mut self, world: &specs::World) {
|
||||
use common::resources::Time;
|
||||
use specs::WorldExt;
|
||||
|
||||
// Time isn't synced between client and server so replace the Time from the
|
||||
// server with the Client's local Time to enable accurate comparison.
|
||||
self.last_change.time = *world.read_resource::<Time>();
|
||||
}
|
||||
}
|
||||
|
||||
impl NetSync for Poise {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for LightEmitter {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Item {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Scale {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Group {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for IsMount {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for IsRider {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Mass {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Density {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Collider {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Sticky {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for CharacterState {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Shockwave {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for BeamSegment {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Alignment {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Player {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Inventory {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for SkillSet {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
// These are synced only from the client's own entity.
|
||||
|
||||
impl NetSync for Combo {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::ClientEntity;
|
||||
}
|
||||
|
||||
impl NetSync for ActiveAbilities {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::ClientEntity;
|
||||
}
|
||||
|
||||
impl NetSync for CanBuild {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::ClientEntity;
|
||||
}
|
@ -4,7 +4,7 @@ use specs::{Component, DerefFlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::ops::Mul;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
||||
/// Energy is represented by u32s within the module, but treated as a float by
|
||||
/// the rest of the game.
|
||||
// As a general rule, all input and output values to public functions should be
|
||||
@ -54,14 +54,31 @@ impl Energy {
|
||||
/// Returns the fraction of energy an entity has remaining
|
||||
pub fn fraction(&self) -> f32 { self.current() / self.maximum().max(1.0) }
|
||||
|
||||
/// Updates the maximum value for energy
|
||||
pub fn update_maximum(&mut self, modifiers: comp::stats::StatsModifier) {
|
||||
/// Calculates a new maximum value and returns it if the value differs from
|
||||
/// the current maximum.
|
||||
///
|
||||
/// Note: The returned value uses an internal format so don't expect it to
|
||||
/// be useful for anything other than a parameter to
|
||||
/// [`Self::update_maximum`].
|
||||
pub fn needs_maximum_update(&self, modifiers: comp::stats::StatsModifier) -> Option<u32> {
|
||||
let maximum = modifiers
|
||||
.compute_maximum(self.base_max())
|
||||
.mul(Self::SCALING_FACTOR_FLOAT)
|
||||
// NaN does not need to be handled here as rust will automatically change to 0 when casting to u32
|
||||
.clamp(0.0, Self::MAX_SCALED_ENERGY as f32) as u32;
|
||||
|
||||
(maximum != self.maximum).then(|| maximum)
|
||||
}
|
||||
|
||||
/// Updates the maximum value for energy.
|
||||
///
|
||||
/// Note: The accepted `u32` value is in the internal format of this type.
|
||||
/// So attempting to pass values that weren't returned from
|
||||
/// [`Self::needs_maximum_update`] can produce strange or unexpected
|
||||
/// results.
|
||||
pub fn update_internal_integer_maximum(&mut self, maximum: u32) {
|
||||
self.maximum = maximum;
|
||||
// Clamp the current energy to enforce the current <= maximum invariant.
|
||||
self.current = self.current.min(self.maximum);
|
||||
}
|
||||
|
||||
|
@ -90,13 +90,29 @@ impl Health {
|
||||
/// Returns the fraction of health an entity has remaining
|
||||
pub fn fraction(&self) -> f32 { self.current() / self.maximum().max(1.0) }
|
||||
|
||||
/// Updates the maximum value for health
|
||||
pub fn update_maximum(&mut self, modifiers: comp::stats::StatsModifier) {
|
||||
/// Calculates a new maximum value and returns it if the value differs from
|
||||
/// the current maximum.
|
||||
///
|
||||
/// Note: The returned value uses an internal format so don't expect it to
|
||||
/// be useful for anything other than a parameter to
|
||||
/// [`Self::update_maximum`].
|
||||
pub fn needs_maximum_update(&self, modifiers: comp::stats::StatsModifier) -> Option<u32> {
|
||||
let maximum = modifiers
|
||||
.compute_maximum(self.base_max())
|
||||
.mul(Self::SCALING_FACTOR_FLOAT)
|
||||
// NaN does not need to be handled here as rust will automatically change to 0 when casting to u32
|
||||
.clamp(0.0, Self::MAX_SCALED_HEALTH as f32) as u32;
|
||||
|
||||
(maximum != self.maximum).then(|| maximum)
|
||||
}
|
||||
|
||||
/// Updates the maximum value for health.
|
||||
///
|
||||
/// Note: The accepted `u32` value is in the internal format of this type.
|
||||
/// So attempting to pass values that weren't returned from
|
||||
/// [`Self::needs_maximum_update`] can produce strange or unexpected
|
||||
/// results.
|
||||
pub fn update_internal_integer_maximum(&mut self, maximum: u32) {
|
||||
self.maximum = maximum;
|
||||
// Clamp the current health to enforce the current <= maximum invariant.
|
||||
self.current = self.current.min(self.maximum);
|
||||
|
@ -30,6 +30,7 @@ impl StatsModifier {
|
||||
base_value * self.mult_mod + self.add_mod
|
||||
}
|
||||
|
||||
// Note: unused for now
|
||||
pub fn update_maximum(&self) -> bool {
|
||||
self.add_mod.abs() > f32::EPSILON || (self.mult_mod - 1.0).abs() > f32::EPSILON
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ pub struct JoinStruct<'a> {
|
||||
pub vel: &'a mut Vel,
|
||||
pub ori: &'a mut Ori,
|
||||
pub mass: &'a Mass,
|
||||
pub density: &'a mut Density,
|
||||
pub density: FlaggedAccessMut<'a, &'a mut Density, Density>,
|
||||
pub energy: FlaggedAccessMut<'a, &'a mut Energy, Energy>,
|
||||
pub inventory: Option<&'a Inventory>,
|
||||
pub controller: &'a mut Controller,
|
||||
@ -171,7 +171,7 @@ impl<'a> JoinData<'a> {
|
||||
vel: j.vel,
|
||||
ori: j.ori,
|
||||
mass: j.mass,
|
||||
density: j.density,
|
||||
density: &j.density,
|
||||
energy: &j.energy,
|
||||
inventory: j.inventory,
|
||||
controller: j.controller,
|
||||
|
@ -105,7 +105,7 @@ impl<'a> System<'a> for Sys {
|
||||
vel,
|
||||
ori,
|
||||
mass,
|
||||
mut density,
|
||||
density,
|
||||
energy,
|
||||
inventory,
|
||||
controller,
|
||||
@ -180,7 +180,7 @@ impl<'a> System<'a> for Sys {
|
||||
vel,
|
||||
ori,
|
||||
mass,
|
||||
density: &mut density,
|
||||
density,
|
||||
energy,
|
||||
inventory,
|
||||
controller,
|
||||
@ -238,15 +238,28 @@ impl Sys {
|
||||
mut state_update: StateUpdate,
|
||||
output_events: &mut OutputEvents,
|
||||
) {
|
||||
// TODO: if checking equality is expensive use optional field in StateUpdate
|
||||
// Here we check for equality with the previous value of these components before
|
||||
// updating them so that the modification detection will not be
|
||||
// triggered unnecessarily. This is important for minimizing updates
|
||||
// sent to the clients (and thus keeping bandwidth usage down).
|
||||
//
|
||||
// TODO: if checking equality is expensive for char_state use optional field in
|
||||
// StateUpdate
|
||||
if *join.char_state != state_update.character {
|
||||
*join.char_state = state_update.character
|
||||
}
|
||||
if *join.density != state_update.density {
|
||||
*join.density = state_update.density
|
||||
}
|
||||
if *join.energy != state_update.energy {
|
||||
*join.energy = state_update.energy;
|
||||
};
|
||||
|
||||
// These components use a different type of change detection.
|
||||
*join.pos = state_update.pos;
|
||||
*join.vel = state_update.vel;
|
||||
*join.ori = state_update.ori;
|
||||
*join.density = state_update.density;
|
||||
*join.energy = state_update.energy;
|
||||
|
||||
join.controller
|
||||
.queued_inputs
|
||||
.append(&mut state_update.queued_inputs);
|
||||
|
@ -92,32 +92,23 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
let stat = stats;
|
||||
|
||||
let update_max_hp = {
|
||||
stat.max_health_modifiers.update_maximum()
|
||||
|| (health.base_max() - health.maximum()).abs() > Health::HEALTH_EPSILON
|
||||
};
|
||||
|
||||
if update_max_hp {
|
||||
health.update_maximum(stat.max_health_modifiers);
|
||||
if let Some(new_max) = health.needs_maximum_update(stat.max_health_modifiers) {
|
||||
// Only call this if we need to since mutable access will trigger sending an
|
||||
// update to the client.
|
||||
health.update_internal_integer_maximum(new_max);
|
||||
}
|
||||
|
||||
let (change_energy, energy_mods) = {
|
||||
// Calculates energy scaling from stats and inventory
|
||||
let energy_mods = StatsModifier {
|
||||
add_mod: stat.max_energy_modifiers.add_mod
|
||||
+ combat::compute_max_energy_mod(inventory),
|
||||
mult_mod: stat.max_energy_modifiers.mult_mod,
|
||||
};
|
||||
(
|
||||
energy_mods.update_maximum()
|
||||
|| (energy.base_max() - energy.maximum()).abs() > Energy::ENERGY_EPSILON,
|
||||
energy_mods,
|
||||
)
|
||||
// Calculates energy scaling from stats and inventory
|
||||
let energy_mods = StatsModifier {
|
||||
add_mod: stat.max_energy_modifiers.add_mod
|
||||
+ combat::compute_max_energy_mod(inventory),
|
||||
mult_mod: stat.max_energy_modifiers.mult_mod,
|
||||
};
|
||||
|
||||
// If modifier sufficiently different, mutably access energy
|
||||
if change_energy {
|
||||
energy.update_maximum(energy_mods);
|
||||
if let Some(new_max) = energy.needs_maximum_update(energy_mods) {
|
||||
// Only call this if we need to since mutable access will trigger sending an
|
||||
// update to the client.
|
||||
energy.update_internal_integer_maximum(new_max);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ use crate::{
|
||||
presence::{Presence, RegionSubscription, RepositionOnChunkLoad},
|
||||
rtsim::RtSim,
|
||||
state_ext::StateExt,
|
||||
sys::sentinel::{DeletedEntities, TrackedComps},
|
||||
sys::sentinel::{DeletedEntities, TrackedStorages},
|
||||
};
|
||||
#[cfg(not(feature = "worldgen"))]
|
||||
use common::grid::Grid;
|
||||
@ -447,7 +447,7 @@ impl Server {
|
||||
state.ecs_mut().write_resource::<TimeOfDay>().0 = settings.start_time;
|
||||
|
||||
// Register trackers
|
||||
sys::sentinel::register_trackers(state.ecs_mut());
|
||||
sys::sentinel::UpdateTrackers::register(state.ecs_mut());
|
||||
|
||||
state.ecs_mut().insert(DeletedEntities::default());
|
||||
|
||||
@ -1025,7 +1025,7 @@ impl Server {
|
||||
)
|
||||
.send(ServerInit::GameSync {
|
||||
// Send client their entity
|
||||
entity_package: TrackedComps::fetch(self.state.ecs())
|
||||
entity_package: TrackedStorages::fetch(self.state.ecs())
|
||||
.create_entity_package(entity, None, None, None)
|
||||
.expect(
|
||||
"We just created this entity as marked() (using create_entity_synced) so \
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::sentinel::{DeletedEntities, ReadTrackers, TrackedComps};
|
||||
use super::sentinel::{DeletedEntities, TrackedStorages, UpdateTrackers};
|
||||
use crate::{
|
||||
client::Client,
|
||||
presence::{Presence, RegionSubscription},
|
||||
@ -6,9 +6,7 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
calendar::Calendar,
|
||||
comp::{Collider, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel},
|
||||
link::Is,
|
||||
mounting::Rider,
|
||||
comp::{Collider, ForceUpdate, InventoryUpdate, Last, Ori, Pos, Vel},
|
||||
outcome::Outcome,
|
||||
region::{Event as RegionEvent, RegionMap},
|
||||
resources::{PlayerPhysicsSettings, TimeOfDay},
|
||||
@ -31,30 +29,25 @@ impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
Read<'a, Tick>,
|
||||
Read<'a, PlayerPhysicsSettings>,
|
||||
TrackedStorages<'a>,
|
||||
ReadExpect<'a, TimeOfDay>,
|
||||
ReadExpect<'a, Calendar>,
|
||||
ReadExpect<'a, RegionMap>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadExpect<'a, UpdateTrackers>,
|
||||
ReadStorage<'a, Pos>,
|
||||
ReadStorage<'a, Vel>,
|
||||
ReadStorage<'a, Ori>,
|
||||
ReadStorage<'a, Inventory>,
|
||||
ReadStorage<'a, RegionSubscription>,
|
||||
ReadStorage<'a, Presence>,
|
||||
ReadStorage<'a, Collider>,
|
||||
ReadStorage<'a, Client>,
|
||||
WriteStorage<'a, Last<Pos>>,
|
||||
WriteStorage<'a, Last<Vel>>,
|
||||
WriteStorage<'a, Last<Ori>>,
|
||||
ReadStorage<'a, Client>,
|
||||
WriteStorage<'a, ForceUpdate>,
|
||||
WriteStorage<'a, InventoryUpdate>,
|
||||
Write<'a, DeletedEntities>,
|
||||
Write<'a, Vec<Outcome>>,
|
||||
Read<'a, PlayerPhysicsSettings>,
|
||||
ReadStorage<'a, Is<Rider>>,
|
||||
ReadStorage<'a, Player>,
|
||||
TrackedComps<'a>,
|
||||
ReadTrackers<'a>,
|
||||
);
|
||||
|
||||
const NAME: &'static str = "entity_sync";
|
||||
@ -66,33 +59,37 @@ impl<'a> System<'a> for Sys {
|
||||
(
|
||||
entities,
|
||||
tick,
|
||||
player_physics_settings,
|
||||
tracked_storages,
|
||||
time_of_day,
|
||||
calendar,
|
||||
region_map,
|
||||
uids,
|
||||
trackers,
|
||||
positions,
|
||||
velocities,
|
||||
orientations,
|
||||
inventories,
|
||||
subscriptions,
|
||||
presences,
|
||||
colliders,
|
||||
clients,
|
||||
mut last_pos,
|
||||
mut last_vel,
|
||||
mut last_ori,
|
||||
clients,
|
||||
mut force_updates,
|
||||
mut inventory_updates,
|
||||
mut deleted_entities,
|
||||
mut outcomes,
|
||||
player_physics_settings,
|
||||
is_rider,
|
||||
players,
|
||||
tracked_comps,
|
||||
trackers,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
let tick = tick.0;
|
||||
|
||||
// Storages already provided in `TrackedStorages` that we need to use
|
||||
// for other things besides change detection.
|
||||
let uids = &tracked_storages.uid;
|
||||
let colliders = &tracked_storages.collider;
|
||||
let inventories = &tracked_storages.inventory;
|
||||
let players = &tracked_storages.player;
|
||||
let is_rider = &tracked_storages.is_rider;
|
||||
|
||||
// To send entity updates
|
||||
// 1. Iterate through regions
|
||||
// 2. Iterate through region subscribers (ie clients)
|
||||
@ -161,7 +158,7 @@ impl<'a> System<'a> for Sys {
|
||||
.get(entity)
|
||||
.map(|pos| (pos, velocities.get(entity), orientations.get(entity)))
|
||||
.and_then(|(pos, vel, ori)| {
|
||||
tracked_comps.create_entity_package(
|
||||
tracked_storages.create_entity_package(
|
||||
entity,
|
||||
Some(*pos),
|
||||
vel.copied(),
|
||||
@ -203,7 +200,7 @@ impl<'a> System<'a> for Sys {
|
||||
// Sync tracked components
|
||||
// Get deleted entities in this region from DeletedEntities
|
||||
let (entity_sync_package, comp_sync_package) = trackers.create_sync_packages(
|
||||
&tracked_comps,
|
||||
&tracked_storages,
|
||||
region.entities(),
|
||||
deleted_entities_in_region,
|
||||
);
|
||||
@ -232,7 +229,7 @@ impl<'a> System<'a> for Sys {
|
||||
for (_, entity, &uid, (&pos, last_pos), vel, ori, force_update, collider) in (
|
||||
region.entities(),
|
||||
&entities,
|
||||
&uids,
|
||||
uids,
|
||||
(&positions, last_pos.mask().maybe()),
|
||||
(&velocities, last_vel.mask().maybe()).maybe(),
|
||||
(&orientations, last_vel.mask().maybe()).maybe(),
|
||||
@ -353,13 +350,22 @@ impl<'a> System<'a> for Sys {
|
||||
// TODO: Sync clients that don't have a position?
|
||||
|
||||
// Sync inventories
|
||||
for (inventory, update, client) in (&inventories, &inventory_updates, &clients).join() {
|
||||
for (inventory, update, client) in (inventories, &inventory_updates, &clients).join() {
|
||||
client.send_fallible(ServerGeneral::InventoryUpdate(
|
||||
inventory.clone(),
|
||||
update.event(),
|
||||
));
|
||||
}
|
||||
|
||||
// Sync components that are only synced for the client's own entity.
|
||||
for (entity, client) in (&entities, &clients).join() {
|
||||
let comp_sync_package =
|
||||
trackers.create_sync_from_client_package(&tracked_storages, entity);
|
||||
if !comp_sync_package.is_empty() {
|
||||
client.send_fallible(ServerGeneral::CompSync(comp_sync_package));
|
||||
}
|
||||
}
|
||||
|
||||
// Sync outcomes
|
||||
for (presence, pos, client) in (presences.maybe(), positions.maybe(), &clients).join() {
|
||||
let is_near = |o_pos: Vec3<f32>| {
|
||||
|
@ -2,18 +2,14 @@
|
||||
use common::{
|
||||
comp::{
|
||||
item::{tool::AbilityMap, MaterialStatManifest},
|
||||
ActiveAbilities, Alignment, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState,
|
||||
Collider, Combo, Density, Energy, Group, Health, Inventory, Item, LightEmitter, Mass, Ori,
|
||||
Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel,
|
||||
Ori, Pos, Vel,
|
||||
},
|
||||
link::Is,
|
||||
mounting::{Mount, Rider},
|
||||
uid::Uid,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use common_net::{
|
||||
msg::EcsCompPacket,
|
||||
sync::{CompSyncPackage, EntityPackage, EntitySyncPackage, UpdateTracker, WorldSyncExt},
|
||||
sync::{CompSyncPackage, EntityPackage, EntitySyncPackage, NetSync, SyncFrom, UpdateTracker},
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use specs::{
|
||||
@ -28,400 +24,217 @@ use vek::*;
|
||||
#[derive(Default)]
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (TrackedComps<'a>, WriteTrackers<'a>);
|
||||
type SystemData = (TrackedStorages<'a>, WriteExpect<'a, UpdateTrackers>);
|
||||
|
||||
const NAME: &'static str = "sentinel";
|
||||
const ORIGIN: Origin = Origin::Server;
|
||||
const PHASE: Phase = Phase::Create;
|
||||
|
||||
fn run(_job: &mut Job<Self>, (comps, mut trackers): Self::SystemData) {
|
||||
record_changes(&comps, &mut trackers);
|
||||
fn run(_job: &mut Job<Self>, (storages, mut trackers): Self::SystemData) {
|
||||
trackers.record_changes(&storages);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 skill_set: ReadStorage<'a, SkillSet>,
|
||||
pub active_abilities: ReadStorage<'a, ActiveAbilities>,
|
||||
pub buffs: ReadStorage<'a, Buffs>,
|
||||
pub auras: ReadStorage<'a, Auras>,
|
||||
pub energy: ReadStorage<'a, Energy>,
|
||||
pub combo: ReadStorage<'a, Combo>,
|
||||
pub health: ReadStorage<'a, Health>,
|
||||
pub poise: ReadStorage<'a, Poise>,
|
||||
pub can_build: ReadStorage<'a, CanBuild>,
|
||||
pub light_emitter: ReadStorage<'a, LightEmitter>,
|
||||
pub item: ReadStorage<'a, Item>,
|
||||
pub scale: ReadStorage<'a, Scale>,
|
||||
pub is_mount: ReadStorage<'a, Is<Mount>>,
|
||||
pub is_rider: ReadStorage<'a, Is<Rider>>,
|
||||
pub group: ReadStorage<'a, Group>,
|
||||
pub mass: ReadStorage<'a, Mass>,
|
||||
pub density: ReadStorage<'a, Density>,
|
||||
pub collider: ReadStorage<'a, Collider>,
|
||||
pub sticky: ReadStorage<'a, Sticky>,
|
||||
pub inventory: ReadStorage<'a, Inventory>,
|
||||
pub character_state: ReadStorage<'a, CharacterState>,
|
||||
pub shockwave: ReadStorage<'a, Shockwave>,
|
||||
pub beam_segment: ReadStorage<'a, BeamSegment>,
|
||||
pub alignment: ReadStorage<'a, Alignment>,
|
||||
/// Holds state like modified bitsets, modification event readers
|
||||
macro_rules! trackers {
|
||||
// Every place where we have `$( /* ... */ )*` will be repeated for each synced component.
|
||||
($($component_name:ident: $component_type:ident,)*) => {
|
||||
#[derive(SystemData)]
|
||||
pub struct TrackedStorages<'a> {
|
||||
// Uids are tracked to detect created entities that should be synced over the network.
|
||||
// Additionally we need access to the uids when generating packets to send to the clients.
|
||||
pub uid: ReadStorage<'a, Uid>,
|
||||
$(pub $component_name: ReadStorage<'a, $component_type>,)*
|
||||
// TODO: these may be used to duplicate items when we attempt to remove
|
||||
// cloning them.
|
||||
pub _ability_map: ReadExpect<'a, AbilityMap>,
|
||||
pub _msm: ReadExpect<'a, MaterialStatManifest>,
|
||||
}
|
||||
|
||||
pub ability_map: ReadExpect<'a, AbilityMap>,
|
||||
pub msm: ReadExpect<'a, MaterialStatManifest>,
|
||||
}
|
||||
impl<'a> TrackedComps<'a> {
|
||||
pub fn create_entity_package(
|
||||
&self,
|
||||
entity: EcsEntity,
|
||||
pos: Option<Pos>,
|
||||
vel: Option<Vel>,
|
||||
ori: Option<Ori>,
|
||||
) -> Option<EntityPackage<EcsCompPacket>> {
|
||||
let uid = self.uid.get(entity).copied()?.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.skill_set
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.active_abilities
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.buffs
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.auras
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.energy
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.combo
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.health
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.poise
|
||||
.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)
|
||||
.map(|item| item.duplicate(&self.ability_map, &self.msm))
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.scale
|
||||
.get(entity)
|
||||
.copied()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.is_mount
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.is_rider
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.group
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.mass.get(entity).copied().map(|c| comps.push(c.into()));
|
||||
self.density
|
||||
.get(entity)
|
||||
.copied()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.collider
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.sticky
|
||||
.get(entity)
|
||||
.copied()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.inventory
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.character_state
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.shockwave
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.beam_segment
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.alignment
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
// Add untracked comps
|
||||
pos.map(|c| comps.push(c.into()));
|
||||
vel.map(|c| comps.push(c.into()));
|
||||
ori.map(|c| comps.push(c.into()));
|
||||
impl TrackedStorages<'_> {
|
||||
/// Create a package containing all the synced components for this entity. This is
|
||||
/// used to initialized the entity's representation on the client (e.g. used when a new
|
||||
/// entity is within the area synced to the client).
|
||||
///
|
||||
/// Note: This is only for components that are synced to the client for all entities.
|
||||
pub fn create_entity_package(
|
||||
&self,
|
||||
entity: EcsEntity,
|
||||
pos: Option<Pos>,
|
||||
vel: Option<Vel>,
|
||||
ori: Option<Ori>,
|
||||
) -> Option<EntityPackage<EcsCompPacket>> {
|
||||
let uid = self.uid.get(entity).copied()?.0;
|
||||
let mut comps = Vec::new();
|
||||
// NOTE: we could potentially include a bitmap indicating which components are present instead of tagging
|
||||
// components with the type in order to save bandwidth
|
||||
//
|
||||
// if the number of optional components sent is less than 1/8 of the number of component types then
|
||||
// then the suggested approach would no longer be favorable
|
||||
$(
|
||||
// Only add components that are synced from any entity.
|
||||
if matches!(
|
||||
<$component_type as NetSync>::SYNC_FROM,
|
||||
SyncFrom::AnyEntity,
|
||||
) {
|
||||
self
|
||||
.$component_name
|
||||
.get(entity)
|
||||
// TODO: should duplicate rather than clone the item
|
||||
//
|
||||
// NetClone trait?
|
||||
//
|
||||
//.map(|item| item.duplicate(&self.ability_map, &self.msm))
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
}
|
||||
)*
|
||||
// Add untracked comps
|
||||
pos.map(|c| comps.push(c.into()));
|
||||
vel.map(|c| comps.push(c.into()));
|
||||
ori.map(|c| comps.push(c.into()));
|
||||
|
||||
Some(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 skill_set: ReadExpect<'a, UpdateTracker<SkillSet>>,
|
||||
pub active_abilities: ReadExpect<'a, UpdateTracker<ActiveAbilities>>,
|
||||
pub buffs: ReadExpect<'a, UpdateTracker<Buffs>>,
|
||||
pub auras: ReadExpect<'a, UpdateTracker<Auras>>,
|
||||
pub energy: ReadExpect<'a, UpdateTracker<Energy>>,
|
||||
pub combo: ReadExpect<'a, UpdateTracker<Combo>>,
|
||||
pub health: ReadExpect<'a, UpdateTracker<Health>>,
|
||||
pub poise: ReadExpect<'a, UpdateTracker<Poise>>,
|
||||
pub can_build: ReadExpect<'a, UpdateTracker<CanBuild>>,
|
||||
pub light_emitter: ReadExpect<'a, UpdateTracker<LightEmitter>>,
|
||||
pub inventory: ReadExpect<'a, UpdateTracker<Inventory>>,
|
||||
pub item: ReadExpect<'a, UpdateTracker<Item>>,
|
||||
pub scale: ReadExpect<'a, UpdateTracker<Scale>>,
|
||||
pub is_mount: ReadExpect<'a, UpdateTracker<Is<Mount>>>,
|
||||
pub is_rider: ReadExpect<'a, UpdateTracker<Is<Rider>>>,
|
||||
pub group: ReadExpect<'a, UpdateTracker<Group>>,
|
||||
pub mass: ReadExpect<'a, UpdateTracker<Mass>>,
|
||||
pub density: ReadExpect<'a, UpdateTracker<Density>>,
|
||||
pub collider: ReadExpect<'a, UpdateTracker<Collider>>,
|
||||
pub sticky: ReadExpect<'a, UpdateTracker<Sticky>>,
|
||||
pub character_state: ReadExpect<'a, UpdateTracker<CharacterState>>,
|
||||
pub shockwave: ReadExpect<'a, UpdateTracker<Shockwave>>,
|
||||
pub beam_segment: ReadExpect<'a, UpdateTracker<BeamSegment>>,
|
||||
pub alignment: ReadExpect<'a, UpdateTracker<Alignment>>,
|
||||
}
|
||||
impl<'a> ReadTrackers<'a> {
|
||||
pub fn create_sync_packages(
|
||||
&self,
|
||||
comps: &TrackedComps,
|
||||
filter: impl Join + Copy,
|
||||
deleted_entities: Vec<u64>,
|
||||
) -> (EntitySyncPackage, CompSyncPackage<EcsCompPacket>) {
|
||||
let entity_sync_package =
|
||||
EntitySyncPackage::new(&comps.uid, &self.uid, filter, deleted_entities);
|
||||
let comp_sync_package = CompSyncPackage::new()
|
||||
.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.skill_set, &comps.skill_set, filter)
|
||||
.with_component(
|
||||
&comps.uid,
|
||||
&*self.active_abilities,
|
||||
&comps.active_abilities,
|
||||
filter,
|
||||
)
|
||||
.with_component(&comps.uid, &*self.buffs, &comps.buffs, filter)
|
||||
.with_component(&comps.uid, &*self.auras, &comps.auras, filter)
|
||||
.with_component(&comps.uid, &*self.energy, &comps.energy, filter)
|
||||
.with_component(&comps.uid, &*self.combo, &comps.combo, filter)
|
||||
.with_component(&comps.uid, &*self.health, &comps.health, filter)
|
||||
.with_component(&comps.uid, &*self.poise, &comps.poise, 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.is_mount, &comps.is_mount, filter)
|
||||
.with_component(&comps.uid, &*self.is_rider, &comps.is_rider, filter)
|
||||
.with_component(&comps.uid, &*self.group, &comps.group, filter)
|
||||
.with_component(&comps.uid, &*self.mass, &comps.mass, filter)
|
||||
.with_component(&comps.uid, &*self.density, &comps.density, filter)
|
||||
.with_component(&comps.uid, &*self.collider, &comps.collider, filter)
|
||||
.with_component(&comps.uid, &*self.sticky, &comps.sticky, filter)
|
||||
.with_component(&comps.uid, &*self.inventory, &comps.inventory, filter)
|
||||
.with_component(
|
||||
&comps.uid,
|
||||
&*self.character_state,
|
||||
&comps.character_state,
|
||||
filter,
|
||||
)
|
||||
.with_component(&comps.uid, &*self.shockwave, &comps.shockwave, filter)
|
||||
.with_component(&comps.uid, &*self.beam_segment, &comps.beam_segment, filter)
|
||||
.with_component(&comps.uid, &*self.alignment, &comps.alignment, filter);
|
||||
Some(EntityPackage { uid, comps })
|
||||
}
|
||||
}
|
||||
|
||||
(entity_sync_package, comp_sync_package)
|
||||
/// Contains an [`UpdateTracker`] for every synced component (that uses this method of
|
||||
/// change detection).
|
||||
///
|
||||
/// This should be inserted into the ecs as a Resource
|
||||
pub struct UpdateTrackers {
|
||||
pub uid: UpdateTracker<Uid>,
|
||||
$($component_name: UpdateTracker<$component_type>,)*
|
||||
}
|
||||
|
||||
impl UpdateTrackers {
|
||||
/// Constructs the update trackers and inserts it into the world as a resource.
|
||||
///
|
||||
/// Components that will be synced must already be registered.
|
||||
pub fn register(world: &mut specs::World) {
|
||||
let trackers = UpdateTrackers {
|
||||
uid: UpdateTracker::<Uid>::new(&world),
|
||||
$($component_name: UpdateTracker::<$component_type>::new(&world),)*
|
||||
};
|
||||
|
||||
world.insert(trackers);
|
||||
// TODO: if we held copies of components for doing diffing, the components that hold that data could be registered here
|
||||
}
|
||||
|
||||
/// Records updates to components that are provided from the tracked storages as a series of events into bitsets
|
||||
/// that can later be joined on.
|
||||
fn record_changes(&mut self, comps: &TrackedStorages) {
|
||||
self.uid.record_changes(&comps.uid);
|
||||
$(
|
||||
self.$component_name.record_changes(&comps.$component_name);
|
||||
)*
|
||||
|
||||
// Enable for logging of counts of component update events.
|
||||
const LOG_COUNTS: bool = false;
|
||||
// Plotting counts via tracy. Env var provided to toggle on so there's no need to
|
||||
// recompile if you are already have a tracy build.
|
||||
let plot_counts = common_base::TRACY_ENABLED && matches!(std::env::var("PLOT_UPDATE_COUNTS").as_deref(), Ok("1"));
|
||||
|
||||
macro_rules! log_counts {
|
||||
($comp:ident, $name:expr) => {
|
||||
if LOG_COUNTS || plot_counts {
|
||||
let tracker = &self.$comp;
|
||||
let inserted = tracker.inserted().into_iter().count();
|
||||
let modified = tracker.modified().into_iter().count();
|
||||
let removed = tracker.removed().into_iter().count();
|
||||
|
||||
if plot_counts {
|
||||
let sum = inserted + modified + removed;
|
||||
common_base::plot!(concat!($name, "updates"), sum as f64);
|
||||
}
|
||||
|
||||
if LOG_COUNTS {
|
||||
tracing::warn!("{:6} insertions detected for {}", inserted, $name);
|
||||
tracing::warn!("{:6} modifications detected for {}", modified, $name);
|
||||
tracing::warn!("{:6} deletions detected for {}", removed, $name);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
$(log_counts!($component_name, concat!(stringify!($component_name), 's'));)*
|
||||
}
|
||||
|
||||
/// Create a [`EntitySyncPackage`] and a [`CompSyncPackage`] to provide updates
|
||||
/// for the set entities specified by the provided filter (e.g. for a region).
|
||||
///
|
||||
/// A deleted entities must be externally constructed and provided here.
|
||||
pub fn create_sync_packages(
|
||||
&self,
|
||||
comps: &TrackedStorages,
|
||||
filter: impl Join + Copy,
|
||||
deleted_entities: Vec<u64>,
|
||||
) -> (EntitySyncPackage, CompSyncPackage<EcsCompPacket>) {
|
||||
let entity_sync_package =
|
||||
EntitySyncPackage::new(&comps.uid, &self.uid, filter, deleted_entities);
|
||||
let mut comp_sync_package = CompSyncPackage::new();
|
||||
|
||||
$(
|
||||
if matches!(
|
||||
<$component_type as NetSync>::SYNC_FROM,
|
||||
SyncFrom::AnyEntity,
|
||||
) {
|
||||
comp_sync_package.add_component_updates(
|
||||
&comps.uid,
|
||||
&self.$component_name,
|
||||
&comps.$component_name,
|
||||
filter,
|
||||
);
|
||||
}
|
||||
)*
|
||||
|
||||
(entity_sync_package, comp_sync_package)
|
||||
}
|
||||
|
||||
|
||||
/// Create sync package for components that are only synced for the client's entity.
|
||||
pub fn create_sync_from_client_package(
|
||||
&self,
|
||||
comps: &TrackedStorages,
|
||||
entity: specs::Entity,
|
||||
) -> CompSyncPackage<EcsCompPacket> {
|
||||
// TODO: this type repeats the entity uid for each component but
|
||||
// we know they will all be the same here, using it for now for
|
||||
// convenience but it could help to make a specific type for this
|
||||
// later.
|
||||
let mut comp_sync_package = CompSyncPackage::new();
|
||||
|
||||
let uid = match comps.uid.get(entity) {
|
||||
Some(uid) => (*uid).into(),
|
||||
// Return empty package if we can't get uid for this entity
|
||||
None => return comp_sync_package,
|
||||
};
|
||||
|
||||
$(
|
||||
if matches!(
|
||||
<$component_type as NetSync>::SYNC_FROM,
|
||||
SyncFrom::ClientEntity,
|
||||
) {
|
||||
comp_sync_package.add_component_update(
|
||||
&self.$component_name,
|
||||
&comps.$component_name,
|
||||
uid,
|
||||
entity,
|
||||
);
|
||||
}
|
||||
)*
|
||||
|
||||
comp_sync_package
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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>>,
|
||||
skill_set: WriteExpect<'a, UpdateTracker<SkillSet>>,
|
||||
active_abilities: WriteExpect<'a, UpdateTracker<ActiveAbilities>>,
|
||||
buffs: WriteExpect<'a, UpdateTracker<Buffs>>,
|
||||
auras: WriteExpect<'a, UpdateTracker<Auras>>,
|
||||
energy: WriteExpect<'a, UpdateTracker<Energy>>,
|
||||
combo: WriteExpect<'a, UpdateTracker<Combo>>,
|
||||
health: WriteExpect<'a, UpdateTracker<Health>>,
|
||||
poise: WriteExpect<'a, UpdateTracker<Poise>>,
|
||||
can_build: WriteExpect<'a, UpdateTracker<CanBuild>>,
|
||||
light_emitter: WriteExpect<'a, UpdateTracker<LightEmitter>>,
|
||||
item: WriteExpect<'a, UpdateTracker<Item>>,
|
||||
scale: WriteExpect<'a, UpdateTracker<Scale>>,
|
||||
is_mounts: WriteExpect<'a, UpdateTracker<Is<Mount>>>,
|
||||
is_riders: WriteExpect<'a, UpdateTracker<Is<Rider>>>,
|
||||
group: WriteExpect<'a, UpdateTracker<Group>>,
|
||||
mass: WriteExpect<'a, UpdateTracker<Mass>>,
|
||||
density: WriteExpect<'a, UpdateTracker<Density>>,
|
||||
collider: WriteExpect<'a, UpdateTracker<Collider>>,
|
||||
sticky: WriteExpect<'a, UpdateTracker<Sticky>>,
|
||||
inventory: WriteExpect<'a, UpdateTracker<Inventory>>,
|
||||
character_state: WriteExpect<'a, UpdateTracker<CharacterState>>,
|
||||
shockwave: WriteExpect<'a, UpdateTracker<Shockwave>>,
|
||||
beam: WriteExpect<'a, UpdateTracker<BeamSegment>>,
|
||||
alignment: WriteExpect<'a, UpdateTracker<Alignment>>,
|
||||
}
|
||||
|
||||
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.skill_set.record_changes(&comps.skill_set);
|
||||
trackers
|
||||
.active_abilities
|
||||
.record_changes(&comps.active_abilities);
|
||||
trackers.buffs.record_changes(&comps.buffs);
|
||||
trackers.auras.record_changes(&comps.auras);
|
||||
trackers.energy.record_changes(&comps.energy);
|
||||
trackers.combo.record_changes(&comps.combo);
|
||||
trackers.health.record_changes(&comps.health);
|
||||
trackers.poise.record_changes(&comps.poise);
|
||||
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.is_mounts.record_changes(&comps.is_mount);
|
||||
trackers.is_riders.record_changes(&comps.is_rider);
|
||||
trackers.group.record_changes(&comps.group);
|
||||
trackers.mass.record_changes(&comps.mass);
|
||||
trackers.density.record_changes(&comps.density);
|
||||
trackers.collider.record_changes(&comps.collider);
|
||||
trackers.sticky.record_changes(&comps.sticky);
|
||||
trackers.inventory.record_changes(&comps.inventory);
|
||||
trackers
|
||||
.character_state
|
||||
.record_changes(&comps.character_state);
|
||||
trackers.shockwave.record_changes(&comps.shockwave);
|
||||
trackers.beam.record_changes(&comps.beam_segment);
|
||||
trackers.alignment.record_changes(&comps.alignment);
|
||||
// Debug how many updates are being sent
|
||||
/*
|
||||
macro_rules! log_counts {
|
||||
($comp:ident, $name:expr) => {
|
||||
// Note: if this will be used in actual server it would be more efficient to
|
||||
// count during record_changes
|
||||
let tracker = &trackers.$comp;
|
||||
let inserted = tracker.inserted().into_iter().count();
|
||||
let modified = tracker.modified().into_iter().count();
|
||||
let removed = tracker.removed().into_iter().count();
|
||||
tracing::warn!("{:6} insertions detected for {}", inserted, $name);
|
||||
tracing::warn!("{:6} modifications detected for {}", modified, $name);
|
||||
tracing::warn!("{:6} deletions detected for {}", removed, $name);
|
||||
};
|
||||
};
|
||||
log_counts!(uid, "Uids");
|
||||
log_counts!(body, "Bodies");
|
||||
log_counts!(buffs, "Buffs");
|
||||
log_counts!(auras, "Auras");
|
||||
log_counts!(player, "Players");
|
||||
log_counts!(stats, "Stats");
|
||||
log_counts!(skill_set, "SkillSet");
|
||||
log_counts!(active_abilities, "ActiveAbilities");
|
||||
log_counts!(energy, "Energies");
|
||||
log_counts!(combo, "Combos");
|
||||
log_vounts!(health, "Healths");
|
||||
log_vounts!(poise, "Poises");
|
||||
log_counts!(light_emitter, "Light emitters");
|
||||
log_counts!(item, "Items");
|
||||
log_counts!(scale, "Scales");
|
||||
log_counts!(is_mounts, "mounts");
|
||||
log_counts!(is_riders, "riders");
|
||||
log_counts!(mass, "Masses");
|
||||
log_counts!(mass, "Densities");
|
||||
log_counts!(collider, "Colliders");
|
||||
log_counts!(sticky, "Stickies");
|
||||
log_counts!(loadout, "Loadouts");
|
||||
log_counts!(character_state, "Character States");
|
||||
log_counts!(shockwave, "Shockwaves");
|
||||
log_counts!(beam, "Beams");
|
||||
log_counts!(alignment, "Alignments");
|
||||
*/
|
||||
}
|
||||
|
||||
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::<SkillSet>();
|
||||
world.register_tracker::<ActiveAbilities>();
|
||||
world.register_tracker::<Buffs>();
|
||||
world.register_tracker::<Auras>();
|
||||
world.register_tracker::<Energy>();
|
||||
world.register_tracker::<Combo>();
|
||||
world.register_tracker::<Health>();
|
||||
world.register_tracker::<Poise>();
|
||||
world.register_tracker::<CanBuild>();
|
||||
world.register_tracker::<LightEmitter>();
|
||||
world.register_tracker::<Item>();
|
||||
world.register_tracker::<Scale>();
|
||||
world.register_tracker::<Is<Mount>>();
|
||||
world.register_tracker::<Is<Rider>>();
|
||||
world.register_tracker::<Group>();
|
||||
world.register_tracker::<Mass>();
|
||||
world.register_tracker::<Density>();
|
||||
world.register_tracker::<Collider>();
|
||||
world.register_tracker::<Sticky>();
|
||||
world.register_tracker::<Inventory>();
|
||||
world.register_tracker::<CharacterState>();
|
||||
world.register_tracker::<Shockwave>();
|
||||
world.register_tracker::<BeamSegment>();
|
||||
world.register_tracker::<Alignment>();
|
||||
}
|
||||
// Import all the component types so they will be available when expanding the
|
||||
// macro below.
|
||||
use common_net::synced_components::*;
|
||||
// Pass `trackers!` macro to this "x macro" which will invoke it with a list
|
||||
// of components. This will declare the types defined in the macro above.
|
||||
common_net::synced_components!(trackers);
|
||||
|
||||
/// Deleted entities grouped by region
|
||||
pub struct DeletedEntities {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::sentinel::{DeletedEntities, TrackedComps};
|
||||
use super::sentinel::{DeletedEntities, TrackedStorages};
|
||||
use crate::{
|
||||
client::Client,
|
||||
presence::{self, Presence, RegionSubscription},
|
||||
@ -34,7 +34,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Client>,
|
||||
WriteStorage<'a, RegionSubscription>,
|
||||
Read<'a, DeletedEntities>,
|
||||
TrackedComps<'a>,
|
||||
TrackedStorages<'a>,
|
||||
);
|
||||
|
||||
const NAME: &'static str = "subscription";
|
||||
@ -222,7 +222,7 @@ pub fn initialize_region_subscription(world: &World, entity: specs::Entity) {
|
||||
);
|
||||
|
||||
let region_map = world.read_resource::<RegionMap>();
|
||||
let tracked_comps = TrackedComps::fetch(world);
|
||||
let tracked_comps = TrackedStorages::fetch(world);
|
||||
for key in ®ions {
|
||||
if let Some(region) = region_map.get(*key) {
|
||||
(
|
||||
|
Loading…
Reference in New Issue
Block a user