Rename SyncFrom::AllEntities to SyncFrom::AnyEntity for more clarity, add more comments for component syncing code, address MR comment

This commit is contained in:
Imbris 2022-01-19 00:56:26 -05:00
parent 3beb3c8205
commit 13d970bf6f
10 changed files with 123 additions and 41 deletions

View File

@ -6,6 +6,8 @@ pub use userdata_dir::userdata_dir;
#[cfg(feature = "tracy")] pub use tracy_client; #[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"); pub const TRACY_ENABLED: bool = cfg!(feature = "tracy");
#[cfg(not(feature = "tracy"))] #[cfg(not(feature = "tracy"))]

View File

@ -3,18 +3,32 @@ use common::comp;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::marker::PhantomData; use std::marker::PhantomData;
/// 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 { macro_rules! comp_packet {
($($component_name:ident: $component_type:ident,)*) => { ($($component_name:ident: $component_type:ident,)*) => {
// `sum_type!` will automatically derive From<T> for EcsCompPacket
// for each variant EcsCompPacket::T(T).
sum_type::sum_type! { sum_type::sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsCompPacket { pub enum EcsCompPacket {
// Note: also use the component_type identifier
// to name the enum variant that contains the component.
$($component_type($component_type),)* $($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), Pos(comp::Pos),
Vel(comp::Vel), Vel(comp::Vel),
Ori(comp::Ori), Ori(comp::Ori),
} }
} }
// `sum_type!` will automatically derive From<PhantomData<T>> for EcsCompPhantom
// for each variant EcsCompPhantom::T(PhantomData<T>).
sum_type::sum_type! { sum_type::sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsCompPhantom { pub enum EcsCompPhantom {
@ -84,5 +98,9 @@ macro_rules! comp_packet {
} }
} }
// Import all the component types so they will be available when expanding the
// macro below.
use crate::synced_components::*; 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); crate::synced_components!(comp_packet);

View File

@ -1,5 +1,5 @@
//! Types of syncing: //! Types of syncing:
//! * synced from all entities //! * synced from any entity (within range)
//! * synced only from the client's entity //! * synced only from the client's entity
//! //!
//! Types of updating //! Types of updating
@ -25,6 +25,10 @@ where
//type UpdateFrom = Self; //type UpdateFrom = Self;
//type Update: From<Self::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; const SYNC_FROM: SyncFrom;
// sync::handle_modify(comp, entity, world) // sync::handle_modify(comp, entity, world)
@ -38,9 +42,9 @@ where
fn pre_modify(&mut self, world: &specs::World) { let _world = world; } fn pre_modify(&mut self, world: &specs::World) { let _world = world; }
} }
/// Whether a component is synced to the client for all entities or for just the /// Whether a component is synced to the client for any entity or for just the
/// client's own entity. /// client's own entity.
pub enum SyncFrom { pub enum SyncFrom {
AllEntities, AnyEntity,
ClientEntity, ClientEntity,
} }

View File

@ -184,6 +184,8 @@ impl<P: CompPacket> CompSyncPackage<P> {
tracker.get_updates_for(uids, storage, filter, &mut self.comp_updates); tracker.get_updates_for(uids, storage, filter, &mut self.comp_updates);
} }
/// 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>( pub fn add_component_update<'a, C: Component + Clone + Send + Sync>(
&mut self, &mut self,
tracker: &UpdateTracker<C>, tracker: &UpdateTracker<C>,

View File

