From f44ac3c69329f30a0acd0699f899160239af075f Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Wed, 6 Nov 2024 22:34:19 +0200 Subject: [PATCH 01/31] Split `replication_messages` into submodules No logical changes. --- src/server/replication_messages.rs | 571 +----------------- .../replication_messages/init_message.rs | 341 +++++++++++ .../replication_messages/update_message.rs | 252 ++++++++ 3 files changed, 599 insertions(+), 565 deletions(-) create mode 100644 src/server/replication_messages/init_message.rs create mode 100644 src/server/replication_messages/update_message.rs diff --git a/src/server/replication_messages.rs b/src/server/replication_messages.rs index 80b54cce..468ecadf 100644 --- a/src/server/replication_messages.rs +++ b/src/server/replication_messages.rs @@ -1,23 +1,22 @@ +pub(super) mod init_message; +pub(super) mod update_message; + use std::{ io::{Cursor, Write}, mem, time::Duration, }; -use bevy::{ecs::component::Tick, prelude::*, ptr::Ptr}; -use bincode::{DefaultOptions, Options}; -use bytes::Bytes; +use bevy::{ecs::component::Tick, prelude::*}; use varint_rs::VarintWriter; -use super::client_entity_map::ClientMapping; use crate::core::{ - channels::ReplicationChannel, - ctx::SerializeCtx, replicated_clients::{ClientBuffers, ReplicatedClient, ReplicatedClients}, - replication_registry::{component_fns::ComponentFns, rule_fns::UntypedRuleFns, FnsId}, replicon_server::RepliconServer, replicon_tick::RepliconTick, }; +use init_message::InitMessage; +use update_message::UpdateMessage; /// Accumulates replication messages and sends them to clients. /// @@ -96,539 +95,6 @@ impl ReplicationMessages { } } -/// A reusable message with replicated data. -/// -/// Contains tick and mappings, insertions, removals and despawns that -/// happened on this tick. -/// Sent over [`ReplicationChannel::Init`] channel. -/// -/// See also [Limits](../index.html#limits) -pub(super) struct InitMessage { - /// Serialized data. - cursor: Cursor>, - - /// Length of the array that updated automatically after writing data. - array_len: u16, - - /// Position of the array from last call of [`Self::start_array`]. - array_pos: u64, - - /// The number of empty arrays at the end. - trailing_empty_arrays: usize, - - /// Entity from last call of [`Self::start_entity_data`]. - data_entity: Entity, - - /// Size in bytes of the component data stored for the currently-being-written entity. - entity_data_size: u16, - - /// Position of entity from last call of [`Self::start_entity_data`]. - entity_data_pos: u64, - - /// Position of entity data length from last call of [`Self::write_data_entity`]. - entity_data_size_pos: u64, -} - -impl InitMessage { - /// Clears the message. - /// - /// Keeps allocated capacity for reuse. - fn reset(&mut self) { - self.cursor.set_position(0); - self.trailing_empty_arrays = 0; - } - - /// Returns size in bytes of the current entity data. - /// - /// See also [`Self::start_entity_data`] and [`Self::end_entity_data`]. - pub(super) fn entity_data_size(&self) -> u16 { - self.entity_data_size - } - - /// Starts writing array by remembering its position to write length after. - /// - /// Arrays can contain entity data or despawns inside. - /// See also [`Self::end_array`], [`Self::write_client_mapping`], [`Self::write_entity`] and [`Self::start_entity_data`]. - pub(super) fn start_array(&mut self) { - debug_assert_eq!(self.array_len, 0); - - self.array_pos = self.cursor.position(); - self.cursor - .set_position(self.array_pos + mem::size_of_val(&self.array_len) as u64); - } - - /// Ends writing array by writing its length into the last remembered position. - /// - /// See also [`Self::start_array`]. - pub(super) fn end_array(&mut self) -> bincode::Result<()> { - if self.array_len != 0 { - let previous_pos = self.cursor.position(); - self.cursor.set_position(self.array_pos); - - bincode::serialize_into(&mut self.cursor, &self.array_len)?; - - self.cursor.set_position(previous_pos); - self.array_len = 0; - self.trailing_empty_arrays = 0; - } else { - self.trailing_empty_arrays += 1; - self.cursor.set_position(self.array_pos); - bincode::serialize_into(&mut self.cursor, &self.array_len)?; - } - - Ok(()) - } - - /// Serializes entity to entity mapping as an array element. - /// - /// Should be called only inside an array and increases its length by 1. - /// See also [`Self::start_array`]. - pub(super) fn write_client_mapping(&mut self, mapping: &ClientMapping) -> bincode::Result<()> { - serialize_entity(&mut self.cursor, mapping.server_entity)?; - serialize_entity(&mut self.cursor, mapping.client_entity)?; - self.array_len = self - .array_len - .checked_add(1) - .ok_or(bincode::ErrorKind::SizeLimit)?; - - Ok(()) - } - - /// Serializes entity as an array element. - /// - /// Reuses previously shared bytes if they exist, or updates them. - /// Should be called only inside an array and increases its length by 1. - /// See also [`Self::start_array`]. - pub(super) fn write_entity<'a>( - &'a mut self, - shared_bytes: &mut Option<&'a [u8]>, - entity: Entity, - ) -> bincode::Result<()> { - write_with(shared_bytes, &mut self.cursor, |cursor| { - serialize_entity(cursor, entity) - })?; - - self.array_len = self - .array_len - .checked_add(1) - .ok_or(bincode::ErrorKind::SizeLimit)?; - - Ok(()) - } - - /// Starts writing entity and its data as an array element. - /// - /// Should be called only inside an array and increases its length by 1. - /// Data can contain components with their IDs or IDs only. - /// Entity will be written lazily after first data write. - /// See also [`Self::end_entity_data`] and [`Self::write_component`]. - pub(super) fn start_entity_data(&mut self, entity: Entity) { - debug_assert_eq!(self.entity_data_size, 0); - - self.data_entity = entity; - self.entity_data_pos = self.cursor.position(); - } - - /// Writes entity for the current data and remembers the position after it to write length later. - /// - /// Should be called only after first data write. - fn write_data_entity(&mut self) -> bincode::Result<()> { - serialize_entity(&mut self.cursor, self.data_entity)?; - self.entity_data_size_pos = self.cursor.position(); - self.cursor.set_position( - self.entity_data_size_pos + mem::size_of_val(&self.entity_data_size) as u64, - ); - - Ok(()) - } - - /// Ends writing entity data by writing its length into the last remembered position. - /// - /// If the entity data is empty, nothing will be written unless `save_empty` is set to true. - /// Should be called only inside an array and increases its length by 1. - /// See also [`Self::start_array`], [`Self::write_component`] and - /// [`Self::write_component_id`]. - pub(super) fn end_entity_data(&mut self, save_empty: bool) -> bincode::Result<()> { - if self.entity_data_size == 0 && !save_empty { - self.cursor.set_position(self.entity_data_pos); - return Ok(()); - } - - if self.entity_data_size == 0 { - self.write_data_entity()?; - } - - let previous_pos = self.cursor.position(); - self.cursor.set_position(self.entity_data_size_pos); - - bincode::serialize_into(&mut self.cursor, &self.entity_data_size)?; - - self.cursor.set_position(previous_pos); - self.entity_data_size = 0; - self.array_len = self - .array_len - .checked_add(1) - .ok_or(bincode::ErrorKind::SizeLimit)?; - - Ok(()) - } - - /// Serializes component and its replication functions ID as an element of entity data. - /// - /// Reuses previously shared bytes if they exist, or updates them. - /// Should be called only inside an entity data and increases its size. - /// See also [`Self::start_entity_data`]. - pub(super) fn write_component<'a>( - &'a mut self, - shared_bytes: &mut Option<&'a [u8]>, - rule_fns: &UntypedRuleFns, - component_fns: &ComponentFns, - ctx: &SerializeCtx, - fns_id: FnsId, - ptr: Ptr, - ) -> bincode::Result<()> { - if self.entity_data_size == 0 { - self.write_data_entity()?; - } - - let size = write_with(shared_bytes, &mut self.cursor, |cursor| { - DefaultOptions::new().serialize_into(&mut *cursor, &fns_id)?; - // SAFETY: `component_fns`, `ptr` and `rule_fns` were created for the same component type. - unsafe { component_fns.serialize(ctx, rule_fns, ptr, cursor) } - })?; - - self.entity_data_size = self - .entity_data_size - .checked_add(size) - .ok_or(bincode::ErrorKind::SizeLimit)?; - - Ok(()) - } - - /// Serializes replication functions ID as an element of entity data. - /// - /// Should be called only inside an entity data and increases its size. - /// See also [`Self::start_entity_data`]. - pub(super) fn write_fns_id(&mut self, fns_id: FnsId) -> bincode::Result<()> { - if self.entity_data_size == 0 { - self.write_data_entity()?; - } - - let previous_pos = self.cursor.position(); - DefaultOptions::new().serialize_into(&mut self.cursor, &fns_id)?; - - let id_size = self.cursor.position() - previous_pos; - self.entity_data_size = self - .entity_data_size - .checked_add(id_size as u16) - .ok_or(bincode::ErrorKind::SizeLimit)?; - - Ok(()) - } - - /// Removes entity data elements from update message and copies it. - /// - /// Ends entity data for the update message. - /// See also [`Self::start_entity_data`] and [`Self::end_entity_data`]. - pub(super) fn take_entity_data( - &mut self, - update_message: &mut UpdateMessage, - ) -> bincode::Result<()> { - if update_message.entity_data_size != 0 { - if self.entity_data_size == 0 { - self.write_data_entity()?; - } - - let slice = update_message.as_slice(); - let offset = update_message.entity_data_size_pos as usize - + mem::size_of_val(&update_message.entity_data_size); - self.cursor.write_all(&slice[offset..]).unwrap(); - - self.entity_data_size = self - .entity_data_size - .checked_add(update_message.entity_data_size) - .ok_or(bincode::ErrorKind::SizeLimit)?; - update_message.entity_data_size = 0; - } - - update_message - .cursor - .set_position(update_message.entity_data_pos); - - Ok(()) - } - - /// Returns the serialized data, excluding trailing empty arrays, as a byte array. - fn as_slice(&self) -> &[u8] { - let slice = self.cursor.get_ref(); - let position = self.cursor.position() as usize; - let extra_len = self.trailing_empty_arrays * mem::size_of_val(&self.array_len); - &slice[..position - extra_len] - } - - /// Sends the message, excluding trailing empty arrays, to the specified client. - /// - /// Updates change tick for the client if there are data to send. - /// Does nothing if there is no data to send. - fn send( - &self, - server: &mut RepliconServer, - client: &mut ReplicatedClient, - server_tick: RepliconTick, - ) -> bincode::Result<()> { - debug_assert_eq!(self.array_len, 0); - debug_assert_eq!(self.entity_data_size, 0); - - let slice = self.as_slice(); - if slice.is_empty() { - trace!("no init data to send for {:?}", client.id()); - return Ok(()); - } - - client.set_init_tick(server_tick); - - let mut header = [0; mem::size_of::()]; - bincode::serialize_into(&mut header[..], &server_tick)?; - - trace!("sending init message to {:?}", client.id()); - server.send( - client.id(), - ReplicationChannel::Init, - Bytes::from([&header, slice].concat()), - ); - - Ok(()) - } -} - -impl Default for InitMessage { - fn default() -> Self { - Self { - cursor: Default::default(), - array_len: Default::default(), - array_pos: Default::default(), - trailing_empty_arrays: Default::default(), - entity_data_size: Default::default(), - entity_data_pos: Default::default(), - entity_data_size_pos: Default::default(), - data_entity: Entity::PLACEHOLDER, - } - } -} - -/// A reusable message with replicated component updates. -/// -/// Contains change tick, current tick and component updates since the last acknowledged tick for each entity. -/// Cannot be applied on the client until the init message matching this update message's change tick -/// has been applied to the client world. -/// The message will be manually split into packets up to max size, and each packet will be applied -/// independently on the client. -/// Message splits only happen per-entity to avoid weird behavior from partial entity updates. -/// Sent over the [`ReplicationChannel::Update`] channel. -/// -/// See also [Limits](../index.html#limits) -pub(super) struct UpdateMessage { - /// Serialized data. - cursor: Cursor>, - - /// Entities and their sizes in the message with data. - entities: Vec<(Entity, usize)>, - - /// Entity from last call of [`Self::start_entity_data`]. - data_entity: Entity, - - /// Size in bytes of the component data stored for the currently-being-written entity. - entity_data_size: u16, - - /// Position of entity from last call of [`Self::start_entity_data`]. - entity_data_pos: u64, - - /// Position of entity data length from last call of [`Self::write_data_entity`]. - entity_data_size_pos: u64, -} - -impl UpdateMessage { - /// Clears the message. - /// - /// Keeps allocated capacity for reuse. - fn reset(&mut self) { - self.cursor.set_position(0); - self.entities.clear(); - } - - /// Starts writing entity and its data. - /// - /// Data can contain components with their IDs. - /// Entity will be written lazily after first data write. - /// See also [`Self::end_entity_data`] and [`Self::write_component`]. - pub(super) fn start_entity_data(&mut self, entity: Entity) { - debug_assert_eq!(self.entity_data_size, 0); - - self.data_entity = entity; - self.entity_data_pos = self.cursor.position(); - } - - /// Writes entity for the current data and remembers the position after it to write length later. - /// - /// Should be called only after first data write. - fn write_data_entity(&mut self) -> bincode::Result<()> { - serialize_entity(&mut self.cursor, self.data_entity)?; - self.entity_data_size_pos = self.cursor.position(); - self.cursor.set_position( - self.entity_data_size_pos + mem::size_of_val(&self.entity_data_size) as u64, - ); - - Ok(()) - } - - /// Ends writing entity data by writing its length into the last remembered position. - /// - /// If the entity data is empty, nothing will be written and the cursor will reset. - /// See also [`Self::start_array`] and [`Self::write_component`]. - pub(super) fn end_entity_data(&mut self) -> bincode::Result<()> { - if self.entity_data_size == 0 { - self.cursor.set_position(self.entity_data_pos); - return Ok(()); - } - - let previous_pos = self.cursor.position(); - self.cursor.set_position(self.entity_data_size_pos); - - bincode::serialize_into(&mut self.cursor, &self.entity_data_size)?; - - self.cursor.set_position(previous_pos); - - let data_size = self.cursor.position() - self.entity_data_pos; - self.entities.push((self.data_entity, data_size as usize)); - - self.entity_data_size = 0; - - Ok(()) - } - - /// Serializes component and its replication functions ID as an element of entity data. - /// - /// Reuses previously shared bytes if they exist, or updates them. - /// Should be called only inside an entity data and increases its size. - /// See also [`Self::start_entity_data`]. - pub(super) fn write_component<'a>( - &'a mut self, - shared_bytes: &mut Option<&'a [u8]>, - rule_fns: &UntypedRuleFns, - component_fns: &ComponentFns, - ctx: &SerializeCtx, - fns_id: FnsId, - ptr: Ptr, - ) -> bincode::Result<()> { - if self.entity_data_size == 0 { - self.write_data_entity()?; - } - - let size = write_with(shared_bytes, &mut self.cursor, |cursor| { - DefaultOptions::new().serialize_into(&mut *cursor, &fns_id)?; - // SAFETY: `component_fns`, `ptr` and `rule_fns` were created for the same component type. - unsafe { component_fns.serialize(ctx, rule_fns, ptr, cursor) } - })?; - - self.entity_data_size = self - .entity_data_size - .checked_add(size) - .ok_or(bincode::ErrorKind::SizeLimit)?; - - Ok(()) - } - - /// Returns the serialized data as a byte array. - fn as_slice(&self) -> &[u8] { - let slice = self.cursor.get_ref(); - let position = self.cursor.position() as usize; - &slice[..position] - } - - /// Splits message according to entities inside it and sends it to the specified client. - /// - /// Does nothing if there is no data to send. - fn send( - &mut self, - server: &mut RepliconServer, - client_buffers: &mut ClientBuffers, - client: &mut ReplicatedClient, - server_tick: RepliconTick, - tick: Tick, - timestamp: Duration, - ) -> bincode::Result<()> { - debug_assert_eq!(self.entity_data_size, 0); - - let mut slice = self.as_slice(); - if slice.is_empty() { - trace!("no updates to send for {:?}", client.id()); - return Ok(()); - } - - trace!("sending update message(s) to {:?}", client.id()); - const TICKS_SIZE: usize = 2 * mem::size_of::(); - let mut header = [0; TICKS_SIZE + mem::size_of::()]; - bincode::serialize_into(&mut header[..], &(client.init_tick(), server_tick))?; - - let mut message_size = 0; - let client_id = client.id(); - let (mut update_index, mut entities) = - client.register_update(client_buffers, tick, timestamp); - for &(entity, data_size) in &self.entities { - // Try to pack back first, then try to pack forward. - if message_size == 0 - || can_pack(header.len(), message_size, data_size) - || can_pack(header.len(), data_size, message_size) - { - entities.push(entity); - message_size += data_size; - } else { - let (message, remaining) = slice.split_at(message_size); - slice = remaining; - message_size = data_size; - - bincode::serialize_into(&mut header[TICKS_SIZE..], &update_index)?; - - server.send( - client_id, - ReplicationChannel::Update, - Bytes::from([&header, message].concat()), - ); - - if !slice.is_empty() { - (update_index, entities) = - client.register_update(client_buffers, tick, timestamp); - } - } - } - - if !slice.is_empty() { - bincode::serialize_into(&mut header[TICKS_SIZE..], &update_index)?; - - server.send( - client_id, - ReplicationChannel::Update, - Bytes::from([&header, slice].concat()), - ); - } - - Ok(()) - } -} - -impl Default for UpdateMessage { - fn default() -> Self { - Self { - cursor: Default::default(), - entities: Default::default(), - entity_data_size: Default::default(), - entity_data_pos: Default::default(), - entity_data_size_pos: Default::default(), - data_entity: Entity::PLACEHOLDER, - } - } -} - /// Writes new data into a cursor and returns the serialized size. /// /// Reuses previously shared bytes if they exist, or updates them. @@ -661,13 +127,6 @@ fn write_with<'a>( Ok(size) } -fn can_pack(header_size: usize, base: usize, add: usize) -> bool { - const MAX_PACKET_SIZE: usize = 1200; // TODO: make it configurable by the messaging backend. - - let dangling = (base + header_size) % MAX_PACKET_SIZE; - (dangling > 0) && ((dangling + add) <= MAX_PACKET_SIZE) -} - /// Serializes `entity` by writing its index and generation as separate varints. /// /// The index is first prepended with a bit flag to indicate if the generation @@ -686,21 +145,3 @@ fn serialize_entity(cursor: &mut Cursor>, entity: Entity) -> bincode::Re Ok(()) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn packing() { - assert!(can_pack(10, 0, 5)); - assert!(can_pack(10, 0, 1190)); - assert!(!can_pack(10, 0, 1191)); - assert!(!can_pack(10, 0, 3000)); - - assert!(can_pack(10, 1189, 1)); - assert!(!can_pack(10, 1190, 0)); - assert!(!can_pack(10, 1190, 1)); - assert!(!can_pack(10, 1190, 3000)); - } -} diff --git a/src/server/replication_messages/init_message.rs b/src/server/replication_messages/init_message.rs new file mode 100644 index 00000000..6ff8eccf --- /dev/null +++ b/src/server/replication_messages/init_message.rs @@ -0,0 +1,341 @@ +use std::{ + io::{Cursor, Write}, + mem, +}; + +use bevy::{prelude::*, ptr::Ptr}; +use bincode::{DefaultOptions, Options}; +use bytes::Bytes; + +use super::update_message::UpdateMessage; +use crate::{ + core::{ + channels::ReplicationChannel, + ctx::SerializeCtx, + replicated_clients::ReplicatedClient, + replication_registry::{component_fns::ComponentFns, rule_fns::UntypedRuleFns, FnsId}, + replicon_server::RepliconServer, + replicon_tick::RepliconTick, + }, + server::client_entity_map::ClientMapping, +}; + +/// A reusable message with replicated data. +/// +/// Contains tick and mappings, insertions, removals and despawns that +/// happened on this tick. +/// Sent over [`ReplicationChannel::Init`] channel. +/// +/// See also [Limits](../index.html#limits) +pub(crate) struct InitMessage { + /// Serialized data. + cursor: Cursor>, + + /// Length of the array that updated automatically after writing data. + array_len: u16, + + /// Position of the array from last call of [`Self::start_array`]. + array_pos: u64, + + /// The number of empty arrays at the end. + trailing_empty_arrays: usize, + + /// Entity from last call of [`Self::start_entity_data`]. + data_entity: Entity, + + /// Size in bytes of the component data stored for the currently-being-written entity. + entity_data_size: u16, + + /// Position of entity from last call of [`Self::start_entity_data`]. + entity_data_pos: u64, + + /// Position of entity data length from last call of [`Self::write_data_entity`]. + entity_data_size_pos: u64, +} + +impl InitMessage { + /// Clears the message. + /// + /// Keeps allocated capacity for reuse. + pub(super) fn reset(&mut self) { + self.cursor.set_position(0); + self.trailing_empty_arrays = 0; + } + + /// Returns size in bytes of the current entity data. + /// + /// See also [`Self::start_entity_data`] and [`Self::end_entity_data`]. + pub(crate) fn entity_data_size(&self) -> u16 { + self.entity_data_size + } + + /// Starts writing array by remembering its position to write length after. + /// + /// Arrays can contain entity data or despawns inside. + /// See also [`Self::end_array`], [`Self::write_client_mapping`], [`Self::write_entity`] and [`Self::start_entity_data`]. + pub(crate) fn start_array(&mut self) { + debug_assert_eq!(self.array_len, 0); + + self.array_pos = self.cursor.position(); + self.cursor + .set_position(self.array_pos + mem::size_of_val(&self.array_len) as u64); + } + + /// Ends writing array by writing its length into the last remembered position. + /// + /// See also [`Self::start_array`]. + pub(crate) fn end_array(&mut self) -> bincode::Result<()> { + if self.array_len != 0 { + let previous_pos = self.cursor.position(); + self.cursor.set_position(self.array_pos); + + bincode::serialize_into(&mut self.cursor, &self.array_len)?; + + self.cursor.set_position(previous_pos); + self.array_len = 0; + self.trailing_empty_arrays = 0; + } else { + self.trailing_empty_arrays += 1; + self.cursor.set_position(self.array_pos); + bincode::serialize_into(&mut self.cursor, &self.array_len)?; + } + + Ok(()) + } + + /// Serializes entity to entity mapping as an array element. + /// + /// Should be called only inside an array and increases its length by 1. + /// See also [`Self::start_array`]. + pub(crate) fn write_client_mapping(&mut self, mapping: &ClientMapping) -> bincode::Result<()> { + super::serialize_entity(&mut self.cursor, mapping.server_entity)?; + super::serialize_entity(&mut self.cursor, mapping.client_entity)?; + self.array_len = self + .array_len + .checked_add(1) + .ok_or(bincode::ErrorKind::SizeLimit)?; + + Ok(()) + } + + /// Serializes entity as an array element. + /// + /// Reuses previously shared bytes if they exist, or updates them. + /// Should be called only inside an array and increases its length by 1. + /// See also [`Self::start_array`]. + pub(crate) fn write_entity<'a>( + &'a mut self, + shared_bytes: &mut Option<&'a [u8]>, + entity: Entity, + ) -> bincode::Result<()> { + super::write_with(shared_bytes, &mut self.cursor, |cursor| { + super::serialize_entity(cursor, entity) + })?; + + self.array_len = self + .array_len + .checked_add(1) + .ok_or(bincode::ErrorKind::SizeLimit)?; + + Ok(()) + } + + /// Starts writing entity and its data as an array element. + /// + /// Should be called only inside an array and increases its length by 1. + /// Data can contain components with their IDs or IDs only. + /// Entity will be written lazily after first data write. + /// See also [`Self::end_entity_data`] and [`Self::write_component`]. + pub(crate) fn start_entity_data(&mut self, entity: Entity) { + debug_assert_eq!(self.entity_data_size, 0); + + self.data_entity = entity; + self.entity_data_pos = self.cursor.position(); + } + + /// Writes entity for the current data and remembers the position after it to write length later. + /// + /// Should be called only after first data write. + fn write_data_entity(&mut self) -> bincode::Result<()> { + super::serialize_entity(&mut self.cursor, self.data_entity)?; + self.entity_data_size_pos = self.cursor.position(); + self.cursor.set_position( + self.entity_data_size_pos + mem::size_of_val(&self.entity_data_size) as u64, + ); + + Ok(()) + } + + /// Ends writing entity data by writing its length into the last remembered position. + /// + /// If the entity data is empty, nothing will be written unless `save_empty` is set to true. + /// Should be called only inside an array and increases its length by 1. + /// See also [`Self::start_array`], [`Self::write_component`] and + /// [`Self::write_component_id`]. + pub(crate) fn end_entity_data(&mut self, save_empty: bool) -> bincode::Result<()> { + if self.entity_data_size == 0 && !save_empty { + self.cursor.set_position(self.entity_data_pos); + return Ok(()); + } + + if self.entity_data_size == 0 { + self.write_data_entity()?; + } + + let previous_pos = self.cursor.position(); + self.cursor.set_position(self.entity_data_size_pos); + + bincode::serialize_into(&mut self.cursor, &self.entity_data_size)?; + + self.cursor.set_position(previous_pos); + self.entity_data_size = 0; + self.array_len = self + .array_len + .checked_add(1) + .ok_or(bincode::ErrorKind::SizeLimit)?; + + Ok(()) + } + + /// Serializes component and its replication functions ID as an element of entity data. + /// + /// Reuses previously shared bytes if they exist, or updates them. + /// Should be called only inside an entity data and increases its size. + /// See also [`Self::start_entity_data`]. + pub(crate) fn write_component<'a>( + &'a mut self, + shared_bytes: &mut Option<&'a [u8]>, + rule_fns: &UntypedRuleFns, + component_fns: &ComponentFns, + ctx: &SerializeCtx, + fns_id: FnsId, + ptr: Ptr, + ) -> bincode::Result<()> { + if self.entity_data_size == 0 { + self.write_data_entity()?; + } + + let size = super::write_with(shared_bytes, &mut self.cursor, |cursor| { + DefaultOptions::new().serialize_into(&mut *cursor, &fns_id)?; + // SAFETY: `component_fns`, `ptr` and `rule_fns` were created for the same component type. + unsafe { component_fns.serialize(ctx, rule_fns, ptr, cursor) } + })?; + + self.entity_data_size = self + .entity_data_size + .checked_add(size) + .ok_or(bincode::ErrorKind::SizeLimit)?; + + Ok(()) + } + + /// Serializes replication functions ID as an element of entity data. + /// + /// Should be called only inside an entity data and increases its size. + /// See also [`Self::start_entity_data`]. + pub(crate) fn write_fns_id(&mut self, fns_id: FnsId) -> bincode::Result<()> { + if self.entity_data_size == 0 { + self.write_data_entity()?; + } + + let previous_pos = self.cursor.position(); + DefaultOptions::new().serialize_into(&mut self.cursor, &fns_id)?; + + let id_size = self.cursor.position() - previous_pos; + self.entity_data_size = self + .entity_data_size + .checked_add(id_size as u16) + .ok_or(bincode::ErrorKind::SizeLimit)?; + + Ok(()) + } + + /// Removes entity data elements from update message and copies it. + /// + /// Ends entity data for the update message. + /// See also [`Self::start_entity_data`] and [`Self::end_entity_data`]. + pub(crate) fn take_entity_data( + &mut self, + update_message: &mut UpdateMessage, + ) -> bincode::Result<()> { + if update_message.entity_data_size != 0 { + if self.entity_data_size == 0 { + self.write_data_entity()?; + } + + let slice = update_message.as_slice(); + let offset = update_message.entity_data_size_pos as usize + + mem::size_of_val(&update_message.entity_data_size); + self.cursor.write_all(&slice[offset..]).unwrap(); + + self.entity_data_size = self + .entity_data_size + .checked_add(update_message.entity_data_size) + .ok_or(bincode::ErrorKind::SizeLimit)?; + update_message.entity_data_size = 0; + } + + update_message + .cursor + .set_position(update_message.entity_data_pos); + + Ok(()) + } + + /// Returns the serialized data, excluding trailing empty arrays, as a byte array. + fn as_slice(&self) -> &[u8] { + let slice = self.cursor.get_ref(); + let position = self.cursor.position() as usize; + let extra_len = self.trailing_empty_arrays * mem::size_of_val(&self.array_len); + &slice[..position - extra_len] + } + + /// Sends the message, excluding trailing empty arrays, to the specified client. + /// + /// Updates change tick for the client if there are data to send. + /// Does nothing if there is no data to send. + pub(super) fn send( + &self, + server: &mut RepliconServer, + client: &mut ReplicatedClient, + server_tick: RepliconTick, + ) -> bincode::Result<()> { + debug_assert_eq!(self.array_len, 0); + debug_assert_eq!(self.entity_data_size, 0); + + let slice = self.as_slice(); + if slice.is_empty() { + trace!("no init data to send for {:?}", client.id()); + return Ok(()); + } + + client.set_init_tick(server_tick); + + let mut header = [0; mem::size_of::()]; + bincode::serialize_into(&mut header[..], &server_tick)?; + + trace!("sending init message to {:?}", client.id()); + server.send( + client.id(), + ReplicationChannel::Init, + Bytes::from([&header, slice].concat()), + ); + + Ok(()) + } +} + +impl Default for InitMessage { + fn default() -> Self { + Self { + cursor: Default::default(), + array_len: Default::default(), + array_pos: Default::default(), + trailing_empty_arrays: Default::default(), + entity_data_size: Default::default(), + entity_data_pos: Default::default(), + entity_data_size_pos: Default::default(), + data_entity: Entity::PLACEHOLDER, + } + } +} diff --git a/src/server/replication_messages/update_message.rs b/src/server/replication_messages/update_message.rs new file mode 100644 index 00000000..0fc2bfd6 --- /dev/null +++ b/src/server/replication_messages/update_message.rs @@ -0,0 +1,252 @@ +use std::{io::Cursor, mem, time::Duration}; + +use bevy::{ecs::component::Tick, prelude::*, ptr::Ptr}; +use bincode::{DefaultOptions, Options}; +use bytes::Bytes; + +use crate::core::{ + channels::ReplicationChannel, + ctx::SerializeCtx, + replicated_clients::{ClientBuffers, ReplicatedClient}, + replication_registry::{component_fns::ComponentFns, rule_fns::UntypedRuleFns, FnsId}, + replicon_server::RepliconServer, + replicon_tick::RepliconTick, +}; + +/// A reusable message with replicated component updates. +/// +/// Contains change tick, current tick and component updates since the last acknowledged tick for each entity. +/// Cannot be applied on the client until the init message matching this update message's change tick +/// has been applied to the client world. +/// The message will be manually split into packets up to max size, and each packet will be applied +/// independently on the client. +/// Message splits only happen per-entity to avoid weird behavior from partial entity updates. +/// Sent over the [`ReplicationChannel::Update`] channel. +/// +/// See also [Limits](../index.html#limits) +pub(crate) struct UpdateMessage { + /// Serialized data. + pub(super) cursor: Cursor>, + + /// Entities and their sizes in the message with data. + entities: Vec<(Entity, usize)>, + + /// Entity from last call of [`Self::start_entity_data`]. + data_entity: Entity, + + /// Size in bytes of the component data stored for the currently-being-written entity. + pub(super) entity_data_size: u16, + + /// Position of entity from last call of [`Self::start_entity_data`]. + pub(super) entity_data_pos: u64, + + /// Position of entity data length from last call of [`Self::write_data_entity`]. + pub(super) entity_data_size_pos: u64, +} + +impl UpdateMessage { + /// Clears the message. + /// + /// Keeps allocated capacity for reuse. + pub(super) fn reset(&mut self) { + self.cursor.set_position(0); + self.entities.clear(); + } + + /// Starts writing entity and its data. + /// + /// Data can contain components with their IDs. + /// Entity will be written lazily after first data write. + /// See also [`Self::end_entity_data`] and [`Self::write_component`]. + pub(crate) fn start_entity_data(&mut self, entity: Entity) { + debug_assert_eq!(self.entity_data_size, 0); + + self.data_entity = entity; + self.entity_data_pos = self.cursor.position(); + } + + /// Writes entity for the current data and remembers the position after it to write length later. + /// + /// Should be called only after first data write. + fn write_data_entity(&mut self) -> bincode::Result<()> { + super::serialize_entity(&mut self.cursor, self.data_entity)?; + self.entity_data_size_pos = self.cursor.position(); + self.cursor.set_position( + self.entity_data_size_pos + mem::size_of_val(&self.entity_data_size) as u64, + ); + + Ok(()) + } + + /// Ends writing entity data by writing its length into the last remembered position. + /// + /// If the entity data is empty, nothing will be written and the cursor will reset. + /// See also [`Self::start_array`] and [`Self::write_component`]. + pub(crate) fn end_entity_data(&mut self) -> bincode::Result<()> { + if self.entity_data_size == 0 { + self.cursor.set_position(self.entity_data_pos); + return Ok(()); + } + + let previous_pos = self.cursor.position(); + self.cursor.set_position(self.entity_data_size_pos); + + bincode::serialize_into(&mut self.cursor, &self.entity_data_size)?; + + self.cursor.set_position(previous_pos); + + let data_size = self.cursor.position() - self.entity_data_pos; + self.entities.push((self.data_entity, data_size as usize)); + + self.entity_data_size = 0; + + Ok(()) + } + + /// Serializes component and its replication functions ID as an element of entity data. + /// + /// Reuses previously shared bytes if they exist, or updates them. + /// Should be called only inside an entity data and increases its size. + /// See also [`Self::start_entity_data`]. + pub(crate) fn write_component<'a>( + &'a mut self, + shared_bytes: &mut Option<&'a [u8]>, + rule_fns: &UntypedRuleFns, + component_fns: &ComponentFns, + ctx: &SerializeCtx, + fns_id: FnsId, + ptr: Ptr, + ) -> bincode::Result<()> { + if self.entity_data_size == 0 { + self.write_data_entity()?; + } + + let size = super::write_with(shared_bytes, &mut self.cursor, |cursor| { + DefaultOptions::new().serialize_into(&mut *cursor, &fns_id)?; + // SAFETY: `component_fns`, `ptr` and `rule_fns` were created for the same component type. + unsafe { component_fns.serialize(ctx, rule_fns, ptr, cursor) } + })?; + + self.entity_data_size = self + .entity_data_size + .checked_add(size) + .ok_or(bincode::ErrorKind::SizeLimit)?; + + Ok(()) + } + + /// Returns the serialized data as a byte array. + pub(super) fn as_slice(&self) -> &[u8] { + let slice = self.cursor.get_ref(); + let position = self.cursor.position() as usize; + &slice[..position] + } + + /// Splits message according to entities inside it and sends it to the specified client. + /// + /// Does nothing if there is no data to send. + pub(super) fn send( + &mut self, + server: &mut RepliconServer, + client_buffers: &mut ClientBuffers, + client: &mut ReplicatedClient, + server_tick: RepliconTick, + tick: Tick, + timestamp: Duration, + ) -> bincode::Result<()> { + debug_assert_eq!(self.entity_data_size, 0); + + let mut slice = self.as_slice(); + if slice.is_empty() { + trace!("no updates to send for {:?}", client.id()); + return Ok(()); + } + + trace!("sending update message(s) to {:?}", client.id()); + const TICKS_SIZE: usize = 2 * mem::size_of::(); + let mut header = [0; TICKS_SIZE + mem::size_of::()]; + bincode::serialize_into(&mut header[..], &(client.init_tick(), server_tick))?; + + let mut message_size = 0; + let client_id = client.id(); + let (mut update_index, mut entities) = + client.register_update(client_buffers, tick, timestamp); + for &(entity, data_size) in &self.entities { + // Try to pack back first, then try to pack forward. + if message_size == 0 + || can_pack(header.len(), message_size, data_size) + || can_pack(header.len(), data_size, message_size) + { + entities.push(entity); + message_size += data_size; + } else { + let (message, remaining) = slice.split_at(message_size); + slice = remaining; + message_size = data_size; + + bincode::serialize_into(&mut header[TICKS_SIZE..], &update_index)?; + + server.send( + client_id, + ReplicationChannel::Update, + Bytes::from([&header, message].concat()), + ); + + if !slice.is_empty() { + (update_index, entities) = + client.register_update(client_buffers, tick, timestamp); + } + } + } + + if !slice.is_empty() { + bincode::serialize_into(&mut header[TICKS_SIZE..], &update_index)?; + + server.send( + client_id, + ReplicationChannel::Update, + Bytes::from([&header, slice].concat()), + ); + } + + Ok(()) + } +} + +impl Default for UpdateMessage { + fn default() -> Self { + Self { + cursor: Default::default(), + entities: Default::default(), + entity_data_size: Default::default(), + entity_data_pos: Default::default(), + entity_data_size_pos: Default::default(), + data_entity: Entity::PLACEHOLDER, + } + } +} + +fn can_pack(header_size: usize, base: usize, add: usize) -> bool { + const MAX_PACKET_SIZE: usize = 1200; // TODO: make it configurable by the messaging backend. + + let dangling = (base + header_size) % MAX_PACKET_SIZE; + (dangling > 0) && ((dangling + add) <= MAX_PACKET_SIZE) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn packing() { + assert!(can_pack(10, 0, 5)); + assert!(can_pack(10, 0, 1190)); + assert!(!can_pack(10, 0, 1191)); + assert!(!can_pack(10, 0, 3000)); + + assert!(can_pack(10, 1189, 1)); + assert!(!can_pack(10, 1190, 0)); + assert!(!can_pack(10, 1190, 1)); + assert!(!can_pack(10, 1190, 3000)); + } +} From 287958979f14feea6d6c50907fd9dbba13844027 Mon Sep 17 00:00:00 2001 From: aecsocket <43144841+aecsocket@users.noreply.github.com> Date: Thu, 7 Nov 2024 23:10:26 +0000 Subject: [PATCH 02/31] Add `aeronet_replicon` to messaging backends list [skip ci] (#348) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 38305ba0..e3ebdb07 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Have any questions? Feel free to ask in the dedicated [`bevy_replicon` channel]( - [`bevy_replicon_renet`](https://github.com/projectharmonia/bevy_replicon_renet) - integration for [`bevy_renet`](https://github.com/lucaspoffo/renet/tree/master/bevy_renet). Maintained by the authors of this crate. - [`bevy_replicon_renet2`](https://github.com/UkoeHB/renet2/tree/main/bevy_replicon_renet2) - integration for [`bevy_renet2`](https://github.com/UkoeHB/renet2/tree/main/bevy_renet2). Includes a WebTransport backend for browsers, and enables servers that can manage multi-platform clients simultaneously. - [`bevy_replicon_quinnet`](https://github.com/Henauxg/bevy_quinnet/tree/main/bevy_replicon_quinnet) - integration for [`bevy_quinnet`](https://github.com/Henauxg/bevy_quinnet). +- [`aeronet_replicon`](https://github.com/aecsocket/aeronet/tree/main/crates/aeronet_replicon) - integration for [`aeronet`](https://github.com/aecsocket/aeronet). Works on any IO layer supported by `aeronet_io`, but requires `aeronet_transport`. #### Helpers From 766ad2f6431cbefe9deec960930bdcc4f84b1bf6 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Thu, 7 Nov 2024 13:36:52 +0200 Subject: [PATCH 03/31] Reorder functions in their call order. For clarity, no functional changes. --- src/server.rs | 114 +++++++++++++++++++++++++------------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/server.rs b/src/server.rs index 958e0635..62cca828 100644 --- a/src/server.rs +++ b/src/server.rs @@ -339,6 +339,63 @@ fn collect_mappings( Ok(()) } +/// Collect entity despawns from this tick into init messages. +fn collect_despawns( + messages: &mut ReplicationMessages, + despawn_buffer: &mut DespawnBuffer, +) -> bincode::Result<()> { + for (message, _) in messages.iter_mut() { + message.start_array(); + } + + for entity in despawn_buffer.drain(..) { + let mut shared_bytes = None; + for (message, _, client) in messages.iter_mut_with_clients() { + client.remove_despawned(entity); + message.write_entity(&mut shared_bytes, entity)?; + } + } + + for (message, _, client) in messages.iter_mut_with_clients() { + for entity in client.drain_lost_visibility() { + message.write_entity(&mut None, entity)?; + } + + message.end_array()?; + } + + Ok(()) +} + +/// Collects component removals from this tick into init messages. +fn collect_removals( + messages: &mut ReplicationMessages, + removal_buffer: &mut RemovalBuffer, + entities_with_removals: &mut EntityHashSet, +) -> bincode::Result<()> { + for (message, _) in messages.iter_mut() { + message.start_array(); + } + + for (entity, remove_ids) in removal_buffer.iter() { + for (message, _) in messages.iter_mut() { + message.start_entity_data(entity); + for fns_info in remove_ids { + message.write_fns_id(fns_info.fns_id())?; + } + entities_with_removals.insert(entity); + message.end_entity_data(false)?; + } + } + removal_buffer.clear(); + + for (message, _) in messages.iter_mut() { + message.end_array()?; + } + + Ok(()) +} + /// Collects component insertions from this tick into init messages, and changes into update messages /// since the last entity tick. fn collect_changes( @@ -509,63 +566,6 @@ unsafe fn get_component_unchecked<'w>( } } -/// Collect entity despawns from this tick into init messages. -fn collect_despawns( - messages: &mut ReplicationMessages, - despawn_buffer: &mut DespawnBuffer, -) -> bincode::Result<()> { - for (message, _) in messages.iter_mut() { - message.start_array(); - } - - for entity in despawn_buffer.drain(..) { - let mut shared_bytes = None; - for (message, _, client) in messages.iter_mut_with_clients() { - client.remove_despawned(entity); - message.write_entity(&mut shared_bytes, entity)?; - } - } - - for (message, _, client) in messages.iter_mut_with_clients() { - for entity in client.drain_lost_visibility() { - message.write_entity(&mut None, entity)?; - } - - message.end_array()?; - } - - Ok(()) -} - -/// Collects component removals from this tick into init messages. -fn collect_removals( - messages: &mut ReplicationMessages, - removal_buffer: &mut RemovalBuffer, - entities_with_removals: &mut EntityHashSet, -) -> bincode::Result<()> { - for (message, _) in messages.iter_mut() { - message.start_array(); - } - - for (entity, remove_ids) in removal_buffer.iter() { - for (message, _) in messages.iter_mut() { - message.start_entity_data(entity); - for fns_info in remove_ids { - message.write_fns_id(fns_info.fns_id())?; - } - entities_with_removals.insert(entity); - message.end_entity_data(false)?; - } - } - removal_buffer.clear(); - - for (message, _) in messages.iter_mut() { - message.end_array()?; - } - - Ok(()) -} - /// Set with replication and event systems related to server. #[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)] pub enum ServerSet { From c37f979df94bafe69e3221a969343166c86240df Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Fri, 8 Nov 2024 18:11:59 +0200 Subject: [PATCH 04/31] Remove `FnsInfo` I think it's nicer with a tuple. Made me notice that `TestFnsEntityExt` need only `FnsId`. --- CHANGELOG.md | 8 +++ src/core/replication_registry.rs | 26 +-------- src/core/replication_registry/test_fns.rs | 46 +++++++--------- src/core/replication_rules.rs | 14 ++--- src/scene.rs | 2 +- src/server.rs | 4 +- src/server/removal_buffer.rs | 18 +++---- src/server/replicated_archetypes.rs | 17 +++--- tests/fns.rs | 64 +++++++++++------------ 9 files changed, 86 insertions(+), 113 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f28a3b1..5b4dffd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- All `TestFnsEntityExt` now accept `FnsId`. + +### Removed + +- `FnsInfo`, use `(ComponentId, FnsId)` instead. + ## [0.28.4] - 2024-10-15 ### Added diff --git a/src/core/replication_registry.rs b/src/core/replication_registry.rs index 30031917..da82b9e1 100644 --- a/src/core/replication_registry.rs +++ b/src/core/replication_registry.rs @@ -103,14 +103,11 @@ impl ReplicationRegistry { &mut self, world: &mut World, rule_fns: RuleFns, - ) -> FnsInfo { + ) -> (ComponentId, FnsId) { let (index, component_id) = self.init_component_fns::(world); self.rules.push((rule_fns.into(), index)); - FnsInfo { - component_id, - fns_id: FnsId(self.rules.len() - 1), - } + (component_id, FnsId(self.rules.len() - 1)) } /// Initializes [`ComponentFns`] for a component and returns its index and ID. @@ -162,25 +159,6 @@ impl Default for ReplicationRegistry { #[deprecated(note = "use `Replicated` instead")] pub type ReplicationFns = ReplicationRegistry; -/// IDs of a registered replication function and its component. -/// -/// Can be obtained from [`ReplicationFns::register_rule_fns`]. -#[derive(Clone, Copy)] -pub struct FnsInfo { - component_id: ComponentId, - fns_id: FnsId, -} - -impl FnsInfo { - pub(crate) fn component_id(&self) -> ComponentId { - self.component_id - } - - pub(crate) fn fns_id(&self) -> FnsId { - self.fns_id - } -} - /// ID of replicaton functions for a component. /// /// Can be obtained from [`ReplicationFns::register_rule_fns`]. diff --git a/src/core/replication_registry/test_fns.rs b/src/core/replication_registry/test_fns.rs index 3edcb893..72bc4496 100644 --- a/src/core/replication_registry/test_fns.rs +++ b/src/core/replication_registry/test_fns.rs @@ -2,7 +2,7 @@ use std::io::Cursor; use bevy::{ecs::world::CommandQueue, prelude::*}; -use super::{FnsInfo, ReplicationRegistry}; +use super::{FnsId, ReplicationRegistry}; use crate::core::{ command_markers::{CommandMarkers, EntityMarkers}, ctx::{DespawnCtx, RemoveCtx, SerializeCtx, WriteCtx}, @@ -12,7 +12,7 @@ use crate::core::{ }; /** -Extension for [`EntityWorldMut`] to call registered replication functions for [`FnsInfo`]. +Extension for [`EntityWorldMut`] to call registered replication functions for [`FnsId`]. See also [`ReplicationRegistry::register_rule_fns`]. @@ -38,21 +38,21 @@ app.add_plugins((MinimalPlugins, RepliconPlugins)); let tick = RepliconTick::default(); -// Register rule functions manually to obtain `FnsInfo`. -let fns_info = app +// Register rule functions manually to obtain `FnsId`. +let (_, fns_id) = app .world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) }); let mut entity = app.world_mut().spawn(DummyComponent); -let data = entity.serialize(fns_info, tick); +let data = entity.serialize(fns_id, tick); entity.remove::(); -entity.apply_write(&data, fns_info, tick); +entity.apply_write(&data, fns_id, tick); assert!(entity.contains::()); -entity.apply_remove(fns_info, tick); +entity.apply_remove(fns_id, tick); assert!(!entity.contains::()); entity.apply_despawn(tick); @@ -67,41 +67,36 @@ pub trait TestFnsEntityExt { /// /// See also [`ReplicationRegistry::register_rule_fns`]. #[must_use] - fn serialize(&mut self, fns_info: FnsInfo, server_tick: RepliconTick) -> Vec; + fn serialize(&mut self, fns_id: FnsId, server_tick: RepliconTick) -> Vec; /// Deserializes a component using a registered function for it and /// writes it into an entity using a write function based on markers. /// /// See also [`AppMarkerExt`](crate::core::command_markers::AppMarkerExt). - fn apply_write( - &mut self, - data: &[u8], - fns_info: FnsInfo, - message_tick: RepliconTick, - ) -> &mut Self; + fn apply_write(&mut self, data: &[u8], fns_id: FnsId, message_tick: RepliconTick) -> &mut Self; /// Remvoes a component using a registered function for it. /// /// See also [`AppMarkerExt`](crate::core::command_markers::AppMarkerExt). - fn apply_remove(&mut self, fns_info: FnsInfo, message_tick: RepliconTick) -> &mut Self; + fn apply_remove(&mut self, fns_id: FnsId, message_tick: RepliconTick) -> &mut Self; /// Despawns an entity using [`ReplicationRegistry::despawn`]. fn apply_despawn(self, message_tick: RepliconTick); } impl TestFnsEntityExt for EntityWorldMut<'_> { - fn serialize(&mut self, fns_info: FnsInfo, server_tick: RepliconTick) -> Vec { + fn serialize(&mut self, fns_id: FnsId, server_tick: RepliconTick) -> Vec { let registry = self.world().resource::(); - let (component_id, component_fns, rule_fns) = registry.get(fns_info.fns_id()); + let (component_id, component_fns, rule_fns) = registry.get(fns_id); let mut cursor = Cursor::default(); let ctx = SerializeCtx { server_tick, component_id, }; - let ptr = self.get_by_id(fns_info.component_id()).unwrap_or_else(|| { + let ptr = self.get_by_id(component_id).unwrap_or_else(|| { let components = self.world().components(); let component_name = components - .get_name(fns_info.component_id()) + .get_name(component_id) .expect("function should require valid component ID"); panic!("serialization function require entity to have {component_name}"); }); @@ -115,12 +110,7 @@ impl TestFnsEntityExt for EntityWorldMut<'_> { cursor.into_inner() } - fn apply_write( - &mut self, - data: &[u8], - fns_info: FnsInfo, - message_tick: RepliconTick, - ) -> &mut Self { + fn apply_write(&mut self, data: &[u8], fns_id: FnsId, message_tick: RepliconTick) -> &mut Self { let mut entity_markers = self.world_scope(EntityMarkers::from_world); let command_markers = self.world().resource::(); entity_markers.read(command_markers, &*self); @@ -136,7 +126,7 @@ impl TestFnsEntityExt for EntityWorldMut<'_> { let mut commands = Commands::new_from_entities(&mut queue, world_cell.entities()); - let (component_id, component_fns, rule_fns) = registry.get(fns_info.fns_id()); + let (component_id, component_fns, rule_fns) = registry.get(fns_id); let mut cursor = Cursor::new(data); let mut ctx = WriteCtx::new(&mut commands, &mut entity_map, component_id, message_tick); @@ -161,7 +151,7 @@ impl TestFnsEntityExt for EntityWorldMut<'_> { self } - fn apply_remove(&mut self, fns_info: FnsInfo, message_tick: RepliconTick) -> &mut Self { + fn apply_remove(&mut self, fns_id: FnsId, message_tick: RepliconTick) -> &mut Self { let mut entity_markers = self.world_scope(EntityMarkers::from_world); let command_markers = self.world().resource::(); entity_markers.read(command_markers, &*self); @@ -175,7 +165,7 @@ impl TestFnsEntityExt for EntityWorldMut<'_> { let mut queue = CommandQueue::default(); let mut commands = Commands::new_from_entities(&mut queue, world_cell.entities()); - let (component_id, component_fns, _) = registry.get(fns_info.fns_id()); + let (component_id, component_fns, _) = registry.get(fns_id); let mut ctx = RemoveCtx { commands: &mut commands, message_tick, diff --git a/src/core/replication_rules.rs b/src/core/replication_rules.rs index 927a9498..2e971d0e 100644 --- a/src/core/replication_rules.rs +++ b/src/core/replication_rules.rs @@ -7,7 +7,7 @@ use bevy::{ }; use serde::{de::DeserializeOwned, Serialize}; -use super::replication_registry::{rule_fns::RuleFns, FnsInfo, ReplicationRegistry}; +use super::replication_registry::{rule_fns::RuleFns, FnsId, ReplicationRegistry}; /// Replication functions for [`App`]. pub trait AppRuleExt { @@ -217,12 +217,12 @@ pub struct ReplicationRule { pub priority: usize, /// Rule components and their serialization/deserialization/removal functions. - pub components: Vec, + pub components: Vec<(ComponentId, FnsId)>, } impl ReplicationRule { /// Creates a new rule with priority equal to the number of serializable components. - pub fn new(components: Vec) -> Self { + pub fn new(components: Vec<(ComponentId, FnsId)>) -> Self { Self { priority: components.len(), components, @@ -233,7 +233,7 @@ impl ReplicationRule { pub(crate) fn matches(&self, archetype: &Archetype) -> bool { self.components .iter() - .all(|fns_info| archetype.contains(fns_info.component_id())) + .all(|&(component_id, _)| archetype.contains(component_id)) } /// Determines whether the rule is applicable to an archetype with removals included and contains at least one removal. @@ -248,10 +248,10 @@ impl ReplicationRule { removed_components: &HashSet, ) -> bool { let mut matches = false; - for fns_info in &self.components { - if removed_components.contains(&fns_info.component_id()) { + for &(component_id, _) in &self.components { + if removed_components.contains(&component_id) { matches = true; - } else if !post_removal_archetype.contains(fns_info.component_id()) { + } else if !post_removal_archetype.contains(component_id) { return false; } } diff --git a/src/scene.rs b/src/scene.rs index b53a1e9b..08def37a 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -73,7 +73,7 @@ pub fn replicate_into(scene: &mut DynamicScene, world: &World) { for component_id in rule .components .iter() - .map(|fns_info| fns_info.component_id()) + .map(|&(component_id, _)| component_id) { // SAFETY: replication rules can be registered only with valid component IDs. let replicated_component = diff --git a/src/server.rs b/src/server.rs index 62cca828..30cc35f9 100644 --- a/src/server.rs +++ b/src/server.rs @@ -380,8 +380,8 @@ fn collect_removals( for (entity, remove_ids) in removal_buffer.iter() { for (message, _) in messages.iter_mut() { message.start_entity_data(entity); - for fns_info in remove_ids { - message.write_fns_id(fns_info.fns_id())?; + for &(_, fns_id) in remove_ids { + message.write_fns_id(fns_id)?; } entities_with_removals.insert(entity); message.end_entity_data(false)?; diff --git a/src/server/removal_buffer.rs b/src/server/removal_buffer.rs index 113f1365..cd7a4faa 100644 --- a/src/server/removal_buffer.rs +++ b/src/server/removal_buffer.rs @@ -13,7 +13,7 @@ use bevy::{ use super::{ServerPlugin, ServerSet}; use crate::core::{ - common_conditions::server_running, replication_registry::FnsInfo, + common_conditions::server_running, replication_registry::FnsId, replication_rules::ReplicationRules, Replicated, }; @@ -131,7 +131,7 @@ impl FromWorld for ReplicatedComponents { let component_ids = rules .iter() .flat_map(|rule| &rule.components) - .map(|fns_info| fns_info.component_id()) + .map(|&(component_id, _)| component_id) .collect(); Self(component_ids) @@ -142,18 +142,18 @@ impl FromWorld for ReplicatedComponents { #[derive(Default, Resource)] pub(crate) struct RemovalBuffer { /// Component removals grouped by entity. - removals: Vec<(Entity, Vec)>, + removals: Vec<(Entity, Vec<(ComponentId, FnsId)>)>, /// [`Vec`]s from removals. /// /// All data is cleared before the insertion. /// Stored to reuse allocated capacity. - ids_buffer: Vec>, + ids_buffer: Vec>, } impl RemovalBuffer { /// Returns an iterator over entities and their removed components. - pub(super) fn iter(&self) -> impl Iterator { + pub(super) fn iter(&self) -> impl Iterator { self.removals .iter() .map(|(entity, remove_ids)| (*entity, &**remove_ids)) @@ -172,15 +172,15 @@ impl RemovalBuffer { .iter() .filter(|rule| rule.matches_removals(archetype, components)) { - for &fns_info in &rule.components { + for &(component_id, fns_id) in &rule.components { // Since rules are sorted by priority, // we are inserting only new components that aren't present. if removed_ids .iter() - .all(|removed_info| removed_info.component_id() != fns_info.component_id()) - && !archetype.contains(fns_info.component_id()) + .all(|&(removed_id, _)| removed_id != component_id) + && !archetype.contains(component_id) { - removed_ids.push(fns_info); + removed_ids.push((component_id, fns_id)); } } } diff --git a/src/server/replicated_archetypes.rs b/src/server/replicated_archetypes.rs index 0fff62e5..ae957cd5 100644 --- a/src/server/replicated_archetypes.rs +++ b/src/server/replicated_archetypes.rs @@ -45,18 +45,18 @@ impl ReplicatedArchetypes { { let mut replicated_archetype = ReplicatedArchetype::new(archetype.id()); for rule in rules.iter().filter(|rule| rule.matches(archetype)) { - for fns_info in &rule.components { + for &(component_id, fns_id) in &rule.components { // Since rules are sorted by priority, // we are inserting only new components that aren't present. if replicated_archetype .components .iter() - .any(|component| component.component_id == fns_info.component_id()) + .any(|component| component.component_id == component_id) { if enabled!(Level::DEBUG) { let component_name = world .components() - .get_name(fns_info.component_id()) + .get_name(component_id) .expect("rules should be registered with valid component"); let component_names: Vec<_> = replicated_archetype @@ -74,16 +74,13 @@ impl ReplicatedArchetypes { } // SAFETY: component ID obtained from this archetype. - let storage_type = unsafe { - archetype - .get_storage_type(fns_info.component_id()) - .unwrap_unchecked() - }; + let storage_type = + unsafe { archetype.get_storage_type(component_id).unwrap_unchecked() }; replicated_archetype.components.push(ReplicatedComponent { - component_id: fns_info.component_id(), + component_id, storage_type, - fns_id: fns_info.fns_id(), + fns_id, }); } } diff --git a/tests/fns.rs b/tests/fns.rs index daf69540..b0b14b05 100644 --- a/tests/fns.rs +++ b/tests/fns.rs @@ -22,14 +22,14 @@ fn serialize_missing_component() { app.add_plugins((MinimalPlugins, RepliconPlugins)); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) }); let mut entity = app.world_mut().spawn_empty(); - let _ = entity.serialize(fns_info, tick); + let _ = entity.serialize(fns_id, tick); } #[test] @@ -38,16 +38,16 @@ fn write() { app.add_plugins((MinimalPlugins, RepliconPlugins)); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) }); let mut entity = app.world_mut().spawn(OriginalComponent); - let data = entity.serialize(fns_info, tick); + let data = entity.serialize(fns_id, tick); entity.remove::(); - entity.apply_write(&data, fns_info, tick); + entity.apply_write(&data, fns_id, tick); assert!(entity.contains::()); } @@ -57,14 +57,14 @@ fn remove() { app.add_plugins((MinimalPlugins, RepliconPlugins)); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) }); let mut entity = app.world_mut().spawn(OriginalComponent); - entity.apply_remove(fns_info, tick); + entity.apply_remove(fns_id, tick); assert!(!entity.contains::()); } @@ -75,15 +75,15 @@ fn write_with_command() { .set_command_fns(replace, command_fns::default_remove::); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) }); let mut entity = app.world_mut().spawn(OriginalComponent); - let data = entity.serialize(fns_info, tick); - entity.apply_write(&data, fns_info, tick); + let data = entity.serialize(fns_id, tick); + entity.apply_write(&data, fns_id, tick); assert!(entity.contains::()); } @@ -94,14 +94,14 @@ fn remove_with_command() { .set_command_fns(replace, command_fns::default_remove::); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) }); let mut entity = app.world_mut().spawn(ReplacedComponent); - entity.apply_remove(fns_info, tick); + entity.apply_remove(fns_id, tick); assert!(!entity.contains::()); } @@ -116,16 +116,16 @@ fn write_without_marker() { ); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) }); let mut entity = app.world_mut().spawn(OriginalComponent); - let data = entity.serialize(fns_info, tick); + let data = entity.serialize(fns_id, tick); entity.remove::(); - entity.apply_write(&data, fns_info, tick); + entity.apply_write(&data, fns_id, tick); assert!(entity.contains::()); } @@ -140,14 +140,14 @@ fn remove_without_marker() { ); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) }); let mut entity = app.world_mut().spawn(OriginalComponent); - entity.apply_remove(fns_info, tick); + entity.apply_remove(fns_id, tick); assert!(!entity.contains::()); } @@ -162,15 +162,15 @@ fn write_with_marker() { ); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) }); let mut entity = app.world_mut().spawn((OriginalComponent, ReplaceMarker)); - let data = entity.serialize(fns_info, tick); - entity.apply_write(&data, fns_info, tick); + let data = entity.serialize(fns_id, tick); + entity.apply_write(&data, fns_id, tick); assert!(entity.contains::()); } @@ -185,14 +185,14 @@ fn remove_with_marker() { ); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) }); let mut entity = app.world_mut().spawn((ReplacedComponent, ReplaceMarker)); - entity.apply_remove(fns_info, tick); + entity.apply_remove(fns_id, tick); assert!(!entity.contains::()); } @@ -212,7 +212,7 @@ fn write_with_multiple_markers() { ); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) @@ -221,8 +221,8 @@ fn write_with_multiple_markers() { let mut entity = app .world_mut() .spawn((OriginalComponent, ReplaceMarker, DummyMarker)); - let data = entity.serialize(fns_info, tick); - entity.apply_write(&data, fns_info, tick); + let data = entity.serialize(fns_id, tick); + entity.apply_write(&data, fns_id, tick); assert!( entity.contains::(), "last marker should take priority" @@ -245,7 +245,7 @@ fn remove_with_mutltiple_markers() { ); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) @@ -254,7 +254,7 @@ fn remove_with_mutltiple_markers() { let mut entity = app .world_mut() .spawn((ReplacedComponent, ReplaceMarker, DummyMarker)); - entity.apply_remove(fns_info, tick); + entity.apply_remove(fns_id, tick); assert!( !entity.contains::(), "last marker should take priority" @@ -280,7 +280,7 @@ fn write_with_priority_marker() { ); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) @@ -289,8 +289,8 @@ fn write_with_priority_marker() { let mut entity = app .world_mut() .spawn((OriginalComponent, ReplaceMarker, DummyMarker)); - let data = entity.serialize(fns_info, tick); - entity.apply_write(&data, fns_info, tick); + let data = entity.serialize(fns_id, tick); + entity.apply_write(&data, fns_id, tick); assert!(entity.contains::()); } @@ -313,7 +313,7 @@ fn remove_with_priority_marker() { ); let tick = RepliconTick::default(); - let fns_info = + let (_, fns_id) = app.world_mut() .resource_scope(|world, mut registry: Mut| { registry.register_rule_fns(world, RuleFns::::default()) @@ -322,7 +322,7 @@ fn remove_with_priority_marker() { let mut entity = app .world_mut() .spawn((ReplacedComponent, ReplaceMarker, DummyMarker)); - entity.apply_remove(fns_info, tick); + entity.apply_remove(fns_id, tick); assert!(!entity.contains::()); } From 7fa9b780a9ae1284f8cfc521cc2431646d5331f8 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Fri, 8 Nov 2024 18:23:52 +0200 Subject: [PATCH 05/31] Reuse `ComponentId` from `get` for clarity `ComponentId` on `ReplicatedArchetype` needed only for comparsion, make it private. --- src/server.rs | 7 ++++--- src/server/replicated_archetypes.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/server.rs b/src/server.rs index 30cc35f9..1624ffc8 100644 --- a/src/server.rs +++ b/src/server.rs @@ -452,6 +452,9 @@ fn collect_changes( marker_ticks.is_added(change_tick.last_run(), change_tick.this_run()); for replicated_component in &replicated_archetype.components { + let (component_id, component_fns, rule_fns) = + registry.get(replicated_component.fns_id); + // SAFETY: component and storage were obtained from this archetype. let (component, ticks) = unsafe { get_component_unchecked( @@ -459,12 +462,10 @@ fn collect_changes( &world.storages().sparse_sets, entity, replicated_component.storage_type, - replicated_component.component_id, + component_id, ) }; - let (component_id, component_fns, rule_fns) = - registry.get(replicated_component.fns_id); let ctx = SerializeCtx { server_tick, component_id, diff --git a/src/server/replicated_archetypes.rs b/src/server/replicated_archetypes.rs index ae957cd5..9d159fa6 100644 --- a/src/server/replicated_archetypes.rs +++ b/src/server/replicated_archetypes.rs @@ -119,7 +119,7 @@ impl ReplicatedArchetype { /// Stores information about a replicated component. pub(super) struct ReplicatedComponent { - pub(super) component_id: ComponentId, + component_id: ComponentId, pub(super) storage_type: StorageType, pub(super) fns_id: FnsId, } From 1f20240489e45785fa6860648d6413af47642db5 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Fri, 8 Nov 2024 18:57:22 +0200 Subject: [PATCH 06/31] Reuse `RemovalBuffer` for checking for removals Instead of storing a `Vec` inside and then insert entities into a separate `HashSet`, just store removals as a `HashMap`. --- src/server.rs | 24 +++++++--------- src/server/removal_buffer.rs | 56 +++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/server.rs b/src/server.rs index 1624ffc8..d2166e60 100644 --- a/src/server.rs +++ b/src/server.rs @@ -12,7 +12,6 @@ use bevy::{ ecs::{ archetype::ArchetypeEntity, component::{ComponentId, ComponentTicks, StorageType}, - entity::EntityHashSet, storage::{SparseSets, Table}, system::SystemChangeTick, }, @@ -251,7 +250,6 @@ impl ServerPlugin { /// Collects [`ReplicationMessages`] and sends them. pub(super) fn send_replication( - mut entities_with_removals: Local, mut messages: Local, mut replicated_archetypes: Local, change_tick: SystemChangeTick, @@ -271,22 +269,24 @@ impl ServerPlugin { ) -> bincode::Result<()> { replicated_archetypes.update(set.p0(), &rules); - let replicated_clients = mem::take(&mut *set.p1()); // Take ownership to avoid borrowing issues. + // Take ownership to avoid borrowing issues. + let replicated_clients = mem::take(&mut *set.p1()); + let mut removal_buffer = mem::take(&mut *set.p4()); messages.prepare(replicated_clients); collect_mappings(&mut messages, &mut set.p2())?; collect_despawns(&mut messages, &mut set.p3())?; - collect_removals(&mut messages, &mut set.p4(), &mut entities_with_removals)?; + collect_removals(&mut messages, &removal_buffer)?; collect_changes( &mut messages, &replicated_archetypes, ®istry, - &entities_with_removals, + &removal_buffer, set.p0(), &change_tick, **server_tick, )?; - entities_with_removals.clear(); + removal_buffer.clear(); let mut client_buffers = mem::take(&mut *set.p5()); let replicated_clients = messages.send( @@ -299,6 +299,7 @@ impl ServerPlugin { // Return borrowed data back. *set.p1() = replicated_clients; + *set.p4() = removal_buffer; *set.p5() = client_buffers; Ok(()) @@ -370,24 +371,21 @@ fn collect_despawns( /// Collects component removals from this tick into init messages. fn collect_removals( messages: &mut ReplicationMessages, - removal_buffer: &mut RemovalBuffer, - entities_with_removals: &mut EntityHashSet, + removal_buffer: &RemovalBuffer, ) -> bincode::Result<()> { for (message, _) in messages.iter_mut() { message.start_array(); } - for (entity, remove_ids) in removal_buffer.iter() { + for (&entity, remove_ids) in removal_buffer.iter() { for (message, _) in messages.iter_mut() { message.start_entity_data(entity); for &(_, fns_id) in remove_ids { message.write_fns_id(fns_id)?; } - entities_with_removals.insert(entity); message.end_entity_data(false)?; } } - removal_buffer.clear(); for (message, _) in messages.iter_mut() { message.end_array()?; @@ -402,7 +400,7 @@ fn collect_changes( messages: &mut ReplicationMessages, replicated_archetypes: &ReplicatedArchetypes, registry: &ReplicationRegistry, - entities_with_removals: &EntityHashSet, + removal_buffer: &RemovalBuffer, world: &World, change_tick: &SystemChangeTick, server_tick: RepliconTick, @@ -515,7 +513,7 @@ fn collect_changes( let new_entity = marker_added || visibility == Visibility::Gained; if new_entity || init_message.entity_data_size() != 0 - || entities_with_removals.contains(&entity.id()) + || removal_buffer.contains_key(&entity.id()) { // If there is any insertion, removal, or we must initialize, include all updates into init message. // and bump the last acknowledged tick to keep entity updates atomic. diff --git a/src/server/removal_buffer.rs b/src/server/removal_buffer.rs index cd7a4faa..288ae2a5 100644 --- a/src/server/removal_buffer.rs +++ b/src/server/removal_buffer.rs @@ -139,10 +139,11 @@ impl FromWorld for ReplicatedComponents { } /// Buffer with removed components. -#[derive(Default, Resource)] +#[derive(Default, Resource, Deref)] pub(crate) struct RemovalBuffer { /// Component removals grouped by entity. - removals: Vec<(Entity, Vec<(ComponentId, FnsId)>)>, + #[deref] + removals: EntityHashMap>, /// [`Vec`]s from removals. /// @@ -152,13 +153,6 @@ pub(crate) struct RemovalBuffer { } impl RemovalBuffer { - /// Returns an iterator over entities and their removed components. - pub(super) fn iter(&self) -> impl Iterator { - self.removals - .iter() - .map(|(entity, remove_ids)| (*entity, &**remove_ids)) - } - /// Registers component removals that match replication rules for an entity. fn update( &mut self, @@ -188,7 +182,7 @@ impl RemovalBuffer { if removed_ids.is_empty() { self.ids_buffer.push(removed_ids); } else { - self.removals.push((entity, removed_ids)); + self.removals.insert(entity, removed_ids); } } @@ -197,7 +191,7 @@ impl RemovalBuffer { /// Keeps the allocated memory for reuse. pub(super) fn clear(&mut self) { self.ids_buffer - .extend(self.removals.drain(..).map(|(_, mut components)| { + .extend(self.removals.drain().map(|(_, mut components)| { components.clear(); components })); @@ -253,16 +247,18 @@ mod tests { app.update(); - app.world_mut() + let entity = app + .world_mut() .spawn((Replicated, ComponentA)) - .remove::(); + .remove::() + .id(); app.update(); let removal_buffer = app.world().resource::(); assert_eq!(removal_buffer.removals.len(), 1); - let (_, removals_id) = removal_buffer.removals.first().unwrap(); + let removals_id = removal_buffer.removals.get(&entity).unwrap(); assert_eq!(removals_id.len(), 1); } @@ -281,16 +277,18 @@ mod tests { app.update(); - app.world_mut() + let entity = app + .world_mut() .spawn((Replicated, ComponentA, ComponentB)) - .remove::<(ComponentA, ComponentB)>(); + .remove::<(ComponentA, ComponentB)>() + .id(); app.update(); let removal_buffer = app.world().resource::(); assert_eq!(removal_buffer.removals.len(), 1); - let (_, removals_id) = removal_buffer.removals.first().unwrap(); + let removals_id = removal_buffer.removals.get(&entity).unwrap(); assert_eq!(removals_id.len(), 2); } @@ -309,16 +307,18 @@ mod tests { app.update(); - app.world_mut() + let entity = app + .world_mut() .spawn((Replicated, ComponentA, ComponentB)) - .remove::(); + .remove::() + .id(); app.update(); let removal_buffer = app.world().resource::(); assert_eq!(removal_buffer.removals.len(), 1); - let (_, removals_id) = removal_buffer.removals.first().unwrap(); + let removals_id = removal_buffer.removals.get(&entity).unwrap(); assert_eq!(removals_id.len(), 1); } @@ -338,16 +338,18 @@ mod tests { app.update(); - app.world_mut() + let entity = app + .world_mut() .spawn((Replicated, ComponentA, ComponentB)) - .remove::<(ComponentA, ComponentB)>(); + .remove::<(ComponentA, ComponentB)>() + .id(); app.update(); let removal_buffer = app.world().resource::(); assert_eq!(removal_buffer.removals.len(), 1); - let (_, removals_id) = removal_buffer.removals.first().unwrap(); + let removals_id = removal_buffer.removals.get(&entity).unwrap(); assert_eq!(removals_id.len(), 2); } @@ -367,16 +369,18 @@ mod tests { app.update(); - app.world_mut() + let entity = app + .world_mut() .spawn((Replicated, ComponentA, ComponentB)) - .remove::(); + .remove::() + .id(); app.update(); let removal_buffer = app.world().resource::(); assert_eq!(removal_buffer.removals.len(), 1); - let (_, removals_id) = removal_buffer.removals.first().unwrap(); + let removals_id = removal_buffer.removals.get(&entity).unwrap(); assert_eq!(removals_id.len(), 1); } From a00436b30c6f02f8968a7f9fc3c690a1394a3fd1 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Fri, 8 Nov 2024 18:57:52 +0200 Subject: [PATCH 07/31] Reorder system params for clarity --- src/server.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/server.rs b/src/server.rs index d2166e60..04d4c2fe 100644 --- a/src/server.rs +++ b/src/server.rs @@ -256,10 +256,10 @@ impl ServerPlugin { mut set: ParamSet<( &World, ResMut, - ResMut, - ResMut, ResMut, ResMut, + ResMut, + ResMut, ResMut, )>, registry: Res, @@ -271,11 +271,13 @@ impl ServerPlugin { // Take ownership to avoid borrowing issues. let replicated_clients = mem::take(&mut *set.p1()); - let mut removal_buffer = mem::take(&mut *set.p4()); + let mut removal_buffer = mem::take(&mut *set.p2()); + let mut client_buffers = mem::take(&mut *set.p3()); + messages.prepare(replicated_clients); - collect_mappings(&mut messages, &mut set.p2())?; - collect_despawns(&mut messages, &mut set.p3())?; + collect_mappings(&mut messages, &mut set.p4())?; + collect_despawns(&mut messages, &mut set.p5())?; collect_removals(&mut messages, &removal_buffer)?; collect_changes( &mut messages, @@ -288,7 +290,6 @@ impl ServerPlugin { )?; removal_buffer.clear(); - let mut client_buffers = mem::take(&mut *set.p5()); let replicated_clients = messages.send( &mut set.p6(), &mut client_buffers, @@ -299,8 +300,8 @@ impl ServerPlugin { // Return borrowed data back. *set.p1() = replicated_clients; - *set.p4() = removal_buffer; - *set.p5() = client_buffers; + *set.p2() = removal_buffer; + *set.p3() = client_buffers; Ok(()) } From bebd5eef2092b9c380a79994c80b485ab5514a5e Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Fri, 8 Nov 2024 20:07:06 +0200 Subject: [PATCH 08/31] Refactor `ReplicationMessages` Instead of consuming `ReplicatedClients` and return it back after send, just pass it along. It makes much easier to reason about the code. This way I discovered an extra mut on one of the public methods. --- src/core/replicated_clients.rs | 2 +- src/server.rs | 54 ++++++++----- src/server/replication_messages.rs | 75 ++++--------------- .../replication_messages/init_message.rs | 2 +- .../replication_messages/update_message.rs | 4 +- 5 files changed, 56 insertions(+), 81 deletions(-) diff --git a/src/core/replicated_clients.rs b/src/core/replicated_clients.rs index f9addca8..4b3ad9d0 100644 --- a/src/core/replicated_clients.rs +++ b/src/core/replicated_clients.rs @@ -289,7 +289,7 @@ impl ReplicatedClient { } /// Gets the change tick for an entity that is replicated to this client. - pub fn get_change_tick(&mut self, entity: Entity) -> Option { + pub fn get_change_tick(&self, entity: Entity) -> Option { self.change_ticks.get(&entity).copied() } diff --git a/src/server.rs b/src/server.rs index 04d4c2fe..1126d443 100644 --- a/src/server.rs +++ b/src/server.rs @@ -270,17 +270,18 @@ impl ServerPlugin { replicated_archetypes.update(set.p0(), &rules); // Take ownership to avoid borrowing issues. - let replicated_clients = mem::take(&mut *set.p1()); + let mut replicated_clients = mem::take(&mut *set.p1()); let mut removal_buffer = mem::take(&mut *set.p2()); let mut client_buffers = mem::take(&mut *set.p3()); - messages.prepare(replicated_clients); + messages.reset(replicated_clients.len()); - collect_mappings(&mut messages, &mut set.p4())?; - collect_despawns(&mut messages, &mut set.p5())?; + collect_mappings(&mut messages, &replicated_clients, &mut set.p4())?; + collect_despawns(&mut messages, &mut replicated_clients, &mut set.p5())?; collect_removals(&mut messages, &removal_buffer)?; collect_changes( &mut messages, + &mut replicated_clients, &replicated_archetypes, ®istry, &removal_buffer, @@ -290,13 +291,23 @@ impl ServerPlugin { )?; removal_buffer.clear(); - let replicated_clients = messages.send( - &mut set.p6(), - &mut client_buffers, - **server_tick, - change_tick.this_run(), - time.elapsed(), - )?; + for ((init_message, update_message), client) in + messages.iter_mut().zip(replicated_clients.iter_mut()) + { + let server = &mut set.p6(); + + init_message.send(server, client, **server_tick)?; + update_message.send( + server, + client, + &mut client_buffers, + **server_tick, + change_tick.this_run(), + time.elapsed(), + )?; + + client.visibility_mut().update(); + } // Return borrowed data back. *set.p1() = replicated_clients; @@ -325,9 +336,10 @@ impl ServerPlugin { /// On deserialization mappings should be processed first, so all referenced entities after it will behave correctly. fn collect_mappings( messages: &mut ReplicationMessages, + replicated_clients: &ReplicatedClients, entity_map: &mut ClientEntityMap, ) -> bincode::Result<()> { - for (message, _, client) in messages.iter_mut_with_clients() { + for ((message, _), client) in messages.iter_mut().zip(replicated_clients.iter()) { message.start_array(); if let Some(mappings) = entity_map.0.get_mut(&client.id()) { @@ -344,6 +356,7 @@ fn collect_mappings( /// Collect entity despawns from this tick into init messages. fn collect_despawns( messages: &mut ReplicationMessages, + replicated_clients: &mut ReplicatedClients, despawn_buffer: &mut DespawnBuffer, ) -> bincode::Result<()> { for (message, _) in messages.iter_mut() { @@ -352,13 +365,13 @@ fn collect_despawns( for entity in despawn_buffer.drain(..) { let mut shared_bytes = None; - for (message, _, client) in messages.iter_mut_with_clients() { + for ((message, _), client) in messages.iter_mut().zip(replicated_clients.iter_mut()) { client.remove_despawned(entity); message.write_entity(&mut shared_bytes, entity)?; } } - for (message, _, client) in messages.iter_mut_with_clients() { + for ((message, _), client) in messages.iter_mut().zip(replicated_clients.iter_mut()) { for entity in client.drain_lost_visibility() { message.write_entity(&mut None, entity)?; } @@ -399,6 +412,7 @@ fn collect_removals( /// since the last entity tick. fn collect_changes( messages: &mut ReplicationMessages, + replicated_clients: &mut ReplicatedClients, replicated_archetypes: &ReplicatedArchetypes, registry: &ReplicationRegistry, removal_buffer: &RemovalBuffer, @@ -428,7 +442,9 @@ fn collect_changes( }; for entity in archetype.entities() { - for (init_message, update_message, client) in messages.iter_mut_with_clients() { + for ((init_message, update_message), client) in + messages.iter_mut().zip(replicated_clients.iter_mut()) + { init_message.start_entity_data(entity.id()); update_message.start_entity_data(entity.id()); client.visibility_mut().cache_visibility(entity.id()); @@ -470,7 +486,9 @@ fn collect_changes( component_id, }; let mut shared_bytes = None; - for (init_message, update_message, client) in messages.iter_mut_with_clients() { + for ((init_message, update_message), client) in + messages.iter_mut().zip(replicated_clients.iter_mut()) + { let visibility = client.visibility().cached_visibility(); if visibility == Visibility::Hidden { continue; @@ -505,7 +523,9 @@ fn collect_changes( } } - for (init_message, update_message, client) in messages.iter_mut_with_clients() { + for ((init_message, update_message), client) in + messages.iter_mut().zip(replicated_clients.iter_mut()) + { let visibility = client.visibility().cached_visibility(); if visibility == Visibility::Hidden { continue; diff --git a/src/server/replication_messages.rs b/src/server/replication_messages.rs index 468ecadf..f5629b01 100644 --- a/src/server/replication_messages.rs +++ b/src/server/replication_messages.rs @@ -1,24 +1,15 @@ pub(super) mod init_message; pub(super) mod update_message; -use std::{ - io::{Cursor, Write}, - mem, - time::Duration, -}; +use std::io::{Cursor, Write}; -use bevy::{ecs::component::Tick, prelude::*}; +use bevy::prelude::*; use varint_rs::VarintWriter; -use crate::core::{ - replicated_clients::{ClientBuffers, ReplicatedClient, ReplicatedClients}, - replicon_server::RepliconServer, - replicon_tick::RepliconTick, -}; use init_message::InitMessage; use update_message::UpdateMessage; -/// Accumulates replication messages and sends them to clients. +/// Accumulates replication messages. /// /// Messages are serialized and deserialized manually because using an intermediate structure /// leads to allocations and according to our benchmarks it's much slower. @@ -26,8 +17,8 @@ use update_message::UpdateMessage; /// Reuses allocated memory from older messages. #[derive(Default)] pub(crate) struct ReplicationMessages { - replicated_clients: ReplicatedClients, - data: Vec<(InitMessage, UpdateMessage)>, + messages: Vec<(InitMessage, UpdateMessage)>, + len: usize, } impl ReplicationMessages { @@ -36,62 +27,26 @@ impl ReplicationMessages { /// Reuses already allocated messages. /// Creates new messages if the number of clients is bigger then the number of allocated messages. /// If there are more messages than the number of clients, then the extra messages remain untouched - /// and iteration methods will not include them. - pub(super) fn prepare(&mut self, replicated_clients: ReplicatedClients) { - self.data - .reserve(replicated_clients.len().saturating_sub(self.data.len())); + /// and [`Self::iter_mut`] will not include them. + pub(super) fn reset(&mut self, clients_count: usize) { + self.len = clients_count; - for index in 0..replicated_clients.len() { - if let Some((init_message, update_message)) = self.data.get_mut(index) { + let additional = clients_count.saturating_sub(self.messages.len()); + self.messages.reserve(additional); + + for index in 0..clients_count { + if let Some((init_message, update_message)) = self.messages.get_mut(index) { init_message.reset(); update_message.reset(); } else { - self.data.push(Default::default()); + self.messages.push(Default::default()); } } - - self.replicated_clients = replicated_clients; } /// Returns iterator over messages for each client. pub(super) fn iter_mut(&mut self) -> impl Iterator { - self.data.iter_mut().take(self.replicated_clients.len()) - } - - /// Same as [`Self::iter_mut`], but also includes [`ReplicatedClient`]. - pub(super) fn iter_mut_with_clients( - &mut self, - ) -> impl Iterator { - self.data - .iter_mut() - .zip(self.replicated_clients.iter_mut()) - .map(|((init_message, update_message), client)| (init_message, update_message, client)) - } - - /// Sends cached messages to clients specified in the last [`Self::prepare`] call. - /// - /// The change tick of each client with an init message is updated to equal the latest replicon tick. - /// messages were sent to clients. If only update messages were sent (or no messages at all) then - /// it will equal the input `last_change_tick`. - pub(super) fn send( - &mut self, - server: &mut RepliconServer, - client_buffers: &mut ClientBuffers, - server_tick: RepliconTick, - tick: Tick, - timestamp: Duration, - ) -> bincode::Result { - for ((init_message, update_message), client) in - self.data.iter_mut().zip(self.replicated_clients.iter_mut()) - { - init_message.send(server, client, server_tick)?; - update_message.send(server, client_buffers, client, server_tick, tick, timestamp)?; - client.visibility_mut().update(); - } - - let replicated_clients = mem::take(&mut self.replicated_clients); - - Ok(replicated_clients) + self.messages.iter_mut().take(self.len) } } diff --git a/src/server/replication_messages/init_message.rs b/src/server/replication_messages/init_message.rs index 6ff8eccf..50908372 100644 --- a/src/server/replication_messages/init_message.rs +++ b/src/server/replication_messages/init_message.rs @@ -294,7 +294,7 @@ impl InitMessage { /// /// Updates change tick for the client if there are data to send. /// Does nothing if there is no data to send. - pub(super) fn send( + pub(crate) fn send( &self, server: &mut RepliconServer, client: &mut ReplicatedClient, diff --git a/src/server/replication_messages/update_message.rs b/src/server/replication_messages/update_message.rs index 0fc2bfd6..7fcc368f 100644 --- a/src/server/replication_messages/update_message.rs +++ b/src/server/replication_messages/update_message.rs @@ -145,11 +145,11 @@ impl UpdateMessage { /// Splits message according to entities inside it and sends it to the specified client. /// /// Does nothing if there is no data to send. - pub(super) fn send( + pub(crate) fn send( &mut self, server: &mut RepliconServer, - client_buffers: &mut ClientBuffers, client: &mut ReplicatedClient, + client_buffers: &mut ClientBuffers, server_tick: RepliconTick, tick: Tick, timestamp: Duration, From f0447ee91e56734f7182e8b43c1c4847f4677bf8 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Sat, 9 Nov 2024 01:38:49 +0200 Subject: [PATCH 09/31] Replace `varint-rs` with `integer-encoding` It's a bit more advanced, I need some of its features for the upcoming work. Also `varint-rs` has not been updated in 4 years. --- Cargo.toml | 2 +- src/client.rs | 6 +++--- src/server/replication_messages.rs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4711939f..fba5e7c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ bevy = { version = "0.14", default-features = false, features = ["serialize"] } bytes = "1.5" bincode = "1.3" serde = "1.0" -varint-rs = "2.2" +integer-encoding = "4.0" ordered-multimap = "0.7" [dev-dependencies] diff --git a/src/client.rs b/src/client.rs index af509501..637edac3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -8,7 +8,7 @@ use std::{io::Cursor, mem}; use bevy::{ecs::world::CommandQueue, prelude::*}; use bincode::{DefaultOptions, Options}; use bytes::Bytes; -use varint_rs::VarintReader; +use integer_encoding::VarIntReader; use crate::core::{ channels::{ReplicationChannel, RepliconChannels}, @@ -514,10 +514,10 @@ fn apply_update_components( /// For details see /// [`ReplicationBuffer::write_entity`](crate::server::replication_message::replication_buffer::write_entity). fn deserialize_entity(cursor: &mut Cursor<&[u8]>) -> bincode::Result { - let flagged_index: u64 = cursor.read_u64_varint()?; + let flagged_index: u64 = cursor.read_varint()?; let has_generation = (flagged_index & 1) > 0; let generation = if has_generation { - cursor.read_u32_varint()? + 1 + cursor.read_varint::()? + 1 } else { 1u32 }; diff --git a/src/server/replication_messages.rs b/src/server/replication_messages.rs index f5629b01..9b1ffd46 100644 --- a/src/server/replication_messages.rs +++ b/src/server/replication_messages.rs @@ -4,7 +4,7 @@ pub(super) mod update_message; use std::io::{Cursor, Write}; use bevy::prelude::*; -use varint_rs::VarintWriter; +use integer_encoding::VarIntWriter; use init_message::InitMessage; use update_message::UpdateMessage; @@ -93,9 +93,9 @@ fn serialize_entity(cursor: &mut Cursor>, entity: Entity) -> bincode::Re let flag = entity.generation() > 1; flagged_index |= flag as u64; - cursor.write_u64_varint(flagged_index)?; + cursor.write_varint(flagged_index)?; if flag { - cursor.write_u32_varint(entity.generation() - 1)?; + cursor.write_varint(entity.generation() - 1)?; } Ok(()) From d1491ac80c24b3c1d057b0a8de94ad711f4fc7e3 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Wed, 13 Nov 2024 02:56:57 +0300 Subject: [PATCH 10/31] Reorganize `core` (#351) * Reorganize `core` - Move replication-related modules from `core` module under `core::replication`. - Move `Replicated` to the `replication` module. - Split the `ctx` module and move event-related contexts under `core::events_registry::ctx` and replication-related contexts under `core::replication_registry::ctx`. * Fix docs --- CHANGELOG.md | 3 + src/client.rs | 14 ++-- src/client/events.rs | 6 +- src/core.rs | 22 ++----- src/core/connected_clients.rs | 2 +- src/core/event_registry.rs | 1 + src/core/event_registry/client_event.rs | 8 ++- src/core/event_registry/ctx.rs | 63 ++++++++++++++++++ src/core/event_registry/server_event.rs | 10 +-- src/core/replication.rs | 15 +++++ src/core/{ => replication}/command_markers.rs | 22 ++++--- src/core/{ => replication}/deferred_entity.rs | 0 .../{ => replication}/replicated_clients.rs | 2 +- .../replicated_clients/client_visibility.rs | 0 .../{ => replication}/replication_registry.rs | 8 ++- .../replication_registry/command_fns.rs | 6 +- .../replication_registry/component_fns.rs | 9 ++- .../replication_registry}/ctx.rs | 66 ++----------------- .../replication_registry/rule_fns.rs | 10 +-- .../replication_registry/test_fns.rs | 18 +++-- .../{ => replication}/replication_rules.rs | 17 +++-- src/lib.rs | 17 +++-- src/parent_sync.rs | 2 +- src/scene.rs | 2 +- src/server.rs | 11 ++-- src/server/despawn_buffer.rs | 2 +- src/server/events.rs | 4 +- src/server/removal_buffer.rs | 10 +-- src/server/replicated_archetypes.rs | 6 +- .../replication_messages/init_message.rs | 9 ++- .../replication_messages/update_message.rs | 9 ++- src/test_app.rs | 2 +- tests/changes.rs | 9 +-- tests/fns.rs | 15 +++-- tests/insertion.rs | 7 +- tests/removal.rs | 5 +- 36 files changed, 236 insertions(+), 176 deletions(-) create mode 100644 src/core/event_registry/ctx.rs create mode 100644 src/core/replication.rs rename src/core/{ => replication}/command_markers.rs (95%) rename src/core/{ => replication}/deferred_entity.rs (100%) rename src/core/{ => replication}/replicated_clients.rs (99%) rename src/core/{ => replication}/replicated_clients/client_visibility.rs (100%) rename src/core/{ => replication}/replication_registry.rs (97%) rename src/core/{ => replication}/replication_registry/command_fns.rs (97%) rename src/core/{ => replication}/replication_registry/component_fns.rs (98%) rename src/core/{ => replication/replication_registry}/ctx.rs (52%) rename src/core/{ => replication}/replication_registry/rule_fns.rs (95%) rename src/core/{ => replication}/replication_registry/test_fns.rs (94%) rename src/core/{ => replication}/replication_rules.rs (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b4dffd3..52ad37bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - All `TestFnsEntityExt` now accept `FnsId`. +- Move replication-related modules from `core` module under `core::replication`. +- Move `Replicated` to the `replication` module. +- Split the `ctx` module and move event-related contexts under `core::events_registry::ctx` and replication-related contexts under `core::replication_registry::ctx`. ### Removed diff --git a/src/client.rs b/src/client.rs index 637edac3..40df014b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -12,15 +12,19 @@ use integer_encoding::VarIntReader; use crate::core::{ channels::{ReplicationChannel, RepliconChannels}, - command_markers::{CommandMarkers, EntityMarkers}, common_conditions::{client_connected, client_just_connected, client_just_disconnected}, - ctx::{DespawnCtx, RemoveCtx, WriteCtx}, - deferred_entity::DeferredEntity, - replication_registry::ReplicationRegistry, + replication::{ + command_markers::{CommandMarkers, EntityMarkers}, + deferred_entity::DeferredEntity, + replication_registry::{ + ctx::{DespawnCtx, RemoveCtx, WriteCtx}, + ReplicationRegistry, + }, + Replicated, + }, replicon_client::RepliconClient, replicon_tick::RepliconTick, server_entity_map::ServerEntityMap, - Replicated, }; use confirm_history::ConfirmHistory; diff --git a/src/client/events.rs b/src/client/events.rs index d5d2dc09..359be3a0 100644 --- a/src/client/events.rs +++ b/src/client/events.rs @@ -1,8 +1,10 @@ use super::{ClientPlugin, ClientSet, ServerInitTick}; use crate::core::{ common_conditions::*, - ctx::{ClientReceiveCtx, ClientSendCtx}, - event_registry::EventRegistry, + event_registry::{ + ctx::{ClientReceiveCtx, ClientSendCtx}, + EventRegistry, + }, replicon_client::RepliconClient, server_entity_map::ServerEntityMap, }; diff --git a/src/core.rs b/src/core.rs index 7b980434..794d596b 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,13 +1,8 @@ pub mod channels; -pub mod command_markers; pub mod common_conditions; pub mod connected_clients; -pub mod ctx; -pub mod deferred_entity; pub mod event_registry; -pub mod replicated_clients; -pub mod replication_registry; -pub mod replication_rules; +pub mod replication; pub mod replicon_client; pub mod replicon_server; pub mod replicon_tick; @@ -17,10 +12,11 @@ use bevy::prelude::*; use serde::{Deserialize, Serialize}; use channels::RepliconChannels; -use command_markers::CommandMarkers; use event_registry::EventRegistry; -use replication_registry::ReplicationRegistry; -use replication_rules::ReplicationRules; +use replication::{ + command_markers::CommandMarkers, replication_registry::ReplicationRegistry, + replication_rules::ReplicationRules, Replicated, +}; pub struct RepliconCorePlugin; @@ -35,14 +31,6 @@ impl Plugin for RepliconCorePlugin { } } -#[deprecated(note = "use `Replicated` instead")] -pub type Replication = Replicated; - -/// Marks entity for replication. -#[derive(Component, Clone, Copy, Default, Reflect, Debug)] -#[reflect(Component)] -pub struct Replicated; - /// Unique client ID. /// /// Could be a client or a dual server-client. diff --git a/src/core/connected_clients.rs b/src/core/connected_clients.rs index c999f4d0..fe1bbc4b 100644 --- a/src/core/connected_clients.rs +++ b/src/core/connected_clients.rs @@ -6,7 +6,7 @@ use crate::core::ClientId; /// /// Inserted as resource by [`ServerPlugin`](crate::server::ServerPlugin). /// -/// See also [ReplicatedClients](super::replicated_clients::ReplicatedClients). +/// See also [ReplicatedClients](super::replication::replicated_clients::ReplicatedClients). #[derive(Resource, Default, Deref)] pub struct ConnectedClients(Vec); diff --git a/src/core/event_registry.rs b/src/core/event_registry.rs index 503fc223..7fea67bd 100644 --- a/src/core/event_registry.rs +++ b/src/core/event_registry.rs @@ -1,4 +1,5 @@ pub(crate) mod client_event; +pub mod ctx; pub(crate) mod server_event; use bevy::{ecs::component::ComponentId, prelude::*}; diff --git a/src/core/event_registry/client_event.rs b/src/core/event_registry/client_event.rs index 3cc57ff3..31c128ec 100644 --- a/src/core/event_registry/client_event.rs +++ b/src/core/event_registry/client_event.rs @@ -16,10 +16,12 @@ use bevy::{ use bincode::{DefaultOptions, Options}; use serde::{de::DeserializeOwned, Serialize}; -use super::EventRegistry; +use super::{ + ctx::{ClientSendCtx, ServerReceiveCtx}, + EventRegistry, +}; use crate::core::{ channels::{RepliconChannel, RepliconChannels}, - ctx::{ClientSendCtx, ServerReceiveCtx}, replicon_client::RepliconClient, replicon_server::RepliconServer, ClientId, @@ -77,7 +79,7 @@ pub trait ClientEventAppExt { reflect::serde::{ReflectSerializer, ReflectDeserializer}, }; use bevy_replicon::{ - core::ctx::{ClientSendCtx, ServerReceiveCtx}, + core::event_registry::ctx::{ClientSendCtx, ServerReceiveCtx}, prelude::*, }; use bincode::{DefaultOptions, Options}; diff --git a/src/core/event_registry/ctx.rs b/src/core/event_registry/ctx.rs new file mode 100644 index 00000000..17e990b6 --- /dev/null +++ b/src/core/event_registry/ctx.rs @@ -0,0 +1,63 @@ +use bevy::{prelude::*, reflect::TypeRegistry}; + +use crate::core::server_entity_map::ServerEntityMap; + +/// Event sending context for client. +#[non_exhaustive] +pub struct ClientSendCtx<'a> { + /// Registry of reflected types. + pub registry: &'a TypeRegistry, + + /// Maps server entities to client entities and vice versa. + pub entity_map: &'a ServerEntityMap, +} + +impl EntityMapper for ClientSendCtx<'_> { + fn map_entity(&mut self, entity: Entity) -> Entity { + *self + .entity_map + .to_server() + .get(&entity) + .unwrap_or_else(|| panic!("client {entity:?} should have a mapping")) + } +} + +/// Event receiving context for server. +#[non_exhaustive] +pub struct ServerReceiveCtx<'a> { + /// Registry of reflected types. + pub registry: &'a TypeRegistry, +} + +/// Event sending context for server. +#[non_exhaustive] +pub struct ServerSendCtx<'a> { + /// Registry of reflected types. + pub registry: &'a TypeRegistry, +} + +/// Event receiving context for client. +#[non_exhaustive] +pub struct ClientReceiveCtx<'a> { + /// Registry of reflected types. + pub registry: &'a TypeRegistry, + + /// Maps server entities to client entities and vice versa. + pub entity_map: &'a ServerEntityMap, + + /// Entities that couldn't be mapped by [`EntityMapper::map_entity`]. + /// + /// We needed it because [`EntityMapper`] doesn't provide a way to handle errors. + pub(crate) invalid_entities: Vec, +} + +impl EntityMapper for ClientReceiveCtx<'_> { + fn map_entity(&mut self, entity: Entity) -> Entity { + if let Some(mapped_entity) = self.entity_map.to_client().get(&entity) { + *mapped_entity + } else { + self.invalid_entities.push(entity); + Entity::PLACEHOLDER + } + } +} diff --git a/src/core/event_registry/server_event.rs b/src/core/event_registry/server_event.rs index d1d30eec..60016b72 100644 --- a/src/core/event_registry/server_event.rs +++ b/src/core/event_registry/server_event.rs @@ -19,13 +19,15 @@ use bytes::Bytes; use ordered_multimap::ListOrderedMultimap; use serde::{de::DeserializeOwned, Serialize}; -use super::EventRegistry; +use super::{ + ctx::{ClientReceiveCtx, ServerSendCtx}, + EventRegistry, +}; use crate::{ core::{ channels::{RepliconChannel, RepliconChannels}, connected_clients::ConnectedClients, - ctx::{ClientReceiveCtx, ServerSendCtx}, - replicated_clients::ReplicatedClients, + replication::replicated_clients::ReplicatedClients, replicon_client::RepliconClient, replicon_server::RepliconServer, replicon_tick::RepliconTick, @@ -84,7 +86,7 @@ pub trait ServerEventAppExt { reflect::serde::{ReflectSerializer, ReflectDeserializer}, }; use bevy_replicon::{ - core::ctx::{ClientReceiveCtx, ServerSendCtx}, + core::event_registry::ctx::{ClientReceiveCtx, ServerSendCtx}, prelude::*, }; use bincode::{DefaultOptions, Options}; diff --git a/src/core/replication.rs b/src/core/replication.rs new file mode 100644 index 00000000..33b30885 --- /dev/null +++ b/src/core/replication.rs @@ -0,0 +1,15 @@ +pub mod command_markers; +pub mod deferred_entity; +pub mod replicated_clients; +pub mod replication_registry; +pub mod replication_rules; + +use bevy::prelude::*; + +#[deprecated(note = "use `Replicated` instead")] +pub type Replication = Replicated; + +/// Marks entity for replication. +#[derive(Component, Clone, Copy, Default, Reflect, Debug)] +#[reflect(Component)] +pub struct Replicated; diff --git a/src/core/command_markers.rs b/src/core/replication/command_markers.rs similarity index 95% rename from src/core/command_markers.rs rename to src/core/replication/command_markers.rs index bfc7d743..0869dd7f 100644 --- a/src/core/command_markers.rs +++ b/src/core/replication/command_markers.rs @@ -2,8 +2,10 @@ use std::cmp::Reverse; use bevy::{ecs::component::ComponentId, prelude::*}; -use super::replication_registry::command_fns::{RemoveFn, WriteFn}; -use crate::core::replication_registry::ReplicationRegistry; +use super::replication_registry::{ + command_fns::{RemoveFn, WriteFn}, + ReplicationRegistry, +}; /// Marker-based functions for [`App`]. /// @@ -50,10 +52,14 @@ pub trait AppMarkerExt { use bevy::{ecs::system::EntityCommands, prelude::*, utils::HashMap}; use bevy_replicon::{ core::{ - command_markers::MarkerConfig, - deferred_entity::DeferredEntity, - ctx::{RemoveCtx, WriteCtx}, - replication_registry::rule_fns::RuleFns, + replication::{ + command_markers::MarkerConfig, + deferred_entity::DeferredEntity, + replication_registry::{ + ctx::{RemoveCtx, WriteCtx}, + rule_fns::RuleFns, + }, + }, replicon_tick::RepliconTick, }, prelude::*, @@ -174,7 +180,7 @@ impl CommandMarkers { /// /// May invalidate previously returned [`CommandMarkerIndex`] due to sorting. /// - /// Use [`ReplicationFns::register_marker`] to register a slot for command functions for this marker. + /// Use [`ReplicationRegistry::register_marker`] to register a slot for command functions for this marker. fn insert(&mut self, marker: CommandMarker) -> CommandMarkerIndex { let key = Reverse(marker.config.priority); let index = self @@ -295,7 +301,7 @@ mod tests { use serde::{Deserialize, Serialize}; use super::*; - use crate::core::replication_registry::{command_fns, ReplicationRegistry}; + use crate::core::replication::replication_registry::command_fns; #[test] #[should_panic] diff --git a/src/core/deferred_entity.rs b/src/core/replication/deferred_entity.rs similarity index 100% rename from src/core/deferred_entity.rs rename to src/core/replication/deferred_entity.rs diff --git a/src/core/replicated_clients.rs b/src/core/replication/replicated_clients.rs similarity index 99% rename from src/core/replicated_clients.rs rename to src/core/replication/replicated_clients.rs index 4b3ad9d0..f15d4c49 100644 --- a/src/core/replicated_clients.rs +++ b/src/core/replication/replicated_clients.rs @@ -16,7 +16,7 @@ use client_visibility::ClientVisibility; /// /// Inserted as resource by [`ServerPlugin`](crate::server::ServerPlugin). /// -/// See also [ConnectedClients](super::connected_clients::ConnectedClients). +/// See also [ConnectedClients](crate::core::connected_clients::ConnectedClients). #[derive(Resource, Default)] pub struct ReplicatedClients { clients: Vec, diff --git a/src/core/replicated_clients/client_visibility.rs b/src/core/replication/replicated_clients/client_visibility.rs similarity index 100% rename from src/core/replicated_clients/client_visibility.rs rename to src/core/replication/replicated_clients/client_visibility.rs diff --git a/src/core/replication_registry.rs b/src/core/replication/replication_registry.rs similarity index 97% rename from src/core/replication_registry.rs rename to src/core/replication/replication_registry.rs index da82b9e1..7682790d 100644 --- a/src/core/replication_registry.rs +++ b/src/core/replication/replication_registry.rs @@ -1,14 +1,16 @@ pub mod command_fns; pub mod component_fns; +pub mod ctx; pub mod rule_fns; pub mod test_fns; use bevy::{ecs::component::ComponentId, prelude::*}; use serde::{Deserialize, Serialize}; -use super::{command_markers::CommandMarkerIndex, ctx::DespawnCtx}; +use super::command_markers::CommandMarkerIndex; use command_fns::{RemoveFn, UntypedCommandFns, WriteFn}; use component_fns::ComponentFns; +use ctx::DespawnCtx; use rule_fns::{RuleFns, UntypedRuleFns}; /// Stores configurable replication functions. @@ -156,12 +158,12 @@ impl Default for ReplicationRegistry { } } -#[deprecated(note = "use `Replicated` instead")] +#[deprecated(note = "use `ReplicationRegistry` instead")] pub type ReplicationFns = ReplicationRegistry; /// ID of replicaton functions for a component. /// -/// Can be obtained from [`ReplicationFns::register_rule_fns`]. +/// Can be obtained from [`ReplicationRegistry::register_rule_fns`]. #[derive(Clone, Copy, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct FnsId(usize); diff --git a/src/core/replication_registry/command_fns.rs b/src/core/replication/replication_registry/command_fns.rs similarity index 97% rename from src/core/replication_registry/command_fns.rs rename to src/core/replication/replication_registry/command_fns.rs index 3875216b..05816605 100644 --- a/src/core/replication_registry/command_fns.rs +++ b/src/core/replication/replication_registry/command_fns.rs @@ -6,11 +6,11 @@ use std::{ use bevy::prelude::*; -use super::rule_fns::RuleFns; -use crate::core::{ +use super::{ ctx::{RemoveCtx, WriteCtx}, - deferred_entity::DeferredEntity, + rule_fns::RuleFns, }; +use crate::core::replication::deferred_entity::DeferredEntity; /// Writing and removal functions for a component, like [`Commands`]. #[derive(Clone, Copy)] diff --git a/src/core/replication_registry/component_fns.rs b/src/core/replication/replication_registry/component_fns.rs similarity index 98% rename from src/core/replication_registry/component_fns.rs rename to src/core/replication/replication_registry/component_fns.rs index 1b5d4447..c22b6781 100644 --- a/src/core/replication_registry/component_fns.rs +++ b/src/core/replication/replication_registry/component_fns.rs @@ -2,10 +2,13 @@ use std::io::Cursor; use bevy::{prelude::*, ptr::Ptr}; -use super::{command_fns::UntypedCommandFns, rule_fns::UntypedRuleFns}; -use crate::core::{ - command_markers::{CommandMarkerIndex, CommandMarkers, EntityMarkers}, +use super::{ + command_fns::UntypedCommandFns, ctx::{RemoveCtx, SerializeCtx, WriteCtx}, + rule_fns::UntypedRuleFns, +}; +use crate::core::replication::{ + command_markers::{CommandMarkerIndex, CommandMarkers, EntityMarkers}, deferred_entity::DeferredEntity, }; diff --git a/src/core/ctx.rs b/src/core/replication/replication_registry/ctx.rs similarity index 52% rename from src/core/ctx.rs rename to src/core/replication/replication_registry/ctx.rs index c518392d..d77d8f5c 100644 --- a/src/core/ctx.rs +++ b/src/core/replication/replication_registry/ctx.rs @@ -1,6 +1,8 @@ -use bevy::{ecs::component::ComponentId, prelude::*, reflect::TypeRegistry}; +use bevy::{ecs::component::ComponentId, prelude::*}; -use super::{replicon_tick::RepliconTick, server_entity_map::ServerEntityMap, Replicated}; +use crate::core::{ + replication::Replicated, replicon_tick::RepliconTick, server_entity_map::ServerEntityMap, +}; /// Replication context for serialization function. #[non_exhaustive] @@ -78,63 +80,3 @@ pub struct DespawnCtx { /// Tick for the currently processing message. pub message_tick: RepliconTick, } - -/// Event sending context for client. -#[non_exhaustive] -pub struct ClientSendCtx<'a> { - /// Registry of reflected types. - pub registry: &'a TypeRegistry, - - /// Maps server entities to client entities and vice versa. - pub entity_map: &'a ServerEntityMap, -} - -impl EntityMapper for ClientSendCtx<'_> { - fn map_entity(&mut self, entity: Entity) -> Entity { - *self - .entity_map - .to_server() - .get(&entity) - .unwrap_or_else(|| panic!("client {entity:?} should have a mapping")) - } -} - -/// Event receiving context for server. -#[non_exhaustive] -pub struct ServerReceiveCtx<'a> { - /// Registry of reflected types. - pub registry: &'a TypeRegistry, -} - -/// Event sending context for server. -#[non_exhaustive] -pub struct ServerSendCtx<'a> { - /// Registry of reflected types. - pub registry: &'a TypeRegistry, -} - -/// Event receiving context for client. -#[non_exhaustive] -pub struct ClientReceiveCtx<'a> { - /// Registry of reflected types. - pub registry: &'a TypeRegistry, - - /// Maps server entities to client entities and vice versa. - pub entity_map: &'a ServerEntityMap, - - /// Entities that couldn't be mapped by [`EntityMapper::map_entity`]. - /// - /// We needed it because [`EntityMapper`] doesn't provide a way to handle errors. - pub(crate) invalid_entities: Vec, -} - -impl EntityMapper for ClientReceiveCtx<'_> { - fn map_entity(&mut self, entity: Entity) -> Entity { - if let Some(mapped_entity) = self.entity_map.to_client().get(&entity) { - *mapped_entity - } else { - self.invalid_entities.push(entity); - Entity::PLACEHOLDER - } - } -} diff --git a/src/core/replication_registry/rule_fns.rs b/src/core/replication/replication_registry/rule_fns.rs similarity index 95% rename from src/core/replication_registry/rule_fns.rs rename to src/core/replication/replication_registry/rule_fns.rs index ada29c17..4ca144b0 100644 --- a/src/core/replication_registry/rule_fns.rs +++ b/src/core/replication/replication_registry/rule_fns.rs @@ -8,11 +8,11 @@ use bevy::{ecs::entity::MapEntities, prelude::*}; use bincode::{DefaultOptions, Options}; use serde::{de::DeserializeOwned, Serialize}; -use crate::core::ctx::{SerializeCtx, WriteCtx}; +use super::ctx::{SerializeCtx, WriteCtx}; /// Type-erased version of [`RuleFns`]. /// -/// Stored inside [`ReplicationFns`](super::ReplicationFns) after registration. +/// Stored inside [`ReplicationRegistry`](super::ReplicationRegistry) after registration. pub(crate) struct UntypedRuleFns { type_id: TypeId, type_name: &'static str, @@ -71,8 +71,8 @@ impl From> for UntypedRuleFns { /// Serialization and deserialization functions for a component. /// -/// See also [`AppRuleExt`](crate::core::replication_rules::AppRuleExt) -/// and [`ReplicationRule`](crate::core::replication_rules::ReplicationRule). +/// See also [`AppRuleExt`](crate::core::replication::replication_rules::AppRuleExt) +/// and [`ReplicationRule`](crate::core::replication::replication_rules::ReplicationRule). pub struct RuleFns { serialize: SerializeFn, deserialize: DeserializeFn, @@ -113,7 +113,7 @@ impl RuleFns { /// If you want to ignore a component, just use its expected size to advance the cursor /// without deserializing (but be careful if the component is dynamically sized). /// - /// See [`MarkerConfig::need_history`](crate::core::command_markers::MarkerConfig::need_history) + /// See [`MarkerConfig::need_history`](crate::core::replication::command_markers::MarkerConfig::need_history) /// for details. pub fn with_consume(mut self, consume: ConsumeFn) -> Self { self.consume = consume; diff --git a/src/core/replication_registry/test_fns.rs b/src/core/replication/replication_registry/test_fns.rs similarity index 94% rename from src/core/replication_registry/test_fns.rs rename to src/core/replication/replication_registry/test_fns.rs index 72bc4496..5592b889 100644 --- a/src/core/replication_registry/test_fns.rs +++ b/src/core/replication/replication_registry/test_fns.rs @@ -2,11 +2,15 @@ use std::io::Cursor; use bevy::{ecs::world::CommandQueue, prelude::*}; -use super::{FnsId, ReplicationRegistry}; -use crate::core::{ - command_markers::{CommandMarkers, EntityMarkers}, +use super::{ ctx::{DespawnCtx, RemoveCtx, SerializeCtx, WriteCtx}, - deferred_entity::DeferredEntity, + FnsId, ReplicationRegistry, +}; +use crate::core::{ + replication::{ + command_markers::{CommandMarkers, EntityMarkers}, + deferred_entity::DeferredEntity, + }, replicon_tick::RepliconTick, server_entity_map::ServerEntityMap, }; @@ -24,7 +28,7 @@ This example shows how to call registered functions on an entity: use bevy::prelude::*; use bevy_replicon::{ core::{ - replication_registry::{ + replication::replication_registry::{ rule_fns::RuleFns, test_fns::TestFnsEntityExt, ReplicationRegistry, }, replicon_tick::RepliconTick, @@ -72,12 +76,12 @@ pub trait TestFnsEntityExt { /// Deserializes a component using a registered function for it and /// writes it into an entity using a write function based on markers. /// - /// See also [`AppMarkerExt`](crate::core::command_markers::AppMarkerExt). + /// See also [`AppMarkerExt`](crate::core::replication::command_markers::AppMarkerExt). fn apply_write(&mut self, data: &[u8], fns_id: FnsId, message_tick: RepliconTick) -> &mut Self; /// Remvoes a component using a registered function for it. /// - /// See also [`AppMarkerExt`](crate::core::command_markers::AppMarkerExt). + /// See also [`AppMarkerExt`](crate::core::replication::command_markers::AppMarkerExt). fn apply_remove(&mut self, fns_id: FnsId, message_tick: RepliconTick) -> &mut Self; /// Despawns an entity using [`ReplicationRegistry::despawn`]. diff --git a/src/core/replication_rules.rs b/src/core/replication/replication_rules.rs similarity index 97% rename from src/core/replication_rules.rs rename to src/core/replication/replication_rules.rs index 2e971d0e..5689437f 100644 --- a/src/core/replication_rules.rs +++ b/src/core/replication/replication_rules.rs @@ -80,9 +80,9 @@ pub trait AppRuleExt { use bevy::prelude::*; use bevy_replicon::{ - core::{ + core::replication::replication_registry::{ ctx::{SerializeCtx, WriteCtx}, - replication_registry::rule_fns::RuleFns, + rule_fns::RuleFns, }, prelude::*, }; @@ -272,9 +272,12 @@ use std::io::Cursor; use bevy::prelude::*; use bevy_replicon::{ - core::{ - ctx::{SerializeCtx, WriteCtx}, - replication_registry::{rule_fns::RuleFns, ReplicationFns}, + core::replication::{ + replication_registry::{ + ctx::{SerializeCtx, WriteCtx}, + rule_fns::RuleFns, + ReplicationRegistry, + }, replication_rules::{GroupReplication, ReplicationRule}, }, prelude::*, @@ -296,7 +299,7 @@ struct PlayerBundle { struct Player; impl GroupReplication for PlayerBundle { - fn register(world: &mut World, registry: &mut ReplicationFns) -> ReplicationRule { + fn register(world: &mut World, registry: &mut ReplicationRegistry) -> ReplicationRule { // Customize serlialization to serialize only `translation`. let transform_info = registry.register_rule_fns( world, @@ -347,7 +350,7 @@ mod tests { use serde::{Deserialize, Serialize}; use super::*; - use crate::{core::replication_registry::ReplicationRegistry, AppRuleExt}; + use crate::AppRuleExt; #[test] fn sorting() { diff --git a/src/lib.rs b/src/lib.rs index 801b2b73..1101dbb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -637,26 +637,29 @@ pub mod test_app; pub mod prelude { #[allow(deprecated)] - pub use super::core::Replication; + pub use super::core::replication::Replication; pub use super::{ core::{ channels::{ChannelKind, RepliconChannel, RepliconChannels}, - command_markers::AppMarkerExt, common_conditions::*, connected_clients::ConnectedClients, event_registry::{ client_event::{ClientEventAppExt, FromClient}, server_event::{SendMode, ServerEventAppExt, ToClients}, }, - replicated_clients::{ - client_visibility::ClientVisibility, ReplicatedClient, ReplicatedClients, - VisibilityPolicy, + replication::{ + command_markers::AppMarkerExt, + replicated_clients::{ + client_visibility::ClientVisibility, ReplicatedClient, ReplicatedClients, + VisibilityPolicy, + }, + replication_rules::AppRuleExt, + Replicated, }, - replication_rules::AppRuleExt, replicon_client::{RepliconClient, RepliconClientStatus}, replicon_server::RepliconServer, - ClientId, Replicated, RepliconCorePlugin, + ClientId, RepliconCorePlugin, }, RepliconPlugins, }; diff --git a/src/parent_sync.rs b/src/parent_sync.rs index d78f1a12..e5ea0e9c 100644 --- a/src/parent_sync.rs +++ b/src/parent_sync.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "client")] use crate::client::ClientSet; -use crate::core::{common_conditions::*, replication_rules::AppRuleExt}; +use crate::core::{common_conditions::*, replication::replication_rules::AppRuleExt}; #[cfg(feature = "server")] use crate::server::ServerSet; diff --git a/src/scene.rs b/src/scene.rs index 08def37a..756e1d49 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -1,6 +1,6 @@ use bevy::{ecs::entity::EntityHashMap, prelude::*, scene::DynamicEntity}; -use crate::{core::replication_rules::ReplicationRules, Replicated}; +use crate::{core::replication::replication_rules::ReplicationRules, Replicated}; /** Fills scene with all replicated entities and their components. diff --git a/src/server.rs b/src/server.rs index 1126d443..0495671f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -24,13 +24,14 @@ use crate::core::{ channels::{ReplicationChannel, RepliconChannels}, common_conditions::{server_just_stopped, server_running}, connected_clients::ConnectedClients, - ctx::SerializeCtx, event_registry::server_event::BufferedServerEvents, - replicated_clients::{ - client_visibility::Visibility, ClientBuffers, ReplicatedClients, VisibilityPolicy, + replication::{ + replicated_clients::{ + client_visibility::Visibility, ClientBuffers, ReplicatedClients, VisibilityPolicy, + }, + replication_registry::{ctx::SerializeCtx, ReplicationRegistry}, + replication_rules::ReplicationRules, }, - replication_registry::ReplicationRegistry, - replication_rules::ReplicationRules, replicon_server::RepliconServer, replicon_tick::RepliconTick, ClientId, diff --git a/src/server/despawn_buffer.rs b/src/server/despawn_buffer.rs index 4b3c6f8e..0f7760f4 100644 --- a/src/server/despawn_buffer.rs +++ b/src/server/despawn_buffer.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use super::{ServerPlugin, ServerSet}; -use crate::core::{common_conditions::server_running, Replicated}; +use crate::core::{common_conditions::server_running, replication::Replicated}; /// Treats removals of [`Replicated`] component as despawns and stores them into [`DespawnBuffer`] resource. /// diff --git a/src/server/events.rs b/src/server/events.rs index 96326d94..39cfc218 100644 --- a/src/server/events.rs +++ b/src/server/events.rs @@ -4,9 +4,9 @@ use super::{server_tick::ServerTick, ServerPlugin, ServerSet}; use crate::core::{ common_conditions::*, connected_clients::ConnectedClients, - ctx::{ServerReceiveCtx, ServerSendCtx}, + event_registry::ctx::{ServerReceiveCtx, ServerSendCtx}, event_registry::{server_event::BufferedServerEvents, EventRegistry}, - replicated_clients::ReplicatedClients, + replication::replicated_clients::ReplicatedClients, replicon_server::RepliconServer, }; diff --git a/src/server/removal_buffer.rs b/src/server/removal_buffer.rs index 288ae2a5..e45b9d7a 100644 --- a/src/server/removal_buffer.rs +++ b/src/server/removal_buffer.rs @@ -13,8 +13,8 @@ use bevy::{ use super::{ServerPlugin, ServerSet}; use crate::core::{ - common_conditions::server_running, replication_registry::FnsId, - replication_rules::ReplicationRules, Replicated, + common_conditions::server_running, + replication::{replication_registry::FnsId, replication_rules::ReplicationRules, Replicated}, }; /// Buffers all replicated component removals in [`RemovalBuffer`] resource. @@ -204,8 +204,10 @@ mod tests { use super::*; use crate::core::{ - replication_registry::ReplicationRegistry, replication_rules::AppRuleExt, - replicon_server::RepliconServer, Replicated, + replication::{ + replication_registry::ReplicationRegistry, replication_rules::AppRuleExt, Replicated, + }, + replicon_server::RepliconServer, }; #[test] diff --git a/src/server/replicated_archetypes.rs b/src/server/replicated_archetypes.rs index 9d159fa6..7f53e685 100644 --- a/src/server/replicated_archetypes.rs +++ b/src/server/replicated_archetypes.rs @@ -10,7 +10,9 @@ use bevy::{ utils::tracing::enabled, }; -use crate::core::{replication_registry::FnsId, replication_rules::ReplicationRules, Replicated}; +use crate::core::replication::{ + replication_registry::FnsId, replication_rules::ReplicationRules, Replicated, +}; /// Cached information about all replicated archetypes. #[derive(Deref)] @@ -129,7 +131,7 @@ mod tests { use serde::{Deserialize, Serialize}; use super::*; - use crate::{core::replication_registry::ReplicationRegistry, AppRuleExt}; + use crate::{core::replication::replication_registry::ReplicationRegistry, AppRuleExt}; #[test] fn empty() { diff --git a/src/server/replication_messages/init_message.rs b/src/server/replication_messages/init_message.rs index 50908372..0df8b98c 100644 --- a/src/server/replication_messages/init_message.rs +++ b/src/server/replication_messages/init_message.rs @@ -11,9 +11,12 @@ use super::update_message::UpdateMessage; use crate::{ core::{ channels::ReplicationChannel, - ctx::SerializeCtx, - replicated_clients::ReplicatedClient, - replication_registry::{component_fns::ComponentFns, rule_fns::UntypedRuleFns, FnsId}, + replication::{ + replicated_clients::ReplicatedClient, + replication_registry::{ + component_fns::ComponentFns, ctx::SerializeCtx, rule_fns::UntypedRuleFns, FnsId, + }, + }, replicon_server::RepliconServer, replicon_tick::RepliconTick, }, diff --git a/src/server/replication_messages/update_message.rs b/src/server/replication_messages/update_message.rs index 7fcc368f..0978f05b 100644 --- a/src/server/replication_messages/update_message.rs +++ b/src/server/replication_messages/update_message.rs @@ -6,9 +6,12 @@ use bytes::Bytes; use crate::core::{ channels::ReplicationChannel, - ctx::SerializeCtx, - replicated_clients::{ClientBuffers, ReplicatedClient}, - replication_registry::{component_fns::ComponentFns, rule_fns::UntypedRuleFns, FnsId}, + replication::{ + replicated_clients::{ClientBuffers, ReplicatedClient}, + replication_registry::{ + component_fns::ComponentFns, ctx::SerializeCtx, rule_fns::UntypedRuleFns, FnsId, + }, + }, replicon_server::RepliconServer, replicon_tick::RepliconTick, }; diff --git a/src/test_app.rs b/src/test_app.rs index bf6f5256..de65acdd 100644 --- a/src/test_app.rs +++ b/src/test_app.rs @@ -2,7 +2,7 @@ use bevy::prelude::*; use crate::{ core::{ - replicated_clients::ReplicatedClients, + replication::replicated_clients::ReplicatedClients, replicon_client::{RepliconClient, RepliconClientStatus}, replicon_server::RepliconServer, ClientId, diff --git a/tests/changes.rs b/tests/changes.rs index 73915c6a..b057c6c1 100644 --- a/tests/changes.rs +++ b/tests/changes.rs @@ -4,10 +4,11 @@ use bevy::{ecs::entity::MapEntities, prelude::*, utils::Duration}; use bevy_replicon::{ client::{confirm_history::ConfirmHistory, ServerInitTick}, core::{ - command_markers::MarkerConfig, - ctx::WriteCtx, - deferred_entity::DeferredEntity, - replication_registry::{command_fns, rule_fns::RuleFns}, + replication::{ + command_markers::MarkerConfig, + deferred_entity::DeferredEntity, + replication_registry::{command_fns, ctx::WriteCtx, rule_fns::RuleFns}, + }, server_entity_map::ServerEntityMap, }, prelude::*, diff --git a/tests/fns.rs b/tests/fns.rs index b0b14b05..8ed176d0 100644 --- a/tests/fns.rs +++ b/tests/fns.rs @@ -3,11 +3,16 @@ use std::io::Cursor; use bevy::prelude::*; use bevy_replicon::{ core::{ - command_markers::MarkerConfig, - ctx::{DespawnCtx, WriteCtx}, - deferred_entity::DeferredEntity, - replication_registry::{ - command_fns, rule_fns::RuleFns, test_fns::TestFnsEntityExt, ReplicationRegistry, + replication::{ + command_markers::MarkerConfig, + deferred_entity::DeferredEntity, + replication_registry::{ + command_fns, + ctx::{DespawnCtx, WriteCtx}, + rule_fns::RuleFns, + test_fns::TestFnsEntityExt, + ReplicationRegistry, + }, }, replicon_tick::RepliconTick, }, diff --git a/tests/insertion.rs b/tests/insertion.rs index d4904550..91d18161 100644 --- a/tests/insertion.rs +++ b/tests/insertion.rs @@ -3,9 +3,10 @@ use std::io::Cursor; use bevy::{ecs::entity::MapEntities, prelude::*}; use bevy_replicon::{ core::{ - ctx::WriteCtx, - deferred_entity::DeferredEntity, - replication_registry::{command_fns, rule_fns::RuleFns}, + replication::{ + deferred_entity::DeferredEntity, + replication_registry::{command_fns, ctx::WriteCtx, rule_fns::RuleFns}, + }, server_entity_map::ServerEntityMap, }, prelude::*, diff --git a/tests/removal.rs b/tests/removal.rs index 2846d4d1..dcfaa642 100644 --- a/tests/removal.rs +++ b/tests/removal.rs @@ -2,10 +2,9 @@ use std::io::Cursor; use bevy::prelude::*; use bevy_replicon::{ - core::{ - ctx::WriteCtx, + core::replication::{ deferred_entity::DeferredEntity, - replication_registry::{command_fns, rule_fns::RuleFns}, + replication_registry::{command_fns, ctx::WriteCtx, rule_fns::RuleFns}, }, prelude::*, test_app::ServerTestAppExt, From 0efa80723af57cac220cbf9ddbe30c08251b7220 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Fri, 15 Nov 2024 23:18:32 +0200 Subject: [PATCH 11/31] Add helper to read entity Better encapsulates the unsafe code. --- src/client.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/client.rs b/src/client.rs index 40df014b..c0153b60 100644 --- a/src/client.rs +++ b/src/client.rs @@ -322,10 +322,7 @@ fn apply_init_components( .entity_map .get_by_server_or_insert(server_entity, || world.spawn(Replicated).id()); - let world_cell = world.as_unsafe_world_cell(); - // SAFETY: have write access and the cell used only to get entities. - let mut client_entity = unsafe { DeferredEntity::new(world_cell, client_entity) }; - let mut commands = Commands::new_from_entities(params.queue, world_cell.entities()); + let (mut client_entity, mut commands) = read_entity(world, params.queue, client_entity); params .entity_markers .read(params.command_markers, &*client_entity); @@ -432,10 +429,7 @@ fn apply_update_components( continue; }; - let world_cell = world.as_unsafe_world_cell(); - // SAFETY: have write access and the cell used only to get entities. - let mut client_entity = unsafe { DeferredEntity::new(world_cell, client_entity) }; - let mut commands = Commands::new_from_entities(params.queue, world_cell.entities()); + let (mut client_entity, mut commands) = read_entity(world, params.queue, client_entity); params .entity_markers .read(params.command_markers, &*client_entity); @@ -513,6 +507,20 @@ fn apply_update_components( Ok(()) } +/// Splits world access into entity that disallows structural ECS changes and commands. +fn read_entity<'w, 's>( + world: &'w mut World, + queue: &'s mut CommandQueue, + client_entity: Entity, +) -> (DeferredEntity<'w>, Commands<'w, 's>) { + let world_cell = world.as_unsafe_world_cell(); + // SAFETY: have write access and the cell used only to get entities. + let client_entity = unsafe { DeferredEntity::new(world_cell, client_entity) }; + let commands = Commands::new_from_entities(queue, world_cell.entities()); + + (client_entity, commands) +} + /// Deserializes `entity` from compressed index and generation. /// /// For details see From 1e49d2d85f31efe83de60500451302e1a03bad8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 18:47:11 +0000 Subject: [PATCH 12/31] Bump codecov/codecov-action from 4 to 5 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 401d5740..422f5b7c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -133,6 +133,6 @@ jobs: name: code-coverage-report - name: Upload to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} From bcb3a76c122f16e9d43491b1f944fed10449f3c5 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Wed, 20 Nov 2024 00:21:01 +0200 Subject: [PATCH 13/31] Put extra logic for `ConfirmHistory::confirm` under else The bit is set in `set_last_tick`, no need to call `set` after it. --- src/client/confirm_history.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/client/confirm_history.rs b/src/client/confirm_history.rs index cbf44f0d..3d974612 100644 --- a/src/client/confirm_history.rs +++ b/src/client/confirm_history.rs @@ -90,10 +90,11 @@ impl ConfirmHistory { pub fn confirm(&mut self, tick: RepliconTick) { if tick > self.last_tick { self.set_last_tick(tick); - } - let ago = self.last_tick - tick; - if ago < u64::BITS { - self.set(ago); + } else { + let ago = self.last_tick - tick; + if ago < u64::BITS { + self.set(ago); + } } } From 64ec83282a5ec74ffd3ff04feda7ece90d3f130c Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Thu, 21 Nov 2024 00:55:10 +0200 Subject: [PATCH 14/31] Reorganize tests --- src/client/confirm_history.rs | 62 +++++++++++------------------------ 1 file changed, 20 insertions(+), 42 deletions(-) diff --git a/src/client/confirm_history.rs b/src/client/confirm_history.rs index 3d974612..397eb4d6 100644 --- a/src/client/confirm_history.rs +++ b/src/client/confirm_history.rs @@ -196,11 +196,11 @@ mod tests { } #[test] - fn contains_any_with_set() { + fn contains_any_with_older() { let mut history = ConfirmHistory::new(RepliconTick::new(1)); assert_eq!(history.mask(), 0b1); - history.set(2); + history.confirm(RepliconTick::new(u32::MAX)); assert_eq!(history.mask(), 0b101); assert!(history.contains_any(RepliconTick::new(0), RepliconTick::new(1))); @@ -217,32 +217,34 @@ mod tests { } #[test] - fn set() { + fn confirm_newer() { let mut history = ConfirmHistory::new(RepliconTick::new(1)); - history.set(1); + history.confirm(RepliconTick::new(2)); - assert!(history.contains(RepliconTick::new(0))); + assert!(!history.contains(RepliconTick::new(0))); assert!(history.contains(RepliconTick::new(1))); - assert!(!history.contains(RepliconTick::new(2))); + assert!(history.contains(RepliconTick::new(2))); } #[test] - fn resize() { - let mut confirmed = ConfirmHistory::new(RepliconTick::new(1)); + fn confirm_older() { + let mut history = ConfirmHistory::new(RepliconTick::new(1)); + assert_eq!(history.mask(), 0b1); - confirmed.set_last_tick(RepliconTick::new(2)); + history.confirm(RepliconTick::new(0)); + assert_eq!(history.mask(), 0b11); - assert!(!confirmed.contains(RepliconTick::new(0))); - assert!(confirmed.contains(RepliconTick::new(1))); - assert!(confirmed.contains(RepliconTick::new(2))); + assert!(history.contains(RepliconTick::new(0))); + assert!(history.contains(RepliconTick::new(1))); + assert!(!history.contains(RepliconTick::new(2))); } #[test] - fn resize_to_same() { + fn confirm_same() { let mut history = ConfirmHistory::new(RepliconTick::new(1)); - history.set_last_tick(RepliconTick::new(1)); + history.confirm(RepliconTick::new(1)); assert!(!history.contains(RepliconTick::new(0))); assert!(history.contains(RepliconTick::new(1))); @@ -250,10 +252,10 @@ mod tests { } #[test] - fn resize_with_wrapping() { + fn confirm_with_wrapping() { let mut history = ConfirmHistory::new(RepliconTick::new(1)); - history.set_last_tick(RepliconTick::new(u64::BITS + 1)); + history.confirm(RepliconTick::new(u64::BITS + 1)); assert!(history.contains(RepliconTick::new(0))); assert!(history.contains(RepliconTick::new(1))); @@ -264,38 +266,14 @@ mod tests { } #[test] - fn resize_with_overflow() { + fn confirm_with_overflow() { let mut history = ConfirmHistory::new(RepliconTick::new(u32::MAX)); - history.set_last_tick(RepliconTick::new(1)); + history.confirm(RepliconTick::new(1)); assert!(!history.contains(RepliconTick::new(0))); assert!(history.contains(RepliconTick::new(1))); assert!(!history.contains(RepliconTick::new(3))); assert!(history.contains(RepliconTick::new(u32::MAX))); } - - #[test] - fn confirm_with_resize() { - let mut history = ConfirmHistory::new(RepliconTick::new(1)); - - history.confirm(RepliconTick::new(2)); - - assert!(!history.contains(RepliconTick::new(0))); - assert!(history.contains(RepliconTick::new(1))); - assert!(history.contains(RepliconTick::new(2))); - } - - #[test] - fn confirm_with_set() { - let mut history = ConfirmHistory::new(RepliconTick::new(1)); - assert_eq!(history.mask(), 0b1); - - history.confirm(RepliconTick::new(0)); - assert_eq!(history.mask(), 0b11); - - assert!(history.contains(RepliconTick::new(0))); - assert!(history.contains(RepliconTick::new(1))); - assert!(!history.contains(RepliconTick::new(2))); - } } From 5e50ca1b6f52b3fb18e0e32a34b6f43279e69efd Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Thu, 21 Nov 2024 00:59:33 +0200 Subject: [PATCH 15/31] Update deny.toml --- deny.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deny.toml b/deny.toml index fbe64dd0..1a43984e 100644 --- a/deny.toml +++ b/deny.toml @@ -7,7 +7,7 @@ allow = [ "ISC", "MIT", "MIT-0", - "Unicode-DFS-2016", + "Unicode-3.0", "Zlib", ] From 5ae9e5f2380d97dd122090f7b52ceeb382408e2d Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Thu, 21 Nov 2024 01:56:51 +0200 Subject: [PATCH 16/31] Assert masks --- src/client/confirm_history.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/client/confirm_history.rs b/src/client/confirm_history.rs index 397eb4d6..4b1268d9 100644 --- a/src/client/confirm_history.rs +++ b/src/client/confirm_history.rs @@ -219,8 +219,8 @@ mod tests { #[test] fn confirm_newer() { let mut history = ConfirmHistory::new(RepliconTick::new(1)); - history.confirm(RepliconTick::new(2)); + assert_eq!(history.mask(), 0b11); assert!(!history.contains(RepliconTick::new(0))); assert!(history.contains(RepliconTick::new(1))); @@ -230,8 +230,6 @@ mod tests { #[test] fn confirm_older() { let mut history = ConfirmHistory::new(RepliconTick::new(1)); - assert_eq!(history.mask(), 0b1); - history.confirm(RepliconTick::new(0)); assert_eq!(history.mask(), 0b11); @@ -243,8 +241,8 @@ mod tests { #[test] fn confirm_same() { let mut history = ConfirmHistory::new(RepliconTick::new(1)); - history.confirm(RepliconTick::new(1)); + assert_eq!(history.mask(), 0b1); assert!(!history.contains(RepliconTick::new(0))); assert!(history.contains(RepliconTick::new(1))); @@ -254,8 +252,8 @@ mod tests { #[test] fn confirm_with_wrapping() { let mut history = ConfirmHistory::new(RepliconTick::new(1)); - history.confirm(RepliconTick::new(u64::BITS + 1)); + assert_eq!(history.mask(), 0b1); assert!(history.contains(RepliconTick::new(0))); assert!(history.contains(RepliconTick::new(1))); @@ -268,8 +266,8 @@ mod tests { #[test] fn confirm_with_overflow() { let mut history = ConfirmHistory::new(RepliconTick::new(u32::MAX)); - history.confirm(RepliconTick::new(1)); + assert_eq!(history.mask(), 0b101); assert!(!history.contains(RepliconTick::new(0))); assert!(history.contains(RepliconTick::new(1))); From df44894205625e6e3dd8f627d6651545d12d3030 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Fri, 22 Nov 2024 04:54:21 +0300 Subject: [PATCH 17/31] Export RTT and packet loss from the backend (#360) * Export statistics API * Add `warning` to method docs that should be called from the backend --- CHANGELOG.md | 5 ++ src/core/connected_clients.rs | 70 +++++++++++++++++++++++-- src/core/event_registry/server_event.rs | 10 ++-- src/core/replicon_client.rs | 51 +++++++++++++++++- src/core/replicon_server.rs | 12 ++++- 5 files changed, 135 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ad37bb..3cfd29d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- RTT and packet loss information for `RepliconClient` and `ConnectedClients`. + ### Changed +- `ConnectedClients` now store `ConnectedClient` instead of `ClientId` with more information about the client. - All `TestFnsEntityExt` now accept `FnsId`. - Move replication-related modules from `core` module under `core::replication`. - Move `Replicated` to the `replication` module. diff --git a/src/core/connected_clients.rs b/src/core/connected_clients.rs index fe1bbc4b..58dbca6c 100644 --- a/src/core/connected_clients.rs +++ b/src/core/connected_clients.rs @@ -7,14 +7,14 @@ use crate::core::ClientId; /// Inserted as resource by [`ServerPlugin`](crate::server::ServerPlugin). /// /// See also [ReplicatedClients](super::replication::replicated_clients::ReplicatedClients). -#[derive(Resource, Default, Deref)] -pub struct ConnectedClients(Vec); +#[derive(Resource, Default, Debug, Deref)] +pub struct ConnectedClients(Vec); impl ConnectedClients { pub(crate) fn add(&mut self, client_id: ClientId) { debug!("adding connected `{client_id:?}`"); - self.0.push(client_id); + self.0.push(ConnectedClient::new(client_id)); } pub(crate) fn remove(&mut self, client_id: ClientId) { @@ -22,8 +22,70 @@ impl ConnectedClients { let index = self .iter() - .position(|test_id| *test_id == client_id) + .position(|client| client.id == client_id) .unwrap_or_else(|| panic!("{client_id:?} should be added before removal")); self.0.remove(index); } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.0.iter_mut() + } +} + +#[derive(Debug, Clone, Copy)] +pub struct ConnectedClient { + id: ClientId, + rtt: f64, + packet_loss: f64, +} + +impl ConnectedClient { + pub fn new(id: ClientId) -> Self { + Self { + id, + rtt: 0.0, + packet_loss: 0.0, + } + } + + /// Returns the associated ID. + pub fn id(&self) -> ClientId { + self.id + } + + /// Returns the round-time trip for the connection. + /// + /// Returns zero if not provided by the backend. + pub fn rtt(&self) -> f64 { + self.rtt + } + + /// Sets the round-time trip for the connection. + /// + ///
+ /// + /// Should only be called from the messaging backend. + /// + ///
+ pub fn set_rtt(&mut self, rtt: f64) { + self.rtt = rtt; + } + + /// Returns the packet loss for the connection. + /// + /// Returns zero if not provided by the backend. + pub fn packet_loss(&self) -> f64 { + self.packet_loss + } + + /// Sets the packet loss for the connection. + /// + ///
+ /// + /// Should only be called from the messaging backend. + /// + ///
+ pub fn set_packet_loss(&mut self, packet_loss: f64) { + self.packet_loss = packet_loss; + } } diff --git a/src/core/event_registry/server_event.rs b/src/core/event_registry/server_event.rs index 60016b72..d95eb33d 100644 --- a/src/core/event_registry/server_event.rs +++ b/src/core/event_registry/server_event.rs @@ -637,14 +637,14 @@ unsafe fn send_independent_event( match *mode { SendMode::Broadcast => { - for &client_id in connected_clients.iter() { - server.send(client_id, event_data.channel_id, message.clone()); + for client in connected_clients.iter() { + server.send(client.id(), event_data.channel_id, message.clone()); } } SendMode::BroadcastExcept(id) => { - for &client_id in connected_clients.iter() { - if client_id != id { - server.send(client_id, event_data.channel_id, message.clone()); + for client in connected_clients.iter() { + if client.id() != id { + server.send(client.id(), event_data.channel_id, message.clone()); } } } diff --git a/src/core/replicon_client.rs b/src/core/replicon_client.rs index dc4b32ba..d0fe10a7 100644 --- a/src/core/replicon_client.rs +++ b/src/core/replicon_client.rs @@ -29,6 +29,9 @@ pub struct RepliconClient { /// List of sent messages and their channels since the last tick. sent_messages: Vec<(u8, Bytes)>, + + rtt: f64, + packet_loss: f64, } impl RepliconClient { @@ -153,14 +156,22 @@ impl RepliconClient { /// Removes all sent messages, returning them as an iterator with channel. /// - /// Should be called only from the messaging backend. + ///
+ /// + /// Should only be called from the messaging backend. + /// + ///
pub fn drain_sent(&mut self) -> impl Iterator + '_ { self.sent_messages.drain(..) } /// Adds a message from the server to the list of received messages. /// - /// Should be called only from the messaging backend. + ///
+ /// + /// Should only be called from the messaging backend. + /// + ///
pub fn insert_received, B: Into>(&mut self, channel_id: I, message: B) { if !self.is_connected() { warn!("trying to insert a received message when the client is not connected"); @@ -175,6 +186,42 @@ impl RepliconClient { channel_messages.push(message.into()); } + + /// Returns the round-time trip for the connection. + /// + /// Returns zero if not provided by the backend. + pub fn rtt(&self) -> f64 { + self.rtt + } + + /// Sets the round-time trip for the connection. + /// + ///
+ /// + /// Should only be called from the messaging backend. + /// + ///
+ pub fn set_rtt(&mut self, rtt: f64) { + self.rtt = rtt; + } + + /// Returns the packet loss for the connection. + /// + /// Returns zero if not provided by the backend. + pub fn packet_loss(&self) -> f64 { + self.packet_loss + } + + /// Sets the packet loss for the connection. + /// + ///
+ /// + /// Should only be called from the messaging backend. + /// + ///
+ pub fn set_packet_loss(&mut self, packet_loss: f64) { + self.packet_loss = packet_loss; + } } /// Connection status of the [`RepliconClient`]. diff --git a/src/core/replicon_server.rs b/src/core/replicon_server.rs index fda865ea..0ab52add 100644 --- a/src/core/replicon_server.rs +++ b/src/core/replicon_server.rs @@ -125,14 +125,22 @@ impl RepliconServer { /// Removes all sent messages, returning them as an iterator with client ID and channel. /// - /// Should be called only from the messaging backend. + ///
+ /// + /// Should only be called from the messaging backend. + /// + ///
pub fn drain_sent(&mut self) -> impl Iterator + '_ { self.sent_messages.drain(..) } /// Adds a message from a client to the list of received messages. /// - /// Should be called only from the messaging backend. + ///
+ /// + /// Should only be called from the messaging backend. + /// + ///
pub fn insert_received, B: Into>( &mut self, client_id: ClientId, From 0c7f0598a3f219233f5be7c4dbe19cdfc81f50ce Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Wed, 27 Nov 2024 01:54:19 +0300 Subject: [PATCH 18/31] Rework messages serialization (#352) Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com> --- CHANGELOG.md | 8 + Cargo.toml | 3 +- benches/replication.rs | 77 ++- src/client.rs | 642 ++++++++++-------- src/client/events.rs | 6 +- src/core/channels.rs | 18 +- src/core/event_registry/server_event.rs | 36 +- src/core/replication.rs | 1 + src/core/replication/change_message_flags.rs | 48 ++ src/core/replication/command_markers.rs | 8 +- src/core/replication/replicated_clients.rs | 120 ++-- .../replicated_clients/client_visibility.rs | 30 +- .../replication_registry/component_fns.rs | 10 +- .../replication_registry/rule_fns.rs | 8 +- .../replication_registry/test_fns.rs | 6 +- src/core/replication/replication_rules.rs | 6 +- src/core/replicon_tick.rs | 2 +- src/lib.rs | 21 +- src/server.rs | 334 ++++++--- src/server/replication_messages.rs | 83 +-- .../replication_messages/change_message.rs | 351 ++++++++++ .../replication_messages/component_changes.rs | 40 ++ .../replication_messages/init_message.rs | 344 ---------- .../replication_messages/mutate_message.rs | 237 +++++++ .../replication_messages/serialized_data.rs | 111 +++ .../replication_messages/update_message.rs | 255 ------- src/server/server_tick.rs | 2 +- tests/connection.rs | 32 +- tests/{changes.rs => mutations.rs} | 126 +++- tests/removal.rs | 32 + tests/server_event.rs | 44 +- tests/stats.rs | 2 +- 32 files changed, 1716 insertions(+), 1327 deletions(-) create mode 100644 src/core/replication/change_message_flags.rs create mode 100644 src/server/replication_messages/change_message.rs create mode 100644 src/server/replication_messages/component_changes.rs delete mode 100644 src/server/replication_messages/init_message.rs create mode 100644 src/server/replication_messages/mutate_message.rs create mode 100644 src/server/replication_messages/serialized_data.rs delete mode 100644 src/server/replication_messages/update_message.rs rename tests/{changes.rs => mutations.rs} (86%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cfd29d2..584730ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Various optimizations for replication messages to use fewer bytes. +- Accept `Vec` instead of `Cursor>` for serialization. - `ConnectedClients` now store `ConnectedClient` instead of `ClientId` with more information about the client. - All `TestFnsEntityExt` now accept `FnsId`. - Move replication-related modules from `core` module under `core::replication`. - Move `Replicated` to the `replication` module. - Split the `ctx` module and move event-related contexts under `core::events_registry::ctx` and replication-related contexts under `core::replication_registry::ctx`. +- Rename `ServerPlugin::change_timeout` into `ServerPlugin::mutations_timeout`. +- Rename `ServerInitTick` into `ServerChangeTick`. +- Rename `ReplicatedClient::init_tick` into `ReplicatedClient::change_tick`. +- Rename `ReplicatedClient::get_change_tick` into `ReplicatedClient::mutation_tick`. +- Rename `ReplicationChannel::Init` into `ReplicationChannel::Changes`. +- Rename `ReplicationChannel::Update` into `ReplicationChannel::Mutations`. ### Removed diff --git a/Cargo.toml b/Cargo.toml index fba5e7c9..7ccaa2d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ bincode = "1.3" serde = "1.0" integer-encoding = "4.0" ordered-multimap = "0.7" +bitflags = "2.6" [dev-dependencies] bevy = { version = "0.14", default-features = false, features = [ @@ -66,7 +67,7 @@ name = "replication" harness = false [[test]] -name = "changes" +name = "mutations" required-features = ["client", "server"] [[test]] diff --git a/benches/replication.rs b/benches/replication.rs index a8a746b7..130be87c 100644 --- a/benches/replication.rs +++ b/benches/replication.rs @@ -49,7 +49,7 @@ fn replication(c: name = &name[MODULE_PREFIX_LEN..]; for clients in [1, 20] { - c.bench_function(&format!("{name}, init send, {clients} client(s)"), |b| { + c.bench_function(&format!("{name}, changes send, {clients} client(s)"), |b| { b.iter_custom(|iter| { let mut elapsed = Duration::ZERO; for _ in 0..iter { @@ -82,53 +82,56 @@ fn replication(c: }) }); - c.bench_function(&format!("{name}, update send, {clients} client(s)"), |b| { - b.iter_custom(|iter| { - let mut server_app = create_app::(); - let mut client_apps = Vec::new(); - for _ in 0..clients { - client_apps.push(create_app::()); - } - - for client_app in &mut client_apps { - server_app.connect_client(client_app); - } - - server_app - .world_mut() - .spawn_batch(vec![(Replicated, C::default()); ENTITIES as usize]); - let mut query = server_app.world_mut().query::<&mut C>(); - - server_app.update(); - for client_app in &mut client_apps { - server_app.exchange_with_client(client_app); - client_app.update(); - assert_eq!(client_app.world().entities().len(), ENTITIES); - } + c.bench_function( + &format!("{name}, mutations send, {clients} client(s)"), + |b| { + b.iter_custom(|iter| { + let mut server_app = create_app::(); + let mut client_apps = Vec::new(); + for _ in 0..clients { + client_apps.push(create_app::()); + } - let mut elapsed = Duration::ZERO; - for _ in 0..iter { - for mut component in query.iter_mut(server_app.world_mut()) { - component.set_changed(); + for client_app in &mut client_apps { + server_app.connect_client(client_app); } - let instant = Instant::now(); - server_app.update(); - elapsed += instant.elapsed(); + server_app + .world_mut() + .spawn_batch(vec![(Replicated, C::default()); ENTITIES as usize]); + let mut query = server_app.world_mut().query::<&mut C>(); + server_app.update(); for client_app in &mut client_apps { server_app.exchange_with_client(client_app); client_app.update(); assert_eq!(client_app.world().entities().len(), ENTITIES); } - } - elapsed - }) - }); + let mut elapsed = Duration::ZERO; + for _ in 0..iter { + for mut component in query.iter_mut(server_app.world_mut()) { + component.set_changed(); + } + + let instant = Instant::now(); + server_app.update(); + elapsed += instant.elapsed(); + + for client_app in &mut client_apps { + server_app.exchange_with_client(client_app); + client_app.update(); + assert_eq!(client_app.world().entities().len(), ENTITIES); + } + } + + elapsed + }) + }, + ); } - c.bench_function(&format!("{name}, init receive"), |b| { + c.bench_function(&format!("{name}, changes receive"), |b| { b.iter_custom(|iter| { let mut elapsed = Duration::ZERO; for _ in 0..iter { @@ -154,7 +157,7 @@ fn replication(c: }) }); - c.bench_function(&format!("{name}, update receive"), |b| { + c.bench_function(&format!("{name}, mutations receive"), |b| { b.iter_custom(|iter| { let mut server_app = create_app::(); let mut client_app = create_app::(); diff --git a/src/client.rs b/src/client.rs index c0153b60..b254c70e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -8,12 +8,13 @@ use std::{io::Cursor, mem}; use bevy::{ecs::world::CommandQueue, prelude::*}; use bincode::{DefaultOptions, Options}; use bytes::Bytes; -use integer_encoding::VarIntReader; +use integer_encoding::{FixedIntReader, VarIntReader}; use crate::core::{ channels::{ReplicationChannel, RepliconChannels}, common_conditions::{client_connected, client_just_connected, client_just_disconnected}, replication::{ + change_message_flags::ChangeMessageFlags, command_markers::{CommandMarkers, EntityMarkers}, deferred_entity::DeferredEntity, replication_registry::{ @@ -37,8 +38,8 @@ impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { app.init_resource::() .init_resource::() - .init_resource::() - .init_resource::() + .init_resource::() + .init_resource::() .configure_sets( PreUpdate, ( @@ -75,18 +76,18 @@ impl ClientPlugin { /// Receives and applies replication messages from the server. /// - /// Tick init messages are sent over the [`ReplicationChannel::Init`] and are applied first to ensure valid state - /// for entity updates. + /// Change messages are sent over the [`ReplicationChannel::Changes`] and are applied first to ensure valid state + /// for component mutations. /// - /// Entity update messages are sent over [`ReplicationChannel::Update`], which means they may appear - /// ahead-of or behind init messages from the same server tick. An update will only be applied if its - /// change tick has already appeared in an init message, otherwise it will be buffered while waiting. - /// Since entity updates can arrive in any order, updates will only be applied if they correspond to a more + /// Mutate messages are sent over [`ReplicationChannel::Mutations`], which means they may appear + /// ahead-of or behind change messages from the same server tick. A mutation will only be applied if its + /// change tick has already appeared in an change message, otherwise it will be buffered while waiting. + /// Since component mutations can arrive in any order, they will only be applied if they correspond to a more /// recent server tick than the last acked server tick for each entity. /// - /// Buffered entity update messages are processed last. + /// Buffered mutate messages are processed last. /// - /// Acknowledgments for received entity update messages are sent back to the server. + /// Acknowledgments for received mutate messages are sent back to the server. /// /// See also [`ReplicationMessages`](crate::server::replication_messages::ReplicationMessages). pub(super) fn receive_replication( @@ -96,7 +97,7 @@ impl ClientPlugin { ) -> bincode::Result<()> { world.resource_scope(|world, mut client: Mut| { world.resource_scope(|world, mut entity_map: Mut| { - world.resource_scope(|world, mut buffered_updates: Mut| { + world.resource_scope(|world, mut buffered_mutations: Mut| { world.resource_scope(|world, command_markers: Mut| { world.resource_scope(|world, registry: Mut| { let mut stats = world.remove_resource::(); @@ -113,7 +114,7 @@ impl ClientPlugin { world, &mut params, &mut client, - &mut buffered_updates, + &mut buffered_mutations, )?; if let Some(stats) = stats { @@ -129,50 +130,52 @@ impl ClientPlugin { } fn reset( - mut init_tick: ResMut, + mut change_tick: ResMut, mut entity_map: ResMut, - mut buffered_updates: ResMut, + mut buffered_mutations: ResMut, ) { - *init_tick = Default::default(); + *change_tick = Default::default(); entity_map.clear(); - buffered_updates.clear(); + buffered_mutations.clear(); } } /// Reads all received messages and applies them. /// -/// Sends acknowledgments for update messages back. +/// Sends acknowledgments for mutate messages back. fn apply_replication( world: &mut World, params: &mut ReceiveParams, client: &mut RepliconClient, - buffered_updates: &mut BufferedUpdates, + buffered_mutations: &mut BufferedMutations, ) -> bincode::Result<()> { - for message in client.receive(ReplicationChannel::Init) { - apply_init_message(world, params, &message)?; + for message in client.receive(ReplicationChannel::Changes) { + apply_change_message(world, params, &message)?; } - // Unlike init messages, we read all updates first, sort them by tick - // in descending order to ensure that the last update will be applied first. - // Since update messages manually split by packet size, we apply all messages, + // Unlike change messages, we read all mutate messages first, sort them by tick + // in descending order to ensure that the last mutation will be applied first. + // Since mutate messages manually split by packet size, we apply all messages, // but skip outdated data per-entity by checking last received tick for it // (unless user requested history via marker). - let init_tick = *world.resource::(); - let acks_size = mem::size_of::() * client.received_count(ReplicationChannel::Update); + let change_tick = *world.resource::(); + let acks_size = mem::size_of::() * client.received_count(ReplicationChannel::Mutations); if acks_size != 0 { let mut acks = Vec::with_capacity(acks_size); - for message in client.receive(ReplicationChannel::Update) { - let update_index = read_update_message(params, buffered_updates, message)?; - bincode::serialize_into(&mut acks, &update_index)?; + for message in client.receive(ReplicationChannel::Mutations) { + let mutate_index = buffer_mutate_message(params, buffered_mutations, message)?; + bincode::serialize_into(&mut acks, &mutate_index)?; } - client.send(ReplicationChannel::Init, acks); + client.send(ReplicationChannel::Changes, acks); } - apply_update_messages(world, params, buffered_updates, init_tick) + apply_mutate_messages(world, params, buffered_mutations, change_tick) } -/// Applies [`InitMessage`](crate::server::replication_messages::InitMessage). -fn apply_init_message( +/// Reads and applies a change message. +/// +/// For details see [`replication_messages`](crate::server::replication_messages). +fn apply_change_message( world: &mut World, params: &mut ReceiveParams, message: &[u8], @@ -184,49 +187,71 @@ fn apply_init_message( stats.bytes += end_pos; } - let message_tick = bincode::deserialize_from(&mut cursor)?; - trace!("applying init message for {message_tick:?}"); - world.resource_mut::().0 = message_tick; - debug_assert!(cursor.position() < end_pos, "init message can't be empty"); + let flags = ChangeMessageFlags::from_bits_retain(cursor.read_fixedint()?); + debug_assert!(!flags.is_empty(), "message can't be empty"); - apply_entity_mappings(world, params, &mut cursor)?; - if cursor.position() == end_pos { - return Ok(()); - } + let message_tick = bincode::deserialize_from(&mut cursor)?; + trace!("applying change message for {message_tick:?}"); + world.resource_mut::().0 = message_tick; - apply_despawns(world, params, &mut cursor, message_tick)?; - if cursor.position() == end_pos { - return Ok(()); - } + let last_flag = flags.last(); + for (_, flag) in flags.iter_names() { + let array_kind = if flag != last_flag { + ArrayKind::Sized + } else { + ArrayKind::Dynamic + }; - apply_init_components( - world, - params, - ComponentsKind::Removal, - &mut cursor, - message_tick, - )?; - if cursor.position() == end_pos { - return Ok(()); + match flag { + ChangeMessageFlags::MAPPINGS => { + debug_assert_eq!(array_kind, ArrayKind::Sized); + let len = apply_array(array_kind, &mut cursor, |cursor| { + apply_entity_mapping(world, params, cursor) + })?; + if let Some(stats) = &mut params.stats { + stats.mappings += len as u32; + } + } + ChangeMessageFlags::DESPAWNS => { + let len = apply_array(array_kind, &mut cursor, |cursor| { + apply_despawn(world, params, cursor, message_tick) + })?; + if let Some(stats) = &mut params.stats { + stats.despawns += len as u32; + } + } + ChangeMessageFlags::REMOVALS => { + let len = apply_array(array_kind, &mut cursor, |cursor| { + apply_removals(world, params, cursor, message_tick) + })?; + if let Some(stats) = &mut params.stats { + stats.entities_changed += len as u32; + } + } + ChangeMessageFlags::CHANGES => { + debug_assert_eq!(array_kind, ArrayKind::Dynamic); + let len = apply_array(array_kind, &mut cursor, |cursor| { + apply_changes(world, params, cursor, message_tick) + })?; + if let Some(stats) = &mut params.stats { + stats.entities_changed += len as u32; + } + } + _ => unreachable!("iteration should yield only named flags"), + } } - apply_init_components( - world, - params, - ComponentsKind::Insert, - &mut cursor, - message_tick, - )?; - Ok(()) } -/// Reads and buffers [`UpdateMessage`](crate::server::replication_messages::UpdateMessage). +/// Reads and buffers mutate message. /// -/// Returns update index to be used for acknowledgment. -fn read_update_message( +/// For details see [`replication_messages`](crate::server::replication_messages). +/// +/// Returns mutate index to be used for acknowledgment. +fn buffer_mutate_message( params: &mut ReceiveParams, - buffered_updates: &mut BufferedUpdates, + buffered_mutations: &mut BufferedMutations, message: Bytes, ) -> bincode::Result { let end_pos: u64 = message.len().try_into().unwrap(); @@ -236,41 +261,49 @@ fn read_update_message( stats.bytes += end_pos; } - let (init_tick, message_tick, update_index) = bincode::deserialize_from(&mut cursor)?; - trace!("received update message for {message_tick:?}"); - buffered_updates.insert(BufferedUpdate { - init_tick, + let change_tick = bincode::deserialize_from(&mut cursor)?; + let message_tick = bincode::deserialize_from(&mut cursor)?; + let mutate_index = cursor.read_varint()?; + trace!("received mutate message for {message_tick:?}"); + buffered_mutations.insert(BufferedMutate { + change_tick, message_tick, message: message.slice(cursor.position() as usize..), }); - Ok(update_index) + Ok(mutate_index) } -/// Applies updates from [`BufferedUpdates`]. +/// Applies mutations from [`BufferedMutations`]. /// -/// If the update message can't be applied yet (because the init message with the +/// If the mutate message can't be applied yet (because the change message with the /// corresponding tick hasn't arrived), it will be kept in the buffer. -fn apply_update_messages( +fn apply_mutate_messages( world: &mut World, params: &mut ReceiveParams, - buffered_updates: &mut BufferedUpdates, - init_tick: ServerInitTick, + buffered_mutations: &mut BufferedMutations, + change_tick: ServerChangeTick, ) -> bincode::Result<()> { let mut result = Ok(()); - buffered_updates.0.retain(|update| { - if update.init_tick > *init_tick { + buffered_mutations.0.retain(|mutate| { + if mutate.change_tick > *change_tick { return true; } - trace!("applying update message for {:?}", update.message_tick); - if let Err(e) = apply_update_components( - world, - params, - &mut Cursor::new(&*update.message), - update.message_tick, - ) { - result = Err(e); + trace!("applying mutate message for {:?}", mutate.message_tick); + let len = apply_array( + ArrayKind::Dynamic, + &mut Cursor::new(&*mutate.message), + |cursor| apply_mutations(world, params, cursor, mutate.message_tick), + ); + + match len { + Ok(len) => { + if let Some(stats) = &mut params.stats { + stats.entities_changed += len as u32; + } + } + Err(e) => result = Err(e), } false @@ -279,231 +312,279 @@ fn apply_update_messages( result } -/// Applies received server mappings from client's pre-spawned entities. -fn apply_entity_mappings( +/// Deserializes and applies server mapping from client's pre-spawned entities. +fn apply_entity_mapping( world: &mut World, params: &mut ReceiveParams, cursor: &mut Cursor<&[u8]>, ) -> bincode::Result<()> { - let mappings_len: u16 = bincode::deserialize_from(&mut *cursor)?; - if let Some(stats) = &mut params.stats { - stats.mappings += mappings_len as u32; + let server_entity = deserialize_entity(cursor)?; + let client_entity = deserialize_entity(cursor)?; + + if let Some(mut entity) = world.get_entity_mut(client_entity) { + debug!("received mapping from {server_entity:?} to {client_entity:?}"); + entity.insert(Replicated); + params.entity_map.insert(server_entity, client_entity); + } else { + // Entity could be despawned on client already. + debug!("received mapping from {server_entity:?} to {client_entity:?}, but the entity doesn't exists"); } - for _ in 0..mappings_len { - let server_entity = deserialize_entity(cursor)?; - let client_entity = deserialize_entity(cursor)?; - - if let Some(mut entity) = world.get_entity_mut(client_entity) { - debug!("received mapping from {server_entity:?} to {client_entity:?}"); - entity.insert(Replicated); - params.entity_map.insert(server_entity, client_entity); - } else { - // Entity could be despawned on client already. - debug!("received mapping from {server_entity:?} to {client_entity:?}, but the entity doesn't exists"); - } + + Ok(()) +} + +/// Deserializes and applies entity despawn from change message. +fn apply_despawn( + world: &mut World, + params: &mut ReceiveParams, + cursor: &mut Cursor<&[u8]>, + message_tick: RepliconTick, +) -> bincode::Result<()> { + // The entity might have already been despawned because of hierarchy or + // with the last replication message, but the server might not yet have received confirmation + // from the client and could include the deletion in the this message. + let server_entity = deserialize_entity(cursor)?; + if let Some(client_entity) = params + .entity_map + .remove_by_server(server_entity) + .and_then(|entity| world.get_entity_mut(entity)) + { + let ctx = DespawnCtx { message_tick }; + (params.registry.despawn)(&ctx, client_entity); } + Ok(()) } -/// Deserializes replicated components of `components_kind` and applies them to the `world`. -fn apply_init_components( +/// Deserializes and applies component removals for an entity. +fn apply_removals( world: &mut World, params: &mut ReceiveParams, - components_kind: ComponentsKind, cursor: &mut Cursor<&[u8]>, message_tick: RepliconTick, ) -> bincode::Result<()> { - let entities_len: u16 = bincode::deserialize_from(&mut *cursor)?; - for _ in 0..entities_len { - let server_entity = deserialize_entity(cursor)?; - let data_size: u16 = bincode::deserialize_from(&mut *cursor)?; - - let client_entity = params - .entity_map - .get_by_server_or_insert(server_entity, || world.spawn(Replicated).id()); - - let (mut client_entity, mut commands) = read_entity(world, params.queue, client_entity); - params - .entity_markers - .read(params.command_markers, &*client_entity); - - if let Some(mut history) = client_entity.get_mut::() { - history.set_last_tick(message_tick); - } else { - commands - .entity(client_entity.id()) - .insert(ConfirmHistory::new(message_tick)); - } + let server_entity = deserialize_entity(cursor)?; - let end_pos = cursor.position() + data_size as u64; - let mut components_len = 0u32; - while cursor.position() < end_pos { - let fns_id = DefaultOptions::new().deserialize_from(&mut *cursor)?; - let (component_id, component_fns, rule_fns) = params.registry.get(fns_id); - match components_kind { - ComponentsKind::Insert => { - let mut ctx = - WriteCtx::new(&mut commands, params.entity_map, component_id, message_tick); - - // SAFETY: `rule_fns` and `component_fns` were created for the same type. - unsafe { - component_fns.write( - &mut ctx, - rule_fns, - params.entity_markers, - &mut client_entity, - cursor, - )?; - } - } - ComponentsKind::Removal => { - let mut ctx = RemoveCtx { - commands: &mut commands, - message_tick, - component_id, - }; - component_fns.remove(&mut ctx, params.entity_markers, &mut client_entity); - } - } - components_len += 1; - } + let client_entity = params + .entity_map + .get_by_server_or_insert(server_entity, || world.spawn(Replicated).id()); - if let Some(stats) = &mut params.stats { - stats.entities_changed += 1; - stats.components_changed += components_len; - } + let (mut client_entity, mut commands) = read_entity(world, params.queue, client_entity); + params + .entity_markers + .read(params.command_markers, &*client_entity); + + if let Some(mut history) = client_entity.get_mut::() { + history.set_last_tick(message_tick); + } else { + commands + .entity(client_entity.id()) + .insert(ConfirmHistory::new(message_tick)); + } + + let len = apply_array(ArrayKind::Sized, cursor, |cursor| { + let fns_id = DefaultOptions::new().deserialize_from(&mut *cursor)?; + let (component_id, component_fns, _) = params.registry.get(fns_id); + let mut ctx = RemoveCtx { + commands: &mut commands, + message_tick, + component_id, + }; + component_fns.remove(&mut ctx, params.entity_markers, &mut client_entity); + + Ok(()) + })?; - params.queue.apply(world); + if let Some(stats) = &mut params.stats { + stats.components_changed += len as u32; } + params.queue.apply(world); + Ok(()) } -/// Deserializes despawns and applies them to the `world`. -fn apply_despawns( +/// Deserializes and applies component insertions and/or mutations for an entity. +fn apply_changes( world: &mut World, params: &mut ReceiveParams, cursor: &mut Cursor<&[u8]>, message_tick: RepliconTick, ) -> bincode::Result<()> { - let entities_len: u16 = bincode::deserialize_from(&mut *cursor)?; - if let Some(stats) = &mut params.stats { - stats.despawns += entities_len as u32; + let server_entity = deserialize_entity(cursor)?; + + let client_entity = params + .entity_map + .get_by_server_or_insert(server_entity, || world.spawn(Replicated).id()); + + let (mut client_entity, mut commands) = read_entity(world, params.queue, client_entity); + params + .entity_markers + .read(params.command_markers, &*client_entity); + + if let Some(mut history) = client_entity.get_mut::() { + history.set_last_tick(message_tick); + } else { + commands + .entity(client_entity.id()) + .insert(ConfirmHistory::new(message_tick)); } - for _ in 0..entities_len { - // The entity might have already been despawned because of hierarchy or - // with the last replication message, but the server might not yet have received confirmation - // from the client and could include the deletion in the this message. - let server_entity = deserialize_entity(cursor)?; - if let Some(client_entity) = params - .entity_map - .remove_by_server(server_entity) - .and_then(|entity| world.get_entity_mut(entity)) - { - let ctx = DespawnCtx { message_tick }; - (params.registry.despawn)(&ctx, client_entity); + + let len = apply_array(ArrayKind::Sized, cursor, |cursor| { + let fns_id = DefaultOptions::new().deserialize_from(&mut *cursor)?; + let (component_id, component_fns, rule_fns) = params.registry.get(fns_id); + let mut ctx = WriteCtx::new(&mut commands, params.entity_map, component_id, message_tick); + + // SAFETY: `rule_fns` and `component_fns` were created for the same type. + unsafe { + component_fns.write( + &mut ctx, + rule_fns, + params.entity_markers, + &mut client_entity, + cursor, + )?; } + + Ok(()) + })?; + + if let Some(stats) = &mut params.stats { + stats.components_changed += len as u32; } + params.queue.apply(world); + Ok(()) } -/// Deserializes replicated component updates and applies them to the `world`. +fn apply_array( + kind: ArrayKind, + cursor: &mut Cursor<&[u8]>, + mut f: impl FnMut(&mut Cursor<&[u8]>) -> bincode::Result<()>, +) -> bincode::Result { + match kind { + ArrayKind::Sized => { + let len = cursor.read_varint()?; + for _ in 0..len { + (f)(cursor)?; + } + + Ok(len) + } + ArrayKind::Dynamic => { + let mut len = 0; + let end = cursor.get_ref().len() as u64; + while cursor.position() < end { + (f)(cursor)?; + len += 1; + } + + Ok(len) + } + } +} + +/// Type of serialized array. +#[derive(PartialEq, Eq, Debug)] +enum ArrayKind { + /// Size is serialized before the array. + Sized, + /// Size is unknown, means that all bytes needs to be consumed. + Dynamic, +} + +/// Deserializes and applies component mutations for all entities. /// /// Consumes all remaining bytes in the cursor. -fn apply_update_components( +fn apply_mutations( world: &mut World, params: &mut ReceiveParams, cursor: &mut Cursor<&[u8]>, message_tick: RepliconTick, ) -> bincode::Result<()> { - let message_end = cursor.get_ref().len() as u64; - while cursor.position() < message_end { - let server_entity = deserialize_entity(cursor)?; - let data_size: u16 = bincode::deserialize_from(&mut *cursor)?; - - let Some(client_entity) = params.entity_map.get_by_server(server_entity) else { - // Update could arrive after a despawn from init message. - debug!("ignoring update received for unknown server's {server_entity:?}"); - cursor.set_position(cursor.position() + data_size as u64); - continue; - }; + let server_entity = deserialize_entity(cursor)?; + let data_size: usize = cursor.read_varint()?; - let (mut client_entity, mut commands) = read_entity(world, params.queue, client_entity); - params - .entity_markers - .read(params.command_markers, &*client_entity); - - let mut history = client_entity - .get_mut::() - .expect("all entities from update should have confirmed ticks"); - let new_entity = message_tick > history.last_tick(); - if new_entity { - history.set_last_tick(message_tick); - } else { - if !params.entity_markers.need_history() { - trace!( - "ignoring outdated update for client's {:?}", - client_entity.id() - ); - cursor.set_position(cursor.position() + data_size as u64); - continue; - } + let Some(client_entity) = params.entity_map.get_by_server(server_entity) else { + // Mutation could arrive after a despawn from change message. + debug!("ignoring mutations received for unknown server's {server_entity:?}"); + cursor.set_position(cursor.position() + data_size as u64); + return Ok(()); + }; - let ago = history.last_tick().get().wrapping_sub(message_tick.get()); - if ago >= u64::BITS { - trace!( - "discarding update {ago} ticks old for client's {:?}", - client_entity.id() - ); - cursor.set_position(cursor.position() + data_size as u64); - continue; - } + let (mut client_entity, mut commands) = read_entity(world, params.queue, client_entity); + params + .entity_markers + .read(params.command_markers, &*client_entity); + + let mut history = client_entity + .get_mut::() + .expect("all entities from mutate message should have confirmed ticks"); + let new_tick = message_tick > history.last_tick(); + if new_tick { + history.set_last_tick(message_tick); + } else { + if !params.entity_markers.need_history() { + trace!( + "ignoring outdated mutations for client's {:?}", + client_entity.id() + ); + cursor.set_position(cursor.position() + data_size as u64); + return Ok(()); + } - history.set(ago); + let ago = history.last_tick().get().wrapping_sub(message_tick.get()); + if ago >= u64::BITS { + trace!( + "discarding {ago} ticks old mutations for client's {:?}", + client_entity.id() + ); + cursor.set_position(cursor.position() + data_size as u64); + return Ok(()); } - let end_pos = cursor.position() + data_size as u64; - let mut components_count = 0u32; - while cursor.position() < end_pos { - let fns_id = DefaultOptions::new().deserialize_from(&mut *cursor)?; - let (component_id, component_fns, rule_fns) = params.registry.get(fns_id); - let mut ctx = - WriteCtx::new(&mut commands, params.entity_map, component_id, message_tick); - - // SAFETY: `rule_fns` and `component_fns` were created for the same type. - unsafe { - if new_entity { - component_fns.write( - &mut ctx, - rule_fns, - params.entity_markers, - &mut client_entity, - cursor, - )?; - } else { - component_fns.consume_or_write( - &mut ctx, - rule_fns, - params.entity_markers, - params.command_markers, - &mut client_entity, - cursor, - )?; - } - } + history.set(ago); + } - components_count += 1; + let end_pos = cursor.position() + data_size as u64; + let mut components_count = 0; + while cursor.position() < end_pos { + let fns_id = DefaultOptions::new().deserialize_from(&mut *cursor)?; + let (component_id, component_fns, rule_fns) = params.registry.get(fns_id); + let mut ctx = WriteCtx::new(&mut commands, params.entity_map, component_id, message_tick); + + // SAFETY: `rule_fns` and `component_fns` were created for the same type. + unsafe { + if new_tick { + component_fns.write( + &mut ctx, + rule_fns, + params.entity_markers, + &mut client_entity, + cursor, + )?; + } else { + component_fns.consume_or_write( + &mut ctx, + rule_fns, + params.entity_markers, + params.command_markers, + &mut client_entity, + cursor, + )?; + } } - if let Some(stats) = &mut params.stats { - stats.entities_changed += 1; - stats.components_changed += components_count; - } + components_count += 1; + } - params.queue.apply(world); + if let Some(stats) = &mut params.stats { + stats.components_changed += components_count; } + params.queue.apply(world); + Ok(()) } @@ -551,14 +632,6 @@ struct ReceiveParams<'a> { registry: &'a ReplicationRegistry, } -/// Type of components replication. -/// -/// Parameter for [`apply_components`]. -enum ComponentsKind { - Insert, - Removal, -} - /// Set with replication and event systems related to client. #[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)] pub enum ClientSet { @@ -614,45 +687,44 @@ pub enum ClientSet { Reset, } -/// Last received tick for init message from server. +/// Last received tick for change messages from the server. /// -/// In other words, last [`RepliconTick`] with a removal, insertion, spawn or despawn. -/// When a component changes, this value is not updated. +/// In other words, the last [`RepliconTick`] with a removal, insertion, spawn or despawn. +/// This value is not updated when mutation messages are received from the server. #[derive(Clone, Copy, Debug, Default, Deref, Resource)] -pub struct ServerInitTick(RepliconTick); +pub struct ServerChangeTick(RepliconTick); -/// All cached buffered updates, used by the replicon client to align replication updates with initialization -/// messages. +/// Cached buffered mutate messages, used to synchronize mutations with change messages. /// /// If [`ClientSet::Reset`] is disabled, then this needs to be cleaned up manually with [`Self::clear`]. #[derive(Default, Resource)] -pub struct BufferedUpdates(Vec); +pub struct BufferedMutations(Vec); -impl BufferedUpdates { +impl BufferedMutations { pub fn clear(&mut self) { self.0.clear(); } - /// Inserts a new update, maintaining sorting by their message tick in descending order. - fn insert(&mut self, update: BufferedUpdate) { + /// Inserts a new buffered message, maintaining sorting by their message tick in descending order. + fn insert(&mut self, mutation: BufferedMutate) { let index = self .0 - .partition_point(|other_update| update.message_tick < other_update.message_tick); - self.0.insert(index, update); + .partition_point(|other_mutation| mutation.message_tick < other_mutation.message_tick); + self.0.insert(index, mutation); } } -/// Caches a partially-deserialized entity update message that is waiting for its tick to appear in an init message. +/// Partially-deserialized mutate message that is waiting for its tick to appear in an change message. /// -/// See also [`crate::server::replication_messages::UpdateMessage`]. -pub(super) struct BufferedUpdate { +/// See also [`crate::server::replication_messages`]. +pub(super) struct BufferedMutate { /// Required tick to wait for. - init_tick: RepliconTick, + change_tick: RepliconTick, - /// The tick this update corresponds to. + /// The tick this mutations corresponds to. message_tick: RepliconTick, - /// Update data. + /// Mutations data. message: Bytes, } diff --git a/src/client/events.rs b/src/client/events.rs index 359be3a0..ca98fcfc 100644 --- a/src/client/events.rs +++ b/src/client/events.rs @@ -1,4 +1,4 @@ -use super::{ClientPlugin, ClientSet, ServerInitTick}; +use super::{ClientPlugin, ClientSet, ServerChangeTick}; use crate::core::{ common_conditions::*, event_registry::{ @@ -85,7 +85,7 @@ impl ClientEventsPlugin { world.resource_scope(|world, registry: Mut| { world.resource_scope(|world, entity_map: Mut| { world.resource_scope(|world, event_registry: Mut| { - let init_tick = **world.resource::(); + let change_tick = **world.resource::(); let mut ctx = ClientReceiveCtx { registry: ®istry.read(), entity_map: &entity_map, @@ -112,7 +112,7 @@ impl ClientEventsPlugin { events.into_inner(), queue.into_inner(), &mut client, - init_tick, + change_tick, ) }; } diff --git a/src/core/channels.rs b/src/core/channels.rs index 96c47d74..7a93d72a 100644 --- a/src/core/channels.rs +++ b/src/core/channels.rs @@ -10,18 +10,18 @@ pub enum ReplicationChannel { /// For sending messages with entity mappings, inserts, removals and despawns. /// /// This is an ordered reliable channel. - Init, - /// For sending messages with component updates. + Changes, + /// For sending messages with component mutations. /// /// This is an unreliable channel. - Update, + Mutations, } impl From for RepliconChannel { fn from(value: ReplicationChannel) -> Self { match value { - ReplicationChannel::Init => ChannelKind::Ordered.into(), - ReplicationChannel::Update => ChannelKind::Unreliable.into(), + ReplicationChannel::Changes => ChannelKind::Ordered.into(), + ReplicationChannel::Mutations => ChannelKind::Unreliable.into(), } } } @@ -53,12 +53,12 @@ impl Default for RepliconChannels { fn default() -> Self { Self { server: vec![ - ReplicationChannel::Init.into(), - ReplicationChannel::Update.into(), + ReplicationChannel::Changes.into(), + ReplicationChannel::Mutations.into(), ], client: vec![ - ReplicationChannel::Init.into(), - ReplicationChannel::Update.into(), + ReplicationChannel::Changes.into(), + ReplicationChannel::Mutations.into(), ], default_max_bytes: 5 * 1024 * 1024, } diff --git a/src/core/event_registry/server_event.rs b/src/core/event_registry/server_event.rs index d95eb33d..d47774d8 100644 --- a/src/core/event_registry/server_event.rs +++ b/src/core/event_registry/server_event.rs @@ -340,9 +340,9 @@ impl ServerEvent { events: PtrMut, queue: PtrMut, client: &mut RepliconClient, - init_tick: RepliconTick, + change_tick: RepliconTick, ) { - (self.receive)(self, ctx, events, queue, client, init_tick); + (self.receive)(self, ctx, events, queue, client, change_tick); } /// Drains events [`ToClients`] and re-emits them as `E` if the server is in the list of the event recipients. @@ -494,12 +494,12 @@ unsafe fn receive( events: PtrMut, queue: PtrMut, client: &mut RepliconClient, - init_tick: RepliconTick, + change_tick: RepliconTick, ) { let events: &mut Events = events.deref_mut(); let queue: &mut ServerEventQueue = queue.deref_mut(); - while let Some((tick, message)) = queue.pop_if_le(init_tick) { + while let Some((tick, message)) = queue.pop_if_le(change_tick) { let mut cursor = Cursor::new(&*message); match event_data.deserialize(ctx, &mut cursor) { Ok(event) => { @@ -529,7 +529,7 @@ unsafe fn receive( continue; } }; - if tick > init_tick { + if tick > change_tick { trace!("queuing event `{}` with `{tick:?}`", any::type_name::()); queue.insert(tick, message.slice(cursor.position() as usize..)); continue; @@ -699,19 +699,19 @@ enum SerializedMessage { } impl SerializedMessage { - /// Optimized to avoid reallocations when clients have the same init tick as other clients receiving the + /// Optimized to avoid reallocations when clients have the same change tick as other clients receiving the /// same message. - fn get_bytes(&mut self, init_tick: RepliconTick) -> bincode::Result { + fn get_bytes(&mut self, change_tick: RepliconTick) -> bincode::Result { match self { // Resolve the raw value into a message with serialized tick. Self::Raw(raw) => { let mut bytes = std::mem::take(raw); - let tick_size = DefaultOptions::new().serialized_size(&init_tick)? as usize; + let tick_size = DefaultOptions::new().serialized_size(&change_tick)? as usize; let padding = RepliconTick::MAX_SERIALIZED_SIZE - tick_size; - DefaultOptions::new().serialize_into(&mut bytes[padding..], &init_tick)?; + DefaultOptions::new().serialize_into(&mut bytes[padding..], &change_tick)?; let bytes = Bytes::from(bytes).slice(padding..); *self = Self::Resolved { - tick: init_tick, + tick: change_tick, tick_size, bytes: bytes.clone(), }; @@ -723,13 +723,13 @@ impl SerializedMessage { tick_size, bytes, } => { - if *tick == init_tick { + if *tick == change_tick { return Ok(bytes.clone()); } - let new_tick_size = DefaultOptions::new().serialized_size(&init_tick)? as usize; + let new_tick_size = DefaultOptions::new().serialized_size(&change_tick)? as usize; let mut new_bytes = Vec::with_capacity(new_tick_size + bytes.len() - *tick_size); - DefaultOptions::new().serialize_into(&mut new_bytes, &init_tick)?; + DefaultOptions::new().serialize_into(&mut new_bytes, &change_tick)?; new_bytes.extend_from_slice(&bytes[*tick_size..]); Ok(new_bytes.into()) } @@ -749,7 +749,7 @@ impl BufferedServerEvent { server: &mut RepliconServer, client: &ReplicatedClient, ) -> bincode::Result<()> { - let message = self.message.get_bytes(client.init_tick())?; + let message = self.message.get_bytes(client.change_tick())?; server.send(client.id(), self.channel, message); Ok(()) } @@ -769,10 +769,10 @@ impl BufferedServerEventSet { } } -/// Caches synchronization-dependent server events until they can be sent with an accurate init tick. +/// Caches synchronization-dependent server events until they can be sent with an accurate change tick. /// /// This exists because replication does not scan the world every tick. If a server event is sent in the same -/// tick as a spawn and the event references that spawn, then the server event's init tick needs to be synchronized +/// tick as a spawn and the event references that spawn, then the server event's change tick needs to be synchronized /// with that spawn on the client. We buffer the event until the spawn can be detected. #[derive(Resource, Default)] pub(crate) struct BufferedServerEvents { @@ -890,9 +890,9 @@ struct ServerEventQueue { impl ServerEventQueue { /// Pops the next event that is at least as old as the specified replicon tick. - fn pop_if_le(&mut self, init_tick: RepliconTick) -> Option<(RepliconTick, Bytes)> { + fn pop_if_le(&mut self, change_tick: RepliconTick) -> Option<(RepliconTick, Bytes)> { let (tick, _) = self.list.front()?; - if *tick > init_tick { + if *tick > change_tick { return None; } self.list diff --git a/src/core/replication.rs b/src/core/replication.rs index 33b30885..a657f8dd 100644 --- a/src/core/replication.rs +++ b/src/core/replication.rs @@ -1,3 +1,4 @@ +pub mod change_message_flags; pub mod command_markers; pub mod deferred_entity; pub mod replicated_clients; diff --git a/src/core/replication/change_message_flags.rs b/src/core/replication/change_message_flags.rs new file mode 100644 index 00000000..834bda3a --- /dev/null +++ b/src/core/replication/change_message_flags.rs @@ -0,0 +1,48 @@ +use bitflags::bitflags; + +bitflags! { + /// Types of data included in the change message if the bit is set. + /// + /// Serialized at the beginning of the message. + #[derive(Default, Clone, Copy, PartialEq, Eq, Debug)] + pub(crate) struct ChangeMessageFlags: u8 { + const MAPPINGS = 0b00000001; + const DESPAWNS = 0b00000010; + const REMOVALS = 0b00000100; + const CHANGES = 0b00001000; + } +} + +impl ChangeMessageFlags { + /// Returns the last set flag in the message. + pub(crate) fn last(self) -> ChangeMessageFlags { + debug_assert!(!self.is_empty()); + let zeroes = u8::BITS - 1 - self.bits().leading_zeros(); + ChangeMessageFlags::from_bits_retain(1 << zeroes) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn last() { + assert_eq!( + ChangeMessageFlags::CHANGES.last(), + ChangeMessageFlags::CHANGES + ); + assert_eq!( + ChangeMessageFlags::MAPPINGS.last(), + ChangeMessageFlags::MAPPINGS + ); + assert_eq!( + ChangeMessageFlags::all().last(), + ChangeMessageFlags::CHANGES + ); + assert_eq!( + (ChangeMessageFlags::DESPAWNS | ChangeMessageFlags::REMOVALS).last(), + ChangeMessageFlags::REMOVALS + ); + } +} diff --git a/src/core/replication/command_markers.rs b/src/core/replication/command_markers.rs index 0869dd7f..ca17361b 100644 --- a/src/core/replication/command_markers.rs +++ b/src/core/replication/command_markers.rs @@ -231,11 +231,11 @@ pub struct MarkerConfig { /// By default set to `0`. pub priority: usize, - /// Represents whether a marker needs to process old updates. + /// Represents whether a marker needs to process old mutations. /// - /// Since updates use [`ChannelKind::Unreliable`](crate::core::channels::ChannelKind), - /// a client may receive an older update for an entity. By default these updates are discarded, - /// but some markers may need them. If this field is set to `true`, old component updates will + /// Since mutations use [`ChannelKind::Unreliable`](crate::core::channels::ChannelKind), + /// a client may receive an older mutation for an entity component. By default these mutations are discarded, + /// but some markers may need them. If this field is set to `true`, old component mutations will /// be passed to the writing function for this marker. /// /// By default set to `false`. diff --git a/src/core/replication/replicated_clients.rs b/src/core/replication/replicated_clients.rs index f15d4c49..615d01c7 100644 --- a/src/core/replication/replicated_clients.rs +++ b/src/core/replication/replicated_clients.rs @@ -172,7 +172,7 @@ pub struct ReplicatedClient { id: ClientId, /// Lowest tick for use in change detection for each entity. - change_ticks: EntityHashMap, + mutation_ticks: EntityHashMap, /// Entity visibility settings. visibility: ClientVisibility, @@ -180,28 +180,28 @@ pub struct ReplicatedClient { /// The last tick in which a replicated entity had an insertion, removal, or gained/lost a component from the /// perspective of the client. /// - /// It should be included in update messages and server events to avoid needless waiting for the next init + /// It should be included in mutate messages and server events to avoid needless waiting for the next change /// message to arrive. - init_tick: RepliconTick, + change_tick: RepliconTick, - /// Update message indexes mapped to their info. - updates: HashMap, + /// Mutate message indices mapped to their info. + mutations: HashMap, - /// Index for the next update message to be sent to this client. + /// Index for the next mutate message to be sent to this client. /// - /// See also [`Self::register_update`]. - next_update_index: u16, + /// See also [`Self::register_mutate_message`]. + next_mutate_index: u16, } impl ReplicatedClient { fn new(id: ClientId, policy: VisibilityPolicy) -> Self { Self { id, - change_ticks: Default::default(), + mutation_ticks: Default::default(), visibility: ClientVisibility::new(policy), - init_tick: Default::default(), - updates: Default::default(), - next_update_index: Default::default(), + change_tick: Default::default(), + mutations: Default::default(), + next_mutate_index: Default::default(), } } @@ -220,24 +220,24 @@ impl ReplicatedClient { &mut self.visibility } - /// Sets the client's init tick. - pub(crate) fn set_init_tick(&mut self, tick: RepliconTick) { - self.init_tick = tick; + /// Sets the client's change tick. + pub(crate) fn set_change_tick(&mut self, tick: RepliconTick) { + self.change_tick = tick; } /// Returns the last tick in which a replicated entity had an insertion, removal, or gained/lost a component from the /// perspective of the client. - pub fn init_tick(&self) -> RepliconTick { - self.init_tick + pub fn change_tick(&self) -> RepliconTick { + self.change_tick } - /// Clears all entities for unacknowledged updates, returning them as an iterator. + /// Clears all entities for unacknowledged mutate messages, returning them as an iterator. /// /// Keeps the allocated memory for reuse. fn drain_entities(&mut self) -> impl Iterator> + '_ { - self.updates + self.mutations .drain() - .map(|(_, update_info)| update_info.entities) + .map(|(_, mutate_info)| mutate_info.entities) } /// Resets all data. @@ -246,98 +246,98 @@ impl ReplicatedClient { fn reset(&mut self, id: ClientId) { self.id = id; self.visibility.clear(); - self.change_ticks.clear(); - self.updates.clear(); - self.next_update_index = 0; + self.mutation_ticks.clear(); + self.mutations.clear(); + self.next_mutate_index = 0; } - /// Registers update at specified `tick` and `timestamp` and returns its index with entities to fill. + /// Registers mutate message at specified `tick` and `timestamp` and returns its index with entities to fill. /// /// Used later to acknowledge updated entities. #[must_use] - pub(crate) fn register_update( + pub(crate) fn register_mutate_message( &mut self, client_buffers: &mut ClientBuffers, tick: Tick, timestamp: Duration, ) -> (u16, &mut Vec) { - let update_index = self.next_update_index; - self.next_update_index = self.next_update_index.overflowing_add(1).0; + let mutate_index = self.next_mutate_index; + self.next_mutate_index = self.next_mutate_index.overflowing_add(1).0; let mut entities = client_buffers.entities.pop().unwrap_or_default(); entities.clear(); - let update_info = UpdateInfo { + let mutate_info = MutateInfo { tick, timestamp, entities, }; - let update_info = self - .updates - .entry(update_index) - .insert(update_info) + let mutate_info = self + .mutations + .entry(mutate_index) + .insert(mutate_info) .into_mut(); - (update_index, &mut update_info.entities) + (mutate_index, &mut mutate_info.entities) } /// Sets the change tick for an entity that is replicated to this client. /// /// The change tick is the reference point for determining if components on an entity have changed and /// need to be replicated. Component changes older than the change limit are assumed to be acked by the client. - pub(crate) fn set_change_tick(&mut self, entity: Entity, tick: Tick) { - self.change_ticks.insert(entity, tick); + pub(crate) fn set_mutation_tick(&mut self, entity: Entity, tick: Tick) { + self.mutation_ticks.insert(entity, tick); } /// Gets the change tick for an entity that is replicated to this client. - pub fn get_change_tick(&self, entity: Entity) -> Option { - self.change_ticks.get(&entity).copied() + pub fn mutation_tick(&self, entity: Entity) -> Option { + self.mutation_ticks.get(&entity).copied() } - /// Marks update with the specified index as acknowledged. + /// Marks mutate message as acknowledged by its index. /// - /// Change limits for all entities from this update will be set to the update's tick if it's higher. + /// Change tick for all entities from this mutate message will be set to the message tick if it's higher. /// /// Keeps allocated memory in the buffers for reuse. - pub(crate) fn acknowledge( + pub(crate) fn ack_mutate_message( &mut self, client_buffers: &mut ClientBuffers, tick: Tick, - update_index: u16, + mutate_index: u16, ) { - let Some(update_info) = self.updates.remove(&update_index) else { + let Some(mutate_info) = self.mutations.remove(&mutate_index) else { debug!( - "received unknown update index {update_index} from {:?}", + "received unknown mutate index {mutate_index} from {:?}", self.id ); return; }; - for entity in &update_info.entities { - let Some(last_tick) = self.change_ticks.get_mut(entity) else { + for entity in &mutate_info.entities { + let Some(last_tick) = self.mutation_ticks.get_mut(entity) else { // We ignore missing entities, since they were probably despawned. continue; }; // Received tick could be outdated because we bump it // if we detect any insertion on the entity in `collect_changes`. - if !last_tick.is_newer_than(update_info.tick, tick) { - *last_tick = update_info.tick; + if !last_tick.is_newer_than(mutate_info.tick, tick) { + *last_tick = mutate_info.tick; } } - client_buffers.entities.push(update_info.entities); + client_buffers.entities.push(mutate_info.entities); trace!( - "{:?} acknowledged an update with {:?}", + "{:?} acknowledged mutate message with {:?}", self.id, - update_info.tick, + mutate_info.tick, ); } /// Removes a despawned entity tracked by this client. pub fn remove_despawned(&mut self, entity: Entity) { - self.change_ticks.remove(&entity); + self.mutation_ticks.remove(&entity); self.visibility.remove_despawned(entity); - // We don't clean up `self.updates` for efficiency reasons. + // We don't clean up `self.mutations` for efficiency reasons. // `Self::acknowledge()` will properly ignore despawned entities. } @@ -346,23 +346,23 @@ impl ReplicatedClient { /// Internal cleanup happens lazily during the iteration. pub(crate) fn drain_lost_visibility(&mut self) -> impl Iterator + '_ { self.visibility.drain_lost_visibility().inspect(|entity| { - self.change_ticks.remove(entity); + self.mutation_ticks.remove(entity); }) } - /// Removes all updates older then `min_timestamp`. + /// Removes all mutate messages older then `min_timestamp`. /// /// Keeps allocated memory in the buffers for reuse. - pub(crate) fn remove_older_updates( + pub(crate) fn cleanup_older_mutations( &mut self, client_buffers: &mut ClientBuffers, min_timestamp: Duration, ) { - self.updates.retain(|_, update_info| { - if update_info.timestamp < min_timestamp { + self.mutations.retain(|_, mutate_info| { + if mutate_info.timestamp < min_timestamp { client_buffers .entities - .push(mem::take(&mut update_info.entities)); + .push(mem::take(&mut mutate_info.entities)); false } else { true @@ -379,13 +379,13 @@ pub(crate) struct ClientBuffers { /// Stored to reuse allocated memory. clients: Vec, - /// [`Vec`]'s from acknowledged update indexes from [`ReplicatedClient`]. + /// [`Vec`]'s from acknowledged [`MutateInfo`]'s. /// /// Stored to reuse allocated capacity. entities: Vec>, } -struct UpdateInfo { +struct MutateInfo { tick: Tick, timestamp: Duration, entities: Vec, diff --git a/src/core/replication/replicated_clients/client_visibility.rs b/src/core/replication/replicated_clients/client_visibility.rs index 7242e73e..f8632fc0 100644 --- a/src/core/replication/replicated_clients/client_visibility.rs +++ b/src/core/replication/replicated_clients/client_visibility.rs @@ -9,11 +9,6 @@ use super::VisibilityPolicy; /// Entity visibility settings for a client. pub struct ClientVisibility { filter: VisibilityFilter, - - /// Visibility for a specific entity that has been cached for re-referencing. - /// - /// Used as an optimization by server replication. - cached_visibility: Visibility, } impl ClientVisibility { @@ -36,10 +31,7 @@ impl ClientVisibility { /// Creates a new instance with a specific filter. fn with_filter(filter: VisibilityFilter) -> Self { - Self { - filter, - cached_visibility: Default::default(), - } + Self { filter } } /// Resets the filter state to as it was after [`Self::new`]. @@ -177,7 +169,7 @@ impl ClientVisibility { // For blacklisting an entity we don't remove the entity right away. // Instead we mark it as queued for removal and remove it // later in `Self::update`. This allows us to avoid accessing - // the blacklist's `removed` field in `Self::get_visibility_state`. + // the blacklist's `removed` field in `Self::visibility_state`. entry.insert(BlacklistInfo::QueuedForRemoval); removed.insert(entity); } else { @@ -200,7 +192,7 @@ impl ClientVisibility { // Instead we mark it as `WhitelistInfo::JustAdded` and then set it to // 'WhitelistInfo::Visible' in `Self::update`. // This allows us to avoid accessing the whitelist's `added` field in - // `Self::get_visibility_state`. + // `Self::visibility_state`. if *list.entry(entity).or_insert(WhitelistInfo::JustAdded) == WhitelistInfo::JustAdded { @@ -227,26 +219,14 @@ impl ClientVisibility { /// Checks if a specific entity is visible. pub fn is_visible(&self, entity: Entity) -> bool { - match self.get_visibility_state(entity) { + match self.visibility_state(entity) { Visibility::Hidden => false, Visibility::Gained | Visibility::Visible => true, } } - /// Caches visibility for a specific entity. - /// - /// Can be obtained later from [`Self::cached_visibility`]. - pub(crate) fn cache_visibility(&mut self, entity: Entity) { - self.cached_visibility = self.get_visibility_state(entity); - } - - /// Returns visibility cached by the last call of [`Self::cache_visibility`]. - pub(crate) fn cached_visibility(&self) -> Visibility { - self.cached_visibility - } - /// Returns visibility of a specific entity. - fn get_visibility_state(&self, entity: Entity) -> Visibility { + pub(crate) fn visibility_state(&self, entity: Entity) -> Visibility { match &self.filter { VisibilityFilter::All => Visibility::Visible, VisibilityFilter::Blacklist { list, .. } => match list.get(&entity) { diff --git a/src/core/replication/replication_registry/component_fns.rs b/src/core/replication/replication_registry/component_fns.rs index c22b6781..a43f67ee 100644 --- a/src/core/replication/replication_registry/component_fns.rs +++ b/src/core/replication/replication_registry/component_fns.rs @@ -88,9 +88,9 @@ impl ComponentFns { ctx: &SerializeCtx, rule_fns: &UntypedRuleFns, ptr: Ptr, - cursor: &mut Cursor>, + message: &mut Vec, ) -> bincode::Result<()> { - (self.serialize)(ctx, rule_fns, ptr, cursor) + (self.serialize)(ctx, rule_fns, ptr, message) } /// Calls the assigned writing function based on entity markers. @@ -174,7 +174,7 @@ impl ComponentFns { /// Signature of component serialization functions that restore the original type. type UntypedSerializeFn = - unsafe fn(&SerializeCtx, &UntypedRuleFns, Ptr, &mut Cursor>) -> bincode::Result<()>; + unsafe fn(&SerializeCtx, &UntypedRuleFns, Ptr, &mut Vec) -> bincode::Result<()>; /// Signature of component writing functions that restore the original type. type UntypedWriteFn = unsafe fn( @@ -198,10 +198,10 @@ unsafe fn untyped_serialize( ctx: &SerializeCtx, rule_fns: &UntypedRuleFns, ptr: Ptr, - cursor: &mut Cursor>, + message: &mut Vec, ) -> bincode::Result<()> { let rule_fns = rule_fns.typed::(); - rule_fns.serialize(ctx, ptr.deref::(), cursor) + rule_fns.serialize(ctx, ptr.deref::(), message) } /// Resolves `rule_fns` to `C` and calls [`UntypedCommandFns::write`] for `C`. diff --git a/src/core/replication/replication_registry/rule_fns.rs b/src/core/replication/replication_registry/rule_fns.rs index 4ca144b0..92805199 100644 --- a/src/core/replication/replication_registry/rule_fns.rs +++ b/src/core/replication/replication_registry/rule_fns.rs @@ -125,9 +125,9 @@ impl RuleFns { &self, ctx: &SerializeCtx, component: &C, - cursor: &mut Cursor>, + message: &mut Vec, ) -> bincode::Result<()> { - (self.serialize)(ctx, component, cursor) + (self.serialize)(ctx, component, message) } /// Deserializes a component from a cursor. @@ -187,7 +187,7 @@ impl Default for RuleFns { } /// Signature of component serialization functions. -pub type SerializeFn = fn(&SerializeCtx, &C, &mut Cursor>) -> bincode::Result<()>; +pub type SerializeFn = fn(&SerializeCtx, &C, &mut Vec) -> bincode::Result<()>; /// Signature of component deserialization functions. pub type DeserializeFn = fn(&mut WriteCtx, &mut Cursor<&[u8]>) -> bincode::Result; @@ -204,7 +204,7 @@ pub type ConsumeFn = pub fn default_serialize( _ctx: &SerializeCtx, component: &C, - cursor: &mut Cursor>, + cursor: &mut Vec, ) -> bincode::Result<()> { DefaultOptions::new().serialize_into(cursor, component) } diff --git a/src/core/replication/replication_registry/test_fns.rs b/src/core/replication/replication_registry/test_fns.rs index 5592b889..791984f6 100644 --- a/src/core/replication/replication_registry/test_fns.rs +++ b/src/core/replication/replication_registry/test_fns.rs @@ -92,7 +92,7 @@ impl TestFnsEntityExt for EntityWorldMut<'_> { fn serialize(&mut self, fns_id: FnsId, server_tick: RepliconTick) -> Vec { let registry = self.world().resource::(); let (component_id, component_fns, rule_fns) = registry.get(fns_id); - let mut cursor = Cursor::default(); + let mut message = Vec::new(); let ctx = SerializeCtx { server_tick, component_id, @@ -107,11 +107,11 @@ impl TestFnsEntityExt for EntityWorldMut<'_> { unsafe { component_fns - .serialize(&ctx, rule_fns, ptr, &mut cursor) + .serialize(&ctx, rule_fns, ptr, &mut message) .expect("serialization into memory should never fail"); } - cursor.into_inner() + message } fn apply_write(&mut self, data: &[u8], fns_id: FnsId, message_tick: RepliconTick) -> &mut Self { diff --git a/src/core/replication/replication_rules.rs b/src/core/replication/replication_rules.rs index 5689437f..0f5d2e74 100644 --- a/src/core/replication/replication_rules.rs +++ b/src/core/replication/replication_rules.rs @@ -98,9 +98,9 @@ pub trait AppRuleExt { fn serialize_translation( _ctx: &SerializeCtx, transform: &Transform, - cursor: &mut Cursor>, + message: &mut Vec, ) -> bincode::Result<()> { - bincode::serialize_into(cursor, &transform.translation) + bincode::serialize_into(message, &transform.translation) } /// Deserializes `translation` and creates [`Transform`] from it. @@ -317,7 +317,7 @@ impl GroupReplication for PlayerBundle { } } -# fn serialize_translation(_: &SerializeCtx, _: &Transform, _: &mut Cursor>) -> bincode::Result<()> { unimplemented!() } +# fn serialize_translation(_: &SerializeCtx, _: &Transform, _: &mut Vec) -> bincode::Result<()> { unimplemented!() } # fn deserialize_translation(_: &mut WriteCtx, _: &mut Cursor<&[u8]>) -> bincode::Result { unimplemented!() } ``` **/ diff --git a/src/core/replicon_tick.rs b/src/core/replicon_tick.rs index 86a5524e..446e2612 100644 --- a/src/core/replicon_tick.rs +++ b/src/core/replicon_tick.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; /// /// All operations on it are wrapping. /// -/// See also [`ServerInitTick`](crate::client::ServerInitTick) and +/// See also [`ServerChangeTick`](crate::client::ServerChangeTick) and /// [`ServerTick`](crate::server::server_tick::ServerTick). #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct RepliconTick(u32); diff --git a/src/lib.rs b/src/lib.rs index 1101dbb6..e2ee2bd3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -583,27 +583,18 @@ For a higher level API consider using [`bevy_replicon_attributes`](https://docs. All events, inserts, removals and despawns will be applied to clients in the same order as on the server. -Entity component updates are grouped by entity, and component groupings may be applied to clients in a different order than on the server. -For example, if two entities are spawned in tick 1 on the server and their components are updated in tick 2, -then the client is guaranteed to see the spawns at the same time, but the component updates may appear in different client ticks. +Entity component mutations are grouped by entity, and component groupings may be applied to clients in a different order than on the server. +For example, if two entities are spawned in tick 1 on the server and their components are mutated in tick 2, +then the client is guaranteed to see the spawns at the same time, but the component mutations may appear in different client ticks. -If a component is dependent on other data, updates to the component will only be applied to the client when that data has arrived. -So if your component references another entity, updates to that component will only be applied when the referenced entity has been spawned on the client. +If a component is dependent on other data, mutations to the component will only be applied to the client when that data has arrived. +So if your component references another entity, mutations to that component will only be applied when the referenced entity has been spawned on the client. -Updates for despawned entities will be discarded automatically, but events or components may reference despawned entities and should be handled with that in mind. +Mutations for despawned entities will be discarded automatically, but events or components may reference despawned entities and should be handled with that in mind. Clients should never assume their world state is the same as the server's on any given tick value-wise. World state on the client is only "eventually consistent" with the server's. -# Limits - -To reduce packet size there are the following limits per replication update: - -- Up to [`u16::MAX`] entities that have added components with up to [`u16::MAX`] bytes of component data. -- Up to [`u16::MAX`] entities that have changed components with up to [`u16::MAX`] bytes of component data. -- Up to [`u16::MAX`] entities that have removed components with up to [`u16::MAX`] bytes of component data. -- Up to [`u16::MAX`] entities that were despawned. - # Troubleshooting If you face any issue, try to enable logging to see what is going on. diff --git a/src/server.rs b/src/server.rs index 0495671f..b94b9997 100644 --- a/src/server.rs +++ b/src/server.rs @@ -6,7 +6,7 @@ pub(super) mod replicated_archetypes; pub(super) mod replication_messages; pub mod server_tick; -use std::{io::Cursor, mem, time::Duration}; +use std::{io::Cursor, mem, ops::Range, time::Duration}; use bevy::{ ecs::{ @@ -29,7 +29,10 @@ use crate::core::{ replicated_clients::{ client_visibility::Visibility, ClientBuffers, ReplicatedClients, VisibilityPolicy, }, - replication_registry::{ctx::SerializeCtx, ReplicationRegistry}, + replication_registry::{ + component_fns::ComponentFns, ctx::SerializeCtx, rule_fns::UntypedRuleFns, + ReplicationRegistry, + }, replication_rules::ReplicationRules, }, replicon_server::RepliconServer, @@ -39,8 +42,8 @@ use crate::core::{ use client_entity_map::ClientEntityMap; use despawn_buffer::{DespawnBuffer, DespawnBufferPlugin}; use removal_buffer::{RemovalBuffer, RemovalBufferPlugin}; -use replicated_archetypes::ReplicatedArchetypes; -use replication_messages::ReplicationMessages; +use replicated_archetypes::{ReplicatedArchetypes, ReplicatedComponent}; +use replication_messages::{serialized_data::SerializedData, ReplicationMessages}; use server_tick::ServerTick; pub struct ServerPlugin { @@ -50,10 +53,10 @@ pub struct ServerPlugin { /// Visibility configuration. pub visibility_policy: VisibilityPolicy, - /// The time after which updates will be considered lost if an acknowledgment is not received for them. + /// The time after which mutations will be considered lost if an acknowledgment is not received for them. /// - /// In practice updates will live at least `update_timeout`, and at most `2*update_timeout`. - pub update_timeout: Duration, + /// In practice mutations will live at least `mutations_timeout`, and at most `2*mutations_timeout`. + pub mutations_timeout: Duration, /// If enabled, replication will be started automatically after connection. /// @@ -70,7 +73,7 @@ impl Default for ServerPlugin { Self { tick_policy: TickPolicy::MaxTickRate(30), visibility_policy: Default::default(), - update_timeout: Duration::from_secs(10), + mutations_timeout: Duration::from_secs(10), replicate_after_connect: true, } } @@ -119,7 +122,8 @@ impl Plugin for ServerPlugin { Self::handle_connections, Self::enable_replication, Self::receive_acks, - Self::cleanup_acks(self.update_timeout).run_if(on_timer(self.update_timeout)), + Self::cleanup_acks(self.mutations_timeout) + .run_if(on_timer(self.mutations_timeout)), ) .chain() .in_set(ServerSet::Receive) @@ -212,14 +216,14 @@ impl ServerPlugin { } fn cleanup_acks( - update_timeout: Duration, + mutations_timeout: Duration, ) -> impl FnMut(ResMut, ResMut, Res