mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'imbris/sync-toggle' into 'master'
Add field to Presence to control when an entity is synced to other clients See merge request veloren/veloren!3972
This commit is contained in:
commit
e8361d5abf
@ -55,6 +55,18 @@ impl PresenceKind {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls whether this entity is synced to other clients.
|
||||
///
|
||||
/// Note, if it ends up being useful this could be generalized to an
|
||||
/// independent component that is required for any entity to be synced
|
||||
/// (as an independent component it could use NullStorage).
|
||||
pub fn sync_me(&self) -> bool {
|
||||
match self {
|
||||
Self::Spectator | Self::LoadingCharacter(_) => false,
|
||||
Self::Character(_) | Self::Possessor => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::comp::{Pos, Vel};
|
||||
use crate::comp::{Pos, Presence, Vel};
|
||||
use common_base::span;
|
||||
use hashbrown::{hash_map::DefaultHashBuilder, HashSet};
|
||||
use indexmap::IndexMap;
|
||||
@ -69,7 +69,12 @@ const NEIGHBOR_OFFSETS: [Vec2<i32>; 8] = [
|
||||
#[derive(Default)]
|
||||
// TODO generic region size (16x16 for now)
|
||||
// TODO compare to sweep and prune approach
|
||||
/// A region system that tracks where entities are
|
||||
/// A region system that tracks where entities are.
|
||||
///
|
||||
/// Note, this structure is primarily intended for tracking which entities need
|
||||
/// to be synchronized to which clients (and as part of that what entities are
|
||||
/// already synchronized). If an entity is marked to not be synchronized to
|
||||
/// other clients it may not appear here.
|
||||
pub struct RegionMap {
|
||||
// Tree?
|
||||
// Sorted Vec? (binary search lookup)
|
||||
@ -92,7 +97,13 @@ impl RegionMap {
|
||||
|
||||
// TODO maintain within a system?
|
||||
// TODO special case large entities
|
||||
pub fn tick(&mut self, pos: ReadStorage<Pos>, vel: ReadStorage<Vel>, entities: Entities) {
|
||||
pub fn tick(
|
||||
&mut self,
|
||||
pos: ReadStorage<Pos>,
|
||||
vel: ReadStorage<Vel>,
|
||||
presence: ReadStorage<Presence>,
|
||||
entities: Entities,
|
||||
) {
|
||||
span!(_guard, "tick", "Region::tick");
|
||||
self.tick += 1;
|
||||
// Clear events within each region
|
||||
@ -101,9 +112,10 @@ impl RegionMap {
|
||||
});
|
||||
|
||||
// Add any untracked entities
|
||||
for (pos, id) in (&pos, &entities, !&self.tracked_entities)
|
||||
for (pos, id) in (&pos, &entities, presence.maybe(), !&self.tracked_entities)
|
||||
.join()
|
||||
.map(|(pos, e, _)| (pos, e.id()))
|
||||
.filter(|(_, _, presence, _)| presence.map_or(true, |p| p.kind.sync_me()))
|
||||
.map(|(pos, e, _, _)| (pos, e.id()))
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
// Add entity
|
||||
@ -123,15 +135,21 @@ impl RegionMap {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, (¤t_region, region_data))| {
|
||||
for (maybe_pos, _maybe_vel, id) in
|
||||
(pos.maybe(), vel.maybe(), ®ion_data.bitset).join()
|
||||
for (maybe_pos, _maybe_vel, maybe_presence, id) in (
|
||||
pos.maybe(),
|
||||
vel.maybe(),
|
||||
presence.maybe(),
|
||||
®ion_data.bitset,
|
||||
)
|
||||
.join()
|
||||
{
|
||||
let should_sync = maybe_presence.map_or(true, |p| p.kind.sync_me());
|
||||
match maybe_pos {
|
||||
// Switch regions for entities which need switching
|
||||
// TODO don't check every tick (use velocity) (and use id to stagger)
|
||||
// Starting parameters at v = 0 check every 100 ticks
|
||||
// tether_length^2 / vel^2 (with a max of every tick)
|
||||
Some(pos) => {
|
||||
Some(pos) if should_sync => {
|
||||
let pos = pos.0.map(|e| e as i32);
|
||||
let key = Self::pos_key(pos);
|
||||
// Consider switching
|
||||
@ -148,7 +166,7 @@ impl RegionMap {
|
||||
},
|
||||
// Remove any non-existant entities (or just ones that lost their position
|
||||
// component) TODO: distribute this between ticks
|
||||
None => {
|
||||
None | Some(_) => {
|
||||
// TODO: shouldn't there be a way to extract the bitset of entities with
|
||||
// positions directly from specs? Yes, with `.mask()` on the component
|
||||
// storage.
|
||||
@ -191,6 +209,13 @@ impl RegionMap {
|
||||
}
|
||||
}
|
||||
|
||||
/// Must be called immediately after succesfully deleting an entity from the
|
||||
/// ecs (i.e. when deleting the entity did not generate a WrongGeneration
|
||||
/// error).
|
||||
pub fn entity_deleted(&mut self, entity: specs::Entity) {
|
||||
self.tracked_entities.remove(entity.id());
|
||||
}
|
||||
|
||||
fn add_entity(&mut self, id: u32, pos: Vec3<i32>, from: Option<Vec2<i32>>) {
|
||||
let key = Self::pos_key(pos);
|
||||
if let Some(region) = self.regions.get_mut(&key) {
|
||||
@ -229,6 +254,8 @@ impl RegionMap {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !self.tracked_entities.contains(id) {
|
||||
return None;
|
||||
} else {
|
||||
// Check neighbors
|
||||
for o in &NEIGHBOR_OFFSETS {
|
||||
@ -251,6 +278,11 @@ impl RegionMap {
|
||||
None
|
||||
}
|
||||
|
||||
/// Checks if this entity is located in the `RegionMap`.
|
||||
pub fn in_region_map(&self, entity: specs::Entity) -> bool {
|
||||
self.tracked_entities.contains(entity.id())
|
||||
}
|
||||
|
||||
fn key_index(&self, key: Vec2<i32>) -> Option<usize> {
|
||||
self.regions.get_full(&key).map(|(i, _, _)| i)
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ use common::{
|
||||
link::Is,
|
||||
mounting::{Mount, Rider, VolumeRider, VolumeRiders},
|
||||
outcome::Outcome,
|
||||
region::RegionMap,
|
||||
resources::{
|
||||
DeltaTime, EntitiesDiedLastTick, GameMode, PlayerEntity, PlayerPhysicsSettings, Time,
|
||||
TimeOfDay, TimeScale,
|
||||
@ -294,7 +293,6 @@ impl State {
|
||||
// TODO: only register on the server
|
||||
ecs.insert(EventBus::<ServerEvent>::default());
|
||||
ecs.insert(comp::group::GroupManager::default());
|
||||
ecs.insert(RegionMap::new());
|
||||
ecs.insert(SysMetrics::default());
|
||||
ecs.insert(PhysicsMetrics::default());
|
||||
ecs.insert(Trades::default());
|
||||
@ -528,16 +526,6 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
// Run RegionMap tick to update entity region occupancy
|
||||
pub fn update_region_map(&self) {
|
||||
span!(_guard, "update_region_map", "State::update_region_map");
|
||||
self.ecs.write_resource::<RegionMap>().tick(
|
||||
self.ecs.read_storage::<comp::Pos>(),
|
||||
self.ecs.read_storage::<comp::Vel>(),
|
||||
self.ecs.entities(),
|
||||
);
|
||||
}
|
||||
|
||||
// Apply terrain changes
|
||||
pub fn apply_terrain_changes(&self, block_update: impl FnMut(&specs::World, Vec<BlockDiff>)) {
|
||||
self.apply_terrain_changes_internal(false, block_update);
|
||||
@ -547,10 +535,9 @@ impl State {
|
||||
/// [State::tick].
|
||||
///
|
||||
/// This only happens if [State::tick] is asked to update terrain itself
|
||||
/// (using `update_terrain_and_regions: true`). [State::tick] is called
|
||||
/// from within both the client and the server ticks, right after
|
||||
/// handling terrain messages; currently, client sets it to true and
|
||||
/// server to false.
|
||||
/// (using `update_terrain: true`). [State::tick] is called from within
|
||||
/// both the client and the server ticks, right after handling terrain
|
||||
/// messages; currently, client sets it to true and server to false.
|
||||
fn apply_terrain_changes_internal(
|
||||
&self,
|
||||
during_tick: bool,
|
||||
@ -628,7 +615,7 @@ impl State {
|
||||
&mut self,
|
||||
dt: Duration,
|
||||
add_systems: impl Fn(&mut DispatcherBuilder),
|
||||
update_terrain_and_regions: bool,
|
||||
update_terrain: bool,
|
||||
mut metrics: Option<&mut StateTickMetrics>,
|
||||
server_constants: &ServerConstants,
|
||||
block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
|
||||
@ -656,10 +643,6 @@ impl State {
|
||||
self.ecs.write_resource::<DeltaTime>().0 =
|
||||
(dt.as_secs_f32() * time_scale as f32).min(MAX_DELTA_TIME);
|
||||
|
||||
if update_terrain_and_regions {
|
||||
self.update_region_map();
|
||||
}
|
||||
|
||||
section_span!(guard, "create dispatcher");
|
||||
// Run systems to update the world.
|
||||
// Create and run a dispatcher for ecs systems.
|
||||
@ -679,7 +662,7 @@ impl State {
|
||||
self.ecs.maintain();
|
||||
drop(guard);
|
||||
|
||||
if update_terrain_and_regions {
|
||||
if update_terrain {
|
||||
self.apply_terrain_changes_internal(true, block_update);
|
||||
}
|
||||
|
||||
|
@ -127,15 +127,16 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity, skip_persisten
|
||||
error!("handle_exit_ingame called with entity that is missing expected components");
|
||||
}
|
||||
|
||||
let maybe_character = state
|
||||
let (maybe_character, sync_me) = state
|
||||
.read_storage::<Presence>()
|
||||
.get(entity)
|
||||
.and_then(|p| p.kind.character_id());
|
||||
.map(|p| (p.kind.character_id(), p.kind.sync_me()))
|
||||
.unzip();
|
||||
let maybe_rtsim = state.read_component_copied::<common::rtsim::RtSimEntity>(entity);
|
||||
state.mut_resource::<IdMaps>().remove_entity(
|
||||
Some(entity),
|
||||
None, // Uid re-mapped, we don't want to remove the mapping
|
||||
maybe_character,
|
||||
maybe_character.flatten(),
|
||||
maybe_rtsim,
|
||||
);
|
||||
|
||||
@ -143,7 +144,9 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity, skip_persisten
|
||||
// Uid to a new entity (and e.g. don't want it to be unmapped).
|
||||
//
|
||||
// Delete old entity
|
||||
if let Err(e) = crate::state_ext::delete_entity_common(state, entity, maybe_uid) {
|
||||
if let Err(e) =
|
||||
crate::state_ext::delete_entity_common(state, entity, maybe_uid, sync_me.unwrap_or(true))
|
||||
{
|
||||
error!(
|
||||
?e,
|
||||
?entity,
|
||||
|
@ -76,6 +76,7 @@ use common::{
|
||||
cmd::ServerChatCommand,
|
||||
comp,
|
||||
event::{EventBus, ServerEvent},
|
||||
region::RegionMap,
|
||||
resources::{BattleMode, GameMode, Time, TimeOfDay},
|
||||
rtsim::{RtSimEntity, RtSimVehicle},
|
||||
shared_server_config::ServerConstants,
|
||||
@ -377,6 +378,9 @@ impl Server {
|
||||
.ecs_mut()
|
||||
.insert(sys::PersistenceScheduler::every(Duration::from_secs(10)));
|
||||
|
||||
// Region map (spatial structure for entity synchronization)
|
||||
state.ecs_mut().insert(RegionMap::new());
|
||||
|
||||
// Server-only components
|
||||
state.ecs_mut().register::<RegionSubscription>();
|
||||
state.ecs_mut().register::<Client>();
|
||||
@ -754,7 +758,7 @@ impl Server {
|
||||
// events so that changes made by server events will be immediately
|
||||
// visible to client synchronization systems, minimizing the latency of
|
||||
// `ServerEvent` mediated effects
|
||||
self.state.update_region_map();
|
||||
self.update_region_map();
|
||||
// NOTE: apply_terrain_changes sends the *new* value since it is not being
|
||||
// synchronized during the tick.
|
||||
self.state.apply_terrain_changes(on_block_update);
|
||||
@ -1092,6 +1096,18 @@ impl Server {
|
||||
.map(|mut t| t.maintain());
|
||||
}
|
||||
|
||||
// Run RegionMap tick to update entity region occupancy
|
||||
fn update_region_map(&mut self) {
|
||||
prof_span!("Server::update_region_map");
|
||||
let ecs = self.state().ecs();
|
||||
ecs.write_resource::<RegionMap>().tick(
|
||||
ecs.read_storage::<comp::Pos>(),
|
||||
ecs.read_storage::<comp::Vel>(),
|
||||
ecs.read_storage::<comp::Presence>(),
|
||||
ecs.entities(),
|
||||
);
|
||||
}
|
||||
|
||||
fn initialize_client(&mut self, client: connection_handler::IncomingClient) -> Entity {
|
||||
let entity = self
|
||||
.state
|
||||
|
@ -1190,20 +1190,21 @@ impl StateExt for State {
|
||||
// NOTE: We expect that these 3 components are never removed from an entity (nor
|
||||
// mutated) (at least not without updating the relevant mappings)!
|
||||
let maybe_uid = self.read_component_copied::<Uid>(entity);
|
||||
let maybe_character = self
|
||||
let (maybe_character, sync_me) = self
|
||||
.read_storage::<Presence>()
|
||||
.get(entity)
|
||||
.and_then(|p| p.kind.character_id());
|
||||
.map(|p| (p.kind.character_id(), p.kind.sync_me()))
|
||||
.unzip();
|
||||
let maybe_rtsim = self.read_component_copied::<RtSimEntity>(entity);
|
||||
|
||||
self.mut_resource::<IdMaps>().remove_entity(
|
||||
Some(entity),
|
||||
maybe_uid,
|
||||
maybe_character,
|
||||
maybe_character.flatten(),
|
||||
maybe_rtsim,
|
||||
);
|
||||
|
||||
delete_entity_common(self, entity, maybe_uid)
|
||||
delete_entity_common(self, entity, maybe_uid, sync_me.unwrap_or(true))
|
||||
}
|
||||
|
||||
fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor> {
|
||||
@ -1241,6 +1242,7 @@ pub(crate) fn delete_entity_common(
|
||||
state: &mut State,
|
||||
entity: EcsEntity,
|
||||
maybe_uid: Option<Uid>,
|
||||
sync_me: bool,
|
||||
) -> Result<(), specs::error::WrongGeneration> {
|
||||
if maybe_uid.is_none() {
|
||||
// For now we expect all entities have a Uid component.
|
||||
@ -1248,8 +1250,24 @@ pub(crate) fn delete_entity_common(
|
||||
}
|
||||
let maybe_pos = state.read_component_copied::<comp::Pos>(entity);
|
||||
|
||||
let res = state.ecs_mut().delete_entity(entity);
|
||||
// TODO: workaround for https://github.com/amethyst/specs/pull/766
|
||||
let actual_gen = state.ecs().entities().entity(entity.id()).gen();
|
||||
let res = if actual_gen == entity.gen() {
|
||||
state.ecs_mut().delete_entity(entity)
|
||||
} else {
|
||||
Err(specs::error::WrongGeneration {
|
||||
action: "delete",
|
||||
actual_gen,
|
||||
entity,
|
||||
})
|
||||
};
|
||||
|
||||
if res.is_ok() {
|
||||
let region_map = state.mut_resource::<common::region::RegionMap>();
|
||||
let uid_pos_region_key = maybe_uid
|
||||
.zip(maybe_pos)
|
||||
.map(|(uid, pos)| (uid, pos, region_map.find_region(entity, pos.0)));
|
||||
region_map.entity_deleted(entity);
|
||||
// Note: Adding the `Uid` to the deleted list when exiting "in-game" relies on
|
||||
// the client not being able to immediately re-enter the game in the
|
||||
// same tick (since we could then mix up the ordering of things and
|
||||
@ -1257,16 +1275,14 @@ pub(crate) fn delete_entity_common(
|
||||
//
|
||||
// The client will ignore requests to delete its own entity that are triggered
|
||||
// by this.
|
||||
if let Some((uid, pos)) = maybe_uid.zip(maybe_pos) {
|
||||
let region_key = state
|
||||
.ecs()
|
||||
.read_resource::<common::region::RegionMap>()
|
||||
.find_region(entity, pos.0);
|
||||
if let Some((uid, pos, region_key)) = uid_pos_region_key {
|
||||
if let Some(region_key) = region_key {
|
||||
state
|
||||
.mut_resource::<DeletedEntities>()
|
||||
.record_deleted_entity(uid, region_key);
|
||||
} else {
|
||||
// If there is a position and sync_me is true, but the entity is not
|
||||
// in a region, something might be wrong.
|
||||
} else if sync_me {
|
||||
// Don't panic if the entity wasn't found in a region, maybe it was just created
|
||||
// and then deleted before the region manager had a chance to assign it a region
|
||||
warn!(
|
||||
|
@ -4,10 +4,13 @@ use common::{
|
||||
calendar::Calendar,
|
||||
comp::{Collider, ForceUpdate, InventoryUpdate, Last, Ori, Player, Pos, Presence, Vel},
|
||||
event::EventBus,
|
||||
link::Is,
|
||||
mounting::Rider,
|
||||
outcome::Outcome,
|
||||
region::{Event as RegionEvent, RegionMap},
|
||||
resources::{PlayerPhysicsSettings, Time, TimeOfDay, TimeScale},
|
||||
terrain::TerrainChunkSize,
|
||||
uid::Uid,
|
||||
vol::RectVolSize,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
@ -233,32 +236,26 @@ impl<'a> System<'a> for Sys {
|
||||
for (client, _, client_entity, client_pos) in &mut subscribers {
|
||||
let mut comp_sync_package = CompSyncPackage::new();
|
||||
|
||||
for (_, entity, &uid, (&pos, last_pos), vel, ori, force_update, collider) in (
|
||||
for (_, entity, &uid, (&pos, last_pos), vel, ori, collider) in (
|
||||
region.entities(),
|
||||
&entities,
|
||||
uids,
|
||||
(&positions, last_pos.mask().maybe()),
|
||||
(&velocities, last_vel.mask().maybe()).maybe(),
|
||||
(&orientations, last_vel.mask().maybe()).maybe(),
|
||||
force_updates.maybe(),
|
||||
colliders.maybe(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
// Decide how regularly to send physics updates.
|
||||
let send_now = if client_entity == &entity {
|
||||
let player_physics_setting = players
|
||||
.get(entity)
|
||||
.and_then(|p| {
|
||||
player_physics_settings.settings.get(&p.uuid()).copied()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
// Don't send client physics updates about itself unless force update is
|
||||
// set or the client is subject to
|
||||
// server-authoritative physics
|
||||
force_update.map_or(false, |f| f.is_forced())
|
||||
|| player_physics_setting.server_authoritative()
|
||||
|| is_rider.get(entity).is_some()
|
||||
should_sync_client_physics(
|
||||
entity,
|
||||
&player_physics_settings,
|
||||
&players,
|
||||
&force_updates,
|
||||
is_rider,
|
||||
)
|
||||
} else if matches!(collider, Some(Collider::Voxel { .. })) {
|
||||
// Things with a voxel collider (airships, etc.) need to have very
|
||||
// stable physics so we always send updated
|
||||
@ -288,27 +285,15 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
};
|
||||
|
||||
if last_pos.is_none() {
|
||||
comp_sync_package.comp_inserted(uid, pos);
|
||||
} else if send_now {
|
||||
comp_sync_package.comp_modified(uid, pos);
|
||||
}
|
||||
|
||||
if let Some((v, last_vel)) = vel {
|
||||
if last_vel.is_none() {
|
||||
comp_sync_package.comp_inserted(uid, *v);
|
||||
} else if send_now {
|
||||
comp_sync_package.comp_modified(uid, *v);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((o, last_ori)) = ori {
|
||||
if last_ori.is_none() {
|
||||
comp_sync_package.comp_inserted(uid, *o);
|
||||
} else if send_now {
|
||||
comp_sync_package.comp_modified(uid, *o);
|
||||
}
|
||||
}
|
||||
add_physics_components(
|
||||
send_now,
|
||||
&mut comp_sync_package,
|
||||
uid,
|
||||
pos,
|
||||
last_pos,
|
||||
ori,
|
||||
vel,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: force update counter only needs to be sent once per frame (and only if
|
||||
@ -326,6 +311,41 @@ impl<'a> System<'a> for Sys {
|
||||
drop(guard);
|
||||
job.cpu_stats.measure(common_ecs::ParMode::Single);
|
||||
|
||||
// Sync components that are only synced for the client's own entity.
|
||||
for (entity, client, &uid, (maybe_pos, last_pos), vel, ori) in (
|
||||
&entities,
|
||||
&clients,
|
||||
uids,
|
||||
(positions.maybe(), last_pos.mask().maybe()),
|
||||
(&velocities, last_vel.mask().maybe()).maybe(),
|
||||
(&orientations, last_vel.mask().maybe()).maybe(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
// Include additional components for clients that aren't in a region (e.g. due
|
||||
// to having no position or have sync_me as `false`) since those
|
||||
// won't be synced above.
|
||||
let include_all_comps = region_map.in_region_map(entity);
|
||||
|
||||
let mut comp_sync_package = trackers.create_sync_from_client_package(
|
||||
&tracked_storages,
|
||||
entity,
|
||||
include_all_comps,
|
||||
);
|
||||
|
||||
if include_all_comps && let Some(&pos) = maybe_pos {
|
||||
let send_now = should_sync_client_physics(entity, &player_physics_settings, &players, &force_updates, is_rider);
|
||||
add_physics_components(send_now, &mut comp_sync_package, uid, pos, last_pos, ori, vel);
|
||||
}
|
||||
|
||||
if !comp_sync_package.is_empty() {
|
||||
client.send_fallible(ServerGeneral::CompSync(
|
||||
comp_sync_package,
|
||||
force_updates.get(entity).map_or(0, |f| f.counter()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Update the last physics components for each entity
|
||||
for (_, &pos, vel, ori, last_pos, last_vel, last_ori) in (
|
||||
&entities,
|
||||
@ -362,8 +382,6 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Sync clients that don't have a position?
|
||||
|
||||
// Sync inventories
|
||||
for (inventory, update, client) in (inventories, &mut inventory_updates, &clients).join() {
|
||||
client.send_fallible(ServerGeneral::InventoryUpdate(
|
||||
@ -372,18 +390,6 @@ impl<'a> System<'a> for Sys {
|
||||
));
|
||||
}
|
||||
|
||||
// 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,
|
||||
force_updates.get(entity).map_or(0, |f| f.counter()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Consume/clear the current outcomes and convert them to a vec
|
||||
let outcomes = outcomes.recv_all().collect::<Vec<_>>();
|
||||
|
||||
@ -438,3 +444,61 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines whether a client should receive an update about its own physics
|
||||
/// components.
|
||||
fn should_sync_client_physics(
|
||||
entity: specs::Entity,
|
||||
player_physics_settings: &PlayerPhysicsSettings,
|
||||
players: &ReadStorage<'_, Player>,
|
||||
force_updates: &WriteStorage<'_, ForceUpdate>,
|
||||
is_rider: &ReadStorage<'_, Is<Rider>>,
|
||||
) -> bool {
|
||||
let player_physics_setting = players
|
||||
.get(entity)
|
||||
.and_then(|p| player_physics_settings.settings.get(&p.uuid()).copied())
|
||||
.unwrap_or_default();
|
||||
// Don't send client physics updates about itself unless force update is
|
||||
// set or the client is subject to
|
||||
// server-authoritative physics
|
||||
force_updates.get(entity).map_or(false, |f| f.is_forced())
|
||||
|| player_physics_setting.server_authoritative()
|
||||
|| is_rider.contains(entity)
|
||||
}
|
||||
|
||||
/// Adds physics components if `send_now` is true or `Option<Last<T>>` is
|
||||
/// `None`.
|
||||
///
|
||||
/// If `Last<T>` isn't present, this is recorded as an insertion rather than a
|
||||
/// modification.
|
||||
fn add_physics_components(
|
||||
send_now: bool,
|
||||
comp_sync_package: &mut CompSyncPackage<common_net::msg::EcsCompPacket>,
|
||||
uid: Uid,
|
||||
pos: Pos,
|
||||
last_pos: Option<u32>,
|
||||
ori: Option<(&Ori, Option<u32>)>,
|
||||
vel: Option<(&Vel, Option<u32>)>,
|
||||
) {
|
||||
if last_pos.is_none() {
|
||||
comp_sync_package.comp_inserted(uid, pos);
|
||||
} else if send_now {
|
||||
comp_sync_package.comp_modified(uid, pos);
|
||||
}
|
||||
|
||||
if let Some((v, last_vel)) = vel {
|
||||
if last_vel.is_none() {
|
||||
comp_sync_package.comp_inserted(uid, *v);
|
||||
} else if send_now {
|
||||
comp_sync_package.comp_modified(uid, *v);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((o, last_ori)) = ori {
|
||||
if last_ori.is_none() {
|
||||
comp_sync_package.comp_inserted(uid, *o);
|
||||
} else if send_now {
|
||||
comp_sync_package.comp_modified(uid, *o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -231,10 +231,14 @@ macro_rules! trackers {
|
||||
|
||||
|
||||
/// Create sync package for components that are only synced for the client's entity.
|
||||
///
|
||||
/// This can optionally include components that are synced "for any entity" for cases
|
||||
/// where other mechanisms don't sync those components.
|
||||
pub fn create_sync_from_client_package(
|
||||
&self,
|
||||
comps: &TrackedStorages,
|
||||
entity: specs::Entity,
|
||||
include_all_comps: bool,
|
||||
) -> 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
|
||||
@ -249,10 +253,10 @@ macro_rules! trackers {
|
||||
};
|
||||
|
||||
$(
|
||||
if matches!(
|
||||
<$component_type as NetSync>::SYNC_FROM,
|
||||
SyncFrom::ClientEntity,
|
||||
) {
|
||||
if match <$component_type as NetSync>::SYNC_FROM {
|
||||
SyncFrom::ClientEntity => true,
|
||||
SyncFrom::AnyEntity => include_all_comps,
|
||||
} {
|
||||
comp_sync_package.add_component_update(
|
||||
&self.$component_name,
|
||||
&comps.$component_name,
|
||||
|
Loading…
Reference in New Issue
Block a user