From de53d663755e6fa8e40124dfffd0c703de1760db Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Sat, 20 Apr 2024 20:10:48 +0300 Subject: [PATCH] Marker-based API (#227) This PR have multiple purposes, but unfortunately they highly related, so I can't split it into multiple PRs. Right for prediction crates like https://github.com/Bendzae/bevy_replicon_snap or https://github.com/RJ/bevy_timewarp we rely on overriding deserialization and remove functions. But this approach have 3 major drawbacks: 1. In order to customize serialization for interpolated or predicted components, user need to copy the code from the crate or use some crate-provided helpers. 2. Serialization function is is unsafe due to pointer manipulation. 3. If you want to override writing or removal only for some entities, it's costly to check if a component present on each writing. Marker-based API solves all of the above. Now for replication user register only `serialize`, `deserialize` and newly added `deserialize_in_place`. These functions assigned to rules as before. So instead of `ComponentFns`, we now have `SerdeFns`. And these function now only do one thing - serialization/deserialization. All other logic now represented by `CommandFns`. Unlike `SerdeFns`, this struct created for each component only once and automatically when user register a `SerdeFns` for a component. It uses default `write` and `remove` functions. To override it, user can register a marker and assign a custom functions for each component. We use markers instead of archetypes because clients do not know in advance the archetype of the entity being deserialized. Then on receive we collect a reusable `Vec` for each marker (by checking if a marker is present) and based on it pick `write`/`remove` functions for each component. We pick first marker that have a registration for the current component and present on an entity (`true` in the `Vec`). Markers are sorted by priority customizable by user. Since for deserialization we call two functions now (`write` and then `deserialize`), we can do a nice trick to remove `unsafe` from ser/de customization and make it even more flexible! Since `write` knows the component type of `deserialize`, we can store a type-erased function pointers and let `write` "restore" the type. "restoration" is done by calling `transmute`, but we abstract it inside `SerdeFns` and check the type if `debug_assertions` enabled. So `serialize` and `deserialize` now just a normal functions with a very simple signature. This also unlocks the usage of `deserialize_in_place` that fallback into `deserialize` if not overridden by user. Similar trick is done for `serialize`, except `read` is non-overridable by user and only used to remove extra unsafety from the public API. The mentioned `read` and `write` are still unsafe since it's possible to pass `SerdeFns` that was created with a different type. And lastly, instead of using `EntityWorldMut` for writing and removal, we use `EntityMut` and `Commands`. Using `EntityWorldMut` have 2 major drawbacks: - You can't borrow a component from `EntityWorldMut` for in-place deserialization and `ClientMapper` at the same time. - Each insertion creates a new archetype. With commands we can spawn new entities inside `ClientMapper` and it will be possible to batch insertions in the future, see: https://github.com/bevyengine/bevy/issues/10154 Huge thanks to @NiseVoid for the idea! --------- Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com> --- CHANGELOG.md | 22 +- src/client.rs | 154 +++++++-- src/client/client_mapper.rs | 61 ++-- src/core.rs | 7 +- src/core/command_markers.rs | 312 +++++++++++++++++ src/core/replication_fns.rs | 432 +++++++++++++++++------- src/core/replication_fns/command_fns.rs | 234 +++++++++++++ src/core/replication_fns/serde_fns.rs | 181 ++++++++++ src/core/replication_rules.rs | 201 ++++++----- src/lib.rs | 14 +- src/parent_sync.rs | 2 +- src/scene.rs | 19 +- src/server.rs | 12 +- src/server/removal_buffer.rs | 24 +- src/server/replicated_archetypes.rs | 25 +- src/server/replication_messages.rs | 24 +- tests/changes.rs | 174 +++++++++- tests/insertion.rs | 148 +++++++- tests/removal.rs | 118 ++++++- 19 files changed, 1805 insertions(+), 359 deletions(-) create mode 100644 src/core/command_markers.rs create mode 100644 src/core/replication_fns/command_fns.rs create mode 100644 src/core/replication_fns/serde_fns.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bdc5e17..a94a030a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,23 +9,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `AppReplicationExt::replicate_group` and `GroupRegistration` trait to register and customize component groups. +- `AppMarkerExt` to customize how components will be written based on markers present without overriding deserialization functions. Now third-party prediction crates could be integrated much easier. +- `AppRuleExt::replicate_group` and `GroupRegistration` trait to register and customize component groups. A group will be replicated only if all its components present on an entity. - `ServerSet::StoreHierarchy` for systems that store hierarchy changes in `ParentSync`. -- `ReplicationRule` that describes how a component or a group of components will be serialized, deserialized and removed. ### Changed -- `AppReplicationExt::replicate_with` now accepts newly added `ReplicationFns` and the function is now `unsafe` (it was never "safe" before, caller must ensure that used `C` can be passed to the serialization function). +- `SerializeFn` now accepts regular `C` instead of `Ptr`. +- `DeserializeFn` now does only deserialization and returns `C`. Use the newly added marker-based API if you want to customize how component will be written. See docs for `AppMarkerExt` for details. +- Rename `AppReplicationExt` into `AppRuleExt`. +- `AppRuleExt::replicate_with` now no longer accepts `RemoveComponentFn`. Use the mentioned marker-based API to customize it instead. +- `AppRuleExt::replicate_with` now additionally accepts `in_place_as_deserialize`. You can use it to customize deserialization if a component is already present or just pass `command_fns::deserialize_in_place` to make it fallback to the passed `deserialize`. +- Writing to entities on client now done via `EntityMut` and `Commands` instead of `EntityWorldMut`. It was needed to support the mentioned in-place deserialization and will possibly allow batching insertions in the future (for details see https://github.com/bevyengine/bevy/issues/10154). - Move `Replication` to `core` module. -- Move all functions-related logic from `ReplicationRules` into a new `ReplicationFns`. -- Rename `serialize_component` into `serialize` and move into `replication_fns` module. -- Rename `deserialize_component` into `deserialize` and move into `replication_fns` module. -- Rename `remove_component` into `remove` and move into `replication_fns` module. +- Move all functions-related logic from `ReplicationRules` into a new `ReplicationFns` and hide `ReplicationRules` from public API. +- Rename `serialize_component` into `default_serialize` and move into `serde_fns` module. +- Rename `deserialize_component` into `default_deserialize` and move into `serde_fns` module. +- Rename `deserialize_mapped_component` into `default_deserialize_mapped` and move into `serde_fns` module. +- Rename `remove_component` into `default_remove` and move into `command_fns` module. - Move `despawn_recursive` into `replication_fns` module. ### Removed -- `dont_replicate` module. Use the newly added `AppReplicationExt::replicate_group` or newtypes. +- `dont_replicate` module. Use the newly added `AppRuleExt::replicate_group` or newtypes. ## [0.24.1] - 2024-03-07 diff --git a/src/client.rs b/src/client.rs index 0ae0a08c..dce59cd5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,12 +4,16 @@ pub mod replicon_client; use std::io::Cursor; -use bevy::{ecs::entity::EntityHashMap, prelude::*}; +use bevy::{ + ecs::{entity::EntityHashMap, system::SystemState}, + prelude::*, +}; use bincode::{DefaultOptions, Options}; use bytes::Bytes; use varint_rs::VarintReader; use crate::core::{ + command_markers::CommandMarkers, common_conditions::{client_connected, client_just_connected, client_just_disconnected}, replication_fns::ReplicationFns, replicon_channels::{ReplicationChannel, RepliconChannels}, @@ -77,28 +81,35 @@ impl ClientPlugin { /// Acknowledgments for received entity update messages are sent back to the server. /// /// See also [`ReplicationMessages`](crate::server::replication_messages::ReplicationMessages). - pub(super) fn receive_replication(world: &mut World) -> bincode::Result<()> { + pub(super) fn receive_replication( + world: &mut World, + state: &mut ReceiveState, + ) -> bincode::Result<()> { world.resource_scope(|world, mut client: Mut| { world.resource_scope(|world, mut entity_map: Mut| { world.resource_scope(|world, mut entity_ticks: Mut| { world.resource_scope(|world, mut buffered_updates: Mut| { - world.resource_scope(|world, replication_fns: Mut| { - let mut stats = world.remove_resource::(); - apply_replication( - world, - &mut client, - &mut entity_map, - &mut entity_ticks, - &mut buffered_updates, - stats.as_mut(), - &replication_fns, - )?; - - if let Some(stats) = stats { - world.insert_resource(stats); - } - - Ok(()) + world.resource_scope(|world, command_markers: Mut| { + world.resource_scope(|world, replication_fns: Mut| { + let mut stats = world.remove_resource::(); + apply_replication( + world, + state, + &mut client, + &mut entity_map, + &mut entity_ticks, + &mut buffered_updates, + stats.as_mut(), + &command_markers, + &replication_fns, + )?; + + if let Some(stats) = stats { + world.insert_resource(stats); + } + + Ok(()) + }) }) }) }) @@ -124,20 +135,24 @@ impl ClientPlugin { /// Sends acknowledgments for update messages back. fn apply_replication( world: &mut World, + state: &mut ReceiveState, client: &mut RepliconClient, entity_map: &mut ServerEntityMap, entity_ticks: &mut ServerEntityTicks, buffered_updates: &mut BufferedUpdates, mut stats: Option<&mut ClientStats>, + command_markers: &CommandMarkers, replication_fns: &ReplicationFns, ) -> Result<(), Box> { while let Some(message) = client.receive(ReplicationChannel::Init) { apply_init_message( &message, world, + state, entity_map, entity_ticks, stats.as_deref_mut(), + command_markers, replication_fns, )?; } @@ -147,10 +162,12 @@ fn apply_replication( let index = apply_update_message( message, world, + state, entity_map, entity_ticks, buffered_updates, stats.as_deref_mut(), + command_markers, replication_fns, replicon_tick, )?; @@ -168,9 +185,11 @@ fn apply_replication( if let Err(e) = apply_update_components( &mut Cursor::new(&*update.message), world, + state, entity_map, entity_ticks, stats.as_deref_mut(), + command_markers, replication_fns, update.message_tick, ) { @@ -188,9 +207,11 @@ fn apply_replication( fn apply_init_message( message: &[u8], world: &mut World, + state: &mut ReceiveState, entity_map: &mut ServerEntityMap, entity_ticks: &mut ServerEntityTicks, mut stats: Option<&mut ClientStats>, + command_markers: &CommandMarkers, replication_fns: &ReplicationFns, ) -> bincode::Result<()> { let end_pos: u64 = message.len().try_into().unwrap(); @@ -226,10 +247,12 @@ fn apply_init_message( apply_init_components( &mut cursor, world, + state, entity_map, entity_ticks, stats.as_deref_mut(), ComponentsKind::Removal, + command_markers, replication_fns, replicon_tick, )?; @@ -240,10 +263,12 @@ fn apply_init_message( apply_init_components( &mut cursor, world, + state, entity_map, entity_ticks, stats, ComponentsKind::Insert, + command_markers, replication_fns, replicon_tick, )?; @@ -260,10 +285,12 @@ fn apply_init_message( fn apply_update_message( message: Bytes, world: &mut World, + state: &mut ReceiveState, entity_map: &mut ServerEntityMap, entity_ticks: &mut ServerEntityTicks, buffered_updates: &mut BufferedUpdates, mut stats: Option<&mut ClientStats>, + command_markers: &CommandMarkers, replication_fns: &ReplicationFns, replicon_tick: RepliconTick, ) -> bincode::Result { @@ -289,9 +316,11 @@ fn apply_update_message( apply_update_components( &mut cursor, world, + state, entity_map, entity_ticks, stats, + command_markers, replication_fns, message_tick, )?; @@ -330,37 +359,64 @@ fn apply_entity_mappings( fn apply_init_components( cursor: &mut Cursor<&[u8]>, world: &mut World, + state: &mut ReceiveState, entity_map: &mut ServerEntityMap, entity_ticks: &mut ServerEntityTicks, mut stats: Option<&mut ClientStats>, components_kind: ComponentsKind, + command_markers: &CommandMarkers, replication_fns: &ReplicationFns, replicon_tick: RepliconTick, ) -> bincode::Result<()> { let entities_len: u16 = bincode::deserialize_from(&mut *cursor)?; for _ in 0..entities_len { - let entity = deserialize_entity(cursor)?; + let server_entity = deserialize_entity(cursor)?; let data_size: u16 = bincode::deserialize_from(&mut *cursor)?; - let mut entity = entity_map.get_by_server_or_spawn(world, entity); - entity_ticks.insert(entity.id(), replicon_tick); + + let client_entity = + entity_map.get_by_server_or_insert(server_entity, || world.spawn(Replication).id()); + entity_ticks.insert(client_entity, replicon_tick); + + let (mut entity_markers, mut commands, mut query) = state.get_mut(world); + let mut client_entity = query + .get_mut(client_entity) + .expect("replicated entities can be despawned only by server"); + entity_markers.extend(command_markers.iter_contains(&client_entity)); 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 fns = replication_fns.component_fns(fns_id); + let (command_fns, serde_fns) = replication_fns.get(fns_id); match components_kind { - ComponentsKind::Insert => { - (fns.deserialize)(&mut entity, entity_map, cursor, replicon_tick)? - } - ComponentsKind::Removal => (fns.remove)(&mut entity, replicon_tick), + ComponentsKind::Insert => unsafe { + // SAFETY: `serde_fns` and `command_fns` were created for the same type. + command_fns.write( + serde_fns, + &entity_markers, + &mut commands, + &mut client_entity, + cursor, + entity_map, + replicon_tick, + )? + }, + ComponentsKind::Removal => command_fns.remove( + &entity_markers, + commands.entity(client_entity.id()), + replicon_tick, + ), } components_len += 1; } + if let Some(stats) = &mut stats { stats.entities_changed += 1; stats.components_changed += components_len; } + + entity_markers.clear(); + state.apply(world); } Ok(()) @@ -403,44 +459,68 @@ fn apply_despawns( fn apply_update_components( cursor: &mut Cursor<&[u8]>, world: &mut World, + state: &mut ReceiveState, entity_map: &mut ServerEntityMap, entity_ticks: &mut ServerEntityTicks, mut stats: Option<&mut ClientStats>, + command_markers: &CommandMarkers, replication_fns: &ReplicationFns, message_tick: RepliconTick, ) -> bincode::Result<()> { let message_end = cursor.get_ref().len() as u64; while cursor.position() < message_end { - let entity = deserialize_entity(cursor)?; + let server_entity = deserialize_entity(cursor)?; let data_size: u16 = bincode::deserialize_from(&mut *cursor)?; - let Some(mut entity) = entity_map.get_by_server(world, entity) else { + + let Some(client_entity) = 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 {entity:?}"); + debug!("ignoring update received for unknown server's {server_entity:?}"); cursor.set_position(cursor.position() + data_size as u64); continue; }; let entity_tick = entity_ticks - .get_mut(&entity.id()) + .get_mut(&client_entity) .expect("all entities from update should have assigned ticks"); if message_tick <= *entity_tick { - trace!("ignoring outdated update for client's {:?}", entity.id()); + trace!("ignoring outdated update for client's {client_entity:?}"); cursor.set_position(cursor.position() + data_size as u64); continue; } *entity_tick = message_tick; + let (mut entity_markers, mut commands, mut query) = state.get_mut(world); + let mut client_entity = query + .get_mut(client_entity) + .expect("replicated entities can be despawned only by server"); + entity_markers.extend(command_markers.iter_contains(&client_entity)); + 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 fns = replication_fns.component_fns(fns_id); - (fns.deserialize)(&mut entity, entity_map, cursor, message_tick)?; + let (command_fns, serde_fns) = replication_fns.get(fns_id); + // SAFETY: `serde_fns` and `command_fns` were created for the same type. + unsafe { + command_fns.write( + serde_fns, + &entity_markers, + &mut commands, + &mut client_entity, + cursor, + entity_map, + message_tick, + )?; + } components_count += 1; } + if let Some(stats) = &mut stats { stats.entities_changed += 1; stats.components_changed += components_count; } + + entity_markers.clear(); + state.apply(world); } Ok(()) @@ -464,6 +544,12 @@ fn deserialize_entity(cursor: &mut Cursor<&[u8]>) -> bincode::Result { Ok(Entity::from_bits(bits)) } +type ReceiveState<'w, 's> = SystemState<( + Local<'s, Vec>, + Commands<'w, 's>, + Query<'w, 's, EntityMut<'w>>, +)>; + /// Type of components replication. /// /// Parameter for [`apply_components`]. diff --git a/src/client/client_mapper.rs b/src/client/client_mapper.rs index dccb2a00..2c13f884 100644 --- a/src/client/client_mapper.rs +++ b/src/client/client_mapper.rs @@ -5,30 +5,24 @@ use crate::core::Replication; /// Maps server entities into client entities inside components. /// /// Spawns new client entity if a mapping doesn't exists. -pub struct ClientMapper<'a> { - world: &'a mut World, - server_to_client: &'a mut EntityHashMap, - client_to_server: &'a mut EntityHashMap, +pub struct ClientMapper<'a, 'w, 's> { + pub commands: &'a mut Commands<'w, 's>, + pub entity_map: &'a mut ServerEntityMap, } -impl<'a> ClientMapper<'a> { - #[inline] - pub fn new(world: &'a mut World, entity_map: &'a mut ServerEntityMap) -> Self { - Self { - world, - server_to_client: &mut entity_map.server_to_client, - client_to_server: &mut entity_map.client_to_server, - } - } -} - -impl EntityMapper for ClientMapper<'_> { +impl EntityMapper for ClientMapper<'_, '_, '_> { fn map_entity(&mut self, entity: Entity) -> Entity { - *self.server_to_client.entry(entity).or_insert_with(|| { - let client_entity = self.world.spawn(Replication).id(); - self.client_to_server.insert(client_entity, entity); - client_entity - }) + *self + .entity_map + .server_to_client + .entry(entity) + .or_insert_with(|| { + let client_entity = self.commands.spawn(Replication).id(); + self.entity_map + .client_to_server + .insert(client_entity, entity); + client_entity + }) } } @@ -60,31 +54,24 @@ impl ServerEntityMap { self.client_to_server.insert(client_entity, server_entity); } - pub(super) fn get_by_server_or_spawn<'a>( + pub(super) fn get_by_server_or_insert( &mut self, - world: &'a mut World, server_entity: Entity, - ) -> EntityWorldMut<'a> { + f: impl FnOnce() -> Entity, + ) -> Entity { match self.server_to_client.entry(server_entity) { - Entry::Occupied(entry) => world.entity_mut(*entry.get()), + Entry::Occupied(entry) => *entry.get(), Entry::Vacant(entry) => { - let client_entity = world.spawn(Replication); - entry.insert(client_entity.id()); - self.client_to_server - .insert(client_entity.id(), server_entity); + let client_entity = (f)(); + entry.insert(client_entity); + self.client_to_server.insert(client_entity, server_entity); client_entity } } } - pub(super) fn get_by_server<'a>( - &mut self, - world: &'a mut World, - server_entity: Entity, - ) -> Option> { - self.server_to_client - .get(&server_entity) - .map(|&entity| world.entity_mut(entity)) + pub(super) fn get_by_server(&mut self, server_entity: Entity) -> Option { + self.server_to_client.get(&server_entity).copied() } pub(super) fn remove_by_server(&mut self, server_entity: Entity) -> Option { diff --git a/src/core.rs b/src/core.rs index ffbce8c5..35563094 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,3 +1,4 @@ +pub mod command_markers; pub mod common_conditions; pub mod replication_fns; pub mod replication_rules; @@ -5,12 +6,13 @@ pub mod replicon_channels; pub mod replicon_tick; use bevy::prelude::*; +use serde::{Deserialize, Serialize}; +use command_markers::CommandMarkers; use replication_fns::ReplicationFns; use replication_rules::ReplicationRules; use replicon_channels::RepliconChannels; use replicon_tick::RepliconTick; -use serde::{Deserialize, Serialize}; pub struct RepliconCorePlugin; @@ -20,7 +22,8 @@ impl Plugin for RepliconCorePlugin { .init_resource::() .init_resource::() .init_resource::() - .init_resource::(); + .init_resource::() + .init_resource::(); } } diff --git a/src/core/command_markers.rs b/src/core/command_markers.rs new file mode 100644 index 00000000..c84678e4 --- /dev/null +++ b/src/core/command_markers.rs @@ -0,0 +1,312 @@ +use std::cmp::Reverse; + +use bevy::{ecs::component::ComponentId, prelude::*}; + +use crate::core::replication_fns::ReplicationFns; + +use super::replication_fns::command_fns::{RemoveFn, WriteFn}; + +/// Marker-based functions for [`App`]. +/// +/// Allows customizing behavior on clients when receiving updates from the server. +/// +/// We check markers on receive instead of archetypes because on client we don't +/// know an incoming entity's archetype in advance. +/// +/// This is mostly needed for third-party crates, most end-users should not need to use it directly. +pub trait AppMarkerExt { + /// Registers a component as a marker. + /// + /// Can be used to override how this component or other components will be written or removed + /// based on marker-component presence. + /// For details see [`Self::set_marker_fns`]. + /// + /// This function registers markers with priority equal to 0. + /// Use [`Self::register_marker_with_priority`] if you have multiple + /// markers affecting the same component. + fn register_marker(&mut self) -> &mut Self; + + /// Same as [`Self::register_marker`], but allows setting a priority. + fn register_marker_with_priority(&mut self, priority: usize) -> &mut Self; + + /** + Associates command functions with a marker for a component. + + If this marker is present on an entity and its priority is the highest, + then these functions will be called for this component during replication + instead of [`default_write`](super::replication_fns::command_fns::default_write) and + [`default_remove`](super::replication_fns::command_fns::default_remove). + See also [`Self::set_command_fns`]. + + # Safety + + The caller must ensure that passed `write` can be safely called with a + [`SerdeFns`](super::replication_fns::serde_fns::SerdeFns) created for `C`. + + # Examples + + In this example we write all received updates for [`Transform`] into user's + `History` if `ComponentsHistory` marker is present on the client entity. In this + scenario, you'd insert `ComponentsHistory` the first time the entity + is replicated (e.g. by detecting a `Player` marker component using the blueprint pattern). + Then [`Transform`] updates after that will be inserted to the history. + + ``` + use std::io::Cursor; + + use bevy::prelude::*; + use bevy_replicon::{ + client::client_mapper::{ClientMapper, ServerEntityMap}, + core::{ + replication_fns::{command_fns, serde_fns::SerdeFns}, + replicon_tick::RepliconTick, + }, + prelude::*, + }; + + # let mut app = App::new(); + # app.add_plugins(RepliconPlugins); + app.register_marker::(); + // SAFETY: `write_history` can be safely called with a `SerdeFns` created for `Transform`. + unsafe { + app.set_marker_fns::( + write_history::, + command_fns::default_remove::, + ); + } + + /// Instead of writing into a component directly, it writes data into [`ComponentHistory`]. + /// + /// # Safety + /// + /// The caller must ensure that `serde_fns` was created for [`Transform`]. + unsafe fn write_history( + serde_fns: &SerdeFns, + commands: &mut Commands, + entity: &mut EntityMut, + cursor: &mut Cursor<&[u8]>, + entity_map: &mut ServerEntityMap, + _replicon_tick: RepliconTick, + ) -> bincode::Result<()> { + let mut mapper = ClientMapper { + commands, + entity_map, + }; + + let component: C = serde_fns.deserialize(cursor, &mut mapper)?; + if let Some(mut history) = entity.get_mut::>() { + history.push(component); + } else { + commands + .entity(entity.id()) + .insert(History(vec![component])); + } + + Ok(()) + } + + /// If this marker is present on an entity, registered components will be stored in [`History`]. + /// + ///Present only on client. + #[derive(Component)] + struct ComponentsHistory; + + /// Stores history of values of `C` received from server. Present only on client. + /// + /// Present only on client. + #[derive(Component, Deref, DerefMut)] + struct History(Vec); + ``` + **/ + unsafe fn set_marker_fns( + &mut self, + write: WriteFn, + remove: RemoveFn, + ) -> &mut Self; + + /// Sets default functions for a component when there are no markers. + /// + /// If there are no markers present on an entity, then these functions will + /// be called for this component during replication instead of + /// [`default_write`](super::replication_fns::command_fns::default_write) and + /// [`default_remove`](super::replication_fns::command_fns::default_remove). + /// See also [`Self::set_marker_fns`]. + /// + /// # Safety + /// + /// The caller must ensure that passed `write` can be safely called with all + /// [`SerdeFns`](super::replication_fns::serde_fns::SerdeFns) created for `C`. + unsafe fn set_command_fns( + &mut self, + write: WriteFn, + remove: RemoveFn, + ) -> &mut Self; +} + +impl AppMarkerExt for App { + fn register_marker(&mut self) -> &mut Self { + self.register_marker_with_priority::(0) + } + + fn register_marker_with_priority(&mut self, priority: usize) -> &mut Self { + let component_id = self.world.init_component::(); + let mut command_markers = self.world.resource_mut::(); + let marker_id = command_markers.insert(CommandMarker { + component_id, + priority, + }); + + let mut replicaton_fns = self.world.resource_mut::(); + replicaton_fns.register_marker(marker_id); + + self + } + + unsafe fn set_marker_fns( + &mut self, + write: WriteFn, + remove: RemoveFn, + ) -> &mut Self { + let component_id = self.world.init_component::(); + let command_markers = self.world.resource::(); + let marker_id = command_markers.marker_id(component_id); + self.world + .resource_scope(|world, mut replication_fns: Mut| unsafe { + // SAFETY: The caller ensured that `write` can be safely called with a `SerdeFns` created for `C`. + replication_fns.set_marker_fns::(world, marker_id, write, remove); + }); + + self + } + + unsafe fn set_command_fns( + &mut self, + write: WriteFn, + remove: RemoveFn, + ) -> &mut Self { + self.world + .resource_scope(|world, mut replication_fns: Mut| unsafe { + // SAFETY: The caller ensured that `write` can be safely called with a `SerdeFns` created for `C`. + replication_fns.set_command_fns::(world, write, remove); + }); + + self + } +} + +/// Registered markers that override functions if present for +/// [`CommandFns`](super::replication_fns::command_fns::CommandFns). +#[derive(Resource, Default)] +pub(crate) struct CommandMarkers(Vec); + +impl CommandMarkers { + /// Inserts a new marker, maintaining sorting by their priority in descending order. + /// + /// May invalidate previously returned [`CommandMarkerIndex`] due to sorting. + /// + /// Use [`ReplicationFns::register_marker`] to register a slot for command functions for this marker. + pub(super) fn insert(&mut self, marker: CommandMarker) -> CommandMarkerIndex { + let index = self + .0 + .binary_search_by_key(&Reverse(marker.priority), |marker| Reverse(marker.priority)) + .unwrap_or_else(|index| index); + + self.0.insert(index, marker); + + CommandMarkerIndex(index) + } + + /// Returns marker ID from its component ID. + fn marker_id(&self, component_id: ComponentId) -> CommandMarkerIndex { + let index = self + .0 + .iter() + .position(|marker| marker.component_id == component_id) + .unwrap_or_else(|| panic!("marker {component_id:?} wasn't registered")); + + CommandMarkerIndex(index) + } + + /// Returns an iterator over markers presence for an entity. + pub(crate) fn iter_contains<'a>( + &'a self, + entity: &'a EntityMut, + ) -> impl Iterator + 'a { + self.0 + .iter() + .map(move |marker| entity.contains_id(marker.component_id)) + } +} + +/// Component marker information. +/// +/// See also [`CommandMarkers`]. +pub(super) struct CommandMarker { + /// Marker ID. + pub(super) component_id: ComponentId, + + /// Priority of this marker. + /// + /// Will affect the order in [`CommandMarkers::insert`]. + pub(super) priority: usize, +} + +/// Can be obtained from [`CommandMarkers::insert`]. +/// +/// Shouldn't be stored anywhere since insertion may invalidate old indices. +#[derive(Clone, Copy, Deref, Debug)] +pub(super) struct CommandMarkerIndex(usize); + +#[cfg(test)] +mod tests { + use serde::{Deserialize, Serialize}; + + use super::*; + use crate::core::replication_fns::{command_fns, ReplicationFns}; + + #[test] + #[should_panic] + fn non_registered_marker() { + let mut app = App::new(); + app.init_resource::() + .init_resource::(); + + // SAFETY: `write` can be safely called with a `SerdeFns` created for `DummyComponent`. + unsafe { + app.set_marker_fns::( + command_fns::default_write::, + command_fns::default_remove::, + ); + } + } + + #[test] + fn sorting() { + let mut app = App::new(); + app.init_resource::() + .init_resource::() + .register_marker::() + .register_marker_with_priority::(2) + .register_marker_with_priority::(1) + .register_marker::(); + + let markers = app.world.resource::(); + let priorities: Vec<_> = markers.0.iter().map(|marker| marker.priority).collect(); + assert_eq!(priorities, [2, 1, 0, 0]); + } + + #[derive(Component)] + struct DummyMarkerA; + + #[derive(Component)] + struct DummyMarkerB; + + #[derive(Component)] + struct DummyMarkerC; + + #[derive(Component)] + struct DummyMarkerD; + + #[derive(Component, Serialize, Deserialize)] + struct DummyComponent; +} diff --git a/src/core/replication_fns.rs b/src/core/replication_fns.rs index a62342a8..d6271f9f 100644 --- a/src/core/replication_fns.rs +++ b/src/core/replication_fns.rs @@ -1,11 +1,15 @@ -use std::io::Cursor; +pub mod command_fns; +pub mod serde_fns; -use bevy::{ecs::entity::MapEntities, prelude::*, ptr::Ptr}; -use bincode::{DefaultOptions, Options}; +use bevy::{ + ecs::{component::ComponentId, entity::MapEntities}, + prelude::*, +}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use super::replicon_tick::RepliconTick; -use crate::client::client_mapper::{ClientMapper, ServerEntityMap}; +use super::{command_markers::CommandMarkerIndex, replicon_tick::RepliconTick}; +use command_fns::{CommandFns, RemoveFn, WriteFn}; +use serde_fns::{DeserializeFn, DeserializeInPlaceFn, SerdeFns, SerializeFn}; /// Stores configurable replication functions. #[derive(Resource)] @@ -16,32 +20,172 @@ pub struct ReplicationFns { /// Useful if you need to intercept despawns and handle them in a special way. pub despawn: DespawnFn, - /// Registered functions for replicated components. - components: Vec, + /// Read/write/remove functions for replicated components. + /// + /// Unique for each component. + commands: Vec<(CommandFns, ComponentId)>, + + /// Serialization/deserialization functions for a component and + /// the component's index in [`Self::commands`]. + /// + /// Can be registered multiple times for the same component for a different + /// [`ReplicationRule`]. + serde: Vec<(SerdeFns, usize)>, + + /// Number of registered markers. + /// + /// Used to initialize new [`CommandFns`] with the registered number of slots. + marker_slots: usize, } impl ReplicationFns { - /// Registers [`ComponentFns`] for a component and returns its ID. + /// Registers marker slot for command functions. + /// + /// Should be used after calling + /// [`CommandMarkers::insert`](super::command_markers::CommandMarkers::insert) + pub(super) fn register_marker(&mut self, marker_id: CommandMarkerIndex) { + self.marker_slots += 1; + for (command_fns, _) in &mut self.commands { + command_fns.add_marker_slot(marker_id); + } + } + + /// Associates command functions with a marker for a component. + /// + /// **Must** be called **after** calling [`Self::register_marker`] with `marker_id`. + /// + /// See also [`Self::set_command_fns`]. + /// + /// # Safety /// - /// Returned ID can be assigned to a component inside - /// [`ReplicationRule`](super::replication_rules::ReplicationRule). + /// The caller must ensure that passed `write` can be safely called with all + /// [`SerdeFns`] registered for `C` with other methods on this struct. /// - /// Could be called multiple times for the same component with different functions. - pub fn register_component_fns(&mut self, fns: ComponentFns) -> ComponentFnsId { - self.components.push(fns); + /// # Panics + /// + /// Panics if the marker wasn't registered. Use [`Self::register_marker`] first. + pub(super) unsafe fn set_marker_fns( + &mut self, + world: &mut World, + marker_id: CommandMarkerIndex, + write: WriteFn, + remove: RemoveFn, + ) { + let (index, _) = self.init_command_fns::(world); + let (command_fns, _) = &mut self.commands[index]; - ComponentFnsId(self.components.len() - 1) + // SAFETY: `command_fns` was created for `C` and the caller ensured + // that `write` can be safely called with a `SerdeFns` created for `C`. + command_fns.set_marker_fns(marker_id, write, remove); } - /// Returns a reference to registered component functions. + /// Sets default functions for a component when there are no markers. /// - /// # Panics + /// See also [`Self::set_marker_fns`]. + /// + /// # Safety + /// + /// The caller must ensure that passed `write` can be safely called with all + /// [`SerdeFns`] registered for `C` with other methods on this struct. + pub(super) unsafe fn set_command_fns( + &mut self, + world: &mut World, + write: WriteFn, + remove: RemoveFn, + ) { + let (index, _) = self.init_command_fns::(world); + let (command_fns, _) = &mut self.commands[index]; + + // SAFETY: `command_fns` was created for `C` and the caller ensured + // that `write` can be safely called with a `SerdeFns` created for `C`. + command_fns.set_fns(write, remove); + } + + /// Same as [`Self::register_serde_fns`], but uses default functions for a component. + /// + /// If your component contains any [`Entity`] inside, use [`Self::register_mapped_serde_fns`]. + pub fn register_default_serde_fns(&mut self, world: &mut World) -> FnsInfo + where + C: Component + Serialize + DeserializeOwned, + { + self.register_serde_fns( + world, + serde_fns::default_serialize::, + serde_fns::default_deserialize::, + serde_fns::in_place_as_deserialize::, + ) + } + + /// Same as [`Self::register_serde_fns`], but uses default functions for a mapped component. + /// + /// Always use it for components that contain entities. + /// + /// See also [`Self::register_default_serde_fns`]. + pub fn register_mapped_serde_fns(&mut self, world: &mut World) -> FnsInfo + where + C: Component + Serialize + DeserializeOwned + MapEntities, + { + self.register_serde_fns( + world, + serde_fns::default_serialize::, + serde_fns::default_deserialize_mapped::, + serde_fns::in_place_as_deserialize::, + ) + } + + /// Registers serialization/deserialization functions for a component. + /// + /// Returned data can be assigned to a + /// [`ReplicationRule`](super::replication_rules::ReplicationRule) + pub fn register_serde_fns( + &mut self, + world: &mut World, + serialize: SerializeFn, + deserialize: DeserializeFn, + deserialize_in_place: DeserializeInPlaceFn, + ) -> FnsInfo { + let (index, component_id) = self.init_command_fns::(world); + let serde_fns = SerdeFns::new(serialize, deserialize, deserialize_in_place); + self.serde.push((serde_fns, index)); + + FnsInfo { + component_id, + fns_id: FnsId(self.serde.len() - 1), + } + } + + /// Initializes [`CommandFns`] for a component and returns its index and ID. + /// + /// If a [`CommandFns`] has already been created for this component, + /// then it returns its index instead of creating a new one. + fn init_command_fns(&mut self, world: &mut World) -> (usize, ComponentId) { + let component_id = world.init_component::(); + let index = self + .commands + .iter() + .position(|&(_, id)| id == component_id) + .unwrap_or_else(|| { + self.commands + .push((CommandFns::new::(self.marker_slots), component_id)); + self.commands.len() - 1 + }); + + (index, component_id) + } + + /// Returns associates functions. /// - /// If functions ID points to an invalid item. - pub(crate) fn component_fns(&self, fns_id: ComponentFnsId) -> &ComponentFns { - self.components + /// See also [`Self::register_serde_fns`]. + pub fn get(&self, fns_id: FnsId) -> (&CommandFns, &SerdeFns) { + let (serde_fns, index) = self + .serde .get(fns_id.0) - .expect("function IDs should should always be valid if obtained from the same instance") + .expect("serde function IDs should be obtained from the same instance"); + + // SAFETY: index obtained from `serde` is always valid. + let (command_fns, _) = unsafe { self.commands.get_unchecked(*index) }; + + (command_fns, serde_fns) } } @@ -49,127 +193,183 @@ impl Default for ReplicationFns { fn default() -> Self { Self { despawn: despawn_recursive, - components: Default::default(), + commands: Default::default(), + serde: Default::default(), + marker_slots: 0, } } } -/// Signature of component serialization functions. -pub type SerializeFn = unsafe fn(Ptr, &mut Cursor>) -> bincode::Result<()>; +/// IDs of a registered replication function and its component. +/// +/// Can be obtained from [`ReplicationFns::register_serde_fns`]. +#[derive(Clone, Copy)] +pub struct FnsInfo { + component_id: ComponentId, + fns_id: FnsId, +} -/// Signature of component deserialization functions. -pub type DeserializeFn = fn( - &mut EntityWorldMut, - &mut ServerEntityMap, - &mut Cursor<&[u8]>, - RepliconTick, -) -> bincode::Result<()>; +impl FnsInfo { + pub(crate) fn component_id(&self) -> ComponentId { + self.component_id + } -/// Signature of component removal functions. -pub type RemoveFn = fn(&mut EntityWorldMut, RepliconTick); + pub(crate) fn fns_id(&self) -> FnsId { + self.fns_id + } +} + +/// ID of replicaton functions for a component. +/// +/// Can be obtained from [`ReplicationFns::register_serde_fns`]. +#[derive(Clone, Copy, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct FnsId(usize); /// Signature of the entity despawn function. pub type DespawnFn = fn(EntityWorldMut, RepliconTick); -/// Functions for a replicated component. -#[derive(Clone)] -pub struct ComponentFns { - /// Function that serializes a component into bytes. - pub serialize: SerializeFn, +/// Default entity despawn function. +pub fn despawn_recursive(entity: EntityWorldMut, _replicon_tick: RepliconTick) { + entity.despawn_recursive(); +} - /// Function that deserializes a component from bytes and inserts it to [`EntityWorldMut`]. - pub deserialize: DeserializeFn, +#[cfg(test)] +mod tests { + use super::*; + use crate::core::command_markers::{CommandMarker, CommandMarkers}; - /// Function that removes a component from [`EntityWorldMut`]. - pub remove: RemoveFn, -} + #[test] + fn marker() { + let mut world = World::new(); + let mut replication_fns = ReplicationFns::default(); + let mut command_markers = CommandMarkers::default(); -impl ComponentFns { - /// Creates a new instance with [`serialize`], [`deserialize`] and [`remove`] functions. - /// - /// If your component contains any [`Entity`] inside, use [`Self::default_mapped_fns`]. - pub fn default_fns() -> Self - where - C: Component + Serialize + DeserializeOwned, - { - Self { - serialize: serialize::, - deserialize: deserialize::, - remove: remove::, + let marker_a = command_markers.insert(CommandMarker { + component_id: world.init_component::(), + priority: 0, + }); + replication_fns.register_marker(marker_a); + + // SAFETY: `write` can be safely called with a `SerdeFns` created for `ComponentA`. + unsafe { + replication_fns.set_marker_fns::( + &mut world, + marker_a, + command_fns::default_write::, + command_fns::default_remove::, + ); } + + let (command_fns_a, _) = &replication_fns.commands[0]; + assert!(command_fns_a.marker_fns(&[false]).is_none()); + assert!(command_fns_a.marker_fns(&[true]).is_some()); } - /// Creates a new instance with [`serialize`], [`deserialize_mapped`] and [`remove`] functions. - /// - /// Always use it for components that contain entities. - pub fn default_mapped_fns() -> Self - where - C: Component + Serialize + DeserializeOwned + MapEntities, - { - Self { - serialize: serialize::, - deserialize: deserialize_mapped::, - remove: remove::, + #[test] + fn two_markers() { + let mut world = World::new(); + let mut replication_fns = ReplicationFns::default(); + let mut command_markers = CommandMarkers::default(); + + let marker_a = command_markers.insert(CommandMarker { + component_id: world.init_component::(), + priority: 1, + }); + replication_fns.register_marker(marker_a); + + let marker_b = command_markers.insert(CommandMarker { + component_id: world.init_component::(), + priority: 0, + }); + replication_fns.register_marker(marker_b); + + // SAFETY: `write` can be safely called with `SerdeFns` for + // `ComponentA` and `ComponentA` for each call respectively. + unsafe { + replication_fns.set_marker_fns::( + &mut world, + marker_a, + command_fns::default_write::, + command_fns::default_remove::, + ); + replication_fns.set_marker_fns::( + &mut world, + marker_b, + command_fns::default_write::, + command_fns::default_remove::, + ); } + + let (command_fns_a, _) = &replication_fns.commands[0]; + assert!(command_fns_a.marker_fns(&[false, false]).is_none()); + assert!(command_fns_a.marker_fns(&[true, false]).is_some()); + assert!(command_fns_a.marker_fns(&[false, true]).is_none()); + assert!(command_fns_a.marker_fns(&[true, true]).is_some()); + + let (command_fns_b, _) = &replication_fns.commands[1]; + assert!(command_fns_b.marker_fns(&[false, false]).is_none()); + assert!(command_fns_b.marker_fns(&[true, false]).is_none()); + assert!(command_fns_b.marker_fns(&[false, true]).is_some()); + assert!(command_fns_b.marker_fns(&[true, true]).is_some()); } -} -/// Represents ID of [`ComponentFns`]. -/// -/// Can be obtained from [`ReplicationFns::register_component_fns`]. -#[derive(Clone, Copy, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub struct ComponentFnsId(usize); + #[test] + fn default_serde_fns() { + let mut world = World::new(); + let mut replication_fns = ReplicationFns::default(); + replication_fns.register_default_serde_fns::(&mut world); + assert_eq!(replication_fns.serde.len(), 1); + assert_eq!(replication_fns.commands.len(), 1); + } -/// Default serialization function. -/// -/// # Safety -/// -/// `C` must be the erased pointee type for this [`Ptr`]. -pub unsafe fn serialize( - component: Ptr, - cursor: &mut Cursor>, -) -> bincode::Result<()> { - let component: &C = component.deref(); - DefaultOptions::new().serialize_into(cursor, component) -} + #[test] + fn mapped_serde_fns() { + let mut world = World::new(); + let mut replication_fns = ReplicationFns::default(); + replication_fns.register_mapped_serde_fns::(&mut world); + assert_eq!(replication_fns.serde.len(), 1); + assert_eq!(replication_fns.commands.len(), 1); + } -/// Default deserialization function. -pub fn deserialize( - entity: &mut EntityWorldMut, - _entity_map: &mut ServerEntityMap, - cursor: &mut Cursor<&[u8]>, - _replicon_tick: RepliconTick, -) -> bincode::Result<()> { - let component: C = DefaultOptions::new().deserialize_from(cursor)?; - entity.insert(component); - - Ok(()) -} + #[test] + fn duplicate_serde_fns() { + let mut world = World::new(); + let mut replication_fns = ReplicationFns::default(); + replication_fns.register_default_serde_fns::(&mut world); + replication_fns.register_default_serde_fns::(&mut world); -/// Like [`deserialize`], but also maps entities before insertion. -pub fn deserialize_mapped( - entity: &mut EntityWorldMut, - entity_map: &mut ServerEntityMap, - cursor: &mut Cursor<&[u8]>, - _replicon_tick: RepliconTick, -) -> bincode::Result<()> { - let mut component: C = DefaultOptions::new().deserialize_from(cursor)?; + assert_eq!(replication_fns.serde.len(), 2); + assert_eq!( + replication_fns.commands.len(), + 1, + "multiple serde registrations for the same component should result only in a single command functions instance" + ); + } - entity.world_scope(|world| { - component.map_entities(&mut ClientMapper::new(world, entity_map)); - }); + #[test] + fn different_serde_fns() { + let mut world = World::new(); + let mut replication_fns = ReplicationFns::default(); + replication_fns.register_default_serde_fns::(&mut world); + replication_fns.register_mapped_serde_fns::(&mut world); - entity.insert(component); + assert_eq!(replication_fns.serde.len(), 2); + assert_eq!(replication_fns.commands.len(), 2); + } - Ok(()) -} + #[derive(Component, Serialize, Deserialize)] + struct ComponentA; -/// Default component removal function. -pub fn remove(entity: &mut EntityWorldMut, _replicon_tick: RepliconTick) { - entity.remove::(); -} + #[derive(Component, Deserialize, Serialize)] + struct ComponentB; -/// Default entity despawn function. -pub fn despawn_recursive(entity: EntityWorldMut, _replicon_tick: RepliconTick) { - entity.despawn_recursive(); + impl MapEntities for ComponentB { + fn map_entities(&mut self, _entity_mapper: &mut M) {} + } + + #[derive(Component)] + struct MarkerA; + + #[derive(Component)] + struct MarkerB; } diff --git a/src/core/replication_fns/command_fns.rs b/src/core/replication_fns/command_fns.rs new file mode 100644 index 00000000..f2af67e6 --- /dev/null +++ b/src/core/replication_fns/command_fns.rs @@ -0,0 +1,234 @@ +use std::io::Cursor; + +use bevy::{ecs::system::EntityCommands, prelude::*, ptr::Ptr}; + +use super::serde_fns::SerdeFns; +use crate::{ + client::client_mapper::{ClientMapper, ServerEntityMap}, + core::{command_markers::CommandMarkerIndex, replicon_tick::RepliconTick}, +}; + +/// Functions that operate on components like [`Commands`]. +/// +/// Unlike [`SerdeFns`] which are selected on the server via +/// [`ReplicationRules`](crate::core::replication_rules::ReplicationRule), the remove/remove +/// functions in `markers` here are selected on the client via marker components. +/// For details see [`AppMarkerExt`](crate::core::command_markers::AppMarkerExt). +pub struct CommandFns { + read: ReadFn, + write: WriteFn, + remove: RemoveFn, + markers: Vec>, +} + +impl CommandFns { + /// Creates a new instance for `C` with default functions and the specified number of empty marker function slots. + pub(super) fn new(marker_slots: usize) -> Self { + Self { + read: default_read::, + write: default_write::, + remove: default_remove::, + markers: vec![None; marker_slots], + } + } + + /// Adds new empty slot for a marker. + /// + /// Use [`Self::set_marker_fns`] to assign functions to it. + pub(super) fn add_marker_slot(&mut self, marker_id: CommandMarkerIndex) { + self.markers.insert(*marker_id, None); + } + + /// Assigns functions to a marker slot. + /// + /// # Safety + /// + /// The caller must ensure that passed `write` can be safely called with all + /// [`SerdeFns`] created for the same type as this instance. + /// + /// # Panics + /// + /// Panics if there is no such slot for the marker. Use [`Self::add_marker_slot`] to assign. + pub(super) unsafe fn set_marker_fns( + &mut self, + marker_id: CommandMarkerIndex, + write: WriteFn, + remove: RemoveFn, + ) { + let fns = self + .markers + .get_mut(*marker_id) + .unwrap_or_else(|| panic!("command fns should have a slot for {marker_id:?}")); + + debug_assert!( + fns.is_none(), + "function for {marker_id:?} can't be set twice" + ); + + *fns = Some((write, remove)); + } + + /// Sets default functions when there are no markers. + /// + /// # Safety + /// + /// The caller must ensure that passed `write` can be safely called with all + /// [`SerdeFns`] created for the same type as this instance. + pub(super) unsafe fn set_fns(&mut self, write: WriteFn, remove: RemoveFn) { + self.write = write; + self.remove = remove; + } + + /// Calls [`default_read`] on the type for which this instance was created. + /// + /// It's a non-overridable function that is used to restore the erased type from [`Ptr`]. + /// To customize serialization behavior, [`SerdeFns`] should be used instead. + /// + /// # Safety + /// + /// The caller must ensure that `ptr` and `serde_fns` were created for the same type as this instance. + pub unsafe fn read( + &self, + serde_fns: &SerdeFns, + ptr: Ptr, + cursor: &mut Cursor>, + ) -> bincode::Result<()> { + (self.read)(serde_fns, ptr, cursor) + } + + /// Calls the assigned writing function based on entity markers. + /// + /// Entity markers store information about which markers are present on an entity. + /// The first-found write function whose marker is present on the entity will be selected + /// (the functions are sorted by priority). + /// If there is no such function, it will use the [`default_write`]. + /// + /// # Safety + /// + /// The caller must ensure that `serde_fns` was created for the same type as this instance. + /// + /// # Panics + /// + /// Panics if `debug_assertions` is enabled and `entity_markers` has a different length than the number of marker slots. + pub unsafe fn write( + &self, + serde_fns: &SerdeFns, + entity_markers: &[bool], + commands: &mut Commands, + entity: &mut EntityMut, + cursor: &mut Cursor<&[u8]>, + entity_map: &mut ServerEntityMap, + replicon_tick: RepliconTick, + ) -> bincode::Result<()> { + let write = self + .marker_fns(entity_markers) + .map(|(write, _)| write) + .unwrap_or(self.write); + + (write)( + serde_fns, + commands, + entity, + cursor, + entity_map, + replicon_tick, + ) + } + + /// Same as [`Self::write`], but calls the assigned remove function. + pub fn remove( + &self, + entity_markers: &[bool], + entity_commands: EntityCommands, + replicon_tick: RepliconTick, + ) { + let remove = self + .marker_fns(entity_markers) + .map(|(_, remove)| remove) + .unwrap_or(self.remove); + + (remove)(entity_commands, replicon_tick) + } + + /// Picks assigned functions based on markers present on an entity. + pub(super) fn marker_fns(&self, entity_markers: &[bool]) -> Option<(WriteFn, RemoveFn)> { + debug_assert_eq!( + entity_markers.len(), + self.markers.len(), + "entity markers length and marker functions slots should match" + ); + + self.markers + .iter() + .zip(entity_markers) + .find_map(|(fns, &enabled)| fns.filter(|_| enabled)) + } +} + +/// Signature of component reading function. +pub type ReadFn = unsafe fn(&SerdeFns, Ptr, &mut Cursor>) -> bincode::Result<()>; + +/// Signature of component writing function. +pub type WriteFn = unsafe fn( + &SerdeFns, + &mut Commands, + &mut EntityMut, + &mut Cursor<&[u8]>, + &mut ServerEntityMap, + RepliconTick, +) -> bincode::Result<()>; + +/// Signature of component removal functions. +pub type RemoveFn = fn(EntityCommands, RepliconTick); + +/// Dereferences a component from a pointer and calls the passed serialization function. +/// +/// # Safety +/// +/// The caller must ensure that `ptr` and `serde_fns` were created for `C`. +pub unsafe fn default_read( + serde_fns: &SerdeFns, + ptr: Ptr, + cursor: &mut Cursor>, +) -> bincode::Result<()> { + serde_fns.serialize(ptr.deref::(), cursor) +} + +/// Default component writing function. +/// +/// If the component does not exist on the entity, it will be deserialized with [`SerdeFns::deserialize`] and inserted via [`Commands`]. +/// If the component exists on the entity, [`SerdeFns::deserialize_in_place`] will be used directly on the entity's component. +/// +/// # Safety +/// +/// The caller must ensure that `serde_fns` was created for `C`. +pub unsafe fn default_write( + serde_fns: &SerdeFns, + commands: &mut Commands, + entity: &mut EntityMut, + cursor: &mut Cursor<&[u8]>, + entity_map: &mut ServerEntityMap, + _replicon_tick: RepliconTick, +) -> bincode::Result<()> { + let mut mapper = ClientMapper { + commands, + entity_map, + }; + + if let Some(mut component) = entity.get_mut::() { + serde_fns.deserialize_in_place(&mut *component, cursor, &mut mapper)?; + } else { + let component: C = serde_fns.deserialize(cursor, &mut mapper)?; + commands.entity(entity.id()).insert(component); + } + + Ok(()) +} + +/// Default component removal function. +pub fn default_remove( + mut entity_commands: EntityCommands, + _replicon_tick: RepliconTick, +) { + entity_commands.remove::(); +} diff --git a/src/core/replication_fns/serde_fns.rs b/src/core/replication_fns/serde_fns.rs new file mode 100644 index 00000000..76f6f323 --- /dev/null +++ b/src/core/replication_fns/serde_fns.rs @@ -0,0 +1,181 @@ +use std::{ + any::{self, TypeId}, + io::Cursor, + mem, +}; + +use bevy::{ecs::entity::MapEntities, prelude::*}; +use bincode::{DefaultOptions, Options}; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::client::client_mapper::ClientMapper; + +/// Erased serialization and deserialization function pointers for a component. +pub struct SerdeFns { + type_id: TypeId, + type_name: &'static str, + + serialize: unsafe fn(), + deserialize: unsafe fn(), + deserialize_in_place: unsafe fn(), +} + +impl SerdeFns { + /// Creates a new instance for `C` by erasing the passed function pointers. + /// + /// All other functions should be called with the same `C`. + pub(super) fn new( + serialize: SerializeFn, + deserialize: DeserializeFn, + deserialize_in_place: DeserializeInPlaceFn, + ) -> Self { + Self { + type_id: TypeId::of::(), + type_name: any::type_name::(), + serialize: unsafe { mem::transmute(serialize) }, + deserialize: unsafe { mem::transmute(deserialize) }, + deserialize_in_place: unsafe { mem::transmute(deserialize_in_place) }, + } + } + + /// Serializes a component into a cursor. + /// + /// # Safety + /// + /// The caller must ensure that the function is called with the same `C` with which it was created. + pub unsafe fn serialize( + &self, + component: &C, + cursor: &mut Cursor>, + ) -> bincode::Result<()> { + self.debug_type_check::(); + + let serialize: SerializeFn = mem::transmute(self.serialize); + (serialize)(component, cursor) + } + + /// Deserializes a component from a cursor. + /// + /// # Safety + /// + /// The caller must ensure that the function is called with the same `C` with which it was created. + pub unsafe fn deserialize( + &self, + cursor: &mut Cursor<&[u8]>, + mapper: &mut ClientMapper, + ) -> bincode::Result { + self.debug_type_check::(); + + let deserialize: DeserializeFn = mem::transmute(self.deserialize); + (deserialize)(cursor, mapper) + } + + /// Same as [`Self::deserialize`], but instead of returning a component, it updates the passed reference. + /// + /// # Safety + /// + /// The caller must ensure that the function is called with the same `C` with which it was created. + pub unsafe fn deserialize_in_place( + &self, + component: &mut C, + cursor: &mut Cursor<&[u8]>, + mapper: &mut ClientMapper, + ) -> bincode::Result<()> { + self.debug_type_check::(); + + let deserialize_in_place: DeserializeInPlaceFn = + mem::transmute(self.deserialize_in_place); + let deserialize: DeserializeFn = mem::transmute(self.deserialize); + (deserialize_in_place)(deserialize, component, cursor, mapper) + } + + /// Panics if a component differs from [`Self::new`]. + fn debug_type_check(&self) { + debug_assert_eq!( + self.type_id, + TypeId::of::(), + "trying to call serde functions with {}, but they were created with {}", + any::type_name::(), + self.type_name, + ); + } +} + +/// Signature of component serialization functions. +pub type SerializeFn = fn(&C, &mut Cursor>) -> bincode::Result<()>; + +/// Signature of component deserialization functions. +pub type DeserializeFn = fn(&mut Cursor<&[u8]>, &mut ClientMapper) -> bincode::Result; + +/// Signature of component deserialization functions. +pub type DeserializeInPlaceFn = + fn(DeserializeFn, &mut C, &mut Cursor<&[u8]>, &mut ClientMapper) -> bincode::Result<()>; + +/// Default component serialization function. +pub fn default_serialize( + component: &C, + cursor: &mut Cursor>, +) -> bincode::Result<()> { + DefaultOptions::new().serialize_into(cursor, component) +} + +/// Default component deserialization function. +pub fn default_deserialize( + cursor: &mut Cursor<&[u8]>, + _mapper: &mut ClientMapper, +) -> bincode::Result { + DefaultOptions::new().deserialize_from(cursor) +} + +/// Like [`default_deserialize`], but also maps entities before insertion. +pub fn default_deserialize_mapped( + cursor: &mut Cursor<&[u8]>, + mapper: &mut ClientMapper, +) -> bincode::Result { + let mut component: C = DefaultOptions::new().deserialize_from(cursor)?; + component.map_entities(mapper); + Ok(component) +} + +/// Default component in-place deserialization function. +/// +/// This implementation just assigns the value from the passed deserialization function. +pub fn in_place_as_deserialize( + deserialize: DeserializeFn, + component: &mut C, + cursor: &mut Cursor<&[u8]>, + mapper: &mut ClientMapper, +) -> bincode::Result<()> { + *component = (deserialize)(cursor, mapper)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use serde::Deserialize; + + use super::*; + + #[test] + #[should_panic] + fn packing() { + let serde_fns = SerdeFns::new( + default_serialize::, + default_deserialize::, + in_place_as_deserialize::, + ); + + // SAFETY: Called with a different type, but should panic in debug mode. + unsafe { + serde_fns + .serialize(&ComponentB, &mut Cursor::default()) + .ok(); + } + } + + #[derive(Component, Serialize, Deserialize)] + struct ComponentA; + + #[derive(Component)] + struct ComponentB; +} diff --git a/src/core/replication_rules.rs b/src/core/replication_rules.rs index 625e213e..c1020991 100644 --- a/src/core/replication_rules.rs +++ b/src/core/replication_rules.rs @@ -7,105 +7,111 @@ use bevy::{ }; use serde::{de::DeserializeOwned, Serialize}; -use super::replication_fns::{ComponentFns, ComponentFnsId, ReplicationFns}; +use super::replication_fns::{ + serde_fns::{self, DeserializeFn, DeserializeInPlaceFn, SerializeFn}, + FnsInfo, ReplicationFns, +}; /// Replication functions for [`App`]. -pub trait AppReplicationExt { +pub trait AppRuleExt { /// Creates a replication rule for a single component. /// /// The component will be replicated if its entity contains the [`Replication`](super::Replication) /// marker component. /// /// Component will be serialized and deserialized as-is using bincode. - /// To customize how the component will be serialized, use [`Self::replicate_group`]. + /// To customize it, use [`Self::replicate_group`]. /// /// If your component contains any [`Entity`] inside, use [`Self::replicate_mapped`]. /// - /// See also [`ComponentFns::default_fns`]. + /// See also [`Self::replicate_with`]. fn replicate(&mut self) -> &mut Self where C: Component + Serialize + DeserializeOwned, { - // SAFETY: Component is registered with the corresponding default serialization function. - unsafe { self.replicate_with::(ComponentFns::default_fns::()) }; - self + self.replicate_with::( + serde_fns::default_serialize::, + serde_fns::default_deserialize::, + serde_fns::in_place_as_deserialize::, + ) } /// Same as [`Self::replicate`], but additionally maps server entities to client inside the component after receiving. /// /// Always use it for components that contain entities. /// - /// See also [`ComponentFns::default_mapped_fns`]. + /// See also [`Self::replicate`]. fn replicate_mapped(&mut self) -> &mut Self where C: Component + Serialize + DeserializeOwned + MapEntities, { - // SAFETY: Component is registered with the corresponding default serialization function. - unsafe { self.replicate_with::(ComponentFns::default_mapped_fns::()) }; - self + self.replicate_with::( + serde_fns::default_serialize::, + serde_fns::default_deserialize_mapped::, + serde_fns::in_place_as_deserialize::, + ) } /** - Same as [`Self::replicate`], but uses the specified functions for serialization, deserialization, and removal. + Same as [`Self::replicate`], but uses the specified functions for serialization and deserialization. - Can be used to customize how the component will be replicated or + Can be used to customize how the component will be passed over the network or for components that don't implement [`Serialize`] or [`DeserializeOwned`]. - # Safety + When a component is inserted or changed on the server, `serialize` will be called. + On receive, `deserialize` will be called to insert new components and `deserialize_in_place` + to change existing ones. - Caller must ensure that component `C` can be safely passed to [`ComponentFns::serialize`]. + The registered `deserialize` function will be passed into `deserialize_in_place` for possible + fallback. This is what the default [`in_place_as_deserialize`](serde_fns::in_place_as_deserialize) does, + use it if you don't need to have different deserialization logic for components that are already present. + But `deserialize_in_place` could be used to optimize deserialization of components that require allocations. + + You can also override how the component will be written, + see [`AppMarkerExt`](super::command_markers::AppMarkerExt). # Examples ``` use std::io::Cursor; - use bevy::{prelude::*, ptr::Ptr}; + use bevy::prelude::*; use bevy_replicon::{ - client::client_mapper::ServerEntityMap, - core::{replication_fns::{self, ComponentFns}, replicon_tick::RepliconTick}, - prelude::*, + client::client_mapper::ClientMapper, core::replication_fns::serde_fns, prelude::*, }; # let mut app = App::new(); # app.add_plugins(RepliconPlugins); - // SAFETY: `serialize_translation` expects `Transform`. - unsafe { - app.replicate_with::(ComponentFns { - serialize: serialize_translation, - deserialize: deserialize_translation, - remove: replication_fns::remove::, - }); - } + app.replicate_with::( + serialize_translation, + deserialize_translation, + serde_fns::in_place_as_deserialize, + ); /// Serializes only `translation` from [`Transform`]. - /// - /// # Safety - /// - /// [`Transform`] must be the erased pointee type for this [`Ptr`]. - unsafe fn serialize_translation(component: Ptr, cursor: &mut Cursor>) -> bincode::Result<()> { - let transform: &Transform = component.deref(); + fn serialize_translation( + transform: &Transform, + cursor: &mut Cursor>, + ) -> bincode::Result<()> { bincode::serialize_into(cursor, &transform.translation) } /// Deserializes `translation` and creates [`Transform`] from it. fn deserialize_translation( - entity: &mut EntityWorldMut, - _entity_map: &mut ServerEntityMap, cursor: &mut Cursor<&[u8]>, - _replicon_tick: RepliconTick, - ) -> bincode::Result<()> { + _mapper: &mut ClientMapper, + ) -> bincode::Result { let translation: Vec3 = bincode::deserialize_from(cursor)?; - entity.insert(Transform::from_translation(translation)); - - Ok(()) + Ok(Transform::from_translation(translation)) } ``` - - The [`remove`](super::replication_fns::remove) used in this example is the default component - removal function, but you can replace it with your own as well. */ - unsafe fn replicate_with(&mut self, component_fns: ComponentFns) -> &mut Self + fn replicate_with( + &mut self, + serialize: SerializeFn, + deserialize: DeserializeFn, + deserialize_in_place: DeserializeInPlaceFn, + ) -> &mut Self where C: Component; @@ -116,7 +122,7 @@ pub trait AppReplicationExt { If a group contains a single component, it will work the same as [`Self::replicate`]. - If an entity matches multiple groups, functions from a group with higher [priority](ReplicationRule::priority) + If an entity matches multiple groups, functions from a group with higher priority will take precedence for overlapping components. For example, a rule with [`Transform`] and a `Player` marker will take precedence over a single [`Transform`] rule. @@ -125,7 +131,7 @@ pub trait AppReplicationExt { Replication for them will be stopped, unless they match other rules. We provide blanket impls for tuples to replicate them as-is, but a user could manually implement the trait - to customize how components will be serialized, deserialized and removed. For details see [`GroupReplication`]. + to customize how components will be serialized and deserialized. For details see [`GroupReplication`]. # Panics @@ -151,18 +157,29 @@ pub trait AppReplicationExt { fn replicate_group(&mut self) -> &mut Self; } -impl AppReplicationExt for App { - unsafe fn replicate_with(&mut self, component_fns: ComponentFns) -> &mut Self +impl AppRuleExt for App { + fn replicate_with( + &mut self, + serialize: SerializeFn, + deserialize: DeserializeFn, + deserialize_in_place: DeserializeInPlaceFn, + ) -> &mut Self where C: Component, { - let component_id = self.world.init_component::(); - let mut replication_fns = self.world.resource_mut::(); - let fns_id = replication_fns.register_component_fns(component_fns); + let rule = self + .world + .resource_scope(|world, mut replication_fns: Mut| { + let fns_info = replication_fns.register_serde_fns( + world, + serialize, + deserialize, + deserialize_in_place, + ); + ReplicationRule::new(vec![fns_info]) + }); - let rule = ReplicationRule::new(vec![(component_id, fns_id)]); self.world.resource_mut::().insert(rule); - self } @@ -180,11 +197,11 @@ impl AppReplicationExt for App { /// All registered rules for components replication. #[derive(Default, Deref, Resource)] -pub struct ReplicationRules(Vec); +pub(crate) struct ReplicationRules(Vec); impl ReplicationRules { /// Inserts a new rule, maintaining sorting by their priority in descending order. - pub fn insert(&mut self, rule: ReplicationRule) { + fn insert(&mut self, rule: ReplicationRule) { let index = self .binary_search_by_key(&Reverse(rule.priority), |rule| Reverse(rule.priority)) .unwrap_or_else(|index| index); @@ -202,33 +219,23 @@ pub struct ReplicationRule { pub priority: usize, /// Rule components and their serialization/deserialization/removal functions. - components: Vec<(ComponentId, ComponentFnsId)>, + pub components: Vec, } impl ReplicationRule { - /// Creates a new rule with priority equal to the number of serialized components. - /// - /// # Safety - /// - /// Caller must ensure that in each pair the associated component can be safely - /// passed to the associated [`ComponentFns::serialize`]. - pub unsafe fn new(components: Vec<(ComponentId, ComponentFnsId)>) -> Self { + /// Creates a new rule with priority equal to the number of serializable components. + pub fn new(components: Vec) -> Self { Self { priority: components.len(), components, } } - /// Returns associated components and functions IDs. - pub(crate) fn components(&self) -> &[(ComponentId, ComponentFnsId)] { - &self.components - } - /// Determines whether an archetype contains all components required by the rule. pub(crate) fn matches(&self, archetype: &Archetype) -> bool { self.components .iter() - .all(|&(component_id, _)| archetype.contains(component_id)) + .all(|fns_info| archetype.contains(fns_info.component_id())) } /// Determines whether the rule is applicable to an archetype with removals included and contains at least one removal. @@ -243,10 +250,10 @@ impl ReplicationRule { removed_components: &HashSet, ) -> bool { let mut matches = false; - for &(component_id, _) in &self.components { - if removed_components.contains(&component_id) { + for fns_info in &self.components { + if removed_components.contains(&fns_info.component_id()) { matches = true; - } else if !post_removal_archetype.contains(component_id) { + } else if !post_removal_archetype.contains(fns_info.component_id()) { return false; } } @@ -256,7 +263,7 @@ impl ReplicationRule { } /** -Describes how a component group should be serialized, deserialized, and removed. +Describes how a component group should be serialized, deserialized, written, and removed. Can be implemented on any struct to create a custom replication group. @@ -265,13 +272,12 @@ Can be implemented on any struct to create a custom replication group. ``` use std::io::Cursor; -use bevy::{prelude::*, ptr::Ptr}; +use bevy::prelude::*; use bevy_replicon::{ - client::client_mapper::ServerEntityMap, + client::client_mapper::ClientMapper, core::{ - replication_rules::{self, GroupReplication, ReplicationRule}, - replication_fns::{self, ReplicationFns, ComponentFns}, - replicon_tick::RepliconTick, + replication_fns::{serde_fns, ReplicationFns}, + replication_rules::{GroupReplication, ReplicationRule}, }, prelude::*, }; @@ -294,35 +300,26 @@ struct Player; impl GroupReplication for PlayerBundle { fn register(world: &mut World, replication_fns: &mut ReplicationFns) -> ReplicationRule { // Customize serlialization to serialize only `translation`. - let transform_id = world.init_component::(); - let transform_fns_id = replication_fns.register_component_fns(ComponentFns { - // For function definitions see the example from `AppReplicationExt::replicate_with`. - serialize: serialize_translation, - deserialize: deserialize_translation, - remove: replication_fns::remove::, // Use default removal function. - }); + let transform_info = replication_fns.register_serde_fns( + world, + serialize_translation, + deserialize_translation, + serde_fns::in_place_as_deserialize, + ); // Serialize `player` as usual. - let visibility_id = world.init_component::(); - let visibility_fns_id = - replication_fns.register_component_fns(ComponentFns::default_fns::()); + let player_info = replication_fns.register_default_serde_fns::(world); // We skip `replication` registration since it's a special component. // It's automatically inserted on clients after replication and // deserialization from scenes. - let components = vec![ - (transform_id, transform_fns_id), - (visibility_id, visibility_fns_id), - ]; - - // SAFETY: all components can be safely passed to their serialization functions. - unsafe { ReplicationRule::new(components) } + ReplicationRule::new(vec![transform_info, player_info]) } } -# fn serialize_translation(_: Ptr, _: &mut Cursor>) -> bincode::Result<()> { unimplemented!() } -# fn deserialize_translation(_: &mut EntityWorldMut, _: &mut ServerEntityMap, _: &mut Cursor<&[u8]>, _: RepliconTick) -> bincode::Result<()> { unimplemented!() } +# fn serialize_translation(_: &Transform, _: &mut Cursor>) -> bincode::Result<()> { unimplemented!() } +# fn deserialize_translation(_: &mut Cursor<&[u8]>, _: &mut ClientMapper) -> bincode::Result { unimplemented!() } ``` **/ pub trait GroupReplication { @@ -337,13 +334,11 @@ macro_rules! impl_registrations { // TODO: initialize with capacity after stabilization: https://github.com/rust-lang/rust/pull/122808 let mut components = Vec::new(); $( - let component_id = world.init_component::<$type>(); - let fns_id = replication_fns.register_component_fns(ComponentFns::default_fns::<$type>()); - components.push((component_id, fns_id)); + let fns_info = replication_fns.register_default_serde_fns::<$type>(world); + components.push(fns_info); )* - // SAFETY: Components are registered with the appropriate default serialization functions. - unsafe { ReplicationRule::new(components) } + ReplicationRule::new(components) } } } @@ -356,7 +351,7 @@ mod tests { use serde::{Deserialize, Serialize}; use super::*; - use crate::{core::replication_fns::ReplicationFns, AppReplicationExt}; + use crate::{core::replication_fns::ReplicationFns, AppRuleExt}; #[test] fn sorting() { diff --git a/src/lib.rs b/src/lib.rs index 7b50c0a3..cb87bcad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ If you remove the [`Replication`] component from an entity on the server, it wil Components will be replicated only on entities marked for replication. By default no components are replicated. -Use [`AppReplicationExt::replicate()`] to enable replication for a component: +Use [`AppRuleExt::replicate()`] to enable replication for a component: ``` # use bevy::prelude::*; @@ -111,7 +111,7 @@ If your component contains an entity then it cannot be deserialized as is because entity IDs are different on server and client. The client should do the mapping. Therefore, to replicate such components properly, they need to implement the [`MapEntities`](bevy::ecs::entity::MapEntities) trait and register -using [`AppReplicationExt::replicate_mapped()`]: +using [`AppRuleExt::replicate_mapped()`]: ``` # use bevy::{prelude::*, ecs::entity::{EntityMapper, MapEntities}}; @@ -130,10 +130,13 @@ impl MapEntities for MappedComponent { By default all components are serialized with [`bincode`] using [`DefaultOptions`](bincode::DefaultOptions). If your component doesn't implement serde traits or you want to serialize it partially (for example, only replicate the `translation` field from [`Transform`]), -you can use [`AppReplicationExt::replicate_with`]. +you can use [`AppRuleExt::replicate_with`]. If you want a group of components to be replicated only if all of them are present on an entity, -you can use [`AppReplicationExt::replicate_group`]. +you can use [`AppRuleExt::replicate_group`]. + +If you want to customize how the received component will be written or removed on clients based +on some marker component (for example, write into a different component), see [`AppMarkerExt`]. In order to serialize Bevy components you need to enable the `serialize` feature on Bevy. @@ -462,8 +465,9 @@ pub mod prelude { ClientPlugin, ClientSet, }, core::{ + command_markers::AppMarkerExt, common_conditions::*, - replication_rules::AppReplicationExt, + replication_rules::AppRuleExt, replicon_channels::{ChannelKind, RepliconChannel, RepliconChannels}, ClientId, Replication, RepliconCorePlugin, }, diff --git a/src/parent_sync.rs b/src/parent_sync.rs index 43bee9fb..7fd5f65d 100644 --- a/src/parent_sync.rs +++ b/src/parent_sync.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use crate::{ client::ClientSet, - core::{common_conditions::has_authority, replication_rules::AppReplicationExt}, + core::{common_conditions::has_authority, replication_rules::AppRuleExt}, server::ServerSet, }; diff --git a/src/scene.rs b/src/scene.rs index 8782b3b2..b865913b 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -17,16 +17,15 @@ or `#[reflect(Component)]` is missing. ``` use bevy::{prelude::*, asset::ron, scene::serde::SceneDeserializer}; -use bevy_replicon::{core::replication_rules::ReplicationRules, prelude::*, scene}; +use bevy_replicon::{prelude::*, scene}; use serde::de::DeserializeSeed; -# let mut world = World::new(); -# world.init_resource::(); -# world.init_resource::(); +# let mut app = App::new(); +# app.add_plugins(RepliconPlugins); // Serialization -let registry = world.resource::(); +let registry = app.world.resource::(); let mut scene = DynamicScene::default(); -scene::replicate_into(&mut scene, &world); +scene::replicate_into(&mut scene, &app.world); let scene = scene .serialize_ron(®istry) .expect("scene should be serialized"); @@ -41,7 +40,7 @@ let mut scene = scene_deserializer .deserialize(&mut deserializer) .expect("ron should be convertible to scene"); -// All saved entities should have `Replication` component. +// Re-insert `Replication` component. for entity in &mut scene.entities { entity.components.push(Replication.clone_value()); } @@ -74,7 +73,11 @@ pub fn replicate_into(scene: &mut DynamicScene, world: &World) { } for rule in rules.iter().filter(|rule| rule.matches(archetype)) { - for &(component_id, _) in rule.components() { + for component_id in rule + .components + .iter() + .map(|fns_info| fns_info.component_id()) + { // SAFETY: replication rules can be registered only with valid component IDs. let replicated_component = unsafe { world.components().get_info_unchecked(component_id) }; diff --git a/src/server.rs b/src/server.rs index 2071cf1f..e4b29e67 100644 --- a/src/server.rs +++ b/src/server.rs @@ -345,7 +345,7 @@ fn collect_changes( ) }; - let fns = replication_fns.component_fns(replicated_component.fns_id); + let (command_fns, serde_fns) = replication_fns.get(replicated_component.fns_id); let mut shared_bytes = None; for (init_message, update_message, client) in messages.iter_mut_with_clients() { let visibility = client.visibility().cached_visibility(); @@ -358,7 +358,8 @@ fn collect_changes( { init_message.write_component( &mut shared_bytes, - fns, + serde_fns, + command_fns, replicated_component.fns_id, component, )?; @@ -369,7 +370,8 @@ fn collect_changes( if ticks.is_changed(tick, change_tick.this_run()) { update_message.write_component( &mut shared_bytes, - fns, + serde_fns, + command_fns, replicated_component.fns_id, component, )?; @@ -477,9 +479,9 @@ fn collect_removals( for (entity, remove_ids) in removal_buffer.iter() { for (message, _, client) in messages.iter_mut_with_clients() { message.start_entity_data(entity); - for &(_, fns_id) in remove_ids { + for fns_info in remove_ids { client.set_change_limit(entity, tick); - message.write_fns_id(fns_id)?; + message.write_fns_id(fns_info.fns_id())?; } message.end_entity_data(false)?; } diff --git a/src/server/removal_buffer.rs b/src/server/removal_buffer.rs index 79db6dcc..5ffb7f3b 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_fns::ComponentFnsId, + common_conditions::server_running, replication_fns::FnsInfo, replication_rules::ReplicationRules, Replication, }; @@ -130,8 +130,8 @@ impl FromWorld for ReplicatedComponents { let rules = world.resource::(); let component_ids = rules .iter() - .flat_map(|rule| rule.components()) - .map(|&(component_id, _)| component_id) + .flat_map(|rule| &rule.components) + .map(|fns_info| fns_info.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<(ComponentId, ComponentFnsId)>)>, + removals: Vec<(Entity, Vec)>, /// [`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 &(component_id, fns_id) in rule.components() { + for &fns_info 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_id, _)| removed_id != component_id) - && !archetype.contains(component_id) + .all(|removed_info| removed_info.component_id() != fns_info.component_id()) + && !archetype.contains(fns_info.component_id()) { - removed_ids.push((component_id, fns_id)); + removed_ids.push(fns_info); } } } @@ -205,9 +205,7 @@ mod tests { use super::*; use crate::{ - core::{ - replication_fns::ReplicationFns, replication_rules::AppReplicationExt, Replication, - }, + core::{replication_fns::ReplicationFns, replication_rules::AppRuleExt, Replication}, server::replicon_server::RepliconServer, }; diff --git a/src/server/replicated_archetypes.rs b/src/server/replicated_archetypes.rs index cdfef7fb..65bca11c 100644 --- a/src/server/replicated_archetypes.rs +++ b/src/server/replicated_archetypes.rs @@ -10,9 +10,7 @@ use bevy::{ utils::tracing::enabled, }; -use crate::core::{ - replication_fns::ComponentFnsId, replication_rules::ReplicationRules, Replication, -}; +use crate::core::{replication_fns::FnsId, replication_rules::ReplicationRules, Replication}; /// Cached information about all replicated archetypes. #[derive(Deref)] @@ -47,18 +45,18 @@ impl ReplicatedArchetypes { { let mut replicated_archetype = ReplicatedArchetype::new(archetype.id()); for rule in rules.iter().filter(|rule| rule.matches(archetype)) { - for &(component_id, fns_id) in rule.components() { + for fns_info 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 == component_id) + .any(|component| component.component_id == fns_info.component_id()) { if enabled!(Level::DEBUG) { let component_name = world .components() - .get_name(component_id) + .get_name(fns_info.component_id()) .expect("rules should be registered with valid component"); let component_names: Vec<_> = replicated_archetype @@ -76,13 +74,16 @@ impl ReplicatedArchetypes { } // SAFETY: component ID obtained from this archetype. - let storage_type = - unsafe { archetype.get_storage_type(component_id).unwrap_unchecked() }; + let storage_type = unsafe { + archetype + .get_storage_type(fns_info.component_id()) + .unwrap_unchecked() + }; replicated_archetype.components.push(ReplicatedComponent { - component_id, + component_id: fns_info.component_id(), storage_type, - fns_id, + fns_id: fns_info.fns_id(), }); } } @@ -123,7 +124,7 @@ impl ReplicatedArchetype { pub(super) struct ReplicatedComponent { pub(super) component_id: ComponentId, pub(super) storage_type: StorageType, - pub(super) fns_id: ComponentFnsId, + pub(super) fns_id: FnsId, } #[cfg(test)] @@ -131,7 +132,7 @@ mod tests { use serde::{Deserialize, Serialize}; use super::*; - use crate::{core::replication_fns::ReplicationFns, AppReplicationExt}; + use crate::{core::replication_fns::ReplicationFns, AppRuleExt}; #[test] fn empty() { diff --git a/src/server/replication_messages.rs b/src/server/replication_messages.rs index 1ed4bf58..70eea767 100644 --- a/src/server/replication_messages.rs +++ b/src/server/replication_messages.rs @@ -15,7 +15,7 @@ use super::{ ClientMapping, ConnectedClient, }; use crate::core::{ - replication_fns::{ComponentFns, ComponentFnsId}, + replication_fns::{command_fns::CommandFns, serde_fns::SerdeFns, FnsId}, replicon_channels::ReplicationChannel, replicon_tick::RepliconTick, }; @@ -289,8 +289,9 @@ impl InitMessage { pub(super) fn write_component<'a>( &'a mut self, shared_bytes: &mut Option<&'a [u8]>, - fns: &ComponentFns, - fns_id: ComponentFnsId, + serde_fns: &SerdeFns, + command_fns: &CommandFns, + fns_id: FnsId, ptr: Ptr, ) -> bincode::Result<()> { if self.entity_data_size == 0 { @@ -299,9 +300,8 @@ impl InitMessage { let size = write_with(shared_bytes, &mut self.cursor, |cursor| { DefaultOptions::new().serialize_into(&mut *cursor, &fns_id)?; - // SAFETY: User ensured that the registered component can be - // safely passed to its serialization function. - unsafe { (fns.serialize)(ptr, cursor) } + // SAFETY: `command_fns`, `ptr` and `serde_fns` were created for the same component type. + unsafe { command_fns.read(serde_fns, ptr, cursor) } })?; self.entity_data_size = self @@ -316,7 +316,7 @@ impl InitMessage { /// /// 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: ComponentFnsId) -> bincode::Result<()> { + pub(super) fn write_fns_id(&mut self, fns_id: FnsId) -> bincode::Result<()> { if self.entity_data_size == 0 { self.write_data_entity()?; } @@ -517,8 +517,9 @@ impl UpdateMessage { pub(super) fn write_component<'a>( &'a mut self, shared_bytes: &mut Option<&'a [u8]>, - fns: &ComponentFns, - fns_id: ComponentFnsId, + serde_fns: &SerdeFns, + command_fns: &CommandFns, + fns_id: FnsId, ptr: Ptr, ) -> bincode::Result<()> { if self.entity_data_size == 0 { @@ -527,9 +528,8 @@ impl UpdateMessage { let size = write_with(shared_bytes, &mut self.cursor, |cursor| { DefaultOptions::new().serialize_into(&mut *cursor, &fns_id)?; - // SAFETY: User ensured that the registered component can be - // safely passed to its serialization function. - unsafe { (fns.serialize)(ptr, cursor) } + // SAFETY: `command_fns`, `ptr` and `serde_fns` were created for the same component type. + unsafe { command_fns.read(serde_fns, ptr, cursor) } })?; self.entity_data_size = self diff --git a/tests/changes.rs b/tests/changes.rs index a51e5ac1..7834f37c 100644 --- a/tests/changes.rs +++ b/tests/changes.rs @@ -1,5 +1,15 @@ +use std::io::Cursor; + use bevy::{prelude::*, utils::Duration}; -use bevy_replicon::{core::replicon_tick::RepliconTick, prelude::*, test_app::ServerTestAppExt}; +use bevy_replicon::{ + client::client_mapper::{ClientMapper, ServerEntityMap}, + core::{ + replication_fns::{command_fns, serde_fns::SerdeFns}, + replicon_tick::RepliconTick, + }, + prelude::*, + test_app::ServerTestAppExt, +}; use serde::{Deserialize, Serialize}; #[test] @@ -98,6 +108,133 @@ fn package_size_component() { assert_eq!(component.0, BIG_DATA); } +#[test] +fn command_fns() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + + // SAFETY: `replace` can be safely called with a `SerdeFns` created for `OriginalComponent`. + unsafe { + app.set_command_fns::( + replace, + command_fns::default_remove::, + ); + } + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app + .world + .spawn((Replication, OriginalComponent(false))) + .id(); + + let client_entity = client_app + .world + .spawn((Replication, ReplacedComponent(false))) + .id(); + + client_app + .world + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + // Change value. + let mut component = server_app + .world + .get_mut::(server_entity) + .unwrap(); + component.0 = true; + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let client_entity = client_app.world.entity(client_entity); + assert!(!client_entity.contains::()); + + let component = client_entity.get::().unwrap(); + assert!(component.0); +} + +#[test] +fn marker() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .register_marker::() + .replicate::(); + + // SAFETY: `replace` can be safely called with a `SerdeFns` created for `OriginalComponent`. + unsafe { + app.set_marker_fns::( + replace, + command_fns::default_remove::, + ); + } + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app + .world + .spawn((Replication, OriginalComponent(false))) + .id(); + + let client_entity = client_app + .world + .spawn((Replication, ReplaceMarker, ReplacedComponent(false))) + .id(); + + client_app + .world + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + // Change value. + let mut component = server_app + .world + .get_mut::(server_entity) + .unwrap(); + component.0 = true; + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let client_entity = client_app.world.entity(client_entity); + assert!(!client_entity.contains::()); + + let component = client_entity.get::().unwrap(); + assert!(component.0); +} + #[test] fn many_entities() { let mut server_app = App::new(); @@ -390,3 +527,38 @@ struct BoolComponent(bool); #[derive(Component, Default, Deserialize, Serialize)] struct VecComponent(Vec); + +#[derive(Component)] +struct ReplaceMarker; + +#[derive(Component, Deserialize, Serialize)] +struct OriginalComponent(bool); + +#[derive(Component, Deserialize, Serialize)] +struct ReplacedComponent(bool); + +/// Deserializes [`OriginalComponent`], but inserts it as [`ReplacedComponent`]. +/// +/// # Safety +/// +/// The caller must ensure that `serde_fns` was created for [`OriginalComponent`]. +unsafe fn replace( + serde_fns: &SerdeFns, + commands: &mut Commands, + entity: &mut EntityMut, + cursor: &mut Cursor<&[u8]>, + entity_map: &mut ServerEntityMap, + _replicon_tick: RepliconTick, +) -> bincode::Result<()> { + let mut mapper = ClientMapper { + commands, + entity_map, + }; + + let component = serde_fns.deserialize::(cursor, &mut mapper)?; + commands + .entity(entity.id()) + .insert(ReplacedComponent(component.0)); + + Ok(()) +} diff --git a/tests/insertion.rs b/tests/insertion.rs index 325d5942..14fce1bf 100644 --- a/tests/insertion.rs +++ b/tests/insertion.rs @@ -1,6 +1,14 @@ +use std::io::Cursor; + use bevy::{ecs::entity::MapEntities, prelude::*}; use bevy_replicon::{ - client::client_mapper::ServerEntityMap, prelude::*, test_app::ServerTestAppExt, + client::client_mapper::{ClientMapper, ServerEntityMap}, + core::{ + replication_fns::{command_fns, serde_fns::SerdeFns}, + replicon_tick::RepliconTick, + }, + prelude::*, + test_app::ServerTestAppExt, }; use serde::{Deserialize, Serialize}; @@ -177,6 +185,111 @@ fn mapped_new_entity() { assert_eq!(client_app.world.entities().len(), 2); } +#[test] +fn command_fns() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + + // SAFETY: `replace` can be safely called with a `SerdeFns` created for `OriginalComponent`. + unsafe { + app.set_command_fns::( + replace, + command_fns::default_remove::, + ); + } + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn(Replication).id(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .insert(OriginalComponent); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + client_app + .world + .query_filtered::<(), (With, Without)>() + .single(&client_app.world); +} + +#[test] +fn marker() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .register_marker::() + .replicate::(); + + // SAFETY: `replace` can be safely called with a `SerdeFns` created for `OriginalComponent`. + unsafe { + app.set_marker_fns::( + replace, + command_fns::default_remove::, + ); + } + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn(Replication).id(); + let client_entity = client_app.world.spawn(Replication).id(); + + client_app + .world + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .insert(OriginalComponent); + + client_app + .world + .entity_mut(server_entity) + .insert(ReplaceMarker); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let client_entity = client_app.world.entity(client_entity); + assert!(!client_entity.contains::()); + assert!(client_entity.contains::()); +} + #[test] fn group() { let mut server_app = App::new(); @@ -321,3 +434,36 @@ struct GroupComponentB; #[derive(Component, Deserialize, Serialize)] struct NotReplicatedComponent; + +#[derive(Component)] +struct ReplaceMarker; + +#[derive(Component, Deserialize, Serialize)] +struct OriginalComponent; + +#[derive(Component, Deserialize, Serialize)] +struct ReplacedComponent; + +/// Deserializes [`OriginalComponent`], but ignores it and inserts [`ReplacedComponent`]. +/// +/// # Safety +/// +/// The caller must ensure that `serde_fns` was created for [`OriginalComponent`]. +unsafe fn replace( + serde_fns: &SerdeFns, + commands: &mut Commands, + entity: &mut EntityMut, + cursor: &mut Cursor<&[u8]>, + entity_map: &mut ServerEntityMap, + _replicon_tick: RepliconTick, +) -> bincode::Result<()> { + let mut mapper = ClientMapper { + commands, + entity_map, + }; + + serde_fns.deserialize::(cursor, &mut mapper)?; + commands.entity(entity.id()).insert(ReplacedComponent); + + Ok(()) +} diff --git a/tests/removal.rs b/tests/removal.rs index 658e3675..585fe1d6 100644 --- a/tests/removal.rs +++ b/tests/removal.rs @@ -1,6 +1,7 @@ use bevy::prelude::*; use bevy_replicon::{ - client::client_mapper::ServerEntityMap, prelude::*, test_app::ServerTestAppExt, + client::client_mapper::ServerEntityMap, core::replication_fns::command_fns, prelude::*, + test_app::ServerTestAppExt, }; use serde::{Deserialize, Serialize}; @@ -47,6 +48,115 @@ fn single() { assert!(!client_entity.contains::()); } +#[test] +fn command_fns() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + + // SAFETY: `replace` can be safely called with a `SerdeFns` created for `DummyComponent`. + unsafe { + app.set_command_fns::( + command_fns::default_write::, + command_fns::default_remove::, + ); + } + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn((Replication, DummyComponent)).id(); + let client_entity = client_app + .world + .spawn((Replication, RemovingComponent)) + .id(); + + client_app + .world + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .remove::(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let client_entity = client_app.world.entity(client_entity); + assert!(!client_entity.contains::()); +} + +#[test] +fn marker() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .register_marker::() + .replicate::(); + + // SAFETY: `replace` can be safely called with a `SerdeFns` created for `DummyComponent`. + unsafe { + app.set_marker_fns::( + command_fns::default_write::, + command_fns::default_remove::, + ); + } + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn((Replication, DummyComponent)).id(); + let client_entity = client_app + .world + .spawn((Replication, RemoveMarker, RemovingComponent)) + .id(); + + client_app + .world + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .remove::(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let client_entity = client_app.world.entity(client_entity); + assert!(!client_entity.contains::()); +} + #[test] fn group() { let mut server_app = App::new(); @@ -247,3 +357,9 @@ struct GroupComponentB; #[derive(Component, Deserialize, Serialize)] struct NotReplicatedComponent; + +#[derive(Component)] +struct RemoveMarker; + +#[derive(Component, Deserialize, Serialize)] +struct RemovingComponent;