From af6a5d1428b885234dca8164e8f0e24ea6c65b52 Mon Sep 17 00:00:00 2001 From: RJ Date: Mon, 2 Oct 2023 10:32:01 +0100 Subject: [PATCH 01/27] Make changes to NetworkTick, now a Resource, trigger sends. Allows for more flexible fixedtimestep integration, with manual incrementing of the network tick. --- examples/tic_tac_toe.rs | 2 +- src/client.rs | 67 ++++++++++---- src/lib.rs | 26 ++++-- src/replicon_core.rs | 17 ++-- src/replicon_core/replication_rules.rs | 71 +++++++++++++-- src/server.rs | 57 ++++++------ tests/common/mod.rs | 5 +- tests/replication.rs | 118 ++++++++++++++++++++++++- tests/server_event.rs | 3 + 9 files changed, 303 insertions(+), 63 deletions(-) diff --git a/examples/tic_tac_toe.rs b/examples/tic_tac_toe.rs index c382d208..1177dfdd 100644 --- a/examples/tic_tac_toe.rs +++ b/examples/tic_tac_toe.rs @@ -34,7 +34,7 @@ fn main() { }), ..Default::default() })) - .add_plugins((ReplicationPlugins, TicTacToePlugin)) + .add_plugins((ReplicationPlugins::default(), TicTacToePlugin)) .run(); } diff --git a/src/client.rs b/src/client.rs index 8a63e320..f821d2fe 100644 --- a/src/client.rs +++ b/src/client.rs @@ -10,11 +10,14 @@ use bevy_renet::{renet::RenetClient, transport::NetcodeClientPlugin, RenetClient use bincode::{DefaultOptions, Options}; use crate::replicon_core::{ - replication_rules::{Mapper, Replication, ReplicationRules}, + replication_rules::{EntityDespawnFn, Mapper, Replication, ReplicationRules}, NetworkTick, REPLICATION_CHANNEL_ID, }; -pub struct ClientPlugin; +#[derive(Default)] +pub struct ClientPlugin { + despawn_fn: Option, +} impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { @@ -45,10 +48,24 @@ impl Plugin for ClientPlugin { Self::reset_system.run_if(resource_removed::()), ), ); + if let Some(entity_despawn_fn) = self.despawn_fn { + app.world + .resource_scope(|_, mut replication_rules: Mut| { + replication_rules.set_despawn_fn(entity_despawn_fn); + }); + } } } impl ClientPlugin { + /// only useful in case you need to replace the default entity despawn function. + /// otherwis just use `ClientPlugin::default()` + pub fn new(despawn_fn: EntityDespawnFn) -> Self { + Self { + despawn_fn: Some(despawn_fn), + } + } + fn diff_receiving_system(world: &mut World) -> Result<(), bincode::Error> { world.resource_scope(|world, mut client: Mut| { world.resource_scope(|world, mut entity_map: Mut| { @@ -57,7 +74,9 @@ impl ClientPlugin { let end_pos: u64 = message.len().try_into().unwrap(); let mut cursor = Cursor::new(message); - if !deserialize_tick(&mut cursor, world)? { + let (network_tick, was_updated) = deserialize_tick(&mut cursor, world)?; + + if !was_updated { continue; } if cursor.position() == end_pos { @@ -70,6 +89,7 @@ impl ClientPlugin { &mut entity_map, &replication_rules, DiffKind::Change, + network_tick, )?; if cursor.position() == end_pos { continue; @@ -81,12 +101,19 @@ impl ClientPlugin { &mut entity_map, &replication_rules, DiffKind::Removal, + network_tick, )?; if cursor.position() == end_pos { continue; } - deserialize_despawns(&mut cursor, world, &mut entity_map)?; + deserialize_despawns( + &mut cursor, + world, + &mut entity_map, + network_tick, + replication_rules.get_despawn_fn(), + )?; } Ok(()) @@ -109,16 +136,19 @@ impl ClientPlugin { /// Deserializes server tick and applies it to [`LastTick`] if it is newer. /// -/// Returns true if [`LastTick`] has been updated. -fn deserialize_tick(cursor: &mut Cursor, world: &mut World) -> Result { - let tick = bincode::deserialize_from(cursor)?; +/// Returns (network_tick, true) if [`LastTick`] has been updated, otherwise (network_tick, false). +fn deserialize_tick( + cursor: &mut Cursor, + world: &mut World, +) -> Result<(NetworkTick, bool), bincode::Error> { + let network_tick = bincode::deserialize_from(cursor)?; let mut last_tick = world.resource_mut::(); - if last_tick.0 < tick { - last_tick.0 = tick; - Ok(true) + if last_tick.0 < network_tick { + last_tick.0 = network_tick; + Ok((network_tick, true)) } else { - Ok(false) + Ok((network_tick, false)) } } @@ -129,6 +159,7 @@ fn deserialize_component_diffs( entity_map: &mut NetworkEntityMap, replication_rules: &ReplicationRules, diff_kind: DiffKind, + tick: NetworkTick, ) -> Result<(), bincode::Error> { let entities_count: u16 = bincode::deserialize_from(&mut *cursor)?; for _ in 0..entities_count { @@ -141,9 +172,9 @@ fn deserialize_component_diffs( let replication_info = unsafe { replication_rules.get_info_unchecked(replication_id) }; match diff_kind { DiffKind::Change => { - (replication_info.deserialize)(&mut entity, entity_map, cursor)? + (replication_info.deserialize)(&mut entity, entity_map, cursor, tick)? } - DiffKind::Removal => (replication_info.remove)(&mut entity), + DiffKind::Removal => (replication_info.remove)(&mut entity, tick), } } } @@ -156,6 +187,8 @@ fn deserialize_despawns( cursor: &mut Cursor, world: &mut World, entity_map: &mut NetworkEntityMap, + tick: NetworkTick, + custom_entity_despawn_fn: Option, ) -> Result<(), bincode::Error> { let entities_count: u16 = bincode::deserialize_from(&mut *cursor)?; for _ in 0..entities_count { @@ -163,11 +196,15 @@ fn deserialize_despawns( // with the last diff, but the server might not yet have received confirmation // from the client and could include the deletion in the latest diff. let server_entity = deserialize_entity(&mut *cursor)?; - if let Some(client_entity) = entity_map + if let Some(mut client_entity) = entity_map .remove_by_server(server_entity) .and_then(|entity| world.get_entity_mut(entity)) { - client_entity.despawn_recursive(); + if let Some(despawn_fn) = custom_entity_despawn_fn { + (despawn_fn)(&mut client_entity, tick); + } else { + client_entity.despawn_recursive(); + } } } diff --git a/src/lib.rs b/src/lib.rs index 8c1c9d51..4467933f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,9 +19,10 @@ app.add_plugins(MinimalPlugins) .add_plugins(ReplicationPlugins); ``` -This group contains necessary replication stuff and setups server and client -plugins to let you host and join games from the same application. If you -planning to separate client and server you can use +This group contains necessary replication stuff and sets up the server and client +plugins to let you host and join games from the same application. + +If you are planning to separate client and server you can use `disable()` to disable [`ClientPlugin`] or [`ServerPlugin`]. You can also configure how often updates are sent from server to clients with [`ServerPlugin`]'s [`TickPolicy`].: @@ -115,6 +116,7 @@ fn deserialize_transform( entity: &mut EntityMut, _entity_map: &mut NetworkEntityMap, cursor: &mut Cursor, + _tick: NetworkTick, ) -> Result<(), bincode::Error> { let translation: Vec3 = bincode::deserialize_from(cursor)?; entity.insert(Transform::from_translation(translation)); @@ -131,6 +133,18 @@ will be replicated. If you need to disable replication for specific component for specific entity, you can insert [`Ignored`] component and replication will be skipped for `T`. +### NetworkTick, and fixed timestep games. + +The [`ServerPlugin`] sends replication data in `PostUpdate` any time the [`NetworkTick`] resource +changes. By default, NetworkTick is incremented in PostUpdate per the [`TickPolicy`]. + +If you set `TickPolicy::Manual`, you can increment [`NetworkTick`] at the start of your +`FixedTimestep` game loop. This value can represent your simulation step, and is made available +to the client in the custom deserialization, despawn andcomponent removal functions. + +One use for this is rollback networking: you may want to rollback time and apply the update +for the NetworkTick frame, which is in the past, then resimulate. + ### "Blueprints" pattern The idea was borrowed from [iyes_scene_tools](https://github.com/IyesGames/iyes_scene_tools#blueprints-pattern). @@ -175,7 +189,7 @@ fn player_init_system( #[derive(Component, Deserialize, Serialize)] struct Player; # fn serialize_transform(_: Ptr, _: &mut Cursor>) -> Result<(), bincode::Error> { unimplemented!() } -# fn deserialize_transform(_: &mut EntityMut, _: &mut NetworkEntityMap, _: &mut Cursor) -> Result<(), bincode::Error> { unimplemented!() } +# fn deserialize_transform(_: &mut EntityMut, _: &mut NetworkEntityMap, _: &mut Cursor, _: NetworkTick) -> Result<(), bincode::Error> { unimplemented!() } ``` This pairs nicely with server state serialization and keeps saves clean. @@ -393,6 +407,8 @@ pub use bevy_renet::*; pub use bincode; use prelude::*; +/// Plugin Group for all replicon plugins. +#[derive(Default)] pub struct ReplicationPlugins; impl PluginGroup for ReplicationPlugins { @@ -400,7 +416,7 @@ impl PluginGroup for ReplicationPlugins { PluginGroupBuilder::start::() .add(RepliconCorePlugin) .add(ParentSyncPlugin) - .add(ClientPlugin) + .add(ClientPlugin::default()) .add(ServerPlugin::default()) } } diff --git a/src/replicon_core.rs b/src/replicon_core.rs index ac806026..654b7456 100644 --- a/src/replicon_core.rs +++ b/src/replicon_core.rs @@ -1,10 +1,9 @@ pub mod replication_rules; -use std::cmp::Ordering; - use bevy::prelude::*; use bevy_renet::renet::{ChannelConfig, SendType}; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; use replication_rules::ReplicationRules; @@ -74,11 +73,19 @@ fn channel_configs(channels: &[SendType]) -> Vec { channel_configs } -/// Corresponds to the number of server update. +/// A tick that increments each time we need the server to compute and sends an update. +/// This is mapped to the bevy Tick within [`ServerTicks`]. /// /// See also [`crate::server::TickPolicy`]. -#[derive(Clone, Copy, Default, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub struct NetworkTick(u32); +#[derive(Clone, Copy, Default, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Resource)] +pub struct NetworkTick(pub u32); + +impl std::ops::Deref for NetworkTick { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} impl NetworkTick { /// Creates a new [`NetworkTick`] wrapping the given value. diff --git a/src/replicon_core/replication_rules.rs b/src/replicon_core/replication_rules.rs index 79a3c157..0b65f912 100644 --- a/src/replicon_core/replication_rules.rs +++ b/src/replicon_core/replication_rules.rs @@ -12,6 +12,8 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::client::{ClientMapper, NetworkEntityMap}; +use super::NetworkTick; + pub trait AppReplicationExt { /// Marks component for replication. /// @@ -36,6 +38,17 @@ pub trait AppReplicationExt { ) -> &mut Self where C: Component; + + /// Same as [`Self::replicate`], but uses the specified functions for serialization and deserialization, + /// and uses the specified removal function for removing components from entities. + fn replicate_and_remove_with( + &mut self, + serialize: SerializeFn, + deserialize: DeserializeFn, + remove: RemoveComponentFn, + ) -> &mut Self + where + C: Component; } impl AppReplicationExt for App { @@ -54,6 +67,18 @@ impl AppReplicationExt for App { } fn replicate_with(&mut self, serialize: SerializeFn, deserialize: DeserializeFn) -> &mut Self + where + C: Component, + { + self.replicate_and_remove_with::(serialize, deserialize, remove_component::) + } + + fn replicate_and_remove_with( + &mut self, + serialize: SerializeFn, + deserialize: DeserializeFn, + remove: RemoveComponentFn, + ) -> &mut Self where C: Component, { @@ -63,7 +88,7 @@ impl AppReplicationExt for App { ignored_id, serialize, deserialize, - remove: remove_component::, + remove, }; let mut replication_rules = self.world.resource_mut::(); @@ -89,6 +114,11 @@ pub(crate) struct ReplicationRules { /// ID of [`Replication`] component. marker_id: ComponentId, + + /// Custom function to handle entity despawning. + /// The default does despawn_recursive() – but if you're doing rollback networking you + /// may need to intercept the despawn and do it differently. + despawn_entity_fn: Option, } impl ReplicationRules { @@ -114,6 +144,18 @@ impl ReplicationRules { Some((replication_id, replication_info)) } + /// Gets custom function used to despawn entities, if provided. + #[inline] + pub(crate) fn get_despawn_fn(&self) -> Option { + self.despawn_entity_fn + } + + /// Sets a custom function for despawning entities on the client, when the server reports the despawn; + /// without this, the default implementation uses despawn_recursive(). + pub(crate) fn set_despawn_fn(&mut self, despawn_entity_fn: EntityDespawnFn) { + self.despawn_entity_fn = Some(despawn_entity_fn); + } + /// Returns meta information about replicated component. /// /// # Safety @@ -133,6 +175,7 @@ impl FromWorld for ReplicationRules { infos: Default::default(), ids: Default::default(), marker_id: world.init_component::(), + despawn_entity_fn: Default::default(), } } } @@ -141,8 +184,18 @@ impl FromWorld for ReplicationRules { pub type SerializeFn = fn(Ptr, &mut Cursor>) -> Result<(), bincode::Error>; /// Signature of component deserialization functions. -pub type DeserializeFn = - fn(&mut EntityMut, &mut NetworkEntityMap, &mut Cursor) -> Result<(), bincode::Error>; +pub type DeserializeFn = fn( + &mut EntityMut, + &mut NetworkEntityMap, + &mut Cursor, + NetworkTick, +) -> Result<(), bincode::Error>; + +/// Signature of component removal functions. +pub type RemoveComponentFn = fn(&mut EntityMut, NetworkTick); + +/// Signature of entity despawning functions. +pub type EntityDespawnFn = fn(&mut EntityMut, NetworkTick); /// Stores meta information about replicated component. pub(crate) struct ReplicationInfo { @@ -156,7 +209,7 @@ pub(crate) struct ReplicationInfo { pub(crate) deserialize: DeserializeFn, /// Function that removes specific component from [`EntityMut`]. - pub(crate) remove: fn(&mut EntityMut), + pub(crate) remove: RemoveComponentFn, } /// Marks entity for replication. @@ -192,7 +245,7 @@ pub trait Mapper { } /// Default serialization function. -fn serialize_component( +pub fn serialize_component( component: Ptr, cursor: &mut Cursor>, ) -> Result<(), bincode::Error> { @@ -202,10 +255,11 @@ fn serialize_component( } /// Default deserialization function. -fn deserialize_component( +pub fn deserialize_component( entity: &mut EntityMut, _entity_map: &mut NetworkEntityMap, cursor: &mut Cursor, + _tick: NetworkTick, ) -> Result<(), bincode::Error> { let component: C = DefaultOptions::new().deserialize_from(cursor)?; entity.insert(component); @@ -214,10 +268,11 @@ fn deserialize_component( } /// Like [`deserialize_component`], but also maps entities before insertion. -fn deserialize_mapped_component( +pub fn deserialize_mapped_component( entity: &mut EntityMut, entity_map: &mut NetworkEntityMap, cursor: &mut Cursor, + _tick: NetworkTick, ) -> Result<(), bincode::Error> { let mut component: C = DefaultOptions::new().deserialize_from(cursor)?; @@ -231,6 +286,6 @@ fn deserialize_mapped_component(entity: &mut EntityMut) { +pub fn remove_component(entity: &mut EntityMut, _tick: NetworkTick) { entity.remove::(); } diff --git a/src/server.rs b/src/server.rs index ea44fc67..e339b4a1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -21,7 +21,6 @@ use bevy_renet::{ RenetServerPlugin, }; use bincode::{DefaultOptions, Options}; -use derive_more::Constructor; use crate::replicon_core::{ replication_rules::{ReplicationId, ReplicationInfo, ReplicationRules}, @@ -32,7 +31,6 @@ use removal_tracker::{RemovalTracker, RemovalTrackerPlugin}; pub const SERVER_ID: u64 = 0; -#[derive(Constructor)] pub struct ServerPlugin { tick_policy: TickPolicy, } @@ -54,13 +52,17 @@ impl Plugin for ServerPlugin { DespawnTrackerPlugin, )) .init_resource::() + .init_resource::() .configure_set( PreUpdate, ServerSet::Receive.after(NetcodeServerPlugin::update_system), ) + // sending happens each time the NetworkTick resource changes .configure_set( PostUpdate, - ServerSet::Send.before(NetcodeServerPlugin::send_packets), + ServerSet::Send + .before(NetcodeServerPlugin::send_packets) + .run_if(resource_changed::()), ) .add_systems( PreUpdate, @@ -81,12 +83,26 @@ impl Plugin for ServerPlugin { if let TickPolicy::MaxTickRate(max_tick_rate) = self.tick_policy { let tick_time = Duration::from_millis(1000 / max_tick_rate as u64); - app.configure_set(PostUpdate, ServerSet::Send.run_if(on_timer(tick_time))); + app.add_systems( + PostUpdate, + increment_network_tick + .before(Self::diffs_sending_system) + .run_if(on_timer(tick_time)), + ); } } } +/// calls NetworkTick.increment() which causes the server to send a diff packet this frame +pub fn increment_network_tick(mut network_tick: ResMut) { + network_tick.increment(); +} + impl ServerPlugin { + pub fn new(tick_policy: TickPolicy) -> Self { + Self { tick_policy } + } + fn acks_receiving_system( mut server_ticks: ResMut, mut server: ResMut, @@ -131,11 +147,11 @@ impl ServerPlugin { replication_rules: Res, despawn_tracker: Res, removal_trackers: Query<(Entity, &RemovalTracker)>, + network_tick: Res, ) -> Result<(), bincode::Error> { let mut server_ticks = set.p2(); - server_ticks.increment(change_tick.this_run()); - - let buffers = prepare_buffers(&mut buffers, &server_ticks)?; + server_ticks.register_network_tick(*network_tick, change_tick.this_run()); + let buffers = prepare_buffers(&mut buffers, &server_ticks, *network_tick)?; collect_changes( buffers, set.p0(), @@ -178,6 +194,7 @@ impl ServerPlugin { fn prepare_buffers<'a>( buffers: &'a mut Vec, server_ticks: &ServerTicks, + network_tick: NetworkTick, ) -> Result<&'a mut [ReplicationBuffer], bincode::Error> { buffers.reserve(server_ticks.acked_ticks.len()); for (index, (&client_id, &tick)) in server_ticks.acked_ticks.iter().enumerate() { @@ -187,12 +204,12 @@ fn prepare_buffers<'a>( .unwrap_or(&Tick::new(0)); if let Some(buffer) = buffers.get_mut(index) { - buffer.reset(client_id, system_tick, server_ticks.current_tick)?; + buffer.reset(client_id, system_tick, network_tick)?; } else { buffers.push(ReplicationBuffer::new( client_id, system_tick, - server_ticks.current_tick, + network_tick, )?); } } @@ -384,9 +401,6 @@ pub enum TickPolicy { /// Used only on server. #[derive(Resource, Default)] pub struct ServerTicks { - /// Current server tick. - current_tick: NetworkTick, - /// Last acknowledged server ticks for all clients. acked_ticks: HashMap, @@ -396,9 +410,8 @@ pub struct ServerTicks { impl ServerTicks { /// Increments current tick by 1 and makes corresponding system tick mapping for it. - fn increment(&mut self, system_tick: Tick) { - self.current_tick.increment(); - self.system_ticks.insert(self.current_tick, system_tick); + fn register_network_tick(&mut self, network_tick: NetworkTick, system_tick: Tick) { + self.system_ticks.insert(network_tick, system_tick); } /// Removes system tick mappings for acks that was acknowledged by everyone. @@ -410,12 +423,6 @@ impl ServerTicks { }) } - /// Returns current server tick. - #[inline] - pub fn current_tick(&self) -> NetworkTick { - self.current_tick - } - /// Returns last acknowledged server ticks for all clients. #[inline] pub fn acked_ticks(&self) -> &HashMap { @@ -466,10 +473,10 @@ impl ReplicationBuffer { fn new( client_id: u64, system_tick: Tick, - current_tick: NetworkTick, + network_tick: NetworkTick, ) -> Result { let mut message = Default::default(); - bincode::serialize_into(&mut message, ¤t_tick)?; + bincode::serialize_into(&mut message, &network_tick)?; Ok(Self { client_id, system_tick, @@ -492,14 +499,14 @@ impl ReplicationBuffer { &mut self, client_id: u64, system_tick: Tick, - current_tick: NetworkTick, + network_tick: NetworkTick, ) -> Result<(), bincode::Error> { self.client_id = client_id; self.system_tick = system_tick; self.message.set_position(0); self.message.get_mut().clear(); self.arrays_with_data = 0; - bincode::serialize_into(&mut self.message, ¤t_tick)?; + bincode::serialize_into(&mut self.message, &network_tick)?; Ok(()) } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 7b85944c..cfc712cc 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -20,6 +20,7 @@ use bevy_renet::renet::{ ChannelConfig, ConnectionConfig, RenetClient, RenetServer, }; use bevy_replicon::prelude::*; +use bevy_replicon::server::increment_network_tick; use serde::{ de::{self, DeserializeSeed, SeqAccess, Visitor}, ser::SerializeStruct, @@ -47,7 +48,9 @@ pub(super) fn connect(server_app: &mut App, client_app: &mut App) { server_app .insert_resource(server) - .insert_resource(server_transport); + .insert_resource(server_transport) + // send every tick. have to increment ourselves because of TickPolicy::Manual + .add_systems(Update, increment_network_tick); client_app .insert_resource(client) diff --git a/tests/replication.rs b/tests/replication.rs index 237060ff..f9bd0183 100644 --- a/tests/replication.rs +++ b/tests/replication.rs @@ -1,7 +1,7 @@ mod common; -use bevy::prelude::*; -use bevy_replicon::{prelude::*, server}; +use bevy::{ecs::world::EntityMut, prelude::*}; +use bevy_replicon::{prelude::*, replicon_core::replication_rules, server}; use bevy_renet::renet::transport::NetcodeClientTransport; use serde::{Deserialize, Serialize}; @@ -190,6 +190,63 @@ fn removal_replication() { assert!(client_entity.contains::()); } +/// custom component removal. stores a copy in WrapperComponent and then removes. +fn custom_removal_fn(entity: &mut EntityMut, tick: NetworkTick) { + let oldval: &T = entity.get::().unwrap(); + entity + .insert(WrapperComponent(oldval.clone(), tick)) + .remove::(); +} + +#[test] +fn custom_removal_replication() { + 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, + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + )) + .replicate_and_remove_with::( + replication_rules::serialize_component::, + replication_rules::deserialize_component::, + custom_removal_fn::, + ); + } + + common::connect(&mut server_app, &mut client_app); + + let server_entity = server_app + .world + .spawn((Replication, TableComponent, NonReplicatingComponent)) + .id(); + + server_app.update(); + + server_app + .world + .entity_mut(server_entity) + .remove::(); + + let client_entity = client_app + .world + .spawn((Replication, TableComponent, NonReplicatingComponent)) + .id(); + + client_app + .world + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.update(); + client_app.update(); + + let client_entity = client_app.world.entity(client_entity); + assert!(!client_entity.contains::()); + assert!(client_entity.contains::>()); + assert!(client_entity.contains::()); +} + #[test] fn despawn_replication() { let mut server_app = App::new(); @@ -237,6 +294,58 @@ fn despawn_replication() { assert!(entity_map.to_server().is_empty()); } +#[derive(Component)] +struct DespawnMarker(u32); + +fn custom_despawn_fn(e: &mut EntityMut, tick: NetworkTick) { + e.insert(DespawnMarker(*tick)); +} + +#[test] +fn custom_despawn_replication() { + 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, + ReplicationPlugins + .set(ServerPlugin::new(TickPolicy::Manual)) + .set(ClientPlugin::new(custom_despawn_fn)), + )); + } + + common::connect(&mut server_app, &mut client_app); + + let server_entity = server_app.world.spawn(Replication).id(); + + server_app.update(); + + server_app.world.despawn(server_entity); + + let client_entity = client_app.world.spawn_empty().id(); + + let mut entity_map = client_app.world.resource_mut::(); + entity_map.insert(server_entity, client_entity); + + server_app.update(); + client_app.update(); + + // rather than being despawned, our custom despawn fn will insert a DespawnMarker. + assert!(client_app + .world + .get_entity(client_entity) + .unwrap() + .contains::()); + + // it is correct that the NetworkEntityMap has removed this entity, because once it's + // despawned on the server, it's gone forever. + // even if the client keeps it around for a few frames, the server won't be sending us + // any more updates about it. + let entity_map = client_app.world.resource::(); + assert!(entity_map.to_client().is_empty()); + assert!(entity_map.to_server().is_empty()); +} + #[test] fn replication_into_scene() { let mut app = App::new(); @@ -280,9 +389,12 @@ impl MapNetworkEntities for MappedComponent { } } -#[derive(Component, Deserialize, Serialize)] +#[derive(Component, Deserialize, Serialize, Clone)] struct TableComponent; +#[derive(Component, Deserialize, Serialize)] +struct WrapperComponent(T, NetworkTick); + #[derive(Component, Deserialize, Serialize)] #[component(storage = "SparseSet")] struct SparseSetComponent; diff --git a/tests/server_event.rs b/tests/server_event.rs index 654f4541..c9cc95d3 100644 --- a/tests/server_event.rs +++ b/tests/server_event.rs @@ -218,6 +218,9 @@ fn local_resending() { )) .add_server_event::(SendPolicy::Ordered); + // send every tick. have to increment ourselves because of TickPolicy::Manual + app.add_systems(Update, bevy_replicon::server::increment_network_tick); + const DUMMY_CLIENT_ID: u64 = 1; for (mode, events_count) in [ (SendMode::Broadcast, 1), From 4ae33392556cbf08305db9ee30fbfdf60d0a6424 Mon Sep 17 00:00:00 2001 From: RJ Date: Mon, 2 Oct 2023 19:35:26 +0100 Subject: [PATCH 02/27] remove unused derive_more dep --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index abe6fba7..8b475289 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,6 @@ bevy = { version = "0.11", default-features = false, features = ["bevy_scene"] } bincode = "1.3" serde = "1.0" strum = { version = "0.25", features = ["derive"] } -derive_more = { version = "0.99", default-features = false, features = [ - "constructor", -] } [dev-dependencies] serde_test = "1.0" From 61ba5ed233837092c26d1ffef7ce074a11a02543 Mon Sep 17 00:00:00 2001 From: RJ Date: Mon, 2 Oct 2023 19:53:39 +0100 Subject: [PATCH 03/27] add custom deserialization with NetworkTick test --- tests/replication.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/replication.rs b/tests/replication.rs index f9bd0183..376acdb8 100644 --- a/tests/replication.rs +++ b/tests/replication.rs @@ -1,7 +1,11 @@ mod common; use bevy::{ecs::world::EntityMut, prelude::*}; -use bevy_replicon::{prelude::*, replicon_core::replication_rules, server}; +use bevy_replicon::{ + prelude::*, + replicon_core::replication_rules::{self, serialize_component}, + server, +}; use bevy_renet::renet::transport::NetcodeClientTransport; use serde::{Deserialize, Serialize}; @@ -96,6 +100,22 @@ fn spawn_replication() { fn insert_replication() { let mut server_app = App::new(); let mut client_app = App::new(); + + use bevy_replicon::prelude::*; + fn custom_deserialize( + entity: &mut EntityMut, + _entity_map: &mut NetworkEntityMap, + cursor: &mut std::io::Cursor, + tick: NetworkTick, + ) -> Result<(), bincode::Error> { + let mut component: CustomComponent = + bincode::Options::deserialize_from(bincode::DefaultOptions::new(), cursor)?; + component.0 = tick; + entity.insert(component); + + Ok(()) + } + for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, @@ -104,8 +124,15 @@ fn insert_replication() { .replicate::() .replicate::() .replicate::() + .replicate_with::( + serialize_component::, + custom_deserialize, + ) .replicate_mapped::(); } + // setting this, to verify it was incremented and included in the replication data + // and made available to custom deserializers. + server_app.world.resource_mut::().0 = 99; common::connect(&mut server_app, &mut client_app); @@ -126,6 +153,7 @@ fn insert_replication() { MappedComponent(server_map_entity), IgnoredComponent, Ignored::::default(), + CustomComponent(NetworkTick(0)), )) .id(); @@ -140,6 +168,9 @@ fn insert_replication() { assert!(client_entity.contains::()); assert!(client_entity.contains::()); assert!(!client_entity.contains::()); + assert!(!client_entity.contains::()); + // a positive number of NetworkTick increments should have happened before replication + assert!(*client_entity.get::().unwrap().0 >= 100); assert_eq!( client_entity.get::().unwrap().0, client_map_entity @@ -395,6 +426,9 @@ struct TableComponent; #[derive(Component, Deserialize, Serialize)] struct WrapperComponent(T, NetworkTick); +#[derive(Component, Deserialize, Serialize)] +struct CustomComponent(NetworkTick); + #[derive(Component, Deserialize, Serialize)] #[component(storage = "SparseSet")] struct SparseSetComponent; From bfae493736d0bb18eacef81a2a40f317579fa5a3 Mon Sep 17 00:00:00 2001 From: RJ Date: Mon, 2 Oct 2023 20:02:13 +0100 Subject: [PATCH 04/27] typos and tweaks --- src/client.rs | 7 ++++--- src/lib.rs | 2 +- src/replicon_core.rs | 2 +- src/server.rs | 2 +- tests/replication.rs | 17 +++++++++-------- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/client.rs b/src/client.rs index f821d2fe..75732ff6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -49,10 +49,11 @@ impl Plugin for ClientPlugin { ), ); if let Some(entity_despawn_fn) = self.despawn_fn { + // register a custom entity despawn function app.world - .resource_scope(|_, mut replication_rules: Mut| { - replication_rules.set_despawn_fn(entity_despawn_fn); - }); + .get_resource_mut::() + .unwrap() + .set_despawn_fn(entity_despawn_fn); } } } diff --git a/src/lib.rs b/src/lib.rs index 4467933f..7d3e8c57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,7 +140,7 @@ changes. By default, NetworkTick is incremented in PostUpdate per the [`TickPoli If you set `TickPolicy::Manual`, you can increment [`NetworkTick`] at the start of your `FixedTimestep` game loop. This value can represent your simulation step, and is made available -to the client in the custom deserialization, despawn andcomponent removal functions. +to the client in the custom deserialization, despawn and component removal functions. One use for this is rollback networking: you may want to rollback time and apply the update for the NetworkTick frame, which is in the past, then resimulate. diff --git a/src/replicon_core.rs b/src/replicon_core.rs index 654b7456..02997675 100644 --- a/src/replicon_core.rs +++ b/src/replicon_core.rs @@ -73,7 +73,7 @@ fn channel_configs(channels: &[SendType]) -> Vec { channel_configs } -/// A tick that increments each time we need the server to compute and sends an update. +/// A tick that increments each time we need the server to compute and send an update. /// This is mapped to the bevy Tick within [`ServerTicks`]. /// /// See also [`crate::server::TickPolicy`]. diff --git a/src/server.rs b/src/server.rs index e339b4a1..90af84f4 100644 --- a/src/server.rs +++ b/src/server.rs @@ -409,7 +409,7 @@ pub struct ServerTicks { } impl ServerTicks { - /// Increments current tick by 1 and makes corresponding system tick mapping for it. + /// Stores mapping between network_tick and the current system_tick fn register_network_tick(&mut self, network_tick: NetworkTick, system_tick: Tick) { self.system_ticks.insert(network_tick, system_tick); } diff --git a/tests/replication.rs b/tests/replication.rs index 376acdb8..cae5a1e1 100644 --- a/tests/replication.rs +++ b/tests/replication.rs @@ -221,18 +221,19 @@ fn removal_replication() { assert!(client_entity.contains::()); } -/// custom component removal. stores a copy in WrapperComponent and then removes. -fn custom_removal_fn(entity: &mut EntityMut, tick: NetworkTick) { - let oldval: &T = entity.get::().unwrap(); - entity - .insert(WrapperComponent(oldval.clone(), tick)) - .remove::(); -} - #[test] fn custom_removal_replication() { let mut server_app = App::new(); let mut client_app = App::new(); + + // custom component removal. stores a copy in WrapperComponent and then removes. + fn custom_removal_fn(entity: &mut EntityMut, tick: NetworkTick) { + let oldval: &T = entity.get::().unwrap(); + entity + .insert(WrapperComponent(oldval.clone(), tick)) + .remove::(); + } + for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, From 0076a0a9815f7bac173115a5aacf67e883e7d562 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 22:46:41 +0300 Subject: [PATCH 05/27] Bring derive_more back Already done in #69. --- Cargo.toml | 3 +++ src/server.rs | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8b475289..abe6fba7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,9 @@ bevy = { version = "0.11", default-features = false, features = ["bevy_scene"] } bincode = "1.3" serde = "1.0" strum = { version = "0.25", features = ["derive"] } +derive_more = { version = "0.99", default-features = false, features = [ + "constructor", +] } [dev-dependencies] serde_test = "1.0" diff --git a/src/server.rs b/src/server.rs index 90af84f4..46775417 100644 --- a/src/server.rs +++ b/src/server.rs @@ -21,6 +21,7 @@ use bevy_renet::{ RenetServerPlugin, }; use bincode::{DefaultOptions, Options}; +use derive_more::Constructor; use crate::replicon_core::{ replication_rules::{ReplicationId, ReplicationInfo, ReplicationRules}, @@ -31,6 +32,7 @@ use removal_tracker::{RemovalTracker, RemovalTrackerPlugin}; pub const SERVER_ID: u64 = 0; +#[derive(Constructor)] pub struct ServerPlugin { tick_policy: TickPolicy, } @@ -99,10 +101,6 @@ pub fn increment_network_tick(mut network_tick: ResMut) { } impl ServerPlugin { - pub fn new(tick_policy: TickPolicy) -> Self { - Self { tick_policy } - } - fn acks_receiving_system( mut server_ticks: ResMut, mut server: ResMut, From 001865f20c9557d45659e64bae58d2220bb1d2bd Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 22:47:43 +0300 Subject: [PATCH 06/27] Remove Default for ReplicationPlugins No longer necessary, looks like a leftover from the previous approach. --- examples/tic_tac_toe.rs | 2 +- src/lib.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/tic_tac_toe.rs b/examples/tic_tac_toe.rs index 1177dfdd..c382d208 100644 --- a/examples/tic_tac_toe.rs +++ b/examples/tic_tac_toe.rs @@ -34,7 +34,7 @@ fn main() { }), ..Default::default() })) - .add_plugins((ReplicationPlugins::default(), TicTacToePlugin)) + .add_plugins((ReplicationPlugins, TicTacToePlugin)) .run(); } diff --git a/src/lib.rs b/src/lib.rs index 7d3e8c57..8d383892 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -408,7 +408,6 @@ pub use bincode; use prelude::*; /// Plugin Group for all replicon plugins. -#[derive(Default)] pub struct ReplicationPlugins; impl PluginGroup for ReplicationPlugins { From feeb4638e2060b50dfa589aed2abee76dcea7e87 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 22:50:52 +0300 Subject: [PATCH 07/27] Return `Result>` from `deserialize_tick` --- src/client.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/client.rs b/src/client.rs index 75732ff6..da8c2e37 100644 --- a/src/client.rs +++ b/src/client.rs @@ -75,11 +75,10 @@ impl ClientPlugin { let end_pos: u64 = message.len().try_into().unwrap(); let mut cursor = Cursor::new(message); - let (network_tick, was_updated) = deserialize_tick(&mut cursor, world)?; - - if !was_updated { + let Some(network_tick) = deserialize_tick(&mut cursor, world)? else { continue; - } + }; + if cursor.position() == end_pos { continue; } @@ -137,19 +136,19 @@ impl ClientPlugin { /// Deserializes server tick and applies it to [`LastTick`] if it is newer. /// -/// Returns (network_tick, true) if [`LastTick`] has been updated, otherwise (network_tick, false). +/// Returns the tick if [`LastTick`] has been updated. fn deserialize_tick( cursor: &mut Cursor, world: &mut World, -) -> Result<(NetworkTick, bool), bincode::Error> { +) -> Result, bincode::Error> { let network_tick = bincode::deserialize_from(cursor)?; let mut last_tick = world.resource_mut::(); if last_tick.0 < network_tick { last_tick.0 = network_tick; - Ok((network_tick, true)) + Ok(Some(network_tick)) } else { - Ok((network_tick, false)) + Ok(None) } } From 5c0d5c38437926ee7d5b9beafa1f3881001ebc1c Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 22:51:41 +0300 Subject: [PATCH 08/27] Reorder imports --- src/replicon_core.rs | 3 ++- src/replicon_core/replication_rules.rs | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/replicon_core.rs b/src/replicon_core.rs index 02997675..6a0e8035 100644 --- a/src/replicon_core.rs +++ b/src/replicon_core.rs @@ -1,9 +1,10 @@ pub mod replication_rules; +use std::cmp::Ordering; + use bevy::prelude::*; use bevy_renet::renet::{ChannelConfig, SendType}; use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; use replication_rules::ReplicationRules; diff --git a/src/replicon_core/replication_rules.rs b/src/replicon_core/replication_rules.rs index 0b65f912..a1468924 100644 --- a/src/replicon_core/replication_rules.rs +++ b/src/replicon_core/replication_rules.rs @@ -10,9 +10,8 @@ use bevy_renet::renet::Bytes; use bincode::{DefaultOptions, Options}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use crate::client::{ClientMapper, NetworkEntityMap}; - use super::NetworkTick; +use crate::client::{ClientMapper, NetworkEntityMap}; pub trait AppReplicationExt { /// Marks component for replication. From 7bdae960ebd5d831940d399e699f596c0cceda01 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 22:56:16 +0300 Subject: [PATCH 09/27] Put network tick increment inside `ServerPlugin` for consistency --- src/server.rs | 13 ++++++------- tests/common/mod.rs | 3 +-- tests/server_event.rs | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/server.rs b/src/server.rs index 46775417..eee67320 100644 --- a/src/server.rs +++ b/src/server.rs @@ -59,7 +59,6 @@ impl Plugin for ServerPlugin { PreUpdate, ServerSet::Receive.after(NetcodeServerPlugin::update_system), ) - // sending happens each time the NetworkTick resource changes .configure_set( PostUpdate, ServerSet::Send @@ -87,7 +86,7 @@ impl Plugin for ServerPlugin { let tick_time = Duration::from_millis(1000 / max_tick_rate as u64); app.add_systems( PostUpdate, - increment_network_tick + Self::increment_network_tick .before(Self::diffs_sending_system) .run_if(on_timer(tick_time)), ); @@ -95,12 +94,12 @@ impl Plugin for ServerPlugin { } } -/// calls NetworkTick.increment() which causes the server to send a diff packet this frame -pub fn increment_network_tick(mut network_tick: ResMut) { - network_tick.increment(); -} - impl ServerPlugin { + /// Increments current server tick which causes the server to send a diff packet this frame. + pub fn increment_network_tick(mut network_tick: ResMut) { + network_tick.increment(); + } + fn acks_receiving_system( mut server_ticks: ResMut, mut server: ResMut, diff --git a/tests/common/mod.rs b/tests/common/mod.rs index cfc712cc..347fee38 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -20,7 +20,6 @@ use bevy_renet::renet::{ ChannelConfig, ConnectionConfig, RenetClient, RenetServer, }; use bevy_replicon::prelude::*; -use bevy_replicon::server::increment_network_tick; use serde::{ de::{self, DeserializeSeed, SeqAccess, Visitor}, ser::SerializeStruct, @@ -50,7 +49,7 @@ pub(super) fn connect(server_app: &mut App, client_app: &mut App) { .insert_resource(server) .insert_resource(server_transport) // send every tick. have to increment ourselves because of TickPolicy::Manual - .add_systems(Update, increment_network_tick); + .add_systems(Update, ServerPlugin::increment_network_tick); client_app .insert_resource(client) diff --git a/tests/server_event.rs b/tests/server_event.rs index c9cc95d3..6f432d79 100644 --- a/tests/server_event.rs +++ b/tests/server_event.rs @@ -219,7 +219,7 @@ fn local_resending() { .add_server_event::(SendPolicy::Ordered); // send every tick. have to increment ourselves because of TickPolicy::Manual - app.add_systems(Update, bevy_replicon::server::increment_network_tick); + app.add_systems(Update, ServerPlugin::increment_network_tick); const DUMMY_CLIENT_ID: u64 = 1; for (mode, events_count) in [ From 26287c9fb3ece6e4635825261b2db75f3bd2c5d2 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 23:04:08 +0300 Subject: [PATCH 10/27] Rename `ServerTicks` into `AckedTicks` --- src/lib.rs | 2 +- src/server.rs | 59 +++++++++++++++-------------------- src/server/despawn_tracker.rs | 14 ++++----- src/server/removal_tracker.rs | 14 ++++----- tests/replication.rs | 6 ++-- 5 files changed, 43 insertions(+), 52 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8d383892..089ca61a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -397,7 +397,7 @@ pub mod prelude { }, NetworkChannels, NetworkTick, RepliconCorePlugin, }, - server::{has_authority, ServerPlugin, ServerSet, ServerTicks, TickPolicy, SERVER_ID}, + server::{has_authority, AckedTicks, ServerPlugin, ServerSet, TickPolicy, SERVER_ID}, ReplicationPlugins, }; } diff --git a/src/server.rs b/src/server.rs index eee67320..dac3a275 100644 --- a/src/server.rs +++ b/src/server.rs @@ -53,7 +53,7 @@ impl Plugin for ServerPlugin { RemovalTrackerPlugin, DespawnTrackerPlugin, )) - .init_resource::() + .init_resource::() .init_resource::() .configure_set( PreUpdate, @@ -100,15 +100,12 @@ impl ServerPlugin { network_tick.increment(); } - fn acks_receiving_system( - mut server_ticks: ResMut, - mut server: ResMut, - ) { + fn acks_receiving_system(mut acked_ticks: ResMut, mut server: ResMut) { for client_id in server.clients_id() { while let Some(message) = server.receive_message(client_id, REPLICATION_CHANNEL_ID) { match bincode::deserialize::(&message) { Ok(tick) => { - let acked_tick = server_ticks.acked_ticks.entry(client_id).or_default(); + let acked_tick = acked_ticks.clients.entry(client_id).or_default(); if *acked_tick < tick { *acked_tick = tick; } @@ -118,20 +115,20 @@ impl ServerPlugin { } } - server_ticks.cleanup_system_ticks(); + acked_ticks.cleanup_system_ticks(); } fn acks_cleanup_system( mut server_events: EventReader, - mut server_ticks: ResMut, + mut acked_ticks: ResMut, ) { for event in &mut server_events { match event { ServerEvent::ClientDisconnected { client_id, .. } => { - server_ticks.acked_ticks.remove(client_id); + acked_ticks.clients.remove(client_id); } ServerEvent::ClientConnected { client_id } => { - server_ticks.acked_ticks.entry(*client_id).or_default(); + acked_ticks.clients.entry(*client_id).or_default(); } } } @@ -140,15 +137,15 @@ impl ServerPlugin { fn diffs_sending_system( mut buffers: Local>, change_tick: SystemChangeTick, - mut set: ParamSet<(&World, ResMut, ResMut)>, + mut set: ParamSet<(&World, ResMut, ResMut)>, replication_rules: Res, despawn_tracker: Res, removal_trackers: Query<(Entity, &RemovalTracker)>, network_tick: Res, ) -> Result<(), bincode::Error> { - let mut server_ticks = set.p2(); - server_ticks.register_network_tick(*network_tick, change_tick.this_run()); - let buffers = prepare_buffers(&mut buffers, &server_ticks, *network_tick)?; + let mut acked_ticks = set.p2(); + acked_ticks.register_network_tick(*network_tick, change_tick.this_run()); + let buffers = prepare_buffers(&mut buffers, &acked_ticks, *network_tick)?; collect_changes( buffers, set.p0(), @@ -176,9 +173,9 @@ impl ServerPlugin { Ok(()) } - fn reset_system(mut server_ticks: ResMut) { - server_ticks.acked_ticks.clear(); - server_ticks.system_ticks.clear(); + fn reset_system(mut acked_ticks: ResMut) { + acked_ticks.clients.clear(); + acked_ticks.system_ticks.clear(); } } @@ -190,15 +187,12 @@ impl ServerPlugin { /// and the returned slice will not include them. fn prepare_buffers<'a>( buffers: &'a mut Vec, - server_ticks: &ServerTicks, + acked_ticks: &AckedTicks, network_tick: NetworkTick, ) -> Result<&'a mut [ReplicationBuffer], bincode::Error> { - buffers.reserve(server_ticks.acked_ticks.len()); - for (index, (&client_id, &tick)) in server_ticks.acked_ticks.iter().enumerate() { - let system_tick = *server_ticks - .system_ticks - .get(&tick) - .unwrap_or(&Tick::new(0)); + buffers.reserve(acked_ticks.clients.len()); + for (index, (&client_id, &tick)) in acked_ticks.clients.iter().enumerate() { + let system_tick = *acked_ticks.system_ticks.get(&tick).unwrap_or(&Tick::new(0)); if let Some(buffer) = buffers.get_mut(index) { buffer.reset(client_id, system_tick, network_tick)?; @@ -211,7 +205,7 @@ fn prepare_buffers<'a>( } } - Ok(&mut buffers[..server_ticks.acked_ticks.len()]) + Ok(&mut buffers[..acked_ticks.clients.len()]) } /// Collect component changes into buffers based on last acknowledged tick. @@ -397,15 +391,15 @@ pub enum TickPolicy { /// /// Used only on server. #[derive(Resource, Default)] -pub struct ServerTicks { +pub struct AckedTicks { /// Last acknowledged server ticks for all clients. - acked_ticks: HashMap, + clients: HashMap, /// Stores mapping from server ticks to system change ticks. system_ticks: HashMap, } -impl ServerTicks { +impl AckedTicks { /// Stores mapping between network_tick and the current system_tick fn register_network_tick(&mut self, network_tick: NetworkTick, system_tick: Tick) { self.system_ticks.insert(network_tick, system_tick); @@ -413,17 +407,14 @@ impl ServerTicks { /// Removes system tick mappings for acks that was acknowledged by everyone. fn cleanup_system_ticks(&mut self) { - self.system_ticks.retain(|tick, _| { - self.acked_ticks - .values() - .all(|acked_tick| acked_tick > tick) - }) + self.system_ticks + .retain(|tick, _| self.clients.values().all(|acked_tick| acked_tick > tick)) } /// Returns last acknowledged server ticks for all clients. #[inline] pub fn acked_ticks(&self) -> &HashMap { - &self.acked_ticks + &self.clients } } diff --git a/src/server/despawn_tracker.rs b/src/server/despawn_tracker.rs index 2e58899d..e543a383 100644 --- a/src/server/despawn_tracker.rs +++ b/src/server/despawn_tracker.rs @@ -5,7 +5,7 @@ use bevy::{ }; use bevy_renet::renet::RenetServer; -use super::{ServerSet, ServerTicks}; +use super::{AckedTicks, ServerSet}; use crate::replicon_core::replication_rules::Replication; /// Tracks entity despawns of entities with [`Replication`] component in [`DespawnTracker`] resource. @@ -44,11 +44,11 @@ impl DespawnTrackerPlugin { fn cleanup_system( change_tick: SystemChangeTick, mut despawn_tracker: ResMut, - server_ticks: Res, + acked_ticks: Res, ) { despawn_tracker.despawns.retain(|(_, tick)| { - server_ticks.acked_ticks.values().any(|acked_tick| { - let system_tick = *server_ticks + acked_ticks.clients.values().any(|acked_tick| { + let system_tick = *acked_ticks .system_ticks .get(acked_tick) .unwrap_or(&Tick::new(0)); @@ -95,15 +95,15 @@ mod tests { let mut app = App::new(); app.add_plugins(DespawnTrackerPlugin) .insert_resource(RenetServer::new(Default::default())) - .init_resource::(); + .init_resource::(); app.update(); // To avoid cleanup. const DUMMY_CLIENT_ID: u64 = 0; app.world - .resource_mut::() - .acked_ticks + .resource_mut::() + .clients .insert(DUMMY_CLIENT_ID, NetworkTick::new(0)); let replicated_entity = app.world.spawn(Replication).id(); diff --git a/src/server/removal_tracker.rs b/src/server/removal_tracker.rs index e8ffef5e..5a1790c0 100644 --- a/src/server/removal_tracker.rs +++ b/src/server/removal_tracker.rs @@ -5,7 +5,7 @@ use bevy::{ }; use bevy_renet::renet::RenetServer; -use super::{ServerSet, ServerTicks}; +use super::{AckedTicks, ServerSet}; use crate::replicon_core::replication_rules::{Replication, ReplicationId, ReplicationRules}; /// Stores component removals in [`RemovalTracker`] component to make them persistent across ticks. @@ -41,13 +41,13 @@ impl RemovalTrackerPlugin { /// Cleanups all acknowledged despawns. fn cleanup_system( change_tick: SystemChangeTick, - server_ticks: Res, + acked_ticks: Res, mut removal_trackers: Query<&mut RemovalTracker>, ) { for mut removal_tracker in &mut removal_trackers { removal_tracker.retain(|_, tick| { - server_ticks.acked_ticks.values().any(|acked_tick| { - let system_tick = *server_ticks + acked_ticks.clients.values().any(|acked_tick| { + let system_tick = *acked_ticks .system_ticks .get(acked_tick) .unwrap_or(&Tick::new(0)); @@ -94,7 +94,7 @@ mod tests { let mut app = App::new(); app.add_plugins(RemovalTrackerPlugin) .insert_resource(RenetServer::new(Default::default())) - .init_resource::() + .init_resource::() .init_resource::() .replicate::(); @@ -103,8 +103,8 @@ mod tests { // To avoid cleanup. const DUMMY_CLIENT_ID: u64 = 0; app.world - .resource_mut::() - .acked_ticks + .resource_mut::() + .clients .insert(DUMMY_CLIENT_ID, NetworkTick::new(0)); let replicated_entity = app.world.spawn((DummyComponent, Replication)).id(); diff --git a/tests/replication.rs b/tests/replication.rs index cae5a1e1..2c41d162 100644 --- a/tests/replication.rs +++ b/tests/replication.rs @@ -31,8 +31,8 @@ fn acked_ticks_cleanup() { server_app.update(); server_app.update(); - let server_ticks = server_app.world.resource::(); - assert!(!server_ticks.acked_ticks().contains_key(&client_id)); + let acked_ticks = server_app.world.resource::(); + assert!(!acked_ticks.acked_ticks().contains_key(&client_id)); } #[test] @@ -51,7 +51,7 @@ fn tick_acks_receiving() { client_app.update(); server_app.update(); - let acked_ticks = server_app.world.resource::(); + let acked_ticks = server_app.world.resource::(); let client_id = client_app .world .resource::() From f8413f91233f73f3ff5ed9e038d6567d90c10b39 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 23:08:02 +0300 Subject: [PATCH 11/27] Rename despawn_entity_fn into despawn_fn --- src/replicon_core/replication_rules.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/replicon_core/replication_rules.rs b/src/replicon_core/replication_rules.rs index a1468924..aa81fa98 100644 --- a/src/replicon_core/replication_rules.rs +++ b/src/replicon_core/replication_rules.rs @@ -117,7 +117,7 @@ pub(crate) struct ReplicationRules { /// Custom function to handle entity despawning. /// The default does despawn_recursive() – but if you're doing rollback networking you /// may need to intercept the despawn and do it differently. - despawn_entity_fn: Option, + despawn_fn: Option, } impl ReplicationRules { @@ -146,13 +146,13 @@ impl ReplicationRules { /// Gets custom function used to despawn entities, if provided. #[inline] pub(crate) fn get_despawn_fn(&self) -> Option { - self.despawn_entity_fn + self.despawn_fn } /// Sets a custom function for despawning entities on the client, when the server reports the despawn; /// without this, the default implementation uses despawn_recursive(). - pub(crate) fn set_despawn_fn(&mut self, despawn_entity_fn: EntityDespawnFn) { - self.despawn_entity_fn = Some(despawn_entity_fn); + pub(crate) fn set_despawn_fn(&mut self, despawn_fn: EntityDespawnFn) { + self.despawn_fn = Some(despawn_fn); } /// Returns meta information about replicated component. @@ -174,7 +174,7 @@ impl FromWorld for ReplicationRules { infos: Default::default(), ids: Default::default(), marker_id: world.init_component::(), - despawn_entity_fn: Default::default(), + despawn_fn: Default::default(), } } } From 314f3bc0978194bb30a114f76ddec8da9772c5bd Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 23:24:26 +0300 Subject: [PATCH 12/27] Rework despawn API - Replace `Option` with default `despawn_recursive` and remove custom despawn test since now it's not a special case. - Allow to initialize it inside `ReplicationRules` instead of setting it as part of plugin (I found it a bit confusing). --- src/client.rs | 34 +++-------------- src/lib.rs | 3 +- src/replicon_core/replication_rules.rs | 33 +++++++--------- tests/replication.rs | 52 -------------------------- 4 files changed, 21 insertions(+), 101 deletions(-) diff --git a/src/client.rs b/src/client.rs index da8c2e37..6850a4c5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -10,14 +10,11 @@ use bevy_renet::{renet::RenetClient, transport::NetcodeClientPlugin, RenetClient use bincode::{DefaultOptions, Options}; use crate::replicon_core::{ - replication_rules::{EntityDespawnFn, Mapper, Replication, ReplicationRules}, + replication_rules::{Mapper, Replication, ReplicationRules}, NetworkTick, REPLICATION_CHANNEL_ID, }; -#[derive(Default)] -pub struct ClientPlugin { - despawn_fn: Option, -} +pub struct ClientPlugin; impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { @@ -48,25 +45,10 @@ impl Plugin for ClientPlugin { Self::reset_system.run_if(resource_removed::()), ), ); - if let Some(entity_despawn_fn) = self.despawn_fn { - // register a custom entity despawn function - app.world - .get_resource_mut::() - .unwrap() - .set_despawn_fn(entity_despawn_fn); - } } } impl ClientPlugin { - /// only useful in case you need to replace the default entity despawn function. - /// otherwis just use `ClientPlugin::default()` - pub fn new(despawn_fn: EntityDespawnFn) -> Self { - Self { - despawn_fn: Some(despawn_fn), - } - } - fn diff_receiving_system(world: &mut World) -> Result<(), bincode::Error> { world.resource_scope(|world, mut client: Mut| { world.resource_scope(|world, mut entity_map: Mut| { @@ -111,8 +93,8 @@ impl ClientPlugin { &mut cursor, world, &mut entity_map, + &replication_rules, network_tick, - replication_rules.get_despawn_fn(), )?; } @@ -187,8 +169,8 @@ fn deserialize_despawns( cursor: &mut Cursor, world: &mut World, entity_map: &mut NetworkEntityMap, + replication_rules: &ReplicationRules, tick: NetworkTick, - custom_entity_despawn_fn: Option, ) -> Result<(), bincode::Error> { let entities_count: u16 = bincode::deserialize_from(&mut *cursor)?; for _ in 0..entities_count { @@ -196,15 +178,11 @@ fn deserialize_despawns( // with the last diff, but the server might not yet have received confirmation // from the client and could include the deletion in the latest diff. let server_entity = deserialize_entity(&mut *cursor)?; - if let Some(mut client_entity) = entity_map + if let Some(client_entity) = entity_map .remove_by_server(server_entity) .and_then(|entity| world.get_entity_mut(entity)) { - if let Some(despawn_fn) = custom_entity_despawn_fn { - (despawn_fn)(&mut client_entity, tick); - } else { - client_entity.despawn_recursive(); - } + (replication_rules.despawn_fn)(client_entity, tick); } } diff --git a/src/lib.rs b/src/lib.rs index 089ca61a..0ff8f3d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -394,6 +394,7 @@ pub mod prelude { replicon_core::{ replication_rules::{ AppReplicationExt, Ignored, MapNetworkEntities, Mapper, Replication, + ReplicationRules, }, NetworkChannels, NetworkTick, RepliconCorePlugin, }, @@ -415,7 +416,7 @@ impl PluginGroup for ReplicationPlugins { PluginGroupBuilder::start::() .add(RepliconCorePlugin) .add(ParentSyncPlugin) - .add(ClientPlugin::default()) + .add(ClientPlugin) .add(ServerPlugin::default()) } } diff --git a/src/replicon_core/replication_rules.rs b/src/replicon_core/replication_rules.rs index aa81fa98..310c3c16 100644 --- a/src/replicon_core/replication_rules.rs +++ b/src/replicon_core/replication_rules.rs @@ -104,7 +104,13 @@ impl AppReplicationExt for App { /// /// See also [`replicate_into_scene`]. #[derive(Resource)] -pub(crate) struct ReplicationRules { +pub struct ReplicationRules { + /// Custom function to handle entity despawning. + /// + /// By default uses [`despawn_recursive`]. + /// May need to intercept the despawn and do it differently. + pub despawn_fn: EntityDespawnFn, + /// Maps component IDs to their replication IDs. ids: HashMap, @@ -113,11 +119,6 @@ pub(crate) struct ReplicationRules { /// ID of [`Replication`] component. marker_id: ComponentId, - - /// Custom function to handle entity despawning. - /// The default does despawn_recursive() – but if you're doing rollback networking you - /// may need to intercept the despawn and do it differently. - despawn_fn: Option, } impl ReplicationRules { @@ -143,18 +144,6 @@ impl ReplicationRules { Some((replication_id, replication_info)) } - /// Gets custom function used to despawn entities, if provided. - #[inline] - pub(crate) fn get_despawn_fn(&self) -> Option { - self.despawn_fn - } - - /// Sets a custom function for despawning entities on the client, when the server reports the despawn; - /// without this, the default implementation uses despawn_recursive(). - pub(crate) fn set_despawn_fn(&mut self, despawn_fn: EntityDespawnFn) { - self.despawn_fn = Some(despawn_fn); - } - /// Returns meta information about replicated component. /// /// # Safety @@ -174,7 +163,7 @@ impl FromWorld for ReplicationRules { infos: Default::default(), ids: Default::default(), marker_id: world.init_component::(), - despawn_fn: Default::default(), + despawn_fn: despawn_recursive, } } } @@ -194,7 +183,7 @@ pub type DeserializeFn = fn( pub type RemoveComponentFn = fn(&mut EntityMut, NetworkTick); /// Signature of entity despawning functions. -pub type EntityDespawnFn = fn(&mut EntityMut, NetworkTick); +pub type EntityDespawnFn = fn(EntityMut, NetworkTick); /// Stores meta information about replicated component. pub(crate) struct ReplicationInfo { @@ -288,3 +277,7 @@ pub fn deserialize_mapped_component(entity: &mut EntityMut, _tick: NetworkTick) { entity.remove::(); } + +pub fn despawn_recursive(entity: EntityMut, _tick: NetworkTick) { + entity.despawn_recursive(); +} diff --git a/tests/replication.rs b/tests/replication.rs index 2c41d162..4f3dd866 100644 --- a/tests/replication.rs +++ b/tests/replication.rs @@ -326,58 +326,6 @@ fn despawn_replication() { assert!(entity_map.to_server().is_empty()); } -#[derive(Component)] -struct DespawnMarker(u32); - -fn custom_despawn_fn(e: &mut EntityMut, tick: NetworkTick) { - e.insert(DespawnMarker(*tick)); -} - -#[test] -fn custom_despawn_replication() { - 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, - ReplicationPlugins - .set(ServerPlugin::new(TickPolicy::Manual)) - .set(ClientPlugin::new(custom_despawn_fn)), - )); - } - - common::connect(&mut server_app, &mut client_app); - - let server_entity = server_app.world.spawn(Replication).id(); - - server_app.update(); - - server_app.world.despawn(server_entity); - - let client_entity = client_app.world.spawn_empty().id(); - - let mut entity_map = client_app.world.resource_mut::(); - entity_map.insert(server_entity, client_entity); - - server_app.update(); - client_app.update(); - - // rather than being despawned, our custom despawn fn will insert a DespawnMarker. - assert!(client_app - .world - .get_entity(client_entity) - .unwrap() - .contains::()); - - // it is correct that the NetworkEntityMap has removed this entity, because once it's - // despawned on the server, it's gone forever. - // even if the client keeps it around for a few frames, the server won't be sending us - // any more updates about it. - let entity_map = client_app.world.resource::(); - assert!(entity_map.to_client().is_empty()); - assert!(entity_map.to_server().is_empty()); -} - #[test] fn replication_into_scene() { let mut app = App::new(); From 2dc568faa9ba7cfd34c306b717db4b313d73ad25 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 23:27:44 +0300 Subject: [PATCH 13/27] Use singular Since we have only one such function. --- src/replicon_core/replication_rules.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/replicon_core/replication_rules.rs b/src/replicon_core/replication_rules.rs index 310c3c16..991b5ad2 100644 --- a/src/replicon_core/replication_rules.rs +++ b/src/replicon_core/replication_rules.rs @@ -182,7 +182,7 @@ pub type DeserializeFn = fn( /// Signature of component removal functions. pub type RemoveComponentFn = fn(&mut EntityMut, NetworkTick); -/// Signature of entity despawning functions. +/// Signature of the entity despawn function. pub type EntityDespawnFn = fn(EntityMut, NetworkTick); /// Stores meta information about replicated component. From b50fc317a89a004d0dd3557e7e1e73a1e70d00db Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 23:31:20 +0300 Subject: [PATCH 14/27] Add removal function to `replicate_with` and remove helpers I would prefer `replicate_with` to have all parameters. --- src/replicon_core/replication_rules.rs | 33 ++++++++++---------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/replicon_core/replication_rules.rs b/src/replicon_core/replication_rules.rs index 991b5ad2..79c25679 100644 --- a/src/replicon_core/replication_rules.rs +++ b/src/replicon_core/replication_rules.rs @@ -29,21 +29,11 @@ pub trait AppReplicationExt { where C: Component + Serialize + DeserializeOwned + MapNetworkEntities; - /// Same as [`Self::replicate`], but uses the specified functions for serialization and deserialization. + /// Same as [`Self::replicate`], but uses the specified functions for serialization, deserialization and removal. fn replicate_with( &mut self, serialize: SerializeFn, deserialize: DeserializeFn, - ) -> &mut Self - where - C: Component; - - /// Same as [`Self::replicate`], but uses the specified functions for serialization and deserialization, - /// and uses the specified removal function for removing components from entities. - fn replicate_and_remove_with( - &mut self, - serialize: SerializeFn, - deserialize: DeserializeFn, remove: RemoveComponentFn, ) -> &mut Self where @@ -55,24 +45,25 @@ impl AppReplicationExt for App { where C: Component + Serialize + DeserializeOwned, { - self.replicate_with::(serialize_component::, deserialize_component::) + self.replicate_with::( + serialize_component::, + deserialize_component::, + remove_component::, + ) } fn replicate_mapped(&mut self) -> &mut Self where C: Component + Serialize + DeserializeOwned + MapNetworkEntities, { - self.replicate_with::(serialize_component::, deserialize_mapped_component::) + self.replicate_with::( + serialize_component::, + deserialize_mapped_component::, + remove_component::, + ) } - fn replicate_with(&mut self, serialize: SerializeFn, deserialize: DeserializeFn) -> &mut Self - where - C: Component, - { - self.replicate_and_remove_with::(serialize, deserialize, remove_component::) - } - - fn replicate_and_remove_with( + fn replicate_with( &mut self, serialize: SerializeFn, deserialize: DeserializeFn, From d4a6a7d436d10b0749482294dd560f65c5166327 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 23:35:29 +0300 Subject: [PATCH 15/27] Remove tests for custom removals I think that this should be a part of third-party crate that implements it. Using custom functions will just work, I don't see much value in testing them. --- tests/replication.rs | 99 ++------------------------------------------ 1 file changed, 3 insertions(+), 96 deletions(-) diff --git a/tests/replication.rs b/tests/replication.rs index 4f3dd866..2a2f239d 100644 --- a/tests/replication.rs +++ b/tests/replication.rs @@ -1,11 +1,7 @@ mod common; -use bevy::{ecs::world::EntityMut, prelude::*}; -use bevy_replicon::{ - prelude::*, - replicon_core::replication_rules::{self, serialize_component}, - server, -}; +use bevy::prelude::*; +use bevy_replicon::{prelude::*, server}; use bevy_renet::renet::transport::NetcodeClientTransport; use serde::{Deserialize, Serialize}; @@ -101,21 +97,6 @@ fn insert_replication() { let mut server_app = App::new(); let mut client_app = App::new(); - use bevy_replicon::prelude::*; - fn custom_deserialize( - entity: &mut EntityMut, - _entity_map: &mut NetworkEntityMap, - cursor: &mut std::io::Cursor, - tick: NetworkTick, - ) -> Result<(), bincode::Error> { - let mut component: CustomComponent = - bincode::Options::deserialize_from(bincode::DefaultOptions::new(), cursor)?; - component.0 = tick; - entity.insert(component); - - Ok(()) - } - for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, @@ -124,15 +105,8 @@ fn insert_replication() { .replicate::() .replicate::() .replicate::() - .replicate_with::( - serialize_component::, - custom_deserialize, - ) .replicate_mapped::(); } - // setting this, to verify it was incremented and included in the replication data - // and made available to custom deserializers. - server_app.world.resource_mut::().0 = 99; common::connect(&mut server_app, &mut client_app); @@ -153,7 +127,6 @@ fn insert_replication() { MappedComponent(server_map_entity), IgnoredComponent, Ignored::::default(), - CustomComponent(NetworkTick(0)), )) .id(); @@ -169,8 +142,6 @@ fn insert_replication() { assert!(client_entity.contains::()); assert!(!client_entity.contains::()); assert!(!client_entity.contains::()); - // a positive number of NetworkTick increments should have happened before replication - assert!(*client_entity.get::().unwrap().0 >= 100); assert_eq!( client_entity.get::().unwrap().0, client_map_entity @@ -221,64 +192,6 @@ fn removal_replication() { assert!(client_entity.contains::()); } -#[test] -fn custom_removal_replication() { - let mut server_app = App::new(); - let mut client_app = App::new(); - - // custom component removal. stores a copy in WrapperComponent and then removes. - fn custom_removal_fn(entity: &mut EntityMut, tick: NetworkTick) { - let oldval: &T = entity.get::().unwrap(); - entity - .insert(WrapperComponent(oldval.clone(), tick)) - .remove::(); - } - - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), - )) - .replicate_and_remove_with::( - replication_rules::serialize_component::, - replication_rules::deserialize_component::, - custom_removal_fn::, - ); - } - - common::connect(&mut server_app, &mut client_app); - - let server_entity = server_app - .world - .spawn((Replication, TableComponent, NonReplicatingComponent)) - .id(); - - server_app.update(); - - server_app - .world - .entity_mut(server_entity) - .remove::(); - - let client_entity = client_app - .world - .spawn((Replication, TableComponent, NonReplicatingComponent)) - .id(); - - client_app - .world - .resource_mut::() - .insert(server_entity, client_entity); - - server_app.update(); - client_app.update(); - - let client_entity = client_app.world.entity(client_entity); - assert!(!client_entity.contains::()); - assert!(client_entity.contains::>()); - assert!(client_entity.contains::()); -} - #[test] fn despawn_replication() { let mut server_app = App::new(); @@ -369,14 +282,8 @@ impl MapNetworkEntities for MappedComponent { } } -#[derive(Component, Deserialize, Serialize, Clone)] -struct TableComponent; - #[derive(Component, Deserialize, Serialize)] -struct WrapperComponent(T, NetworkTick); - -#[derive(Component, Deserialize, Serialize)] -struct CustomComponent(NetworkTick); +struct TableComponent; #[derive(Component, Deserialize, Serialize)] #[component(storage = "SparseSet")] From 6f818c0544d0d684418f07c467133c6ec28bbd76 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 23:42:40 +0300 Subject: [PATCH 16/27] Use dedicated newtype resource for current tick I also removed public access from the tick value. You still can get its value or create a new one. Similar to how it's done for Bevy's `Tick`. --- src/lib.rs | 4 +++- src/replicon_core.rs | 11 ++--------- src/server.rs | 16 ++++++++++------ 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0ff8f3d0..749fee51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -398,7 +398,9 @@ pub mod prelude { }, NetworkChannels, NetworkTick, RepliconCorePlugin, }, - server::{has_authority, AckedTicks, ServerPlugin, ServerSet, TickPolicy, SERVER_ID}, + server::{ + has_authority, AckedTicks, CurrentTick, ServerPlugin, ServerSet, TickPolicy, SERVER_ID, + }, ReplicationPlugins, }; } diff --git a/src/replicon_core.rs b/src/replicon_core.rs index 6a0e8035..3fb8c9a2 100644 --- a/src/replicon_core.rs +++ b/src/replicon_core.rs @@ -78,15 +78,8 @@ fn channel_configs(channels: &[SendType]) -> Vec { /// This is mapped to the bevy Tick within [`ServerTicks`]. /// /// See also [`crate::server::TickPolicy`]. -#[derive(Clone, Copy, Default, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Resource)] -pub struct NetworkTick(pub u32); - -impl std::ops::Deref for NetworkTick { - type Target = u32; - fn deref(&self) -> &Self::Target { - &self.0 - } -} +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct NetworkTick(u32); impl NetworkTick { /// Creates a new [`NetworkTick`] wrapping the given value. diff --git a/src/server.rs b/src/server.rs index dac3a275..0f98089b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -54,7 +54,7 @@ impl Plugin for ServerPlugin { DespawnTrackerPlugin, )) .init_resource::() - .init_resource::() + .init_resource::() .configure_set( PreUpdate, ServerSet::Receive.after(NetcodeServerPlugin::update_system), @@ -63,7 +63,7 @@ impl Plugin for ServerPlugin { PostUpdate, ServerSet::Send .before(NetcodeServerPlugin::send_packets) - .run_if(resource_changed::()), + .run_if(resource_changed::()), ) .add_systems( PreUpdate, @@ -96,7 +96,7 @@ impl Plugin for ServerPlugin { impl ServerPlugin { /// Increments current server tick which causes the server to send a diff packet this frame. - pub fn increment_network_tick(mut network_tick: ResMut) { + pub fn increment_network_tick(mut network_tick: ResMut) { network_tick.increment(); } @@ -140,12 +140,12 @@ impl ServerPlugin { mut set: ParamSet<(&World, ResMut, ResMut)>, replication_rules: Res, despawn_tracker: Res, + current_tick: Res, removal_trackers: Query<(Entity, &RemovalTracker)>, - network_tick: Res, ) -> Result<(), bincode::Error> { let mut acked_ticks = set.p2(); - acked_ticks.register_network_tick(*network_tick, change_tick.this_run()); - let buffers = prepare_buffers(&mut buffers, &acked_ticks, *network_tick)?; + acked_ticks.register_network_tick(**current_tick, change_tick.this_run()); + let buffers = prepare_buffers(&mut buffers, &acked_ticks, **current_tick)?; collect_changes( buffers, set.p0(), @@ -387,6 +387,10 @@ pub enum TickPolicy { Manual, } +/// Stores current server tick. +#[derive(Default, Deref, DerefMut, Resource)] +pub struct CurrentTick(NetworkTick); + /// Stores information about ticks. /// /// Used only on server. From 11ce5568c1df232c71108e427d05c1060ffa885f Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 2 Oct 2023 23:52:02 +0300 Subject: [PATCH 17/27] Update docs --- src/lib.rs | 8 ++++---- src/replicon_core.rs | 2 +- src/replicon_core/replication_rules.rs | 2 -- src/server.rs | 4 ++-- tests/common/mod.rs | 3 +-- tests/server_event.rs | 6 ++---- 6 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 749fee51..cec4b592 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,15 +135,15 @@ you can insert [`Ignored`] component and replication will be skipped for `T`. ### NetworkTick, and fixed timestep games. -The [`ServerPlugin`] sends replication data in `PostUpdate` any time the [`NetworkTick`] resource -changes. By default, NetworkTick is incremented in PostUpdate per the [`TickPolicy`]. +The [`ServerPlugin`] sends replication data in `PostUpdate` any time the [`CurrentTick`] resource +changes. By default, its incremented in `PostUpdate` per the [`TickPolicy`]. -If you set `TickPolicy::Manual`, you can increment [`NetworkTick`] at the start of your +If you set [`TickPolicy::Manual`], you can increment [`CurrentTick`] at the start of your `FixedTimestep` game loop. This value can represent your simulation step, and is made available to the client in the custom deserialization, despawn and component removal functions. One use for this is rollback networking: you may want to rollback time and apply the update -for the NetworkTick frame, which is in the past, then resimulate. +for the tick frame, which is in the past, then resimulate. ### "Blueprints" pattern diff --git a/src/replicon_core.rs b/src/replicon_core.rs index 3fb8c9a2..29624655 100644 --- a/src/replicon_core.rs +++ b/src/replicon_core.rs @@ -75,7 +75,7 @@ fn channel_configs(channels: &[SendType]) -> Vec { } /// A tick that increments each time we need the server to compute and send an update. -/// This is mapped to the bevy Tick within [`ServerTicks`]. +/// This is mapped to the bevy Tick in [`crate::server::AckedTicks`]. /// /// See also [`crate::server::TickPolicy`]. #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] diff --git a/src/replicon_core/replication_rules.rs b/src/replicon_core/replication_rules.rs index 79c25679..5c3838e2 100644 --- a/src/replicon_core/replication_rules.rs +++ b/src/replicon_core/replication_rules.rs @@ -92,8 +92,6 @@ impl AppReplicationExt for App { } /// Stores information about which components will be serialized and how. -/// -/// See also [`replicate_into_scene`]. #[derive(Resource)] pub struct ReplicationRules { /// Custom function to handle entity despawning. diff --git a/src/server.rs b/src/server.rs index 0f98089b..84bb7324 100644 --- a/src/server.rs +++ b/src/server.rs @@ -387,7 +387,7 @@ pub enum TickPolicy { Manual, } -/// Stores current server tick. +/// Stores current server [`NetworkTick`]. #[derive(Default, Deref, DerefMut, Resource)] pub struct CurrentTick(NetworkTick); @@ -404,7 +404,7 @@ pub struct AckedTicks { } impl AckedTicks { - /// Stores mapping between network_tick and the current system_tick + /// Stores mapping between `network_tick` and the current `system_tick`. fn register_network_tick(&mut self, network_tick: NetworkTick, system_tick: Tick) { self.system_ticks.insert(network_tick, system_tick); } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 347fee38..9eb3e9a9 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -48,8 +48,7 @@ pub(super) fn connect(server_app: &mut App, client_app: &mut App) { server_app .insert_resource(server) .insert_resource(server_transport) - // send every tick. have to increment ourselves because of TickPolicy::Manual - .add_systems(Update, ServerPlugin::increment_network_tick); + .add_systems(Update, ServerPlugin::increment_network_tick); // Increment tick every frame per `TickPolicy::Manual`. client_app .insert_resource(client) diff --git a/tests/server_event.rs b/tests/server_event.rs index 6f432d79..373f6cda 100644 --- a/tests/server_event.rs +++ b/tests/server_event.rs @@ -216,10 +216,8 @@ fn local_resending() { TimePlugin, ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), )) - .add_server_event::(SendPolicy::Ordered); - - // send every tick. have to increment ourselves because of TickPolicy::Manual - app.add_systems(Update, ServerPlugin::increment_network_tick); + .add_server_event::(SendPolicy::Ordered) + .add_systems(Update, ServerPlugin::increment_network_tick); // Increment tick every frame per `TickPolicy::Manual`. const DUMMY_CLIENT_ID: u64 = 1; for (mode, events_count) in [ From 44ef2278d587ee7e266c8b1c17ae2dd12b644506 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Tue, 3 Oct 2023 00:02:44 +0300 Subject: [PATCH 18/27] Reduce diffs --- src/client.rs | 9 ++++----- src/server.rs | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client.rs b/src/client.rs index 6850a4c5..eac5b650 100644 --- a/src/client.rs +++ b/src/client.rs @@ -60,7 +60,6 @@ impl ClientPlugin { let Some(network_tick) = deserialize_tick(&mut cursor, world)? else { continue; }; - if cursor.position() == end_pos { continue; } @@ -123,12 +122,12 @@ fn deserialize_tick( cursor: &mut Cursor, world: &mut World, ) -> Result, bincode::Error> { - let network_tick = bincode::deserialize_from(cursor)?; + let tick = bincode::deserialize_from(cursor)?; let mut last_tick = world.resource_mut::(); - if last_tick.0 < network_tick { - last_tick.0 = network_tick; - Ok(Some(network_tick)) + if last_tick.0 < tick { + last_tick.0 = tick; + Ok(Some(tick)) } else { Ok(None) } diff --git a/src/server.rs b/src/server.rs index ee42e46e..8758046f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -145,6 +145,7 @@ impl ServerPlugin { ) -> Result<(), bincode::Error> { let mut acked_ticks = set.p2(); acked_ticks.register_network_tick(**current_tick, change_tick.this_run()); + let buffers = prepare_buffers(&mut buffers, &acked_ticks, **current_tick)?; collect_changes( buffers, From 9e22d9c48c94622640d96bf5917b1f6168f16f66 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Tue, 3 Oct 2023 00:02:50 +0300 Subject: [PATCH 19/27] Better docs --- src/replicon_core/replication_rules.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/replicon_core/replication_rules.rs b/src/replicon_core/replication_rules.rs index 5c3838e2..ef7c5355 100644 --- a/src/replicon_core/replication_rules.rs +++ b/src/replicon_core/replication_rules.rs @@ -262,11 +262,12 @@ pub fn deserialize_mapped_component(entity: &mut EntityMut, _tick: NetworkTick) { entity.remove::(); } +/// Default entity despawn function. pub fn despawn_recursive(entity: EntityMut, _tick: NetworkTick) { entity.despawn_recursive(); } From f68b2a2306b055f04d5a0d4d32701660c907e9ea Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Tue, 3 Oct 2023 00:08:48 +0300 Subject: [PATCH 20/27] Use more consistent naming --- src/server.rs | 6 +++--- tests/common/mod.rs | 2 +- tests/server_event.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/server.rs b/src/server.rs index 8758046f..2eaa0758 100644 --- a/src/server.rs +++ b/src/server.rs @@ -86,7 +86,7 @@ impl Plugin for ServerPlugin { let tick_time = Duration::from_millis(1000 / max_tick_rate as u64); app.add_systems( PostUpdate, - Self::increment_network_tick + Self::increment_tick .before(Self::diffs_sending_system) .run_if(on_timer(tick_time)), ); @@ -96,8 +96,8 @@ impl Plugin for ServerPlugin { impl ServerPlugin { /// Increments current server tick which causes the server to send a diff packet this frame. - pub fn increment_network_tick(mut network_tick: ResMut) { - network_tick.increment(); + pub fn increment_tick(mut current_tick: ResMut) { + current_tick.increment(); } fn acks_receiving_system(mut acked_ticks: ResMut, mut server: ResMut) { diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 9eb3e9a9..35006038 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -48,7 +48,7 @@ pub(super) fn connect(server_app: &mut App, client_app: &mut App) { server_app .insert_resource(server) .insert_resource(server_transport) - .add_systems(Update, ServerPlugin::increment_network_tick); // Increment tick every frame per `TickPolicy::Manual`. + .add_systems(Update, ServerPlugin::increment_tick); // Increment tick every frame per `TickPolicy::Manual`. client_app .insert_resource(client) diff --git a/tests/server_event.rs b/tests/server_event.rs index 373f6cda..5549d806 100644 --- a/tests/server_event.rs +++ b/tests/server_event.rs @@ -217,7 +217,7 @@ fn local_resending() { ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), )) .add_server_event::(SendPolicy::Ordered) - .add_systems(Update, ServerPlugin::increment_network_tick); // Increment tick every frame per `TickPolicy::Manual`. + .add_systems(Update, ServerPlugin::increment_tick); // Increment tick every frame per `TickPolicy::Manual`. const DUMMY_CLIENT_ID: u64 = 1; for (mode, events_count) in [ From ef5b6642c53acafe9f66023e2ad40562990459b5 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Tue, 3 Oct 2023 00:17:45 +0300 Subject: [PATCH 21/27] Use `NetworkTick` as resource directly --- src/lib.rs | 8 +++----- src/replicon_core.rs | 2 +- src/server.rs | 18 +++++++----------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cec4b592..6e3bec34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,10 +135,10 @@ you can insert [`Ignored`] component and replication will be skipped for `T`. ### NetworkTick, and fixed timestep games. -The [`ServerPlugin`] sends replication data in `PostUpdate` any time the [`CurrentTick`] resource +The [`ServerPlugin`] sends replication data in `PostUpdate` any time the [`NetworkTick`] resource changes. By default, its incremented in `PostUpdate` per the [`TickPolicy`]. -If you set [`TickPolicy::Manual`], you can increment [`CurrentTick`] at the start of your +If you set [`TickPolicy::Manual`], you can increment [`NetworkTick`] at the start of your `FixedTimestep` game loop. This value can represent your simulation step, and is made available to the client in the custom deserialization, despawn and component removal functions. @@ -398,9 +398,7 @@ pub mod prelude { }, NetworkChannels, NetworkTick, RepliconCorePlugin, }, - server::{ - has_authority, AckedTicks, CurrentTick, ServerPlugin, ServerSet, TickPolicy, SERVER_ID, - }, + server::{has_authority, AckedTicks, ServerPlugin, ServerSet, TickPolicy, SERVER_ID}, ReplicationPlugins, }; } diff --git a/src/replicon_core.rs b/src/replicon_core.rs index d82156f1..ac592f8b 100644 --- a/src/replicon_core.rs +++ b/src/replicon_core.rs @@ -78,7 +78,7 @@ fn channel_configs(channels: &[SendType]) -> Vec { /// This is mapped to the bevy Tick in [`crate::server::AckedTicks`]. /// /// See also [`crate::server::TickPolicy`]. -#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Resource, Serialize)] pub struct NetworkTick(u32); impl NetworkTick { diff --git a/src/server.rs b/src/server.rs index 93b01300..2a98aead 100644 --- a/src/server.rs +++ b/src/server.rs @@ -52,7 +52,7 @@ impl Plugin for ServerPlugin { DespawnTrackerPlugin, )) .init_resource::() - .init_resource::() + .init_resource::() .configure_set( PreUpdate, ServerSet::Receive.after(NetcodeServerPlugin::update_system), @@ -61,7 +61,7 @@ impl Plugin for ServerPlugin { PostUpdate, ServerSet::Send .before(NetcodeServerPlugin::send_packets) - .run_if(resource_changed::()), + .run_if(resource_changed::()), ) .add_systems( PreUpdate, @@ -98,8 +98,8 @@ impl ServerPlugin { } /// Increments current server tick which causes the server to send a diff packet this frame. - pub fn increment_tick(mut current_tick: ResMut) { - current_tick.increment(); + pub fn increment_tick(mut network_tick: ResMut) { + network_tick.increment(); } fn acks_receiving_system(mut acked_ticks: ResMut, mut server: ResMut) { @@ -143,13 +143,13 @@ impl ServerPlugin { mut set: ParamSet<(&World, ResMut, ResMut)>, replication_rules: Res, despawn_tracker: Res, - current_tick: Res, + network_tick: Res, removal_trackers: Query<(Entity, &RemovalTracker)>, ) -> Result<(), bincode::Error> { let mut acked_ticks = set.p2(); - acked_ticks.register_network_tick(**current_tick, change_tick.this_run()); + acked_ticks.register_network_tick(*network_tick, change_tick.this_run()); - let buffers = prepare_buffers(&mut buffers, &acked_ticks, **current_tick)?; + let buffers = prepare_buffers(&mut buffers, &acked_ticks, *network_tick)?; collect_changes( buffers, set.p0(), @@ -391,10 +391,6 @@ pub enum TickPolicy { Manual, } -/// Stores current server [`NetworkTick`]. -#[derive(Default, Deref, DerefMut, Resource)] -pub struct CurrentTick(NetworkTick); - /// Stores information about ticks. /// /// Used only on server. From 18b8bc485dd3db90cb8b266ff05448ef2671451f Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Tue, 3 Oct 2023 00:21:48 +0300 Subject: [PATCH 22/27] Fix doctests --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6e3bec34..05daac58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,11 +95,11 @@ you can use [`AppReplicationExt::replicate_with`]: ```rust # use std::io::Cursor; # use bevy::{ecs::world::EntityMut, prelude::*, ptr::Ptr, utils::HashMap}; -# use bevy_replicon::{prelude::*, renet::Bytes}; +# use bevy_replicon::{prelude::*, renet::Bytes, replicon_core::replicaiton_rules}; # use serde::{Deserialize, Serialize}; # let mut app = App::new(); # app.add_plugins(ReplicationPlugins); -app.replicate_with::(serialize_transform, deserialize_transform); +app.replicate_with::(serialize_transform, deserialize_transform, replicaiton_rules::remove_component); /// Serializes only translation. fn serialize_transform( @@ -161,11 +161,11 @@ your initialization systems to [`ClientSet::Receive`]: ```rust # use std::io::Cursor; # use bevy::{ecs::world::EntityMut, prelude::*, ptr::Ptr, utils::HashMap}; -# use bevy_replicon::{prelude::*, renet::Bytes}; +# use bevy_replicon::{prelude::*, renet::Bytes, replicon_core::replicaiton_rules}; # use serde::{Deserialize, Serialize}; # let mut app = App::new(); # app.add_plugins(ReplicationPlugins); -app.replicate_with::(serialize_transform, deserialize_transform) +app.replicate_with::(serialize_transform, deserialize_transform, replicaiton_rules::remove_component) .replicate::() .add_systems(PreUpdate, player_init_system.after(ClientSet::Receive)); From bb9d25eb4118858b8a2e7379f1ecf40cdfa5c629 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Tue, 3 Oct 2023 00:22:02 +0300 Subject: [PATCH 23/27] Fix formatting --- src/server.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server.rs b/src/server.rs index 2a98aead..e75e8b95 100644 --- a/src/server.rs +++ b/src/server.rs @@ -103,7 +103,6 @@ impl ServerPlugin { } fn acks_receiving_system(mut acked_ticks: ResMut, mut server: ResMut) { - for client_id in server.clients_id() { while let Some(message) = server.receive_message(client_id, REPLICATION_CHANNEL_ID) { match bincode::deserialize::(&message) { From 907cfc272b98b8b04d8569402fcec52d056acc3e Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Tue, 3 Oct 2023 00:37:56 +0300 Subject: [PATCH 24/27] Fix typo --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 05daac58..43e52862 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,11 +95,11 @@ you can use [`AppReplicationExt::replicate_with`]: ```rust # use std::io::Cursor; # use bevy::{ecs::world::EntityMut, prelude::*, ptr::Ptr, utils::HashMap}; -# use bevy_replicon::{prelude::*, renet::Bytes, replicon_core::replicaiton_rules}; +# use bevy_replicon::{prelude::*, renet::Bytes, replicon_core::replication_rules}; # use serde::{Deserialize, Serialize}; # let mut app = App::new(); # app.add_plugins(ReplicationPlugins); -app.replicate_with::(serialize_transform, deserialize_transform, replicaiton_rules::remove_component); +app.replicate_with::(serialize_transform, deserialize_transform, replication_rules::remove_component); /// Serializes only translation. fn serialize_transform( @@ -161,11 +161,11 @@ your initialization systems to [`ClientSet::Receive`]: ```rust # use std::io::Cursor; # use bevy::{ecs::world::EntityMut, prelude::*, ptr::Ptr, utils::HashMap}; -# use bevy_replicon::{prelude::*, renet::Bytes, replicon_core::replicaiton_rules}; +# use bevy_replicon::{prelude::*, renet::Bytes, replicon_core::replication_rules}; # use serde::{Deserialize, Serialize}; # let mut app = App::new(); # app.add_plugins(ReplicationPlugins); -app.replicate_with::(serialize_transform, deserialize_transform, replicaiton_rules::remove_component) +app.replicate_with::(serialize_transform, deserialize_transform, replication_rules::remove_component) .replicate::() .add_systems(PreUpdate, player_init_system.after(ClientSet::Receive)); From 9c5c7c60c95e25d60bc7f9a96a3b29f734908305 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Tue, 3 Oct 2023 00:44:13 +0300 Subject: [PATCH 25/27] Fix doctests --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 43e52862..73ab5cbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,7 +99,7 @@ you can use [`AppReplicationExt::replicate_with`]: # use serde::{Deserialize, Serialize}; # let mut app = App::new(); # app.add_plugins(ReplicationPlugins); -app.replicate_with::(serialize_transform, deserialize_transform, replication_rules::remove_component); +app.replicate_with::(serialize_transform, deserialize_transform, replication_rules::remove_component::); /// Serializes only translation. fn serialize_transform( @@ -165,7 +165,7 @@ your initialization systems to [`ClientSet::Receive`]: # use serde::{Deserialize, Serialize}; # let mut app = App::new(); # app.add_plugins(ReplicationPlugins); -app.replicate_with::(serialize_transform, deserialize_transform, replication_rules::remove_component) +app.replicate_with::(serialize_transform, deserialize_transform, replication_rules::remove_component::) .replicate::() .add_systems(PreUpdate, player_init_system.after(ClientSet::Receive)); From 108c81751a509a1dae8bef2330f666305fe5b8d3 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Tue, 3 Oct 2023 01:40:25 +0300 Subject: [PATCH 26/27] Apply doc suggestions [skip ci] Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com> --- src/replicon_core/replication_rules.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/replicon_core/replication_rules.rs b/src/replicon_core/replication_rules.rs index ef7c5355..72b81ee9 100644 --- a/src/replicon_core/replication_rules.rs +++ b/src/replicon_core/replication_rules.rs @@ -29,7 +29,7 @@ pub trait AppReplicationExt { where C: Component + Serialize + DeserializeOwned + MapNetworkEntities; - /// 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, deserialization, and removal. fn replicate_with( &mut self, serialize: SerializeFn, @@ -97,7 +97,7 @@ pub struct ReplicationRules { /// Custom function to handle entity despawning. /// /// By default uses [`despawn_recursive`]. - /// May need to intercept the despawn and do it differently. + /// Useful if you need to intercept despawns and handle them in a special way. pub despawn_fn: EntityDespawnFn, /// Maps component IDs to their replication IDs. From 27f008432d8232236167e28dcc9966c4b1235cc8 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Tue, 3 Oct 2023 01:46:39 +0300 Subject: [PATCH 27/27] Apply suggestions from @UkoeHB --- benches/replication.rs | 2 +- src/server.rs | 27 +++++++++++++++++++-------- tests/common/mod.rs | 3 +-- tests/replication.rs | 12 ++++++------ tests/server_event.rs | 13 ++++++------- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/benches/replication.rs b/benches/replication.rs index 1dc71e49..8f270ccd 100644 --- a/benches/replication.rs +++ b/benches/replication.rs @@ -157,7 +157,7 @@ fn replication(c: &mut Criterion) { fn setup_app(app: &mut App) { app.add_plugins(( MinimalPlugins, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::EveryFrame)), )) .replicate::(); diff --git a/src/server.rs b/src/server.rs index e75e8b95..f00b00c5 100644 --- a/src/server.rs +++ b/src/server.rs @@ -80,14 +80,23 @@ impl Plugin for ServerPlugin { ), ); - if let TickPolicy::MaxTickRate(max_tick_rate) = self.tick_policy { - let tick_time = Duration::from_millis(1000 / max_tick_rate as u64); - app.add_systems( - PostUpdate, - Self::increment_tick - .before(Self::diffs_sending_system) - .run_if(on_timer(tick_time)), - ); + match self.tick_policy { + TickPolicy::MaxTickRate(max_tick_rate) => { + let tick_time = Duration::from_millis(1000 / max_tick_rate as u64); + app.add_systems( + PostUpdate, + Self::increment_tick + .before(Self::diffs_sending_system) + .run_if(on_timer(tick_time)), + ); + } + TickPolicy::EveryFrame => { + app.add_systems( + PostUpdate, + Self::increment_tick.before(Self::diffs_sending_system), + ); + } + TickPolicy::Manual => (), } } } @@ -386,6 +395,8 @@ pub enum TickPolicy { /// /// By default it's 30 updates per second. MaxTickRate(u16), + /// Send updates from server every frame. + EveryFrame, /// [`ServerSet::Send`] should be manually configured. Manual, } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 35006038..7b85944c 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -47,8 +47,7 @@ pub(super) fn connect(server_app: &mut App, client_app: &mut App) { server_app .insert_resource(server) - .insert_resource(server_transport) - .add_systems(Update, ServerPlugin::increment_tick); // Increment tick every frame per `TickPolicy::Manual`. + .insert_resource(server_transport); client_app .insert_resource(client) diff --git a/tests/replication.rs b/tests/replication.rs index 2a2f239d..27273c4d 100644 --- a/tests/replication.rs +++ b/tests/replication.rs @@ -13,7 +13,7 @@ fn acked_ticks_cleanup() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::EveryFrame)), )); } @@ -38,7 +38,7 @@ fn tick_acks_receiving() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::EveryFrame)), )); } @@ -63,7 +63,7 @@ fn spawn_replication() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::EveryFrame)), )) .replicate::(); } @@ -100,7 +100,7 @@ fn insert_replication() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::EveryFrame)), )) .replicate::() .replicate::() @@ -155,7 +155,7 @@ fn removal_replication() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::EveryFrame)), )) .replicate::(); } @@ -199,7 +199,7 @@ fn despawn_replication() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::EveryFrame)), )); } diff --git a/tests/server_event.rs b/tests/server_event.rs index 5549d806..56f42d4c 100644 --- a/tests/server_event.rs +++ b/tests/server_event.rs @@ -34,7 +34,7 @@ fn sending_receiving() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::EveryFrame)), )) .add_server_event::(SendPolicy::Ordered); } @@ -79,7 +79,7 @@ fn sending_receiving_and_mapping() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::EveryFrame)), )) .add_mapped_server_event::(SendPolicy::Ordered); } @@ -120,7 +120,7 @@ fn sending_receiving_reflect() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::EveryFrame)), )) .register_type::() .add_server_reflect_event::( @@ -171,7 +171,7 @@ fn sending_receiving_and_mapping_reflect() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::EveryFrame)), )) .register_type::() .add_mapped_server_reflect_event::(SendPolicy::Ordered); @@ -214,10 +214,9 @@ fn local_resending() { let mut app = App::new(); app.add_plugins(( TimePlugin, - ReplicationPlugins.set(ServerPlugin::new(TickPolicy::Manual)), + ReplicationPlugins.set(ServerPlugin::new(TickPolicy::EveryFrame)), )) - .add_server_event::(SendPolicy::Ordered) - .add_systems(Update, ServerPlugin::increment_tick); // Increment tick every frame per `TickPolicy::Manual`. + .add_server_event::(SendPolicy::Ordered); const DUMMY_CLIENT_ID: u64 = 1; for (mode, events_count) in [