@ -139,17 +139,17 @@ impl<C: Component + Clone + Send + Sync> UpdateTracker<C> {
C::Storage: specs::storage::Tracked, C::Storage: specs::storage::Tracked,
{ {
let id = entity.id(); let id = entity.id();
// Generate inserted update if one exists // Generate update if one exists.
// //
// Note: presence of the id in these bitsets should be mutually exclusive // Note: presence of the id in these bitsets should be mutually exclusive
if self.inserted.contains(id) { if self.modified.contains(id) {
storage
.get(entity)
.map(|comp| CompUpdateKind::Inserted(P::from(comp.clone())))
} else if self.modified.contains(id) {
storage storage
.get(entity) .get(entity)
.map(|comp| CompUpdateKind::Modified(P::from(comp.clone()))) .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) { } else if self.removed.contains(id) {
Some(CompUpdateKind::Removed(P::Phantom::from(PhantomData::<C>))) Some(CompUpdateKind::Removed(P::Phantom::from(PhantomData::<C>)))
} else { } else {

View File

@ -14,6 +14,8 @@
//! when using the x macro defined here which requires this. //! when using the x macro defined here which requires this.
/// This provides a lowercase name and the component type. /// This provides a lowercase name and the component type.
///
/// See [module](self) level docs for more details.
#[macro_export] #[macro_export]
macro_rules! synced_components { macro_rules! synced_components {
($macro:ident) => { ($macro:ident) => {
@ -70,13 +72,26 @@ macro_rules! reexport_comps {
use common::link::Is; use common::link::Is;
use common::mounting::{Mount, Rider}; 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 IsMount = Is<Mount>;
pub type IsRider = Is<Rider>; 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;)* $(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); synced_components!(reexport_comps);
// =============================== // ===============================
@ -85,28 +100,30 @@ synced_components!(reexport_comps);
use crate::sync::{NetSync, SyncFrom}; use crate::sync::{NetSync, SyncFrom};
// These are synced from any entity within range.
impl NetSync for Body { impl NetSync for Body {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Stats { impl NetSync for Stats {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Buffs { impl NetSync for Buffs {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Auras { impl NetSync for Auras {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Energy { impl NetSync for Energy {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Health { impl NetSync for Health {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
fn pre_insert(&mut self, world: &specs::World) { fn pre_insert(&mut self, world: &specs::World) {
use common::resources::Time; use common::resources::Time;
@ -128,78 +145,78 @@ impl NetSync for Health {
} }
impl NetSync for Poise { impl NetSync for Poise {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for LightEmitter { impl NetSync for LightEmitter {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Item { impl NetSync for Item {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Scale { impl NetSync for Scale {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Group { impl NetSync for Group {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for IsMount { impl NetSync for IsMount {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for IsRider { impl NetSync for IsRider {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Mass { impl NetSync for Mass {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Density { impl NetSync for Density {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Collider { impl NetSync for Collider {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Sticky { impl NetSync for Sticky {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for CharacterState { impl NetSync for CharacterState {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Shockwave { impl NetSync for Shockwave {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for BeamSegment { impl NetSync for BeamSegment {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Alignment { impl NetSync for Alignment {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Player { impl NetSync for Player {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Inventory { impl NetSync for Inventory {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for SkillSet { impl NetSync for SkillSet {
const SYNC_FROM: SyncFrom = SyncFrom::AllEntities; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
// SyncFrom::ClientEntity // These are synced only from the client's own entity.
impl NetSync for Combo { impl NetSync for Combo {
const SYNC_FROM: SyncFrom = SyncFrom::ClientEntity; const SYNC_FROM: SyncFrom = SyncFrom::ClientEntity;

View File

@ -238,7 +238,13 @@ impl Sys {
mut state_update: StateUpdate, mut state_update: StateUpdate,
output_events: &mut OutputEvents, 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 { if *join.char_state != state_update.character {
*join.char_state = state_update.character *join.char_state = state_update.character
} }
@ -249,9 +255,11 @@ impl Sys {
*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.pos = state_update.pos;
*join.vel = state_update.vel; *join.vel = state_update.vel;
*join.ori = state_update.ori; *join.ori = state_update.ori;
join.controller join.controller
.queued_inputs .queued_inputs
.append(&mut state_update.queued_inputs); .append(&mut state_update.queued_inputs);

View File

@ -93,6 +93,8 @@ impl<'a> System<'a> for Sys {
let stat = stats; let stat = stats;
if let Some(new_max) = health.needs_maximum_update(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); health.update_internal_integer_maximum(new_max);
} }
@ -104,6 +106,8 @@ impl<'a> System<'a> for Sys {
}; };
if let Some(new_max) = energy.needs_maximum_update(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); energy.update_internal_integer_maximum(new_max);
} }
} }

View File

@ -82,6 +82,8 @@ impl<'a> System<'a> for Sys {
) { ) {
let tick = tick.0; 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 uids = &tracked_storages.uid;
let colliders = &tracked_storages.collider; let colliders = &tracked_storages.collider;
let inventories = &tracked_storages.inventory; let inventories = &tracked_storages.inventory;
@ -355,7 +357,7 @@ impl<'a> System<'a> for Sys {
)); ));
} }
// Sync components that just sync to the client's entity // Sync components that are only synced for the client's own entity.
for (entity, client) in (&entities, &clients).join() { for (entity, client) in (&entities, &clients).join() {
let comp_sync_package = let comp_sync_package =
trackers.create_sync_from_client_package(&tracked_storages, entity); trackers.create_sync_from_client_package(&tracked_storages, entity);

View File

@ -37,16 +37,26 @@ impl<'a> System<'a> for Sys {
/// Holds state like modified bitsets, modification event readers /// Holds state like modified bitsets, modification event readers
macro_rules! trackers { macro_rules! trackers {
// Every place where we have `$( /* ... */ )*` will be repeated for each synced component.
($($component_name:ident: $component_type:ident,)*) => { ($($component_name:ident: $component_type:ident,)*) => {
#[derive(SystemData)] #[derive(SystemData)]
pub struct TrackedStorages<'a> { 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 uid: ReadStorage<'a, Uid>,
$(pub $component_name: ReadStorage<'a, $component_type>,)* $(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 _ability_map: ReadExpect<'a, AbilityMap>,
pub _msm: ReadExpect<'a, MaterialStatManifest>, pub _msm: ReadExpect<'a, MaterialStatManifest>,
} }
impl TrackedStorages<'_> { 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( pub fn create_entity_package(
&self, &self,
entity: EcsEntity, entity: EcsEntity,
@ -62,10 +72,10 @@ macro_rules! trackers {
// if the number of optional components sent is less than 1/8 of the number of component types then // 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 // then the suggested approach would no longer be favorable
$( $(
// Only add components that are synced from any entity.
if matches!( if matches!(
<$component_type as NetSync>::SYNC_FROM, <$component_type as NetSync>::SYNC_FROM,
SyncFrom::AllEntities, SyncFrom::AnyEntity,
) { ) {
self self
.$component_name .$component_name
@ -88,6 +98,9 @@ macro_rules! trackers {
} }
} }
/// Contains an [`UpdateTracker`] for every synced component (that uses this method of
/// change detection).
///
/// This should be inserted into the ecs as a Resource /// This should be inserted into the ecs as a Resource
pub struct UpdateTrackers { pub struct UpdateTrackers {
pub uid: UpdateTracker<Uid>, pub uid: UpdateTracker<Uid>,
@ -108,14 +121,18 @@ macro_rules! trackers {
// TODO: if we held copies of components for doing diffing, the components that hold that data could be registered here // TODO: if we held copies of components for doing diffing, the components that hold that data could be registered here
} }
/// Update trackers /// 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) { fn record_changes(&mut self, comps: &TrackedStorages) {
self.uid.record_changes(&comps.uid); self.uid.record_changes(&comps.uid);
$( $(
self.$component_name.record_changes(&comps.$component_name); self.$component_name.record_changes(&comps.$component_name);
)* )*
// Enable for logging of counts of component update events.
const LOG_COUNTS: bool = false; 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")); let plot_counts = common_base::TRACY_ENABLED && matches!(std::env::var("PLOT_UPDATE_COUNTS").as_deref(), Ok("1"));
macro_rules! log_counts { macro_rules! log_counts {
@ -142,6 +159,10 @@ macro_rules! trackers {
$(log_counts!($component_name, concat!(stringify!($component_name), 's'));)* $(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( pub fn create_sync_packages(
&self, &self,
comps: &TrackedStorages, comps: &TrackedStorages,
@ -155,7 +176,7 @@ macro_rules! trackers {
$( $(
if matches!( if matches!(
<$component_type as NetSync>::SYNC_FROM, <$component_type as NetSync>::SYNC_FROM,
SyncFrom::AllEntities, SyncFrom::AnyEntity,
) { ) {
comp_sync_package.add_component_updates( comp_sync_package.add_component_updates(
&comps.uid, &comps.uid,
@ -170,7 +191,7 @@ macro_rules! trackers {
} }
/// Create sync package for components that are only synced to the client entity /// Create sync package for components that are only synced for the client's entity.
pub fn create_sync_from_client_package( pub fn create_sync_from_client_package(
&self, &self,
comps: &TrackedStorages, comps: &TrackedStorages,
@ -208,7 +229,11 @@ macro_rules! trackers {
} }
} }
// Import all the component types so they will be available when expanding the
// macro below.
use common_net::synced_components::*; 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); common_net::synced_components!(trackers);
/// Deleted entities grouped by region /// Deleted entities grouped by region