diff --git a/Cargo.lock b/Cargo.lock index 5cb013a59..302118407 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -686,7 +686,7 @@ dependencies = [ "shrev 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "simple_logger 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "specs 0.15.0 (git+https://github.com/AndreaCatania/specs?branch=emsig)", + "specs 0.15.0 (git+https://github.com/slide-rs/specs)", "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1253,11 +1253,6 @@ name = "nodrop" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "nonzero_signed" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "num-bigint" version = "0.2.2" @@ -2021,15 +2016,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "specs" version = "0.15.0" -source = "git+https://github.com/AndreaCatania/specs?branch=emsig#1abeb1439186062c0ee3389950f7f74e65b98801" +source = "git+https://github.com/slide-rs/specs#48fa4f0a8c21c44b406f6498322cfbbdd79c3ea8" dependencies = [ "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "derivative 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hashbrown 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "hibitset 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mopa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "nonzero_signed 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "shred 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "shrev 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2636,7 +2629,6 @@ dependencies = [ "checksum ncollide3d 0.20.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ee57cac70a2892e89fab7d5fd295b0ad544d1f877fa70fe8ae4be477514dd61" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" -"checksum nonzero_signed 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "02783a0482333b0d3f5f5411b8fb60454a596696da041da0470ac9ef3e6e37d8" "checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718" "checksum num-bigint-dig 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3cd60678022301da54082fcc383647fc895cba2795f868c871d58d29c8922595" "checksum num-complex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fcb0cf31fb3ff77e6d2a6ebd6800df7fdcd106f2ad89113c9130bcd07f93dffc" @@ -2722,7 +2714,7 @@ dependencies = [ "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum slotmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "759fd553261805f128e2900bf69ab3d034260bc338caf7f0ee54dbf035c85acd" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" -"checksum specs 0.15.0 (git+https://github.com/AndreaCatania/specs?branch=emsig)" = "" +"checksum specs 0.15.0 (git+https://github.com/slide-rs/specs)" = "" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum stream-cipher 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8861bc80f649f5b4c9bd38b696ae9af74499d479dbfb327f0607de6b326a36bc" "checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 4dafd8661..e93176df5 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -205,6 +205,10 @@ pub fn derive_packet(_item: TokenStream) -> TokenStream { fn ty(&self) -> PacketType { PacketType::#ident } + + fn box_clone(&self) -> Box { + Box::new((*self).clone()) + } } }; diff --git a/core/src/network/packet/implementation.rs b/core/src/network/packet/implementation.rs index 190478e6d..bf36963df 100644 --- a/core/src/network/packet/implementation.rs +++ b/core/src/network/packet/implementation.rs @@ -79,7 +79,11 @@ lazy_static! { }; } -fn bla() {} +macro_rules! box_clone_impl { + ($this:ident) => { + return Box::new((*$this).clone()); + }; +} // SERVERBOUND @@ -114,6 +118,10 @@ impl Packet for Handshake { fn ty(&self) -> PacketType { PacketType::Handshake } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(PartialEq, Eq, Clone)] @@ -169,6 +177,10 @@ impl Packet for EncryptionResponse { fn ty(&self) -> PacketType { PacketType::EncryptionResponse } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Packet, Clone)] @@ -269,6 +281,10 @@ impl Packet for PluginMessageServerbound { fn ty(&self) -> PacketType { PacketType::PluginMessageServerbound } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Packet, Clone)] @@ -318,6 +334,10 @@ impl Packet for UseEntity { fn ty(&self) -> PacketType { PacketType::UseEntity } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(AsAny, new, Clone)] @@ -438,6 +458,10 @@ impl Packet for PlayerDigging { fn ty(&self) -> PacketType { PacketType::PlayerDigging } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)] @@ -481,6 +505,10 @@ impl Packet for EntityAction { fn ty(&self) -> PacketType { PacketType::EntityAction } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, FromPrimitive, ToPrimitive)] @@ -621,6 +649,10 @@ impl Packet for AnimationServerbound { fn ty(&self) -> PacketType { PacketType::AnimationServerbound } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Packet, Clone)] @@ -685,6 +717,10 @@ impl Packet for PlayerBlockPlacement { fn ty(&self) -> PacketType { PacketType::PlayerBlockPlacement } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Packet, Clone)] @@ -723,6 +759,10 @@ impl Packet for EncryptionRequest { fn ty(&self) -> PacketType { PacketType::EncryptionRequest } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Packet, Clone)] @@ -842,6 +882,10 @@ impl Packet for SpawnPlayer { fn ty(&self) -> PacketType { PacketType::SpawnPlayer } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Clone)] @@ -863,6 +907,10 @@ impl Packet for AnimationClientbound { fn ty(&self) -> PacketType { PacketType::AnimationClientbound } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Clone)] @@ -888,6 +936,10 @@ impl Packet for Statistics { fn ty(&self) -> PacketType { PacketType::Statistics } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Packet, Clone)] @@ -961,6 +1013,10 @@ impl Packet for BossBar { fn ty(&self) -> PacketType { PacketType::BossBar } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Clone, Debug)] @@ -1078,6 +1134,10 @@ impl Packet for WindowItems { fn ty(&self) -> PacketType { PacketType::WindowItems } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Packet, Clone)] @@ -1119,6 +1179,10 @@ impl Packet for PluginMessageClientbound { fn ty(&self) -> PacketType { PacketType::PluginMessageClientbound } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Packet, Clone)] @@ -1189,6 +1253,10 @@ impl Packet for Explosion { fn ty(&self) -> PacketType { PacketType::Explosion } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Packet, Clone)] @@ -1285,6 +1353,10 @@ impl Packet for ChunkData { fn ty(&self) -> PacketType { PacketType::ChunkData } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Packet, Clone)] @@ -1406,6 +1478,10 @@ impl Packet for CombatEvent { fn ty(&self) -> PacketType { unimplemented!() } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(new, Clone)] @@ -1469,6 +1545,10 @@ impl Packet for PlayerInfo { fn ty(&self) -> PacketType { PacketType::PlayerInfo } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Debug, Clone)] @@ -1521,7 +1601,7 @@ pub struct UseBed { #[derive(Default, AsAny, new, Clone)] pub struct DestroyEntities { - entity_ids: Vec, + pub entity_ids: Vec, } impl Packet for DestroyEntities { @@ -1540,6 +1620,10 @@ impl Packet for DestroyEntities { fn ty(&self) -> PacketType { PacketType::DestroyEntities } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Packet, Clone)] @@ -1587,6 +1671,10 @@ impl Packet for PacketEntityMetadata { fn ty(&self) -> PacketType { PacketType::EntityMetadata } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } } #[derive(Default, AsAny, new, Packet, Clone)] diff --git a/core/src/network/packet/mod.rs b/core/src/network/packet/mod.rs index b888f8ffa..e1466b776 100644 --- a/core/src/network/packet/mod.rs +++ b/core/src/network/packet/mod.rs @@ -19,6 +19,9 @@ pub trait Packet: AsAny + Send { fn read_from(&mut self, buf: &mut dyn PacketBuf) -> Result<(), ()>; fn write_to(&self, buf: &mut ByteBuf); fn ty(&self) -> PacketType; + + /// Returns a clone of this packet in a dynamic box. + fn box_clone(&self) -> Box; } #[derive(Clone, Debug)] diff --git a/server/src/entity/broadcast.rs b/server/src/entity/broadcast.rs index e982ecd9e..9e3c39086 100644 --- a/server/src/entity/broadcast.rs +++ b/server/src/entity/broadcast.rs @@ -1,20 +1,122 @@ //! Module for broadcasting when an entity comes within -//! range of a player. +//! range of a player. Also handles sending the correct +//! packet to spawn entities on the client. +//! +//! Sending entities to a client is handled lazily: +//! an internal queue is kept of entities to send to players, +//! and each tick, a system flushes this queue and sends +//! the correct packet. This is done because of the number +//! of components which need to be accessed to send an entity +//! to a player. +use crate::chunk_logic::ChunkHolders; use crate::entity::movement::degrees_to_stops; use crate::entity::{EntityType, VelocityComponent}; use crate::entity::{Metadata, NamedComponent, PositionComponent}; -use crate::network::{send_packet_to_all_players, NetworkComponent}; +use crate::network::{send_packet_boxed_to_player, send_packet_to_player, NetworkComponent}; use crate::util::protocol_velocity; +use crossbeam::queue::SegQueue; use feather_core::network::packet::implementation::SpawnObject; use feather_core::network::packet::implementation::{PacketEntityMetadata, SpawnPlayer}; +use feather_core::Packet; use shrev::EventChannel; -use specs::{ - Entities, Entity, Read, ReadStorage, ReaderId, System, SystemData, World, WriteStorage, -}; +use specs::{Entity, Read, ReadStorage, ReaderId, System, Write, WriteStorage}; use uuid::Uuid; -//const ITEM_OBJECT_ID: i8 = 2; +/// Handles lazy sending of entities to a client. +#[derive(Debug, Default)] +pub struct EntitySender { + /// A queue of entities to lazily send. + queue: SegQueue, +} + +impl EntitySender { + /// Lazily sends an entity to a client. + pub fn send_entity_to_player(&self, player: Entity, entity: Entity) { + self.queue.push(SendRequest { player, entity }) + } +} + +/// An entity send request, containing +/// the player to send to and the entity +/// to send. +#[derive(Debug)] +struct SendRequest { + player: Entity, + entity: Entity, +} + +/// Event which is triggered when an entity +/// is sent to a client. This can be used to send +/// associated information, such as entity equipment. +#[derive(Debug, Clone)] +pub struct EntitySendEvent { + /// The player for which this event was triggered. + pub player: Entity, + /// The entity which was sent to the player. + pub entity: Entity, +} + +/// System for flushing the `EntitySender` queue +/// and sending the correct packets for the given +/// entities. +pub struct EntitySendSystem; + +impl<'a> System<'a> for EntitySendSystem { + type SystemData = ( + ReadStorage<'a, PositionComponent>, + ReadStorage<'a, NamedComponent>, + ReadStorage<'a, NetworkComponent>, + ReadStorage<'a, VelocityComponent>, + ReadStorage<'a, EntityType>, + WriteStorage<'a, Metadata>, + Write<'a, EventChannel>, + Read<'a, EntitySender>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + positions, + nameds, + networks, + velocities, + types, + mut metadatas, + mut send_events, + entity_sender, + ) = data; + + while let Ok(request) = entity_sender.queue.pop() { + let ty = types.get(request.entity).unwrap(); + let metadata = metadatas.get_mut(request.entity).unwrap(); + let position = positions.get(request.entity).unwrap(); + let velocity = velocities.get(request.entity); + let named = nameds.get(request.entity); + + let network = networks.get(request.player).unwrap(); + + // Send corresponding packet to player. + let packet = + packet_to_spawn_entity(request.entity, *ty, &position, metadata, velocity, named); + send_packet_boxed_to_player(&network, packet); + + // Send metadata. + let entity_metadata = PacketEntityMetadata { + entity_id: request.entity.id() as i32, + metadata: metadata.to_raw_metadata(), + }; + + send_packet_to_player(network, entity_metadata); + + // Trigger event. + let event = EntitySendEvent { + player: request.player, + entity: request.entity, + }; + send_events.single_write(event); + } + } +} /// Event triggered when an entity of any /// type is spawned. @@ -28,8 +130,8 @@ pub struct EntitySpawnEvent { /// System for broadcasting when an entity is spawned. /// -/// Different entity types require different packets -/// to send. +/// Broadcasts are lazily queued for sending +/// and are sent by `EntitySendSystem`. /// /// This system listens to `EntitySpawnEvent`s. #[derive(Default)] @@ -40,85 +142,102 @@ pub struct EntityBroadcastSystem { impl<'a> System<'a> for EntityBroadcastSystem { type SystemData = ( ReadStorage<'a, PositionComponent>, - ReadStorage<'a, NamedComponent>, ReadStorage<'a, NetworkComponent>, - ReadStorage<'a, VelocityComponent>, - WriteStorage<'a, Metadata>, + Read<'a, ChunkHolders>, + Read<'a, EntitySender>, Read<'a, EventChannel>, - Entities<'a>, ); fn run(&mut self, data: Self::SystemData) { - let (positions, nameds, networks, velocities, mut metadatas, events, entities) = data; - - for event in events.read(&mut self.reader.as_mut().unwrap()) { - let position = positions.get(event.entity).unwrap(); - let metadata = metadatas.get_mut(event.entity).unwrap(); - let velocity = velocities.get(event.entity).cloned().unwrap_or_default(); - let (velocity_x, velocity_y, velocity_z) = protocol_velocity(*velocity); - - // Send spawn packet to clients. - // The packet type depends on the type - // of entity. - - // The Player Info packet was already sent by `JoinBroadcastSystem`. - match event.ty { - EntityType::Player => { - let named = nameds.get(event.entity).unwrap(); - let packet = SpawnPlayer { - entity_id: event.entity.id() as i32, - player_uuid: named.uuid, - x: position.current.x, - y: position.current.y, - z: position.current.z, - yaw: degrees_to_stops(position.current.yaw), - pitch: degrees_to_stops(position.current.pitch), - metadata: metadata.to_raw_metadata(), - }; - - send_packet_to_all_players(&networks, &entities, packet, Some(event.entity)); - } - EntityType::Item => { - let packet = SpawnObject { - entity_id: event.entity.id() as i32, - object_uuid: Uuid::new_v4(), - ty: 2, // Type 2 for item stack - x: position.current.x, - y: position.current.y, - z: position.current.z, - pitch: degrees_to_stops(position.current.pitch), - yaw: degrees_to_stops(position.current.yaw), - data: 1, // Has velocity - velocity_x, - velocity_y, - velocity_z, - }; - - send_packet_to_all_players(&networks, &entities, packet, Some(event.entity)); + let (positions, networks, chunk_holders, entity_sender, spawn_events) = data; + + for event in spawn_events.read(self.reader.as_mut().unwrap()) { + // Broadcast entity to players who can see it. + let position = match positions.get(event.entity) { + Some(position) => position, + None => continue, + }; + let chunk = position.current.chunk_pos(); + + if let Some(holders) = chunk_holders.holders_for(chunk) { + for holder in holders { + if networks.get(*holder).is_none() { + // Not a player. + continue; + } + + // Don't send player to themself. + if *holder == event.entity { + continue; + } + + entity_sender.send_entity_to_player(*holder, event.entity); } - _ => unimplemented!(), } + } + } - // Send metadata. - let entity_metadata = PacketEntityMetadata { - entity_id: event.entity.id() as i32, + setup_impl!(reader); +} + +/// Returns the packet needed to spawn an entity +/// with given type, position, metadata, optional velocity, +/// and optional name. +fn packet_to_spawn_entity( + entity: Entity, + ty: EntityType, + position: &PositionComponent, + metadata: &mut Metadata, + velocity: Option<&VelocityComponent>, + named: Option<&NamedComponent>, +) -> Box { + let velocity = velocity.cloned().unwrap_or_default(); // Use default velocity of (0, 0, 0) + let (velocity_x, velocity_y, velocity_z) = protocol_velocity(velocity.0); + + // Different entity types require different + // packets to send. + match ty { + EntityType::Player => { + let named = named.unwrap(); + let packet = SpawnPlayer { + entity_id: entity.id() as i32, + player_uuid: named.uuid, + x: position.current.x, + y: position.current.y, + z: position.current.z, + yaw: degrees_to_stops(position.current.yaw), + pitch: degrees_to_stops(position.current.pitch), metadata: metadata.to_raw_metadata(), }; - send_packet_to_all_players(&networks, &entities, entity_metadata, None); - // Players should know their own metadata - } - } - fn setup(&mut self, world: &mut World) { - Self::SystemData::setup(world); + Box::new(packet) + } + EntityType::Item => { + let packet = SpawnObject { + entity_id: entity.id() as i32, + object_uuid: Uuid::new_v4(), + ty: 2, // Type 2 for item stack + x: position.current.x, + y: position.current.y, + z: position.current.z, + pitch: degrees_to_stops(position.current.pitch), + yaw: degrees_to_stops(position.current.yaw), + data: 1, // Has velocity + velocity_x, + velocity_y, + velocity_z, + }; - self.reader = Some(world.fetch_mut::>().register_reader()); + Box::new(packet) + } + _ => unimplemented!(), } } #[cfg(test)] mod tests { use super::*; + use crate::player::ChunkCrossSystem; use crate::testframework as t; use feather_core::network::cast_packet; use feather_core::network::packet::PacketType; @@ -152,7 +271,9 @@ mod tests { #[test] fn test_spawn_item() { let (mut w, mut d) = t::builder() - .with(EntityBroadcastSystem::default(), "") + .with(EntityBroadcastSystem::default(), "broadcast") + .with(ChunkCrossSystem::default(), "chunk_cross") + .with_dep(EntitySendSystem, "", &["broadcast", "chunk_cross"]) .build(); let player = t::add_player(&mut w); diff --git a/server/src/entity/chunk.rs b/server/src/entity/chunk.rs index 888afad2a..3bcebbbe5 100644 --- a/server/src/entity/chunk.rs +++ b/server/src/entity/chunk.rs @@ -5,6 +5,7 @@ use crate::entity::{EntityDestroyEvent, EntitySpawnEvent, PositionComponent}; use feather_core::world::ChunkPosition; use fnv::FnvHashMap; +use hashbrown::HashSet; use shrev::EventChannel; use specs::storage::ComponentEvent; use specs::{ @@ -58,6 +59,31 @@ impl ChunkEntities { self.0.remove(&chunk); } } + + /// Returns a vector of all entities in all chunks + /// within the given view distance of another chunk. + pub fn entites_within_view_distance( + &self, + chunk: ChunkPosition, + view_distance: u8, + ) -> HashSet { + let mut result = HashSet::new(); + + // 1 is subtracted from the view distance because of some odd + // client-side glitch (or maybe it's our fault?) where the last chunk within the view distance + // is not loaded correctly. + let view_distance = i32::from(view_distance) - 1; + + for x_offset in -view_distance..=view_distance { + for z_offset in -view_distance..=view_distance { + let chunk = ChunkPosition::new(chunk.x + x_offset, chunk.z + z_offset); + + result.extend(self.entities_in_chunk(chunk)); + } + } + + result + } } /// System for updating the `ChunkEntities`. @@ -86,11 +112,8 @@ impl<'a> System<'a> for ChunkEntityUpdateSystem { self.dirty.clear(); for event in positions.channel().read(self.move_reader.as_mut().unwrap()) { - match event { - ComponentEvent::Inserted(id) | ComponentEvent::Modified(id) => { - self.dirty.add(*id); - } - _ => (), + if let ComponentEvent::Modified(id) = event { + self.dirty.add(*id); } } @@ -132,6 +155,8 @@ impl<'a> System<'a> for ChunkEntityUpdateSystem { } } +// Tests here cannot use the `testframework::add_entity` function +// because it automatically adds a ChunkEntities entry for the entity. #[cfg(test)] mod tests { use super::*; @@ -155,10 +180,24 @@ mod tests { #[test] fn test_new_entity() { - let (mut w, mut d) = t::init_world(); + let (mut w, mut d) = t::builder() + .with(ChunkEntityUpdateSystem::default(), "") + .build(); let pos = position!(1.0, 64.0, 1003.5); - let entity = t::add_entity_with_pos(&mut w, EntityType::Player, pos, true); + let entity = w + .create_entity() + .with(PositionComponent { + current: pos, + previous: pos, + }) + .build(); + + let event = EntitySpawnEvent { + entity, + ty: EntityType::Player, + }; + t::trigger_event(&w, event); d.dispatch(&w); w.maintain(); @@ -172,22 +211,26 @@ mod tests { #[test] fn test_moved_entity() { - let (mut w, mut d) = t::init_world(); + let (mut w, mut d) = t::builder() + .with(ChunkEntityUpdateSystem::default(), "") + .build(); let pos = position!(1.0, 64.0, -14.0); let old_pos = position!(1.0, 64.0, -18.0); - let entity = t::add_entity_with_pos(&mut w, EntityType::Player, pos, false); + let entity = w + .create_entity() + .with(PositionComponent { + current: old_pos, + previous: old_pos, + }) + .build(); - { - let mut chunk_entities = w.fetch_mut::(); - chunk_entities.add_to_chunk(old_pos.chunk_pos(), entity); - - w.write_component::() - .get_mut(entity) - .unwrap() - .previous = old_pos; - } + // Trigger flagged storage event. + w.write_component::() + .get_mut(entity) + .unwrap() + .current = pos; d.dispatch(&w); w.maintain(); @@ -204,16 +247,13 @@ mod tests { #[test] fn test_destroyed_entity() { - let (mut w, mut d) = t::init_world(); + let (mut w, mut d) = t::builder() + .with(ChunkEntityUpdateSystem::default(), "") + .build(); let pos = position!(100.0, -100.0, -100.0); let entity = t::add_entity_with_pos(&mut w, EntityType::Player, pos, false); - { - let mut chunk_entities = w.fetch_mut::(); - chunk_entities.add_to_chunk(pos.chunk_pos(), entity); - } - let event = EntityDestroyEvent { entity }; t::trigger_event(&w, event); @@ -223,4 +263,33 @@ mod tests { let chunk_entities = w.fetch::(); assert!(chunk_entities.entities_in_chunk(pos.chunk_pos()).is_empty()); } + + #[test] + fn test_entities_within_view_distance() { + let mut chunk_entities = ChunkEntities::default(); + + let mut world = World::new(); + let entity1 = world.create_entity().build(); + let entity2 = world.create_entity().build(); + let entity3 = world.create_entity().build(); + let entity4 = world.create_entity().build(); + + let chunk1 = ChunkPosition::new(0, 0); + let chunk2 = ChunkPosition::new(0, 3); + let chunk3 = ChunkPosition::new(0, 4); + let chunk4 = ChunkPosition::new(-3, -3); + + chunk_entities.add_to_chunk(chunk1, entity1); + chunk_entities.add_to_chunk(chunk2, entity2); + chunk_entities.add_to_chunk(chunk3, entity3); + chunk_entities.add_to_chunk(chunk4, entity4); + + let view_distance = 4; + let entities = chunk_entities.entites_within_view_distance(chunk1, view_distance); + + assert!(entities.contains(&entity1)); + assert!(entities.contains(&entity2)); + assert!(!entities.contains(&entity3)); + assert!(entities.contains(&entity4)); + } } diff --git a/server/src/entity/component.rs b/server/src/entity/component.rs index bf749e417..ce0fdd26d 100644 --- a/server/src/entity/component.rs +++ b/server/src/entity/component.rs @@ -16,7 +16,7 @@ impl Component for PlayerComponent { type Storage = BTreeStorage; } -#[derive(Debug, PartialEq)] +#[derive(Default, Debug, PartialEq)] pub struct PositionComponent { /// The current position of this entity. pub current: Position, diff --git a/server/src/entity/item.rs b/server/src/entity/item.rs index 71e50cf35..44324a02a 100644 --- a/server/src/entity/item.rs +++ b/server/src/entity/item.rs @@ -1,7 +1,6 @@ //! Logic for working with item entities. use crate::entity::metadata::{self, Metadata}; use crate::entity::{ChunkEntities, EntityDestroyEvent, PlayerComponent, PositionComponent}; -use crate::network::{send_packet_to_all_players, NetworkComponent}; use crate::physics::nearby_entities; use crate::player::{ InventoryComponent, InventoryUpdateEvent, PlayerItemDropEvent, PLAYER_EYE_HEIGHT, @@ -207,11 +206,11 @@ impl<'a> System<'a> for ItemCollectSystem { ReadStorage<'a, PositionComponent>, ReadStorage<'a, PlayerComponent>, ReadStorage<'a, ItemComponent>, - ReadStorage<'a, NetworkComponent>, WriteStorage<'a, Metadata>, Write<'a, EventChannel>, Write<'a, EventChannel>, Read<'a, ChunkEntities>, + Read<'a, Util>, Read<'a, TickCount>, Entities<'a>, ); @@ -222,11 +221,11 @@ impl<'a> System<'a> for ItemCollectSystem { positions, players, items, - networks, mut metadatas, mut inventory_events, mut destroy_events, chunk_entities, + util, tick, entities, ) = data; @@ -284,7 +283,7 @@ impl<'a> System<'a> for ItemCollectSystem { collector: player.id() as i32, count: i32::from(stack.amount - amount_left), }; - send_packet_to_all_players(&networks, &entities, packet, None); + util.broadcast_entity_update(player, packet, None); if amount_left == 0 { entities.delete(other).unwrap(); @@ -327,10 +326,8 @@ pub fn item_meta(stack: ItemStack) -> Metadata { #[cfg(test)] mod tests { use super::*; - use crate::entity::chunk::ChunkEntityUpdateSystem; use crate::entity::EntitySpawnEvent; use crate::entity::EntityType; - use crate::systems::CHUNK_ENTITIES_UPDATE; use crate::testframework as t; use feather_core::inventory::SLOT_HOTBAR_OFFSET; use feather_core::network::cast_packet; @@ -378,12 +375,7 @@ mod tests { #[test] fn test_item_merge_system() { let (mut w, mut d) = t::builder() - .with(ChunkEntityUpdateSystem::default(), "chunk_entity_update") - .with_dep( - ItemMergeSystem::default(), - "item_merge", - &["chunk_entity_update"], - ) // Required so nearby_entities() works + .with_dep(ItemMergeSystem::default(), "item_merge", &[]) .build(); let item1 = @@ -419,8 +411,7 @@ mod tests { #[test] fn test_item_collect_system() { let (mut w, mut d) = t::builder() - .with(ChunkEntityUpdateSystem::default(), CHUNK_ENTITIES_UPDATE) - .with_dep(ItemCollectSystem::default(), "", &[CHUNK_ENTITIES_UPDATE]) + .with_dep(ItemCollectSystem::default(), "", &[]) .build(); let player = t::add_player(&mut w); diff --git a/server/src/entity/metadata.rs b/server/src/entity/metadata.rs index 6a4a6e665..65120218e 100644 --- a/server/src/entity/metadata.rs +++ b/server/src/entity/metadata.rs @@ -2,12 +2,12 @@ #![allow(clippy::too_many_arguments)] // TODO: builder patterm -use crate::network::{send_packet_to_all_players, NetworkComponent}; +use crate::util::Util; use feather_core::packet::PacketEntityMetadata; use feather_core::{EntityMetadata, Slot}; use specs::storage::ComponentEvent; use specs::{ - BitSet, Component, Entities, FlaggedStorage, Join, ReadStorage, ReaderId, System, VecStorage, + BitSet, Component, Entities, FlaggedStorage, Join, Read, ReaderId, System, VecStorage, WriteStorage, }; @@ -61,14 +61,10 @@ pub struct MetadataBroadcastSystem { } impl<'a> System<'a> for MetadataBroadcastSystem { - type SystemData = ( - WriteStorage<'a, Metadata>, - ReadStorage<'a, NetworkComponent>, - Entities<'a>, - ); + type SystemData = (WriteStorage<'a, Metadata>, Read<'a, Util>, Entities<'a>); fn run(&mut self, data: Self::SystemData) { - let (mut metadatas, networks, entities) = data; + let (mut metadatas, util, entities) = data; self.dirty.clear(); @@ -85,7 +81,7 @@ impl<'a> System<'a> for MetadataBroadcastSystem { metadata: metadata.to_raw_metadata(), }; - send_packet_to_all_players(&networks, &entities, packet, None); + util.broadcast_entity_update(entity, packet, None); } metadatas.set_event_emission(true); @@ -138,7 +134,7 @@ mod tests { .build(); // Metadata is inserted here, which causes update event - let entity = t::add_entity(&mut w, EntityType::Test, false); + let entity = t::add_entity(&mut w, EntityType::Test, true); let player = t::add_player(&mut w); d.dispatch(&w); diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index 419639073..98ceafd57 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -12,12 +12,15 @@ mod movement; mod types; use crate::systems::{ - CHUNK_ENTITIES_UPDATE, ENTITY_DESTROY, ENTITY_DESTROY_BROADCAST, ENTITY_METADATA_BROADCAST, - ENTITY_MOVE_BROADCAST, ENTITY_SPAWN_BROADCAST, ENTITY_VELOCITY_BROADCAST, ITEM_COLLECT, - ITEM_MERGE, ITEM_SPAWN, JOIN_BROADCAST, + CHUNK_CROSS, CHUNK_ENTITIES_UPDATE, ENTITY_DESTROY, ENTITY_DESTROY_BROADCAST, + ENTITY_METADATA_BROADCAST, ENTITY_MOVE_BROADCAST, ENTITY_SEND, ENTITY_SPAWN_BROADCAST, + ENTITY_VELOCITY_BROADCAST, ITEM_COLLECT, ITEM_MERGE, ITEM_SPAWN, JOIN_BROADCAST, }; +pub use broadcast::EntitySendSystem; +pub use broadcast::EntitySender; pub use broadcast::EntitySpawnEvent; pub use chunk::ChunkEntities; +pub use chunk::ChunkEntityUpdateSystem; pub use component::{NamedComponent, PlayerComponent, PositionComponent, VelocityComponent}; pub use destroy::EntityDestroyEvent; pub use item::ItemComponent; @@ -29,7 +32,6 @@ use crate::entity::destroy::EntityDestroyBroadcastSystem; use crate::entity::item::ItemCollectSystem; use crate::entity::metadata::MetadataBroadcastSystem; use broadcast::EntityBroadcastSystem; -use chunk::ChunkEntityUpdateSystem; use component::ComponentResetSystem; use destroy::EntityDestroySystem; use item::{ItemMergeSystem, ItemSpawnSystem}; @@ -65,8 +67,9 @@ pub fn init_broadcast(dispatcher: &mut DispatcherBuilder) { dispatcher.add( EntityBroadcastSystem::default(), ENTITY_SPAWN_BROADCAST, - &[JOIN_BROADCAST], + &[JOIN_BROADCAST, CHUNK_CROSS], ); + dispatcher.add(EntitySendSystem, ENTITY_SEND, &[ENTITY_SPAWN_BROADCAST]); dispatcher.add( EntityVelocityBroadcastSystem::default(), ENTITY_VELOCITY_BROADCAST, diff --git a/server/src/entity/movement.rs b/server/src/entity/movement.rs index 2249c0fa7..597eb3487 100644 --- a/server/src/entity/movement.rs +++ b/server/src/entity/movement.rs @@ -1,5 +1,5 @@ use specs::storage::ComponentEvent; -use specs::{BitSet, Entities, Entity, Join, ReadStorage, ReaderId, System}; +use specs::{BitSet, Entities, Entity, Join, Read, ReadStorage, ReaderId, System}; use feather_core::network::packet::implementation::{ EntityHeadLook, EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, EntityVelocity, @@ -7,8 +7,7 @@ use feather_core::network::packet::implementation::{ use feather_core::world::Position; use crate::entity::{PositionComponent, VelocityComponent}; -use crate::network::{send_packet_to_all_players, NetworkComponent}; -use crate::util::protocol_velocity; +use crate::util::{protocol_velocity, Util}; /// System for broadcasting when an entity moves. #[derive(Default)] @@ -19,13 +18,13 @@ pub struct EntityMoveBroadcastSystem { impl<'a> System<'a> for EntityMoveBroadcastSystem { type SystemData = ( - ReadStorage<'a, NetworkComponent>, ReadStorage<'a, PositionComponent>, + Read<'a, Util>, Entities<'a>, ); fn run(&mut self, data: Self::SystemData) { - let (networks, positions, entities) = data; + let (positions, util, entities) = data; self.dirty.clear(); @@ -38,14 +37,8 @@ impl<'a> System<'a> for EntityMoveBroadcastSystem { } } - for (entity, position, _) in (&entities, &positions, &self.dirty).join() { - broadcast_entity_movement( - entity, - position.previous, - position.current, - &networks, - &entities, - ); + for (position, entity, _) in (&positions, &entities, &self.dirty).join() { + broadcast_entity_movement(entity, position.previous, position.current, &util); } } @@ -62,13 +55,13 @@ pub struct EntityVelocityBroadcastSystem { impl<'a> System<'a> for EntityVelocityBroadcastSystem { type SystemData = ( - ReadStorage<'a, NetworkComponent>, ReadStorage<'a, VelocityComponent>, + Read<'a, Util>, Entities<'a>, ); fn run(&mut self, data: Self::SystemData) { - let (networks, velocities, entities) = data; + let (velocities, util, entities) = data; self.dirty.clear(); @@ -90,22 +83,21 @@ impl<'a> System<'a> for EntityVelocityBroadcastSystem { velocity_z, }; - send_packet_to_all_players(&networks, &entities, packet, Some(entity)); + util.broadcast_entity_update(entity, packet, Some(entity)); } } flagged_setup_impl!(VelocityComponent, reader); } -/// Broadcasts to all joined players that an entity has moved. +/// Broadcasts to nearby players that an entity has moved. #[allow(clippy::too_many_arguments)] #[allow(clippy::float_cmp)] pub fn broadcast_entity_movement( entity: Entity, old_pos: Position, new_pos: Position, - networks: &ReadStorage, - entities: &Entities, + util: &Util, ) { if old_pos == new_pos { return; @@ -136,10 +128,10 @@ pub fn broadcast_entity_movement( degrees_to_stops(new_pos.pitch), new_pos.on_ground, ); - send_packet_to_all_players(networks, entities, packet, Some(entity)); + util.broadcast_entity_update(entity, packet, Some(entity)); } else { let packet = EntityRelativeMove::new(entity.id() as i32, rx, ry, rz, new_pos.on_ground); - send_packet_to_all_players(networks, entities, packet, Some(entity)); + util.broadcast_entity_update(entity, packet, Some(entity)); } } else { let packet = EntityLook::new( @@ -148,13 +140,13 @@ pub fn broadcast_entity_movement( degrees_to_stops(new_pos.pitch), new_pos.on_ground, ); - send_packet_to_all_players(networks, entities, packet, Some(entity)); + util.broadcast_entity_update(entity, packet, Some(entity)); } // Entity Head Look also needs to be sent if the entity turned its head if has_looked { let packet = EntityHeadLook::new(entity.id() as i32, degrees_to_stops(new_pos.yaw)); - send_packet_to_all_players(networks, entities, packet, Some(entity)); + util.broadcast_entity_update(entity, packet, Some(entity)); } } diff --git a/server/src/main.rs b/server/src/main.rs index be02d53c9..9e871371c 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -42,7 +42,7 @@ use prelude::*; use crate::entity::{EntityDestroyEvent, NamedComponent}; use crate::network::send_packet_to_player; use crate::player::PlayerDisconnectEvent; -use crate::systems::{ITEM_SPAWN, JOIN_HANDLER, NETWORK, SPAWNER}; +use crate::systems::{BROADCASTER, ITEM_SPAWN, JOIN_HANDLER, NETWORK, SPAWNER}; use crate::util::Util; use backtrace::Backtrace; use feather_core::level; @@ -242,6 +242,10 @@ fn init_world<'a, 'b>( player::init_broadcast(&mut dispatcher); entity::init_broadcast(&mut dispatcher); + // Broadcast system needs to run last. + dispatcher.add_barrier(); + dispatcher.add(util::BroadcasterSystem, BROADCASTER, &[]); + let mut dispatcher = dispatcher.build(); dispatcher.setup(&mut world); diff --git a/server/src/player/animation.rs b/server/src/player/animation.rs index 880c82000..acf600b75 100644 --- a/server/src/player/animation.rs +++ b/server/src/player/animation.rs @@ -1,11 +1,12 @@ -use crate::network::{send_packet_to_all_players, NetworkComponent, PacketQueue}; +use crate::network::PacketQueue; +use crate::util::Util; use feather_core::network::cast_packet; use feather_core::network::packet::implementation::{AnimationClientbound, AnimationServerbound}; use feather_core::network::packet::PacketType; use feather_core::{ClientboundAnimation, Hand}; use shrev::EventChannel; use specs::SystemData; -use specs::{Entities, Entity, Read, ReadStorage, ReaderId, System, World, Write}; +use specs::{Entity, Read, ReaderId, System, World, Write}; /// Event which is triggered when a player causes /// an animation. @@ -53,25 +54,16 @@ pub struct AnimationBroadcastSystem { } impl<'a> System<'a> for AnimationBroadcastSystem { - type SystemData = ( - Read<'a, EventChannel>, - ReadStorage<'a, NetworkComponent>, - Entities<'a>, - ); + type SystemData = (Read<'a, EventChannel>, Read<'a, Util>); fn run(&mut self, data: Self::SystemData) { - let (events, net_comps, entities) = data; + let (events, util) = data; for event in events.read(&mut self.reader.as_mut().unwrap()) { // Broadcast animation let packet = AnimationClientbound::new(event.player.id() as i32, event.animation); - send_packet_to_all_players( - &net_comps, - &entities, - packet, - Some(event.player), // Don't send player their own animation - ); + util.broadcast_entity_update(event.player, packet, Some(event.player)) } } diff --git a/server/src/player/broadcast.rs b/server/src/player/broadcast.rs index 99d7b65fa..d293ca489 100644 --- a/server/src/player/broadcast.rs +++ b/server/src/player/broadcast.rs @@ -1,12 +1,16 @@ -use crate::entity::{NamedComponent, PlayerComponent, PositionComponent}; +use crate::config::Config; +use crate::entity::{ + ChunkEntities, EntitySender, NamedComponent, PlayerComponent, PositionComponent, +}; use crate::joinhandler::PlayerJoinEvent; use crate::network::{send_packet_to_all_players, send_packet_to_player, NetworkComponent}; use crate::player::chat::ChatBroadcastEvent; -use feather_core::network::packet::implementation::{PlayerInfo, PlayerInfoAction, SpawnPlayer}; +use feather_core::network::packet::implementation::{PlayerInfo, PlayerInfoAction}; use feather_core::Gamemode; use shrev::EventChannel; use specs::SystemData; use specs::{Entities, Entity, Join, Read, ReadStorage, ReaderId, System, World, Write}; +use std::sync::Arc; use uuid::Uuid; /// System for broadcasting when a player joins @@ -29,11 +33,25 @@ impl<'a> System<'a> for JoinBroadcastSystem { ReadStorage<'a, PlayerComponent>, ReadStorage<'a, NetworkComponent>, Write<'a, EventChannel>, + Read<'a, ChunkEntities>, + Read<'a, EntitySender>, + Read<'a, Arc>, Entities<'a>, ); fn run(&mut self, data: Self::SystemData) { - let (join_events, positions, nameds, player_comps, net_comps, mut chat, entities) = data; + let ( + join_events, + positions, + nameds, + player_comps, + net_comps, + mut chat, + chunk_entities, + entity_sender, + config, + entities, + ) = data; for event in join_events.read(&mut self.reader.as_mut().unwrap()) { // Broadcast join @@ -55,18 +73,16 @@ impl<'a> System<'a> for JoinBroadcastSystem { let player_info = get_player_initialization_packet(position, named, player_comp); send_packet_to_player(net_comp, player_info); + } + } - let spawn_player = SpawnPlayer { - entity_id: entity.id() as i32, - player_uuid: named.uuid, - x: position.current.x, - y: position.current.y, - z: position.current.z, - yaw: degrees_to_stops(position.current.yaw), - pitch: degrees_to_stops(position.current.pitch), - metadata: Default::default(), - }; - send_packet_to_player(net_comp, spawn_player); + // Send entities within view distance to new player + for entity in chunk_entities.entites_within_view_distance( + position.current.chunk_pos(), + config.server.view_distance, + ) { + if entity != event.player { + entity_sender.send_entity_to_player(event.player, entity); } } @@ -186,7 +202,3 @@ impl<'a> System<'a> for DisconnectBroadcastSystem { ); } } - -fn degrees_to_stops(degs: f32) -> u8 { - ((degs / 360.0) * 256.0) as u8 -} diff --git a/server/src/player/digging.rs b/server/src/player/digging.rs index b4fd5d510..3c3c2c68a 100644 --- a/server/src/player/digging.rs +++ b/server/src/player/digging.rs @@ -4,9 +4,7 @@ //! for completely unrelated actions, including eating, shooting bows, //! swapping items out the the offhand, and dropping items. -use specs::{ - Entities, Entity, LazyUpdate, Read, ReadStorage, ReaderId, System, World, Write, WriteStorage, -}; +use specs::{Entity, LazyUpdate, Read, ReadStorage, ReaderId, System, World, Write, WriteStorage}; use feather_core::network::cast_packet; use feather_core::network::packet::implementation::{ @@ -19,8 +17,9 @@ use feather_core::{Gamemode, Item}; use crate::disconnect_player; use crate::entity::PlayerComponent; -use crate::network::{send_packet_to_all_players, NetworkComponent, PacketQueue}; +use crate::network::PacketQueue; use crate::player::{InventoryComponent, InventoryUpdateEvent}; +use crate::util::Util; use feather_core::inventory::{ItemStack, SlotIndex, SLOT_HOTBAR_OFFSET}; use shrev::EventChannel; use specs::SystemData; @@ -247,14 +246,10 @@ pub struct BlockUpdateBroadcastSystem { } impl<'a> System<'a> for BlockUpdateBroadcastSystem { - type SystemData = ( - ReadStorage<'a, NetworkComponent>, - Read<'a, EventChannel>, - Entities<'a>, - ); + type SystemData = (Read<'a, EventChannel>, Read<'a, Util>); fn run(&mut self, data: Self::SystemData) { - let (networks, events, entities) = data; + let (events, util) = data; // Process events for event in events.read(&mut self.reader.as_mut().unwrap()) { @@ -268,7 +263,7 @@ impl<'a> System<'a> for BlockUpdateBroadcastSystem { }; let packet = BlockChange::new(event.pos, i32::from(event.new_block.native_state_id())); - send_packet_to_all_players(&networks, &entities, packet, neq); + util.broadcast_chunk_update(event.pos.chunk_pos(), packet, neq); } } diff --git a/server/src/player/inventory.rs b/server/src/player/inventory.rs index 9d4a81847..2e5cda605 100644 --- a/server/src/player/inventory.rs +++ b/server/src/player/inventory.rs @@ -1,10 +1,9 @@ use crate::disconnect_player; use crate::entity::PlayerComponent; use crate::joinhandler::PlayerJoinEvent; -use crate::network::{ - send_packet_to_all_players, send_packet_to_player, NetworkComponent, PacketQueue, -}; +use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue}; use crate::player::digging::PlayerItemDropEvent; +use crate::util::Util; use feather_core::inventory::{ Inventory, InventoryType, SlotIndex, HOTBAR_SIZE, SLOT_ARMOR_CHEST, SLOT_ARMOR_FEET, SLOT_ARMOR_HEAD, SLOT_ARMOR_LEGS, SLOT_HOTBAR_OFFSET, SLOT_OFFHAND, @@ -257,14 +256,13 @@ pub struct HeldItemBroadcastSystem { impl<'a> System<'a> for HeldItemBroadcastSystem { type SystemData = ( - ReadStorage<'a, NetworkComponent>, ReadStorage<'a, InventoryComponent>, Read<'a, EventChannel>, - Entities<'a>, + Read<'a, Util>, ); fn run(&mut self, data: Self::SystemData) { - let (networks, inventories, events, entities) = data; + let (inventories, events, util) = data; for event in events.read(&mut self.reader.as_mut().unwrap()) { let inv = inventories.get(event.player).unwrap(); @@ -281,7 +279,7 @@ impl<'a> System<'a> for HeldItemBroadcastSystem { item, ); - send_packet_to_all_players(&networks, &entities, packet, Some(event.player)); + util.broadcast_entity_update(event.player, packet, Some(event.player)); } } } diff --git a/server/src/player/mod.rs b/server/src/player/mod.rs index bb6e11b94..c926dd86c 100644 --- a/server/src/player/mod.rs +++ b/server/src/player/mod.rs @@ -22,10 +22,13 @@ mod movement; /// Module for handling player block placements. mod placement; mod resource_pack; +mod view; pub use broadcast::PlayerDisconnectEvent; -pub use movement::{send_chunk_to_player, ChunkPendingComponent, LoadedChunksComponent}; +pub use movement::{ + send_chunk_to_player, ChunkCrossSystem, ChunkPendingComponent, LoadedChunksComponent, +}; pub use animation::PlayerAnimationEvent; @@ -35,11 +38,12 @@ pub use digging::{BlockUpdateCause, BlockUpdateEvent, PlayerItemDropEvent}; use crate::player::inventory::SetSlotSystem; use crate::player::placement::BlockPlacementSystem; +use crate::player::view::ViewUpdateSystem; use crate::systems::{ ANIMATION_BROADCAST, BLOCK_BREAK_BROADCAST, BLOCK_PLACEMENT, CHAT_BROADCAST, CHUNK_CROSS, CHUNK_SEND, CLIENT_CHUNK_UNLOAD, CREATIVE_INVENTORY, DISCONNECT_BROADCAST, EQUIPMENT_SEND, HELD_ITEM_BROADCAST, HELD_ITEM_CHANGE, JOIN_BROADCAST, NETWORK, PLAYER_ANIMATION, PLAYER_CHAT, - PLAYER_DIGGING, PLAYER_INIT, PLAYER_MOVEMENT, RESOURCE_PACK_SEND, SET_SLOT, + PLAYER_DIGGING, PLAYER_INIT, PLAYER_MOVEMENT, RESOURCE_PACK_SEND, SET_SLOT, VIEW_UPDATE, }; use animation::{AnimationBroadcastSystem, PlayerAnimationSystem}; use broadcast::{DisconnectBroadcastSystem, JoinBroadcastSystem}; @@ -50,8 +54,8 @@ use init::PlayerInitSystem; use inventory::{ CreativeInventorySystem, EquipmentSendSystem, HeldItemBroadcastSystem, HeldItemChangeSystem, }; -use movement::{ChunkCrossSystem, ChunkSendSystem, ClientChunkUnloadSystem, PlayerMovementSystem}; -pub use resource_pack::ResourcePackSendSystem; +use movement::{ChunkSendSystem, ClientChunkUnloadSystem, PlayerMovementSystem}; +use resource_pack::ResourcePackSendSystem; use specs::DispatcherBuilder; pub const PLAYER_EYE_HEIGHT: f64 = 1.62; @@ -68,6 +72,7 @@ pub fn init_logic(dispatcher: &mut DispatcherBuilder) { } pub fn init_handlers(dispatcher: &mut DispatcherBuilder) { + dispatcher.add(ViewUpdateSystem::default(), VIEW_UPDATE, &[]); dispatcher.add(ChunkCrossSystem::default(), CHUNK_CROSS, &[]); dispatcher.add(ClientChunkUnloadSystem, CLIENT_CHUNK_UNLOAD, &[]); dispatcher.add(PlayerInitSystem::default(), PLAYER_INIT, &[]); diff --git a/server/src/player/movement.rs b/server/src/player/movement.rs index 7d28aebd4..ffa94dcb5 100644 --- a/server/src/player/movement.rs +++ b/server/src/player/movement.rs @@ -163,6 +163,19 @@ impl Component for ChunkPendingComponent { /// that it is unloaded. const CHUNK_UNLOAD_TIME: u64 = TPS * 5; // 5 seconds +/// Event which is triggered when a player crosses +/// chunk boundaries, causing their position's chunk +/// to change. +#[derive(Debug, Clone)] +pub struct ChunkCrossEvent { + /// The player affected by this event. + pub player: Entity, + /// The old chunk position. + pub old: ChunkPosition, + /// The new chunk position. + pub new: ChunkPosition, +} + /// System that checks when a player crosses chunk boundaries. /// When the player does so, the system sends Chunk Data packets /// for chunks within the view distance and also unloads @@ -183,6 +196,7 @@ impl<'a> System<'a> for ChunkCrossSystem { WriteStorage<'a, ChunkHolderComponent>, ReadStorage<'a, NetworkComponent>, Write<'a, ChunkHolders>, + Write<'a, EventChannel>, Read<'a, ChunkWorkerHandle>, Read<'a, LazyUpdate>, Entities<'a>, @@ -198,6 +212,7 @@ impl<'a> System<'a> for ChunkCrossSystem { mut chunk_holder_comps, net_comps, mut holders, + mut cross_events, chunk_handle, lazy, entities, @@ -264,6 +279,14 @@ impl<'a> System<'a> for ChunkCrossSystem { let time = tick_count.0 + CHUNK_UNLOAD_TIME; loaded_chunks.unload_queue.push_back((chunk, time)); } + + // Trigger chunk cross event. + let event = ChunkCrossEvent { + player, + old: old_chunk_pos, + new: new_chunk_pos, + }; + cross_events.single_write(event); } } } diff --git a/server/src/player/view.rs b/server/src/player/view.rs new file mode 100644 index 000000000..a518dd3d0 --- /dev/null +++ b/server/src/player/view.rs @@ -0,0 +1,189 @@ +//! This module implements creating and destroying +//! entities on the client when a player moves. +//! +//! When a player crosses chunk boundaries, the following +//! takes place: +//! * We send a `Destroy Entities` packet containing all +//! entities which are no longer within the view distance. +//! * We spawn an entity on the client for every entity +//! which is now within the view distance. +//! +//! This is handled by `ViewUpdateSystem`, which listens +//! to `ChunkCrossEvent`s. + +use crate::config::Config; +use crate::entity::{ChunkEntities, EntitySender}; +use crate::network::{send_packet_to_player, NetworkComponent}; +use crate::player::movement::ChunkCrossEvent; +use feather_core::network::packet::implementation::DestroyEntities; +use shrev::EventChannel; +use specs::{Read, ReadStorage, ReaderId, System}; +use std::sync::Arc; + +/// System for updating entities visible +/// by the client. +#[derive(Default)] +pub struct ViewUpdateSystem { + reader: Option>, +} + +impl<'a> System<'a> for ViewUpdateSystem { + type SystemData = ( + ReadStorage<'a, NetworkComponent>, + Read<'a, EventChannel>, + Read<'a, ChunkEntities>, + Read<'a, Arc>, + Read<'a, EntitySender>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (networks, cross_events, chunk_entities, config, entity_sender) = data; + + for event in cross_events.read(self.reader.as_mut().unwrap()) { + // Find new and old entities. + let old_entities = + chunk_entities.entites_within_view_distance(event.old, config.server.view_distance); + let new_entities = + chunk_entities.entites_within_view_distance(event.new, config.server.view_distance); + + let mut to_destroy = vec![]; + + // Compute entities which are only present in one of the sets. + // If an entity is only present in `old_entities` and not `new_entities`, + // it should be destroyed on the client. + // If an entity is only present in `new_entities`, it should be spawned. + for entity in old_entities.symmetric_difference(&new_entities) { + if *entity == event.player { + continue; + } + + if old_entities.contains(entity) { + // Entity is in `old_entities` but not in `new_entities`. + // Destroy it. If the entity is a player, also destroy this player + // on the client. + to_destroy.push(entity.id() as i32); + + if let Some(network) = networks.get(*entity) { + let packet = DestroyEntities { + entity_ids: vec![event.player.id() as i32], + }; + send_packet_to_player(network, packet); + } + } else { + // Entity is in `new_entities` but not in `old_entities`. + // Spawn it. If the entity is a player, also send this player + // to that entity. + entity_sender.send_entity_to_player(event.player, *entity); + + if networks.get(*entity).is_some() { + entity_sender.send_entity_to_player(*entity, event.player); + } + } + } + + if !to_destroy.is_empty() { + let packet = DestroyEntities { + entity_ids: to_destroy, + }; + send_packet_to_player(&networks.get(event.player).unwrap(), packet); + } + } + } + + setup_impl!(reader); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::entity::{EntitySendSystem, EntityType}; + use crate::testframework as t; + use feather_core::network::cast_packet; + use feather_core::network::packet::implementation::{SpawnObject, SpawnPlayer}; + use feather_core::{ChunkPosition, PacketType}; + use hashbrown::HashSet; + use specs::WorldExt; + + #[test] + fn test_view_update_system() { + let (mut world, mut dispatcher) = t::builder() + .with(ViewUpdateSystem::default(), "view") + .with_dep(EntitySendSystem, "", &["view"]) + .build(); + + let player_chunk = ChunkPosition::new(0, 0); + + let player1 = t::add_player_without_holder(&mut world); + let player2 = t::add_player_without_holder(&mut world); + + let entity1 = t::add_entity_without_holder(&mut world, EntityType::Item, true); + let entity2 = t::add_entity_without_holder(&mut world, EntityType::Item, true); + let entity3 = t::add_entity_without_holder(&mut world, EntityType::Item, true); + let entity4 = t::add_entity_without_holder(&mut world, EntityType::Item, true); + + let mut config = Config::default(); + config.server.view_distance = 4; + world.insert(Arc::new(config)); + + { + let mut chunk_entities = world.fetch_mut::(); + chunk_entities.add_to_chunk(player_chunk, player1.entity); + chunk_entities.add_to_chunk(player_chunk, player2.entity); + chunk_entities.add_to_chunk(ChunkPosition::new(3, -3), entity1); + chunk_entities.add_to_chunk(player_chunk, entity2); + chunk_entities.add_to_chunk(ChunkPosition::new(4, -3), entity3); + chunk_entities.add_to_chunk(ChunkPosition::new(100, 103), entity4); + } + + let event = ChunkCrossEvent { + player: player1.entity, + old: ChunkPosition::new(100, 103), + new: player_chunk, + }; + t::trigger_event(&world, event); + + dispatcher.dispatch(&world); + world.maintain(); + + let packets = t::received_packets(&player1, None); + + let mut received_spawns = HashSet::new(); + + for packet in packets { + match packet.ty() { + PacketType::DestroyEntities => { + let packet = cast_packet::(&*packet); + let destroyed = packet.entity_ids.iter().cloned().collect::>(); + assert_eq!(destroyed.len(), 1); + assert!(destroyed.contains(&(entity4.id() as i32))); + } + PacketType::SpawnObject => { + let packet = cast_packet::(&*packet); + received_spawns.insert(packet.entity_id); + } + PacketType::SpawnPlayer => { + let packet = cast_packet::(&*packet); + received_spawns.insert(packet.entity_id); + } + _ => (), + } + } + + println!("{:?}", received_spawns); + assert_eq!(received_spawns.len(), 3); + assert!(received_spawns.contains(&(entity1.id() as i32))); + assert!(received_spawns.contains(&(entity2.id() as i32))); + assert!(received_spawns.contains(&(player2.entity.id() as i32))); + + // Confirm that `player1` was sent to `player2`. + let packets = t::received_packets(&player2, None); + assert_eq!(packets.len(), 2); // One for Spawn Player, one for metadata + + let packet = packets + .into_iter() + .find(|packet| packet.ty() == PacketType::SpawnPlayer) + .unwrap(); + let packet = cast_packet::(&*packet); + assert_eq!(packet.entity_id, player1.entity.id() as i32); + } +} diff --git a/server/src/systems.rs b/server/src/systems.rs index 9732c2e6b..2d615e36a 100644 --- a/server/src/systems.rs +++ b/server/src/systems.rs @@ -22,6 +22,7 @@ pub const CHUNK_CROSS: &str = "chunk_cross"; pub const PLAYER_INIT: &str = "player_init"; pub const CLIENT_CHUNK_UNLOAD: &str = "client_chunk_unload"; +pub const VIEW_UPDATE: &str = "view_update"; pub const HELD_ITEM_BROADCAST: &str = "held_item_broadcast"; pub const JOIN_BROADCAST: &str = "join_broadcast"; pub const DISCONNECT_BROADCAST: &str = "disconnect_broadcast"; @@ -44,6 +45,7 @@ pub const ITEM_MERGE: &str = "item_merge"; pub const ENTITY_MOVE_BROADCAST: &str = "entity_move_broadcast"; pub const ENTITY_SPAWN_BROADCAST: &str = "entity_spawn_broadcast"; +pub const ENTITY_SEND: &str = "entity_send"; pub const ENTITY_VELOCITY_BROADCAST: &str = "entity_velocity_broadcast"; pub const ENTITY_DESTROY_BROADCAST: &str = "entity_destroy_broadcast"; pub const ENTITY_METADATA_BROADCAST: &str = "entity_metadata_broadcast"; @@ -59,3 +61,5 @@ pub const NETWORK: &str = "network"; pub const TIME_INCREMENT: &str = "time_increment"; pub const TIME_SEND: &str = "time_send"; + +pub const BROADCASTER: &str = "broadcaster"; diff --git a/server/src/testframework.rs b/server/src/testframework.rs index 44e339116..1e94c68d8 100644 --- a/server/src/testframework.rs +++ b/server/src/testframework.rs @@ -4,32 +4,32 @@ use std::net::TcpListener; use std::sync::atomic::AtomicUsize; use std::sync::Arc; +use glm::DVec3; use mio_extras::channel::{channel, Receiver, Sender}; use rand::Rng; +use shrev::EventChannel; use specs::{Builder, Dispatcher, DispatcherBuilder, Entity, ReaderId, System, World, WorldExt}; use uuid::Uuid; +use feather_core::level::LevelData; use feather_core::network::packet::{Packet, PacketType}; +use feather_core::world::block::Block; +use feather_core::world::chunk::Chunk; use feather_core::world::{BlockPosition, ChunkMap, ChunkPosition, Position}; use feather_core::Gamemode; +use crate::chunk_logic::ChunkHolders; use crate::config::Config; +use crate::entity::metadata::{self, Metadata}; use crate::entity::{ - EntityDestroyEvent, EntitySpawnEvent, EntityType, ItemComponent, NamedComponent, + ChunkEntities, EntityDestroyEvent, EntitySpawnEvent, EntityType, ItemComponent, NamedComponent, PlayerComponent, PositionComponent, VelocityComponent, }; use crate::io::ServerToWorkerMessage; use crate::network::{NetworkComponent, PacketQueue}; use crate::player::{InventoryComponent, PlayerDisconnectEvent}; +use crate::util::BroadcasterSystem; use crate::PlayerCount; -use feather_core::level::LevelData; -use feather_core::world::chunk::Chunk; -use glm::DVec3; -use shrev::EventChannel; - -use crate::entity::metadata::{self, Metadata}; - -use feather_core::world::block::Block; /// Initializes a Specs world and dispatcher /// using default configuration options and an @@ -61,10 +61,36 @@ pub struct Player { /// Adds a player to the world, inserting /// all the necessary components. Returns /// a number of useful channels. +/// +/// # Notes +/// * A `ChunkHolders` and `ChunkEntities` entry +/// is created for the player. If this behavior is not +/// desired, use `add_player_without_holder`. pub fn add_player(world: &mut World) -> Player { + let player = add_player_without_holder(world); + + let mut chunk_holders = world.fetch_mut::(); + + let view_distance = i32::from(world.fetch::>().server.view_distance); + + for x in -view_distance..=view_distance { + for z in -view_distance..=view_distance { + chunk_holders.insert_holder(ChunkPosition::new(x, z), player.entity); + } + } + + let mut chunk_entities = world.fetch_mut::(); + chunk_entities.add_to_chunk(ChunkPosition::new(0, 0), player.entity); + + player +} + +/// Adds a player to the world without adding the `ChunkHolders` +/// and `ChunkEntities` entries. +pub fn add_player_without_holder(world: &mut World) -> Player { let (ns1, nr1) = channel(); let (ns2, nr2) = channel(); - let e = world + let entity = world .create_entity() .with(NetworkComponent::new(ns1, nr2)) .with(PlayerComponent { @@ -81,10 +107,11 @@ pub fn add_player(world: &mut World) -> Player { }) .with(InventoryComponent::default()) .with(Metadata::Player(metadata::Player::default())) + .with(EntityType::Player) .build(); Player { - entity: e, + entity, network_sender: ns2, network_receiver: nr1, } @@ -209,10 +236,26 @@ pub fn triggered_events( /// Creates an entity at the origin with zero /// velocity. +/// +/// +/// # Notes +/// * A `ChunkHolders` and `ChunkEntities` entry +/// is created for the entity. If this behavior is not +/// desired, use `add_entity_without_holder`. pub fn add_entity(world: &mut World, ty: EntityType, trigger_spawn_event: bool) -> Entity { add_entity_with_pos(world, ty, Position::default(), trigger_spawn_event) } +/// Creates an entity at the origin with zero velocity, without +/// adding a chunk holder or chunk entities entry for it. +pub fn add_entity_without_holder( + world: &mut World, + ty: EntityType, + trigger_spawn_event: bool, +) -> Entity { + add_entity_without_holder_with_pos(world, ty, Position::default(), trigger_spawn_event) +} + /// Creates an entity with the given position /// and zero velocity. pub fn add_entity_with_pos( @@ -230,6 +273,21 @@ pub fn add_entity_with_pos( ) } +pub fn add_entity_without_holder_with_pos( + world: &mut World, + ty: EntityType, + pos: Position, + trigger_spawn_event: bool, +) -> Entity { + add_entity_without_holder_with_pos_and_vel( + world, + ty, + pos, + glm::vec3(0.0, 0.0, 0.0), + trigger_spawn_event, + ) +} + /// Creates an entity with the given position and velocity. pub fn add_entity_with_pos_and_vel( world: &mut World, @@ -237,6 +295,22 @@ pub fn add_entity_with_pos_and_vel( pos: Position, vel: DVec3, trigger_spawn_event: bool, +) -> Entity { + let entity = + add_entity_without_holder_with_pos_and_vel(world, ty, pos, vel, trigger_spawn_event); + + let mut chunk_entities = world.fetch_mut::(); + chunk_entities.add_to_chunk(pos.chunk_pos(), entity); + + entity +} + +pub fn add_entity_without_holder_with_pos_and_vel( + world: &mut World, + ty: EntityType, + pos: Position, + vel: DVec3, + trigger_spawn_event: bool, ) -> Entity { let entity = world .create_entity() @@ -357,7 +431,16 @@ impl<'a, 'b> TestBuilder<'a, 'b> { self.world .insert(EventChannel::::new()); self.world.insert(EventChannel::::new()); + self.world.insert(EventChannel::::new()); self.world.insert(crate::time::Time(0)); + self.world.insert(ChunkHolders::default()); + self.world.insert(ChunkEntities::default()); + self.world.insert(Arc::new(Config::default())); + + // Insert the broadcaster system, since it is so commonly + // used that it should be used for all tests. + self.dispatcher.add_barrier(); + self.dispatcher.add(BroadcasterSystem, "", &[]); let mut dispatcher = self.dispatcher.build(); dispatcher.setup(&mut self.world); @@ -388,9 +471,10 @@ pub fn builder<'a, 'b>() -> TestBuilder<'a, 'b> { /// all other tests would fail if the testing /// framework didn't work. mod tests { + use feather_core::network::packet::implementation::{DisconnectPlay, LoginStart}; + use crate::entity::{PlayerComponent, PositionComponent}; use crate::network::{send_packet_to_player, NetworkComponent}; - use feather_core::network::packet::implementation::{DisconnectPlay, LoginStart}; use super::*; diff --git a/server/src/util/broadcaster.rs b/server/src/util/broadcaster.rs new file mode 100644 index 000000000..58cdd448c --- /dev/null +++ b/server/src/util/broadcaster.rs @@ -0,0 +1,116 @@ +//! Implements a broadcaster, used to lazily broadcast +//! packets to players able to see a given entity. + +use crate::chunk_logic::ChunkHolders; +use crate::entity::PositionComponent; +use crate::network::{send_packet_boxed_to_player, NetworkComponent}; +use crate::util::Util; +use crossbeam::queue::SegQueue; +use feather_core::{ChunkPosition, Packet}; +use specs::{Entities, Entity, Read, ReadStorage, System}; + +/// Broadcaster used to lazily broadcast packets. +#[derive(Default)] +pub struct Broadcaster { + /// Internal queue of broadcasts to send. + queue: SegQueue, +} + +impl Broadcaster { + /// Lazily broadcasts a packet to all players + /// able to see a given entity. + pub fn broadcast_entity_update

(&self, entity: Entity, packet: P, neq: Option) + where + P: Packet + 'static, + { + self.queue.push(BroadcastRequest { + condition: BroadcastCondition::Entity(entity), + packet: Box::new(packet), + neq, + }); + } + + /// Lazily broadcasts a packet to all players + /// able to see a given chunk. + pub fn broadcast_chunk_update

(&self, chunk: ChunkPosition, packet: P, neq: Option) + where + P: Packet + 'static, + { + self.queue.push(BroadcastRequest { + condition: BroadcastCondition::Chunk(chunk), + packet: Box::new(packet), + neq, + }); + } +} + +/// A broadcast request. +struct BroadcastRequest { + /// Packet will only be sent to players able to see + /// this entity or chunk. + condition: BroadcastCondition, + /// The packet to broadcast. + packet: Box, + /// Optional entity not to send to. + neq: Option, +} + +#[derive(Debug, Clone, Copy)] +enum BroadcastCondition { + Entity(Entity), + Chunk(ChunkPosition), +} + +/// System for flushing the `Broadcaster` queue and broadcasting +/// the necessary packets. +pub struct BroadcasterSystem; + +impl<'a> System<'a> for BroadcasterSystem { + type SystemData = ( + ReadStorage<'a, PositionComponent>, + ReadStorage<'a, NetworkComponent>, + Read<'a, Util>, + Read<'a, ChunkHolders>, + Entities<'a>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (positions, networks, util, chunk_holders, entities) = data; + + let broadcaster = &util.broadcaster; + + while let Ok(request) = broadcaster.queue.pop() { + // Broadcast packet. + // Iterate over entities in the chunk_holders + // entry for the chunk. If they are a player, + // send the packet. + // This works because any player able to see + // a chunk will always have a chunk holder on the chunk. + + let chunk = match request.condition { + BroadcastCondition::Entity(entity) => { + // Prevents a panic if the entity was destroyed. + if !entities.is_alive(entity) { + continue; + } + + positions.get(entity).unwrap().current.chunk_pos() + } + BroadcastCondition::Chunk(chunk) => chunk, + }; + if let Some(holders) = chunk_holders.holders_for(chunk) { + for holder in holders { + if let Some(neq) = request.neq.as_ref() { + if *holder == *neq { + continue; + } + } + + if let Some(network) = networks.get(*holder) { + send_packet_boxed_to_player(network, request.packet.box_clone()); + } + } + } + } + } +} diff --git a/server/src/util/mod.rs b/server/src/util/mod.rs index a0fa9d94e..328b20fec 100644 --- a/server/src/util/mod.rs +++ b/server/src/util/mod.rs @@ -1,16 +1,20 @@ //! Assorted utilities for use in Feather's codebase. use bumpalo::Bump; -use feather_core::{ItemStack, Position}; +use feather_core::{ChunkPosition, ItemStack, Packet, Position}; use glm::DVec3; use spawn::Spawner; use thread_local::ThreadLocal; #[macro_use] mod macros; +mod broadcaster; mod spawn; +use broadcaster::Broadcaster; +pub use broadcaster::BroadcasterSystem; pub use macros::*; pub use spawn::SpawnerSystem; +use specs::Entity; /// Converts float-based velocity in blocks per tick /// to the obnoxious format used by the protocol. @@ -42,7 +46,10 @@ pub fn protocol_velocity(vel: DVec3) -> (i16, i16, i16) { /// needs. Note, however, that the entity isn't created /// until the handling dispatcher stage. These functions simply /// redirect to `entity::Spawner`. -#[derive(Debug, Default)] +/// * `broadcast` - lazily broadcasts a packet to all players +/// who are able to see a given chunk. This can be used +/// to broadcast movement updates, for example. +#[derive(Default)] pub struct Util { /// Thread-local bump allocator, reset /// every tick. @@ -51,6 +58,8 @@ pub struct Util { bump: ThreadLocal, /// The spawner, used to lazily spawn entities. spawner: Spawner, + /// The broadcaster, used to lazily broadcast packets. + broadcaster: Broadcaster, } impl Util { @@ -82,4 +91,85 @@ impl Util { bump.reset(); } } + + /// Broadcasts a packet to all players who + /// are able to see a given entity. + /// + /// The packet is sent lazily in a separate system. + /// + /// If `neq` is set to an entity, the packet + /// will not be sent to that player. + /// + /// This function runs in linear time with + /// regard to the number of players able to see + /// the entity. + pub fn broadcast_entity_update

(&self, entity: Entity, packet: P, neq: Option) + where + P: Packet + Clone + 'static, + { + self.broadcaster + .broadcast_entity_update(entity, packet, neq); + } + + /// Broadcasts a packet to all players who + /// are able to see a given chunk. + /// + /// The packet is sent lazily in a separate system. + /// + /// If `neq` is set to an entity, the packet + /// will not be sent to that player. + /// + /// This function runs in linear time with + /// regard to the number of players able to see + /// the chunk. + pub fn broadcast_chunk_update

(&self, chunk: ChunkPosition, packet: P, neq: Option) + where + P: Packet + Clone + 'static, + { + self.broadcaster.broadcast_chunk_update(chunk, packet, neq); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::chunk_logic::ChunkHolders; + use crate::testframework as t; + use crate::util::broadcaster::BroadcasterSystem; + use feather_core::network::packet::implementation::EntityHeadLook; + use feather_core::PacketType; + use specs::WorldExt; + + #[test] + fn test_broadcast() { + let mut chunk_holders = ChunkHolders::default(); + + let chunk = ChunkPosition::new(0, 0); + let other_chunk = ChunkPosition::new(10, 1); + + let (mut world, mut dispatcher) = t::builder().with(BroadcasterSystem, "").build(); + let player1 = t::add_player(&mut world); + let player2 = t::add_player(&mut world); + let player3 = t::add_player(&mut world); + + chunk_holders.insert_holder(chunk, player1.entity); + chunk_holders.insert_holder(chunk, player2.entity); + chunk_holders.insert_holder(other_chunk, player3.entity); + + let packet = EntityHeadLook::default(); + + let util = Util::default(); + + util.broadcast_entity_update(player1.entity, packet, Some(player2.entity)); + + world.insert(util); + world.insert(chunk_holders); + + dispatcher.dispatch(&world); + world.maintain(); + + t::assert_packet_received(&player1, PacketType::EntityHeadLook); + t::assert_packet_not_received(&player2, PacketType::EntityHeadLook); + t::assert_packet_not_received(&player3, PacketType::EntityHeadLook); + } }