diff --git a/common/src/region.rs b/common/src/region.rs index cd34173684..dda545ab3a 100644 --- a/common/src/region.rs +++ b/common/src/region.rs @@ -269,6 +269,28 @@ impl RegionMap { None } + /// Checks if this entity is located in the `RegionMap` using the provided + /// position to limit which regions are checked. + /// + /// May produce false negatives (e.g. if for some reason the entity is not + /// in a region near the provided position). + pub fn in_region_map_relaxed(&self, entity: specs::Entity, pos: Vec3) -> bool { + let id = entity.id(); + let pos = pos.map(|e| e as i32); + + // Compute key for most likely region + let key = Self::pos_key(pos); + if let Some(region) = self.regions.get(&key) && region.entities().contains(id) { return true } + + // Get the base of the four nearest regions. + let quad_base = Self::pos_key(pos - (REGION_SIZE / 2) as i32); + [(0, 0), (0, 1), (1, 0), (1, 1)] + .iter() + .map(|&offset| Vec2::::from(offset) + quad_base) + // skip key we already checked + .any(|k| k != key && self.regions.get(&key).is_some_and(|r| r.entities().contains(id))) + } + fn key_index(&self, key: Vec2) -> Option { self.regions.get_full(&key).map(|(i, _, _)| i) } diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 7d5feff755..c87df21e16 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -362,8 +362,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 +370,19 @@ impl<'a> System<'a> for Sys { )); } - // TODO: this seems like the ideal spot to sync other components for clients - // that don't have a position or otherwise aren't included in the regions that - // the client is subscribed to... - // - // Maybe we can pass a bool into `create_sync_from_client_package`... renamed it - // to create_sync_package_for_client_entity(?) - // create_client_sync_package(?) - // // 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); + for (entity, client, maybe_pos) in (&entities, &clients, positions.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 = + !maybe_pos.is_some_and(|pos| region_map.in_region_map_relaxed(entity, pos.0)); + + let comp_sync_package = trackers.create_sync_from_client_package( + &tracked_storages, + entity, + include_all_comps, + ); if !comp_sync_package.is_empty() { client.send_fallible(ServerGeneral::CompSync( comp_sync_package, diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index 2f699d8810..ecc844fff6 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -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 { // 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,