From ec5bf55a36c0ac949ef2c88b59670369d8414fde Mon Sep 17 00:00:00 2001 From: Connor Carpenter Date: Thu, 23 Feb 2023 20:36:06 -0600 Subject: [PATCH] Release 0.17 (#141) - This release is mostly an internal refactor although some APIs have changed. - Tick/Time synchronization has been completely re-written, and now the Server & Client clocks should be within 1 milliseconds of eachother. - Another major change is that the synchronization will be resilient to the Server app is running slower than intended. Client time should slow down and speed up seamlessly with the Server. - Now the Client/Server has a new step in the handshake process which allows them to collect network stats and get in sync before returning a Connect Event and giving control to your app. -Sending and receiving messages on a TickBufferedChannel now requires a new API so that the app can specify with which Tick the message should be associated. Now use `Client.send_tick_buffered_message(tick, message)` and `Server.receive_tick_buffered_messages(tick)` for TickBuffered channels. - The API now recognizes that there are two moments in time the Client must be aware of: the authoritative time of the Server, and the time of the Client which is predicted forward into the future. Therefore now there are `Client.client_tick()`, `Client.client_interpolation()`, `Client.server_tick()`, and `Client.server_interpolation()` methods for use. - In the Bevy adapter crates now, instead of the strange conditional `Tick` stage, there is a Tick event which is triggered from an EventReader like the other Bevy events. --- adapters/bevy/client/src/client.rs | 32 +- adapters/bevy/client/src/events.rs | 6 + adapters/bevy/client/src/lib.rs | 1 - adapters/bevy/client/src/plugin.rs | 25 +- adapters/bevy/client/src/resource.rs | 13 - adapters/bevy/client/src/stage.rs | 2 - adapters/bevy/client/src/systems.rs | 204 ++++--- adapters/bevy/server/src/events.rs | 5 +- adapters/bevy/server/src/lib.rs | 3 +- adapters/bevy/server/src/plugin.rs | 20 +- adapters/bevy/server/src/resource.rs | 10 - adapters/bevy/server/src/server.rs | 17 +- adapters/bevy/server/src/stage.rs | 2 - adapters/bevy/server/src/systems.rs | 109 ++-- adapters/bevy/shared/src/flag.rs | 18 - adapters/bevy/shared/src/lib.rs | 2 - adapters/hecs/client/src/lib.rs | 4 +- client/src/client.rs | 274 +++++----- client/src/client_config.rs | 15 +- client/src/connection/base_time_manager.rs | 122 +++++ .../channel_tick_buffer_sender.rs | 58 +- client/src/connection/connection.rs | 160 +++--- client/src/connection/handshake_manager.rs | 167 ++++-- .../src/connection/handshake_time_manager.rs | 156 ++++++ client/src/connection/mod.rs | 6 + .../tick_buffer_sender.rs | 6 +- client/src/{tick => connection}/tick_queue.rs | 0 client/src/connection/time_manager.rs | 501 ++++++++++++++++++ client/src/events.rs | 39 +- client/src/lib.rs | 8 +- client/src/protocol/entity_manager.rs | 12 - client/src/tick/config.rs | 27 - client/src/tick/mod.rs | 5 - client/src/tick/tick_manager.rs | 208 -------- demos/basic/client/app/src/app.rs | 11 +- demos/basic/server/src/app.rs | 2 +- demos/bevy/client/src/app.rs | 7 +- demos/bevy/client/src/resources.rs | 3 +- demos/bevy/client/src/systems/events.rs | 51 +- demos/bevy/client/src/systems/mod.rs | 2 - demos/bevy/client/src/systems/tick.rs | 41 -- demos/bevy/server/src/main.rs | 18 +- demos/bevy/server/src/resources.rs | 5 +- demos/bevy/server/src/systems/events.rs | 69 ++- demos/bevy/server/src/systems/init.rs | 3 +- demos/bevy/server/src/systems/mod.rs | 2 - demos/bevy/server/src/systems/tick.rs | 39 -- .../shared/src/behavior/process_command.rs | 2 +- demos/bevy/shared/src/protocol.rs | 4 +- demos/hecs/client/src/systems/events.rs | 6 +- demos/hecs/server/src/systems/events.rs | 2 +- demos/macroquad/client/src/app.rs | 46 +- demos/macroquad/server/src/app.rs | 45 +- demos/socket/server/src/app.rs | 2 +- .../channel_tick_buffer_receiver.rs | 27 +- server/src/connection/connection.rs | 71 ++- server/src/connection/handshake_manager.rs | 58 +- server/src/connection/mod.rs | 7 +- .../src/connection/ping_config.rs | 0 server/src/connection/ping_manager.rs | 63 +++ server/src/connection/tick_buffer_messages.rs | 59 +++ .../tick_buffer_receiver.rs | 9 +- server/src/connection/time_manager.rs | 154 ++++++ server/src/events.rs | 10 +- server/src/lib.rs | 2 +- server/src/protocol/entity_manager.rs | 14 - server/src/protocol/world_channel.rs | 4 +- server/src/server.rs | 391 ++++++-------- server/src/server_config.rs | 5 + server/src/tick/mod.rs | 3 - server/src/tick/tick_manager.rs | 42 -- shared/src/connection/connection_config.rs | 6 - shared/src/connection/mod.rs | 3 +- shared/src/connection/packet_type.rs | 32 +- shared/src/connection/ping_manager.rs | 141 ----- shared/src/connection/ping_store.rs | 78 +++ shared/src/constants.rs | 3 +- shared/src/game_time.rs | 267 ++++++++++ shared/src/lib.rs | 6 +- shared/src/messages/channel.rs | 6 +- shared/src/messages/default_channels.rs | 6 +- shared/src/protocol.rs | 7 +- shared/tests/derive_replicate.rs | 16 +- .../backends/wasm_bindgen/packet_sender.rs | 2 +- socket/shared/src/link_condition_logic.rs | 1 - socket/shared/src/link_conditioner_config.rs | 18 +- test/tests/handshake.rs | 10 +- 87 files changed, 2519 insertions(+), 1599 deletions(-) delete mode 100644 adapters/bevy/client/src/resource.rs delete mode 100644 adapters/bevy/server/src/resource.rs delete mode 100644 adapters/bevy/shared/src/flag.rs create mode 100644 client/src/connection/base_time_manager.rs rename client/src/{tick => connection}/channel_tick_buffer_sender.rs (86%) create mode 100644 client/src/connection/handshake_time_manager.rs rename client/src/{tick => connection}/tick_buffer_sender.rs (95%) rename client/src/{tick => connection}/tick_queue.rs (100%) create mode 100644 client/src/connection/time_manager.rs delete mode 100644 client/src/tick/config.rs delete mode 100644 client/src/tick/mod.rs delete mode 100644 client/src/tick/tick_manager.rs delete mode 100644 demos/bevy/client/src/systems/tick.rs delete mode 100644 demos/bevy/server/src/systems/tick.rs rename server/src/{tick => connection}/channel_tick_buffer_receiver.rs (84%) rename {shared => server}/src/connection/ping_config.rs (100%) create mode 100644 server/src/connection/ping_manager.rs create mode 100644 server/src/connection/tick_buffer_messages.rs rename server/src/{tick => connection}/tick_buffer_receiver.rs (85%) create mode 100644 server/src/connection/time_manager.rs delete mode 100644 server/src/tick/mod.rs delete mode 100644 server/src/tick/tick_manager.rs delete mode 100644 shared/src/connection/ping_manager.rs create mode 100644 shared/src/connection/ping_store.rs create mode 100644 shared/src/game_time.rs diff --git a/adapters/bevy/client/src/client.rs b/adapters/bevy/client/src/client.rs index 091ff72c3..dc796f029 100644 --- a/adapters/bevy/client/src/client.rs +++ b/adapters/bevy/client/src/client.rs @@ -9,8 +9,8 @@ use bevy_ecs::{ use naia_client::{Client as NaiaClient, EntityRef, NaiaClientError}; use naia_bevy_shared::{ - Channel, EntityDoesNotExistError, EntityHandle, EntityHandleConverter, Message, WorldProxy, - WorldRef, + Channel, EntityDoesNotExistError, EntityHandle, EntityHandleConverter, Message, Tick, + WorldProxy, WorldRef, }; use super::state::State; @@ -69,15 +69,13 @@ impl<'a> Client<'a> { self.client.jitter() } - // Interpolation - - pub fn interpolation(&self) -> Option { - self.client.interpolation() - } - //// Messages //// pub fn send_message(&mut self, message: &M) { - self.client.send_message::(message) + self.client.send_message::(message); + } + + pub fn send_tick_buffer_message(&mut self, tick: &Tick, message: &M) { + self.client.send_tick_buffer_message::(tick, message); } //// Entities //// @@ -92,9 +90,23 @@ impl<'a> Client<'a> { //// Ticks //// - pub fn client_tick(&self) -> Option { + pub fn client_tick(&self) -> Option { self.client.client_tick() } + + pub fn server_tick(&self) -> Option { + self.client.server_tick() + } + + // Interpolation + + pub fn client_interpolation(&self) -> Option { + self.client.client_interpolation() + } + + pub fn server_interpolation(&self) -> Option { + self.client.server_interpolation() + } } impl<'a> SystemParam for Client<'a> { diff --git a/adapters/bevy/client/src/events.rs b/adapters/bevy/client/src/events.rs index 83cb9e6b5..746086663 100644 --- a/adapters/bevy/client/src/events.rs +++ b/adapters/bevy/client/src/events.rs @@ -56,6 +56,12 @@ impl MessageEvents { } } +// ClientTickEvent +pub struct ClientTickEvent(pub Tick); + +// ServerTickEvent +pub struct ServerTickEvent(pub Tick); + // SpawnEntityEvent pub struct SpawnEntityEvent(pub Entity); diff --git a/adapters/bevy/client/src/lib.rs b/adapters/bevy/client/src/lib.rs index 39ca111b9..1d0f3b6a7 100644 --- a/adapters/bevy/client/src/lib.rs +++ b/adapters/bevy/client/src/lib.rs @@ -6,7 +6,6 @@ pub mod events; mod client; mod commands; mod plugin; -mod resource; mod stage; mod state; mod systems; diff --git a/adapters/bevy/client/src/plugin.rs b/adapters/bevy/client/src/plugin.rs index 5bb401b85..d179ab43c 100644 --- a/adapters/bevy/client/src/plugin.rs +++ b/adapters/bevy/client/src/plugin.rs @@ -9,12 +9,12 @@ use naia_bevy_shared::Protocol; use super::{ events::{ - ConnectEvent, DespawnEntityEvent, DisconnectEvent, ErrorEvent, InsertComponentEvents, - MessageEvents, RejectEvent, RemoveComponentEvents, SpawnEntityEvent, UpdateComponentEvents, + ClientTickEvent, ConnectEvent, DespawnEntityEvent, DisconnectEvent, ErrorEvent, + InsertComponentEvents, MessageEvents, RejectEvent, RemoveComponentEvents, ServerTickEvent, + SpawnEntityEvent, UpdateComponentEvents, }, - resource::ClientResource, stage::{PrivateStage, Stage}, - systems::{before_receive_events, finish_tick, should_receive, should_tick}, + systems::{before_receive_events, should_receive}, }; struct PluginConfig { @@ -56,12 +56,13 @@ impl PluginType for Plugin { app // RESOURCES // .insert_resource(client) - .init_resource::() // EVENTS // .add_event::() .add_event::() .add_event::() .add_event::() + .add_event::() + .add_event::() .add_event::() .add_event::() .add_event::() @@ -80,19 +81,7 @@ impl PluginType for Plugin { Stage::ReceiveEvents, SystemStage::single_threaded().with_run_criteria(should_receive), ) - // tick // - .add_stage_after( - CoreStage::PostUpdate, - Stage::Tick, - SystemStage::single_threaded().with_run_criteria(should_tick), - ) - .add_stage_after( - Stage::Tick, - PrivateStage::AfterTick, - SystemStage::parallel().with_run_criteria(should_tick), - ) // SYSTEMS // - .add_system_to_stage(PrivateStage::BeforeReceiveEvents, before_receive_events) - .add_system_to_stage(PrivateStage::AfterTick, finish_tick); + .add_system_to_stage(PrivateStage::BeforeReceiveEvents, before_receive_events); } } diff --git a/adapters/bevy/client/src/resource.rs b/adapters/bevy/client/src/resource.rs deleted file mode 100644 index 3886a9c5c..000000000 --- a/adapters/bevy/client/src/resource.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::default::Default; - -use bevy_ecs::prelude::Resource; - -use naia_bevy_shared::Flag; - -#[derive(Default, Resource)] -pub struct ClientResource { - pub ticker: Flag, - pub connector: Flag, - pub disconnector: Flag, - pub rejector: Flag, -} diff --git a/adapters/bevy/client/src/stage.rs b/adapters/bevy/client/src/stage.rs index 3929de1a6..fac2411de 100644 --- a/adapters/bevy/client/src/stage.rs +++ b/adapters/bevy/client/src/stage.rs @@ -3,11 +3,9 @@ use bevy_ecs::schedule::StageLabel; #[derive(Debug, Clone, PartialEq, Eq, Hash, StageLabel)] pub enum Stage { ReceiveEvents, - Tick, } #[derive(Debug, Clone, PartialEq, Eq, Hash, StageLabel)] pub enum PrivateStage { BeforeReceiveEvents, - AfterTick, } diff --git a/adapters/bevy/client/src/systems.rs b/adapters/bevy/client/src/systems.rs index cce9ee991..bd866acce 100644 --- a/adapters/bevy/client/src/systems.rs +++ b/adapters/bevy/client/src/systems.rs @@ -2,7 +2,7 @@ use bevy_ecs::event::Events; use bevy_ecs::{ entity::Entity, schedule::ShouldRun, - system::{Res, ResMut}, + system::Res, world::{Mut, World}, }; @@ -12,122 +12,118 @@ use naia_bevy_shared::WorldProxyMut; mod naia_events { pub use naia_client::{ - ConnectEvent, DespawnEntityEvent, DisconnectEvent, ErrorEvent, InsertComponentEvent, - MessageEvent, RejectEvent, RemoveComponentEvent, SpawnEntityEvent, TickEvent, - UpdateComponentEvent, + ClientTickEvent, ConnectEvent, DespawnEntityEvent, DisconnectEvent, ErrorEvent, + InsertComponentEvent, MessageEvent, RejectEvent, RemoveComponentEvent, ServerTickEvent, + SpawnEntityEvent, UpdateComponentEvent, }; } mod bevy_events { pub use crate::events::{ - ConnectEvent, DespawnEntityEvent, DisconnectEvent, ErrorEvent, InsertComponentEvents, - MessageEvents, RejectEvent, RemoveComponentEvents, SpawnEntityEvent, UpdateComponentEvents, + ClientTickEvent, ConnectEvent, DespawnEntityEvent, DisconnectEvent, ErrorEvent, + InsertComponentEvents, MessageEvents, RejectEvent, RemoveComponentEvents, ServerTickEvent, + SpawnEntityEvent, UpdateComponentEvents, }; } -use crate::resource::ClientResource; - pub fn before_receive_events(world: &mut World) { world.resource_scope(|world, mut client: Mut>| { - world.resource_scope(|world, mut client_resource: Mut| { - let mut events = client.receive(world.proxy_mut()); - if !events.is_empty() { - unsafe { - // Connect Event - let mut connect_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - for _ in events.read::() { - connect_event_writer.send(bevy_events::ConnectEvent); - } - - // Disconnect Event - let mut disconnect_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - for _ in events.read::() { - disconnect_event_writer.send(bevy_events::DisconnectEvent); - } - - // Reject Event - let mut reject_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - for _ in events.read::() { - reject_event_writer.send(bevy_events::RejectEvent); - } - - // Tick Event - for _ in events.read::() { - client_resource.ticker.set(); - } - - // Error Event - let mut error_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - for error in events.read::() { - error_event_writer.send(bevy_events::ErrorEvent(error)); - } - - // Message Event - let mut message_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - message_event_writer.send(bevy_events::MessageEvents::from(&mut events)); - - // Spawn Entity Event - let mut spawn_entity_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - for entity in events.read::() { - spawn_entity_event_writer.send(bevy_events::SpawnEntityEvent(entity)); - } - - // Despawn Entity Event - let mut despawn_entity_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - for entity in events.read::() { - despawn_entity_event_writer.send(bevy_events::DespawnEntityEvent(entity)); - } - - // Insert Component Event - let mut insert_component_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - insert_component_event_writer - .send(bevy_events::InsertComponentEvents::from(&mut events)); - - // Update Component Event - let mut update_component_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - update_component_event_writer - .send(bevy_events::UpdateComponentEvents::from(&mut events)); - - // Remove Component Event - let mut remove_component_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - remove_component_event_writer - .send(bevy_events::RemoveComponentEvents::from(&mut events)); + let mut events = client.receive(world.proxy_mut()); + if !events.is_empty() { + unsafe { + // Connect Event + let mut connect_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + for _ in events.read::() { + connect_event_writer.send(bevy_events::ConnectEvent); } - } - }); - }); -} -pub fn should_tick(resource: Res) -> ShouldRun { - if resource.ticker.is_set() { - ShouldRun::Yes - } else { - ShouldRun::No - } -} + // Disconnect Event + let mut disconnect_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + for _ in events.read::() { + disconnect_event_writer.send(bevy_events::DisconnectEvent); + } + + // Reject Event + let mut reject_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + for _ in events.read::() { + reject_event_writer.send(bevy_events::RejectEvent); + } -pub fn finish_tick(mut resource: ResMut) { - resource.ticker.reset(); + // Error Event + let mut error_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + for error in events.read::() { + error_event_writer.send(bevy_events::ErrorEvent(error)); + } + + // Client Tick Event + let mut client_tick_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + for tick in events.read::() { + client_tick_event_writer.send(bevy_events::ClientTickEvent(tick)); + } + + // Server Tick Event + let mut server_tick_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + for tick in events.read::() { + server_tick_event_writer.send(bevy_events::ServerTickEvent(tick)); + } + + // Message Event + let mut message_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + message_event_writer.send(bevy_events::MessageEvents::from(&mut events)); + + // Spawn Entity Event + let mut spawn_entity_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + for entity in events.read::() { + spawn_entity_event_writer.send(bevy_events::SpawnEntityEvent(entity)); + } + + // Despawn Entity Event + let mut despawn_entity_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + for entity in events.read::() { + despawn_entity_event_writer.send(bevy_events::DespawnEntityEvent(entity)); + } + + // Insert Component Event + let mut insert_component_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + insert_component_event_writer + .send(bevy_events::InsertComponentEvents::from(&mut events)); + + // Update Component Event + let mut update_component_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + update_component_event_writer + .send(bevy_events::UpdateComponentEvents::from(&mut events)); + + // Remove Component Event + let mut remove_component_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + remove_component_event_writer + .send(bevy_events::RemoveComponentEvents::from(&mut events)); + } + } + }); } pub fn should_receive(client: Res>) -> ShouldRun { diff --git a/adapters/bevy/server/src/events.rs b/adapters/bevy/server/src/events.rs index 9de24fcff..ecb1d7c2b 100644 --- a/adapters/bevy/server/src/events.rs +++ b/adapters/bevy/server/src/events.rs @@ -1,6 +1,6 @@ use std::{any::Any, collections::HashMap}; -use naia_bevy_shared::{Channel, ChannelKind, Message, MessageKind}; +use naia_bevy_shared::{Channel, ChannelKind, Message, MessageKind, Tick}; use naia_server::{Events, NaiaServerError, User, UserKey}; // ConnectEvent @@ -12,6 +12,9 @@ pub struct DisconnectEvent(pub UserKey, pub User); // ErrorEvent pub struct ErrorEvent(pub NaiaServerError); +// TickEvent +pub struct TickEvent(pub Tick); + // AuthEvents pub struct AuthEvents { inner: HashMap)>>, diff --git a/adapters/bevy/server/src/lib.rs b/adapters/bevy/server/src/lib.rs index dde4ae47d..988336128 100644 --- a/adapters/bevy/server/src/lib.rs +++ b/adapters/bevy/server/src/lib.rs @@ -1,4 +1,4 @@ -pub use naia_bevy_shared::Random; +pub use naia_bevy_shared::{Random, Tick}; pub use naia_server::{RoomKey, ServerAddrs, ServerConfig, UserKey}; pub mod events; @@ -6,7 +6,6 @@ pub mod events; mod commands; mod entity_mut; mod plugin; -mod resource; mod server; mod stage; mod state; diff --git a/adapters/bevy/server/src/plugin.rs b/adapters/bevy/server/src/plugin.rs index 0d3360e06..4f86432b8 100644 --- a/adapters/bevy/server/src/plugin.rs +++ b/adapters/bevy/server/src/plugin.rs @@ -8,10 +8,9 @@ use naia_server::{Server, ServerConfig}; use naia_bevy_shared::Protocol; use super::{ - events::{AuthEvents, ConnectEvent, DisconnectEvent, ErrorEvent, MessageEvents}, - resource::ServerResource, + events::{AuthEvents, ConnectEvent, DisconnectEvent, ErrorEvent, MessageEvents, TickEvent}, stage::{PrivateStage, Stage}, - systems::{before_receive_events, finish_tick, should_receive, should_tick}, + systems::{before_receive_events, should_receive}, }; struct PluginConfig { @@ -53,11 +52,11 @@ impl PluginType for Plugin { app // RESOURCES // .insert_resource(server) - .init_resource::() // EVENTS // .add_event::() .add_event::() .add_event::() + .add_event::() .add_event::() .add_event::() // STAGES // @@ -71,18 +70,7 @@ impl PluginType for Plugin { Stage::ReceiveEvents, SystemStage::single_threaded().with_run_criteria(should_receive), ) - .add_stage_after( - CoreStage::PostUpdate, - Stage::Tick, - SystemStage::single_threaded().with_run_criteria(should_tick), - ) - .add_stage_after( - Stage::Tick, - PrivateStage::AfterTick, - SystemStage::parallel().with_run_criteria(should_tick), - ) // SYSTEMS // - .add_system_to_stage(PrivateStage::BeforeReceiveEvents, before_receive_events) - .add_system_to_stage(PrivateStage::AfterTick, finish_tick); + .add_system_to_stage(PrivateStage::BeforeReceiveEvents, before_receive_events); } } diff --git a/adapters/bevy/server/src/resource.rs b/adapters/bevy/server/src/resource.rs deleted file mode 100644 index e1089b42f..000000000 --- a/adapters/bevy/server/src/resource.rs +++ /dev/null @@ -1,10 +0,0 @@ -use std::default::Default; - -use bevy_ecs::prelude::Resource; - -use naia_bevy_shared::Flag; - -#[derive(Default, Resource)] -pub struct ServerResource { - pub ticker: Flag, -} diff --git a/adapters/bevy/server/src/server.rs b/adapters/bevy/server/src/server.rs index 8a4b21f1d..0dbc6843a 100644 --- a/adapters/bevy/server/src/server.rs +++ b/adapters/bevy/server/src/server.rs @@ -3,10 +3,11 @@ use bevy_ecs::{ system::SystemParam, world::{Mut, World}, }; +use std::time::Duration; use naia_server::{ - EntityRef, RoomKey, RoomMut, RoomRef, Server as NaiaServer, ServerAddrs, UserKey, UserMut, - UserRef, UserScopeMut, + EntityRef, RoomKey, RoomMut, RoomRef, Server as NaiaServer, ServerAddrs, TickBufferMessages, + UserKey, UserMut, UserRef, UserScopeMut, }; use naia_bevy_shared::{ @@ -65,6 +66,10 @@ impl<'world, 'state> Server<'world, 'state> { self.server.broadcast_message::(message); } + pub fn receive_tick_buffer_messages(&mut self, tick: &Tick) -> TickBufferMessages { + self.server.receive_tick_buffer_messages(tick) + } + //// Updates //// pub fn scope_checks(&self) -> Vec<(RoomKey, UserKey, Entity)> { @@ -154,12 +159,12 @@ impl<'world, 'state> Server<'world, 'state> { //// Ticks //// - pub fn client_tick(&self, user_key: &UserKey) -> Option { - self.server.client_tick(user_key) + pub fn current_tick(&self) -> Tick { + self.server.current_tick() } - pub fn server_tick(&self) -> Option { - self.server.server_tick() + pub fn average_tick_duration(&self) -> Duration { + self.server.average_tick_duration() } // Crate-public methods diff --git a/adapters/bevy/server/src/stage.rs b/adapters/bevy/server/src/stage.rs index 3929de1a6..fac2411de 100644 --- a/adapters/bevy/server/src/stage.rs +++ b/adapters/bevy/server/src/stage.rs @@ -3,11 +3,9 @@ use bevy_ecs::schedule::StageLabel; #[derive(Debug, Clone, PartialEq, Eq, Hash, StageLabel)] pub enum Stage { ReceiveEvents, - Tick, } #[derive(Debug, Clone, PartialEq, Eq, Hash, StageLabel)] pub enum PrivateStage { BeforeReceiveEvents, - AfterTick, } diff --git a/adapters/bevy/server/src/systems.rs b/adapters/bevy/server/src/systems.rs index a37aae9d0..2c44a3d14 100644 --- a/adapters/bevy/server/src/systems.rs +++ b/adapters/bevy/server/src/systems.rs @@ -4,7 +4,7 @@ use bevy_ecs::{ entity::Entity, event::Events, schedule::ShouldRun, - system::{Res, ResMut}, + system::Res, world::{Mut, World}, }; @@ -17,77 +17,62 @@ mod naia_events { } mod bevy_events { - pub use crate::events::{AuthEvents, ConnectEvent, DisconnectEvent, ErrorEvent, MessageEvents}; + pub use crate::events::{ + AuthEvents, ConnectEvent, DisconnectEvent, ErrorEvent, MessageEvents, TickEvent, + }; } -use super::resource::ServerResource; - pub fn before_receive_events(world: &mut World) { world.resource_scope(|world, mut server: Mut>| { - world.resource_scope(|world, mut server_resource: Mut| { - let mut events = server.receive(); - if events.is_empty() { - // In the future, may want to stall the system if we don't receive any events - // to keep from the system running empty and using up CPU. - sleep(Duration::from_millis(5)); - } else { - unsafe { - // Connect Event - let mut connect_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - for user_key in events.read::() { - connect_event_writer.send(bevy_events::ConnectEvent(user_key)); - } - - // Disconnect Event - let mut disconnect_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - for (user_key, user) in events.read::() { - disconnect_event_writer.send(bevy_events::DisconnectEvent(user_key, user)); - } - - // Tick Event - for _ in events.read::() { - server_resource.ticker.set(); - } + let mut events = server.receive(); + unsafe { + // Connect Event + let mut connect_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + for user_key in events.read::() { + connect_event_writer.send(bevy_events::ConnectEvent(user_key)); + } - // Error Event - let mut error_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - for error in events.read::() { - error_event_writer.send(bevy_events::ErrorEvent(error)); - } + // Disconnect Event + let mut disconnect_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + for (user_key, user) in events.read::() { + disconnect_event_writer.send(bevy_events::DisconnectEvent(user_key, user)); + } - // Message Event - let mut message_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - message_event_writer.send(bevy_events::MessageEvents::from(&mut events)); + // Error Event + let mut error_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + for error in events.read::() { + error_event_writer.send(bevy_events::ErrorEvent(error)); + } - // Auth Event - let mut auth_event_writer = world - .get_resource_unchecked_mut::>() - .unwrap(); - auth_event_writer.send(bevy_events::AuthEvents::from(&mut events)); - } + // Tick Event + let mut tick_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + for tick in events.read::() { + tick_event_writer.send(bevy_events::TickEvent(tick)); } - }); - }); -} -pub fn should_tick(resource: Res) -> ShouldRun { - if resource.ticker.is_set() { - ShouldRun::Yes - } else { - ShouldRun::No - } -} + // Message Event + let mut message_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + message_event_writer.send(bevy_events::MessageEvents::from(&mut events)); -pub fn finish_tick(mut resource: ResMut) { - resource.ticker.reset(); + // Auth Event + let mut auth_event_writer = world + .get_resource_unchecked_mut::>() + .unwrap(); + auth_event_writer.send(bevy_events::AuthEvents::from(&mut events)); + } + + sleep(Duration::from_millis(1)); + }); } pub fn should_receive(server: Res>) -> ShouldRun { diff --git a/adapters/bevy/shared/src/flag.rs b/adapters/bevy/shared/src/flag.rs deleted file mode 100644 index d46233cb0..000000000 --- a/adapters/bevy/shared/src/flag.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[derive(Default)] -pub struct Flag { - set: bool, -} - -impl Flag { - pub fn set(&mut self) { - self.set = true; - } - - pub fn reset(&mut self) { - self.set = false; - } - - pub fn is_set(&self) -> bool { - self.set - } -} diff --git a/adapters/bevy/shared/src/lib.rs b/adapters/bevy/shared/src/lib.rs index af07b19a4..ca0c10199 100644 --- a/adapters/bevy/shared/src/lib.rs +++ b/adapters/bevy/shared/src/lib.rs @@ -11,14 +11,12 @@ pub use naia_shared::{ mod component_access; mod component_ref; -mod flag; mod protocol; mod protocol_plugin; mod world_data; mod world_proxy; pub use component_access::{ComponentAccess, ComponentAccessor}; -pub use flag::Flag; pub use protocol::Protocol; pub use protocol_plugin::ProtocolPlugin; pub use world_data::WorldData; diff --git a/adapters/hecs/client/src/lib.rs b/adapters/hecs/client/src/lib.rs index b133fc308..39586dfa6 100644 --- a/adapters/hecs/client/src/lib.rs +++ b/adapters/hecs/client/src/lib.rs @@ -1,5 +1,5 @@ pub use naia_client::{ - Client, ClientConfig, ConnectEvent, DespawnEntityEvent, DisconnectEvent, ErrorEvent, - InsertComponentEvent, RemoveComponentEvent, SpawnEntityEvent, TickEvent, + Client, ClientConfig, ClientTickEvent, ConnectEvent, DespawnEntityEvent, DisconnectEvent, + ErrorEvent, InsertComponentEvent, RemoveComponentEvent, SpawnEntityEvent, }; pub use naia_hecs_shared::{Protocol, WorldWrapper}; diff --git a/client/src/client.rs b/client/src/client.rs index 1ad940555..e4c000b7b 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -9,10 +9,11 @@ use naia_client_socket::Socket; pub use naia_shared::{ BitReader, BitWriter, Channel, ChannelKind, ChannelKinds, ConnectionConfig, - EntityDoesNotExistError, EntityHandle, EntityHandleConverter, Message, PacketType, PingConfig, - PingIndex, Protocol, Replicate, Serde, SocketConfig, StandardHeader, Tick, Timer, Timestamp, - WorldMutType, WorldRefType, + EntityDoesNotExistError, EntityHandle, EntityHandleConverter, Message, PacketType, Protocol, + Replicate, Serde, SocketConfig, StandardHeader, Tick, Timer, Timestamp, WorldMutType, + WorldRefType, }; +use naia_shared::{GameInstant, PingIndex}; use crate::{ connection::{ @@ -21,7 +22,6 @@ use crate::{ io::Io, }, protocol::entity_ref::EntityRef, - tick::tick_manager::TickManager, }; use super::{client_config::ClientConfig, error::NaiaClientError, events::Events}; @@ -39,8 +39,6 @@ pub struct Client { handshake_manager: HandshakeManager, // Events incoming_events: Events, - // Ticks - tick_manager: Option, } impl Client { @@ -49,11 +47,11 @@ impl Client { let mut protocol: Protocol = protocol.into(); protocol.lock(); - let handshake_manager = HandshakeManager::new(client_config.send_handshake_interval); - - let tick_manager = protocol - .tick_interval - .map(|duration| TickManager::new(duration, client_config.tick_config)); + let handshake_manager = HandshakeManager::new( + client_config.send_handshake_interval, + client_config.ping_interval, + client_config.handshake_pings, + ); let compression_config = protocol.compression.clone(); @@ -70,8 +68,6 @@ impl Client { handshake_manager, // Events incoming_events: Events::new(), - // Ticks - tick_manager, } } @@ -143,40 +139,44 @@ impl Client { return std::mem::take(&mut self.incoming_events); } - let mut did_tick = false; - - // update current tick - if let Some(tick_manager) = &mut self.tick_manager { - if tick_manager.recv_client_tick() { - did_tick = true; - - // apply updates on tick boundary - let receiving_tick = tick_manager.client_receiving_tick(); - connection.process_buffered_packets( - &self.protocol, - &mut world, - receiving_tick, - &mut self.incoming_events, - ); - } - } else { + let (receiving_tick_happened, sending_tick_happened) = + connection.time_manager.check_ticks(); + + if let Some((prev_receiving_tick, current_receiving_tick)) = receiving_tick_happened { + // apply updates on tick boundary connection.process_buffered_packets( &self.protocol, &mut world, - 0, &mut self.incoming_events, ); + + // receive (process) messages + connection.receive_messages(&mut self.incoming_events); + + let mut index_tick = prev_receiving_tick.wrapping_add(1); + loop { + self.incoming_events.push_server_tick(index_tick); + + if index_tick == current_receiving_tick { + break; + } + index_tick = index_tick.wrapping_add(1); + } } - // receive (process) messages - connection.receive_messages(&mut self.incoming_events); + if let Some((prev_sending_tick, current_sending_tick)) = sending_tick_happened { + // send outgoing packets + connection.send_outgoing_packets(&self.protocol, &mut self.io); - // send outgoing packets - connection.send_outgoing_packets(&self.protocol, &mut self.io, &self.tick_manager); + let mut index_tick = prev_sending_tick.wrapping_add(1); + loop { + self.incoming_events.push_client_tick(index_tick); - // tick event - if did_tick { - self.incoming_events.push_tick(); + if index_tick == current_sending_tick { + break; + } + index_tick = index_tick.wrapping_add(1); + } } } else { self.handshake_manager @@ -200,19 +200,11 @@ impl Client { panic!("Cannot send message to Server on this Channel"); } - let tick_buffered = channel_settings.tick_buffered(); + if channel_settings.tick_buffered() { + panic!("Cannot call `Client.send_message()` on a Tick Buffered Channel, use `Client.send_tick_buffered_message()` instead"); + } - if tick_buffered { - if let Some(client_tick) = self.client_tick() { - if let Some(connection) = self.server_connection.as_mut() { - connection - .tick_buffer - .as_mut() - .expect("connection does not have a tick buffer") - .send_message(&client_tick, channel_kind, message); - } - } - } else if let Some(connection) = &mut self.server_connection { + if let Some(connection) = &mut self.server_connection { connection .base .message_manager @@ -220,6 +212,34 @@ impl Client { } } + pub fn send_tick_buffer_message(&mut self, tick: &Tick, message: &M) { + let cloned_message = M::clone_box(message); + self.send_tick_buffer_message_inner(tick, &ChannelKind::of::(), cloned_message); + } + + fn send_tick_buffer_message_inner( + &mut self, + tick: &Tick, + channel_kind: &ChannelKind, + message: Box, + ) { + let channel_settings = self.protocol.channel_kinds.channel(channel_kind); + + if !channel_settings.can_send_to_server() { + panic!("Cannot send message to Server on this Channel"); + } + + if !channel_settings.tick_buffered() { + panic!("Can only use `Client.send_tick_buffer_message()` on a Channel that is configured for it."); + } + + if let Some(connection) = self.server_connection.as_mut() { + connection + .tick_buffer + .send_message(tick, channel_kind, message); + } + } + // Entities /// Retrieves an EntityRef that exposes read-only operations for the @@ -246,7 +266,7 @@ impl Client { self.server_connection .as_ref() .expect("it is expected that you should verify whether the client is connected before calling this method") - .ping_manager.rtt + .time_manager.rtt() } /// Gets the average Jitter measured in connection to the Server @@ -254,26 +274,43 @@ impl Client { self.server_connection .as_ref() .expect("it is expected that you should verify whether the client is connected before calling this method") - .ping_manager.jitter + .time_manager.jitter() } // Ticks /// Gets the current tick of the Client pub fn client_tick(&self) -> Option { - return self - .tick_manager - .as_ref() - .map(|tick_manager| tick_manager.client_sending_tick()); + if let Some(connection) = &self.server_connection { + return Some(connection.time_manager.client_sending_tick); + } + return None; + } + + /// Gets the current tick of the Server + pub fn server_tick(&self) -> Option { + if let Some(connection) = &self.server_connection { + return Some(connection.time_manager.client_receiving_tick); + } + return None; } // Interpolation - /// Gets the interpolation tween amount for the current frame - pub fn interpolation(&self) -> Option { - self.tick_manager - .as_ref() - .map(|tick_manager| tick_manager.interpolation()) + /// Gets the interpolation tween amount for the current frame, for use by entities on the Client Tick (i.e. predicted) + pub fn client_interpolation(&self) -> Option { + if let Some(connection) = &self.server_connection { + return Some(connection.time_manager.client_interpolation()); + } + return None; + } + + /// Gets the interpolation tween amount for the current frame, for use by entities on the Server Tick (i.e. authoritative) + pub fn server_interpolation(&self) -> Option { + if let Some(connection) = &self.server_connection { + return Some(connection.time_manager.server_interpolation()); + } + return None; } // Bandwidth monitoring @@ -288,7 +325,6 @@ impl Client { // internal functions fn maintain_socket(&mut self) { - // get current tick if let Some(server_connection) = self.server_connection.as_mut() { // connection already established @@ -301,47 +337,16 @@ impl Client { .base .write_outgoing_header(PacketType::Heartbeat, &mut writer); - // write client tick - if let Some(tick_manager) = self.tick_manager.as_mut() { - tick_manager.write_client_tick(&mut writer); - } - // send packet - match self.io.send_writer(&mut writer) { - Ok(()) => {} - Err(_) => { - // TODO: pass this on and handle above - warn!("Client Error: Cannot send heartbeat packet to Server"); - } + if self.io.send_writer(&mut writer).is_err() { + // TODO: pass this on and handle above + warn!("Client Error: Cannot send heartbeat packet to Server"); } server_connection.base.mark_sent(); } // send pings - if server_connection.ping_manager.should_send_ping() { - let mut writer = BitWriter::new(); - - // write header - server_connection - .base - .write_outgoing_header(PacketType::Ping, &mut writer); - - // write client tick - if let Some(tick_manager) = self.tick_manager.as_mut() { - tick_manager.write_client_tick(&mut writer); - } - - // write body - server_connection.ping_manager.write_ping(&mut writer); - - // send packet - match self.io.send_writer(&mut writer) { - Ok(()) => {} - Err(_) => { - // TODO: pass this on and handle above - warn!("Client Error: Cannot send ping packet to Server"); - } - } + if server_connection.time_manager.send_ping(&mut self.io) { server_connection.base.mark_sent(); } @@ -372,21 +377,32 @@ impl Client { // Read incoming header server_connection.process_incoming_header(&header); - // Record incoming tick - let mut incoming_tick = 0; + // read server tick + let Ok(server_tick) = Tick::de(&mut reader) else { + warn!("unable to parse server_tick from packet"); + continue; + }; - if let Some(tick_manager) = self.tick_manager.as_mut() { - incoming_tick = tick_manager.read_server_tick( - &mut reader, - server_connection.ping_manager.rtt, - server_connection.ping_manager.jitter, - ); - } + // read time since last tick + let Ok(server_tick_instant) = GameInstant::de(&mut reader) else { + warn!("unable to parse server_tick_instant from packet"); + continue; + }; + + server_connection + .time_manager + .recv_tick_instant(&server_tick, &server_tick_instant); // Handle based on PacketType match header.packet_type { PacketType::Data => { - server_connection.buffer_data_packet(incoming_tick, &mut reader); + if server_connection + .buffer_data_packet(&server_tick, &mut reader) + .is_err() + { + warn!("unable to parse data packet"); + continue; + } } PacketType::Heartbeat => { // already marked as heard, job done @@ -404,26 +420,25 @@ impl Client { .base .write_outgoing_header(PacketType::Pong, &mut writer); - // write server tick - if let Some(tick_manager) = self.tick_manager.as_ref() { - tick_manager.write_client_tick(&mut writer); - } - // write index ping_index.ser(&mut writer); // send packet - match self.io.send_writer(&mut writer) { - Ok(()) => {} - Err(_) => { - // TODO: pass this on and handle above - warn!("Client Error: Cannot send pong packet to Server"); - } + if self.io.send_writer(&mut writer).is_err() { + // TODO: pass this on and handle above + warn!("Client Error: Cannot send pong packet to Server"); } server_connection.base.mark_sent(); } PacketType::Pong => { - server_connection.ping_manager.process_pong(&mut reader); + if server_connection + .time_manager + .read_pong(&mut reader) + .is_err() + { + // TODO: pass this on and handle above + warn!("Client Error: Cannot process pong packet from Server"); + } } _ => { // no other packet types matter when connection @@ -448,14 +463,14 @@ impl Client { match self.io.recv_reader() { Ok(Some(mut reader)) => { match self.handshake_manager.recv(&mut reader) { - Some(HandshakeResult::Connected) => { + Some(HandshakeResult::Connected(time_manager)) => { // new connect! let server_addr = self.server_address_unwrapped(); self.server_connection = Some(Connection::new( server_addr, &self.client_config.connection, - &self.protocol.tick_interval, &self.protocol.channel_kinds, + time_manager, )); self.incoming_events.push_connection(&server_addr); } @@ -494,24 +509,17 @@ impl Client { fn disconnect_cleanup(&mut self) { // this is very similar to the newtype method .. can we coalesce and reduce // duplication? - let tick_manager = { - if let Some(duration) = self.protocol.tick_interval { - Some(TickManager::new( - duration, - self.client_config.tick_config.clone(), - )) - } else { - None - } - }; self.io = Io::new( &self.client_config.connection.bandwidth_measure_duration, &self.protocol.compression, ); self.server_connection = None; - self.handshake_manager = HandshakeManager::new(self.client_config.send_handshake_interval); - self.tick_manager = tick_manager; + self.handshake_manager = HandshakeManager::new( + self.client_config.send_handshake_interval, + self.client_config.ping_interval, + self.client_config.handshake_pings, + ); } fn server_address_unwrapped(&self) -> SocketAddr { diff --git a/client/src/client_config.rs b/client/src/client_config.rs index 121b13332..abeb6103f 100644 --- a/client/src/client_config.rs +++ b/client/src/client_config.rs @@ -2,8 +2,6 @@ use std::{default::Default, time::Duration}; use naia_shared::ConnectionConfig; -use crate::TickConfig; - /// Contains Config properties which will be used by a Server or Client #[derive(Clone)] pub struct ClientConfig { @@ -11,8 +9,14 @@ pub struct ClientConfig { pub connection: ConnectionConfig, /// The duration between the resend of certain connection handshake messages pub send_handshake_interval: Duration, - /// Configuration for options related to Tick syncing function - pub tick_config: TickConfig, + /// The duration to wait before sending a ping message to the remote host, + /// in order to estimate RTT time + pub ping_interval: Duration, + /// The number of network samples to take before completing the Connection Handshake. + /// Increase this for greater accuracy of network statistics, at the cost of the handshake + /// taking longer. Keep in mind that the network measurements affect how likely commands + /// are able to arrive at the server before processing. + pub handshake_pings: u8, } impl Default for ClientConfig { @@ -20,7 +24,8 @@ impl Default for ClientConfig { Self { connection: ConnectionConfig::default(), send_handshake_interval: Duration::from_millis(250), - tick_config: TickConfig::default(), + ping_interval: Duration::from_secs(1), + handshake_pings: 10, } } } diff --git a/client/src/connection/base_time_manager.rs b/client/src/connection/base_time_manager.rs new file mode 100644 index 000000000..cb26c1f31 --- /dev/null +++ b/client/src/connection/base_time_manager.rs @@ -0,0 +1,122 @@ +use naia_shared::{ + sequence_greater_than, BitReader, BitWriter, GameDuration, GameInstant, Instant, PacketType, + PingIndex, PingStore, Serde, SerdeErr, StandardHeader, UnsignedVariableInteger, +}; + +use log::warn; + +use crate::connection::io::Io; + +/// Responsible for keeping track of internal time, as well as sending and receiving Ping/Pong messages +pub struct BaseTimeManager { + pub start_instant: Instant, + sent_pings: PingStore, + most_recent_ping: PingIndex, + never_been_pinged: bool, +} + +impl BaseTimeManager { + pub fn new() -> Self { + let now = Instant::now(); + Self { + start_instant: now, + sent_pings: PingStore::new(), + most_recent_ping: 0, + never_been_pinged: true, + } + } + + // Ping & Pong + + pub fn send_ping(&mut self, io: &mut Io) { + let mut writer = BitWriter::new(); + + // write header + StandardHeader::new(PacketType::Ping, 0, 0, 0).ser(&mut writer); + + // Record ping + let ping_index = self.sent_pings.push_new(self.game_time_now()); + + // write index + ping_index.ser(&mut writer); + + // send packet + if io.send_writer(&mut writer).is_err() { + // TODO: pass this on and handle above + warn!("Client Error: Cannot send ping packet to Server"); + } + } + + pub fn read_pong( + &mut self, + reader: &mut BitReader, + ) -> Result, SerdeErr> { + // important to record receipt time ASAP + let client_received_time = self.game_time_now(); + + // read ping index + let ping_index = PingIndex::de(reader)?; + + // get client sent time from ping index + let Some(client_sent_time) = self.sent_pings.remove(ping_index) else { + warn!("Unknown pong received"); + + // TODO: should bubble up another error + return Err(SerdeErr); + }; + + // read server received time + let server_received_time = GameInstant::de(reader)?; + + // read average tick duration + // convert from microseconds to milliseconds + let tick_duration_avg = (UnsignedVariableInteger::<9>::de(reader)?.get() as f32) / 1000.0; + + let tick_speeedup_potential = + (UnsignedVariableInteger::<9>::de(reader)?.get() as f32) / 1000.0; + + // read server sent time + let server_sent_time = GameInstant::de(reader)?; + + // if this is the most recent Ping or the 1st ping, apply values + if sequence_greater_than(ping_index, self.most_recent_ping) || self.never_been_pinged { + self.never_been_pinged = false; + self.most_recent_ping = ping_index; + + let send_offset_millis = server_received_time.offset_from(&client_sent_time); + let recv_offset_millis = server_sent_time.offset_from(&client_received_time); + + let round_trip_time_millis = client_received_time + .time_since(&client_sent_time) + .as_millis(); + let server_process_time_millis = server_sent_time + .time_since(&server_received_time) + .as_millis(); + + // Final values + let time_offset_millis = (send_offset_millis + recv_offset_millis) / 2; + let round_trip_delay_millis = round_trip_time_millis - server_process_time_millis; + + return Ok(Some(( + tick_duration_avg, + tick_speeedup_potential, + time_offset_millis, + round_trip_delay_millis, + ))); + } + + return Ok(None); + } + + pub fn game_time_now(&self) -> GameInstant { + GameInstant::new(&self.start_instant) + } + + pub fn game_time_since(&self, previous_instant: &GameInstant) -> GameDuration { + self.game_time_now().time_since(previous_instant) + } + + pub fn sent_pings_clear(&mut self) { + self.sent_pings.clear(); + } +} diff --git a/client/src/tick/channel_tick_buffer_sender.rs b/client/src/connection/channel_tick_buffer_sender.rs similarity index 86% rename from client/src/tick/channel_tick_buffer_sender.rs rename to client/src/connection/channel_tick_buffer_sender.rs index 2d67b15ee..c7614298d 100644 --- a/client/src/tick/channel_tick_buffer_sender.rs +++ b/client/src/connection/channel_tick_buffer_sender.rs @@ -1,33 +1,27 @@ -use std::{collections::VecDeque, time::Duration}; +use std::collections::VecDeque; -use log::{info, warn}; +use log::warn; use naia_shared::{ sequence_greater_than, sequence_less_than, wrapping_diff, BitWrite, BitWriter, ChannelWriter, - Instant, Message, MessageKinds, Serde, ShortMessageIndex, Tick, TickBufferSettings, - UnsignedVariableInteger, MESSAGE_HISTORY_SIZE, + Message, MessageKinds, Serde, ShortMessageIndex, Tick, TickBufferSettings, + UnsignedVariableInteger, }; pub struct ChannelTickBufferSender { sending_messages: OutgoingMessages, outgoing_messages: VecDeque<(Tick, Vec<(ShortMessageIndex, Box)>)>, - resend_interval: Duration, - resend_interval_millis: u32, - last_sent: Instant, + last_sent: Tick, + never_sent: bool, } impl ChannelTickBufferSender { - pub fn new(tick_duration: &Duration, settings: &TickBufferSettings) -> Self { - let resend_interval = Duration::from_millis( - ((settings.tick_resend_factor as u128) * tick_duration.as_millis()) as u64, - ); - + pub fn new(settings: TickBufferSettings) -> Self { Self { - sending_messages: OutgoingMessages::new(), + sending_messages: OutgoingMessages::new(settings.message_capacity), outgoing_messages: VecDeque::new(), - resend_interval, - resend_interval_millis: resend_interval.as_millis() as u32, - last_sent: Instant::now(), + last_sent: 0, + never_sent: true, } } @@ -36,34 +30,29 @@ impl ChannelTickBufferSender { client_sending_tick: &Tick, server_receivable_tick: &Tick, ) { - if self.last_sent.elapsed() >= self.resend_interval { + if sequence_greater_than(*client_sending_tick, self.last_sent) || self.never_sent { // Remove messages that would never be able to reach the Server self.sending_messages .pop_back_until_excluding(server_receivable_tick); - self.last_sent = Instant::now(); + self.last_sent = *client_sending_tick; + self.never_sent = true; // Loop through outstanding messages and add them to the outgoing list for (message_tick, message_map) in self.sending_messages.iter() { if sequence_greater_than(*message_tick, *client_sending_tick) { - //info!("found message that is more recent than client sending tick! (how?)"); + warn!("Sending message that is more recent than client sending tick! This shouldn't be possible."); break; } + let messages = message_map.collect_messages(); self.outgoing_messages.push_back((*message_tick, messages)); } - - // if self.next_send_messages.len() > 0 { - // info!("next_send_messages.len() = {} messages", - // self.next_send_messages.len()); } } } pub fn send_message(&mut self, host_tick: &Tick, message: Box) { self.sending_messages.push(*host_tick, message); - - self.last_sent = Instant::now(); - self.last_sent.subtract_millis(self.resend_interval_millis); } pub fn has_messages(&self) -> bool { @@ -244,12 +233,15 @@ struct OutgoingMessages { // front big, back small // front recent, back past buffer: VecDeque<(Tick, MessageMap)>, + // this is the maximum length of the buffer + capacity: usize, } impl OutgoingMessages { - pub fn new() -> Self { + pub fn new(capacity: usize) -> Self { OutgoingMessages { buffer: VecDeque::new(), + capacity, } } @@ -277,9 +269,8 @@ impl OutgoingMessages { self.buffer.push_front((message_tick, msg_map)); // a good time to prune down this list - while self.buffer.len() > MESSAGE_HISTORY_SIZE.into() { + while self.buffer.len() > self.capacity { self.buffer.pop_back(); - info!("pruning outgoing_messages buffer cause it got too big"); } } @@ -297,10 +288,6 @@ impl OutgoingMessages { } } - pub fn iter(&self) -> impl Iterator { - self.buffer.iter() - } - pub fn remove_message(&mut self, tick: &Tick, message_index: &ShortMessageIndex) { let mut index = self.buffer.len(); @@ -318,7 +305,6 @@ impl OutgoingMessages { if *old_tick == *tick { // found it! message_map.remove(message_index); - //info!("removed delivered message! tick: {}, msg_id: {}", tick, msg_id); if message_map.len() == 0 { remove = true; } @@ -340,4 +326,8 @@ impl OutgoingMessages { } } } + + pub fn iter(&self) -> impl Iterator { + self.buffer.iter() + } } diff --git a/client/src/connection/connection.rs b/client/src/connection/connection.rs index 4028e5b44..75d3a2a7f 100644 --- a/client/src/connection/connection.rs +++ b/client/src/connection/connection.rs @@ -1,19 +1,19 @@ -use std::{hash::Hash, net::SocketAddr, time::Duration}; +use std::{hash::Hash, net::SocketAddr}; use log::warn; use naia_shared::{ BaseConnection, BitReader, BitWriter, ChannelKinds, ConnectionConfig, HostType, Instant, - OwnedBitReader, PacketType, PingManager, Protocol, ProtocolIo, Serde, StandardHeader, Tick, + OwnedBitReader, PacketType, Protocol, ProtocolIo, Serde, SerdeErr, StandardHeader, Tick, WorldMutType, }; use crate::{ + connection::{ + tick_buffer_sender::TickBufferSender, tick_queue::TickQueue, time_manager::TimeManager, + }, events::Events, protocol::entity_manager::EntityManager, - tick::{ - tick_buffer_sender::TickBufferSender, tick_manager::TickManager, tick_queue::TickQueue, - }, }; use super::io::Io; @@ -21,8 +21,8 @@ use super::io::Io; pub struct Connection { pub base: BaseConnection, pub entity_manager: EntityManager, - pub ping_manager: PingManager, - pub tick_buffer: Option, + pub time_manager: TimeManager, + pub tick_buffer: TickBufferSender, /// Small buffer when receiving updates (entity actions, entity updates) from the server /// to make sure we receive them in order jitter_buffer: TickQueue, @@ -32,17 +32,15 @@ impl Connection { pub fn new( address: SocketAddr, connection_config: &ConnectionConfig, - tick_duration: &Option, channel_kinds: &ChannelKinds, + time_manager: TimeManager, ) -> Self { - let tick_buffer = tick_duration - .as_ref() - .map(|duration| TickBufferSender::new(channel_kinds, duration)); + let tick_buffer = TickBufferSender::new(channel_kinds); Connection { base: BaseConnection::new(address, HostType::Client, connection_config, channel_kinds), entity_manager: EntityManager::default(), - ping_manager: PingManager::new(&connection_config.ping), + time_manager, tick_buffer, jitter_buffer: TickQueue::new(), } @@ -51,17 +49,18 @@ impl Connection { // Incoming data pub fn process_incoming_header(&mut self, header: &StandardHeader) { - match &mut self.tick_buffer { - Some(tick_buffer) => self - .base - .process_incoming_header(header, &mut Some(tick_buffer)), - None => self.base.process_incoming_header(header, &mut None), - } + self.base + .process_incoming_header(header, &mut Some(&mut self.tick_buffer)); } - pub fn buffer_data_packet(&mut self, incoming_tick: Tick, reader: &mut BitReader) { + pub fn buffer_data_packet( + &mut self, + incoming_tick: &Tick, + reader: &mut BitReader, + ) -> Result<(), SerdeErr> { self.jitter_buffer - .add_item(incoming_tick, reader.to_owned()); + .add_item(*incoming_tick, reader.to_owned()); + Ok(()) } /// Read the packets (raw bits) from the jitter buffer that correspond to the @@ -76,9 +75,10 @@ impl Connection { &mut self, protocol: &Protocol, world: &mut W, - receiving_tick: Tick, incoming_events: &mut Events, ) { + let receiving_tick = self.time_manager.client_receiving_tick; + while let Some((server_tick, owned_reader)) = self.jitter_buffer.pop_item(receiving_tick) { let mut reader = owned_reader.borrow(); @@ -99,14 +99,17 @@ impl Connection { // read entity updates { - let updates_result = self.entity_manager.read_updates( - &protocol.component_kinds, - world, - server_tick, - &mut reader, - incoming_events, - ); - if updates_result.is_err() { + if self + .entity_manager + .read_updates( + &protocol.component_kinds, + world, + server_tick, + &mut reader, + incoming_events, + ) + .is_err() + { // TODO: Except for cosmic radiation .. Server should never send a malformed packet .. handle this warn!("Error reading incoming entity updates from packet!"); continue; @@ -115,13 +118,16 @@ impl Connection { // read entity actions { - let actions_result = self.entity_manager.read_actions( - &protocol.component_kinds, - world, - &mut reader, - incoming_events, - ); - if actions_result.is_err() { + if self + .entity_manager + .read_actions( + &protocol.component_kinds, + world, + &mut reader, + incoming_events, + ) + .is_err() + { // TODO: Except for cosmic radiation .. Server should never send a malformed packet .. handle this warn!("Error reading incoming entity actions from packet!"); continue; @@ -145,17 +151,12 @@ impl Connection { /// * messages /// * acks from reliable channels /// * acks from the `EntityActionReceiver` for all [`EntityAction`]s - pub fn send_outgoing_packets( - &mut self, - protocol: &Protocol, - io: &mut Io, - tick_manager_opt: &Option, - ) { - self.collect_outgoing_messages(tick_manager_opt); + pub fn send_outgoing_packets(&mut self, protocol: &Protocol, io: &mut Io) { + self.collect_outgoing_messages(); let mut any_sent = false; loop { - if self.send_outgoing_packet(protocol, io, tick_manager_opt) { + if self.send_outgoing_packet(protocol, io) { any_sent = true; } else { break; @@ -166,35 +167,22 @@ impl Connection { } } - fn collect_outgoing_messages(&mut self, tick_manager_opt: &Option) { + fn collect_outgoing_messages(&mut self) { let now = Instant::now(); self.base .message_manager - .collect_outgoing_messages(&now, &self.ping_manager.rtt); - - if let Some(tick_manager) = tick_manager_opt { - self.tick_buffer - .as_mut() - .expect("connection is not configured with a Tick Buffer") - .collect_outgoing_messages( - &tick_manager.client_sending_tick(), - &tick_manager.server_receivable_tick(), - ); - } + .collect_outgoing_messages(&now, &self.time_manager.rtt()); + + self.tick_buffer.collect_outgoing_messages( + &self.time_manager.client_sending_tick, + &self.time_manager.server_receivable_tick, + ); } // Sends packet and returns whether or not a packet was sent - fn send_outgoing_packet( - &mut self, - protocol: &Protocol, - io: &mut Io, - tick_manager_opt: &Option, - ) -> bool { - let tick_buffer_has_outgoing_messages = match &self.tick_buffer { - Some(tick_buffer) => tick_buffer.has_outgoing_messages(), - None => false, - }; + fn send_outgoing_packet(&mut self, protocol: &Protocol, io: &mut Io) -> bool { + let tick_buffer_has_outgoing_messages = self.tick_buffer.has_outgoing_messages(); if self.base.message_manager.has_outgoing_messages() || tick_buffer_has_outgoing_messages { let next_packet_index = self.base.next_packet_index(); @@ -214,24 +202,23 @@ impl Connection { let mut has_written = false; - if let Some(tick_manager) = tick_manager_opt { - // write tick - let client_tick = tick_manager.write_client_tick(&mut bit_writer); + // write tick + let client_tick: Tick = self.time_manager.client_sending_tick; + client_tick.ser(&mut bit_writer); - // write tick buffered messages - self.tick_buffer.as_mut().unwrap().write_messages( - &protocol, - &channel_writer, - &mut bit_writer, - next_packet_index, - &client_tick, - &mut has_written, - ); + // write tick buffered messages + self.tick_buffer.write_messages( + &protocol, + &channel_writer, + &mut bit_writer, + next_packet_index, + &client_tick, + &mut has_written, + ); - // finish tick buffered messages - false.ser(&mut bit_writer); - bit_writer.release_bits(1); - } + // finish tick buffered messages + false.ser(&mut bit_writer); + bit_writer.release_bits(1); // write messages { @@ -249,12 +236,9 @@ impl Connection { } // send packet - match io.send_writer(&mut bit_writer) { - Ok(()) => {} - Err(_) => { - // TODO: pass this on and handle above - warn!("Client Error: Cannot send data packet to Server"); - } + if io.send_writer(&mut bit_writer).is_err() { + // TODO: pass this on and handle above + warn!("Client Error: Cannot send data packet to Server"); } return true; diff --git a/client/src/connection/handshake_manager.rs b/client/src/connection/handshake_manager.rs index 5d08e182d..aafffd0ea 100644 --- a/client/src/connection/handshake_manager.rs +++ b/client/src/connection/handshake_manager.rs @@ -1,38 +1,62 @@ -use log::warn; use std::time::Duration; -use naia_shared::{BitReader, BitWriter, FakeEntityConverter, Message, MessageKinds, Serde}; -pub use naia_shared::{ - ConnectionConfig, PacketType, Replicate, StandardHeader, Timer, Timestamp as stamp_time, - WorldMutType, WorldRefType, +use log::warn; + +use naia_shared::{ + BitReader, BitWriter, FakeEntityConverter, Message, MessageKinds, PacketType, Serde, + StandardHeader, Timer, Timestamp as stamp_time, }; use super::io::Io; +use crate::connection::{handshake_time_manager::HandshakeTimeManager, time_manager::TimeManager}; pub type Timestamp = u64; -#[derive(Debug, Eq, PartialEq)] pub enum HandshakeState { AwaitingChallengeResponse, - AwaitingConnectResponse, + AwaitingValidateResponse, + TimeSync(HandshakeTimeManager), + AwaitingConnectResponse(TimeManager), Connected, } +impl HandshakeState { + fn get_index(&self) -> u8 { + match self { + HandshakeState::AwaitingChallengeResponse => 0, + HandshakeState::AwaitingValidateResponse => 1, + HandshakeState::TimeSync(_) => 2, + HandshakeState::AwaitingConnectResponse(_) => 3, + HandshakeState::Connected => 4, + } + } +} + +impl Eq for HandshakeState {} + +impl PartialEq for HandshakeState { + fn eq(&self, other: &Self) -> bool { + other.get_index() == self.get_index() + } +} + pub enum HandshakeResult { - Connected, + Connected(TimeManager), Rejected, } pub struct HandshakeManager { + ping_interval: Duration, + handshake_pings: u8, + pub connection_state: HandshakeState, handshake_timer: Timer, pre_connection_timestamp: Timestamp, pre_connection_digest: Option>, - pub connection_state: HandshakeState, auth_message: Option>, } impl HandshakeManager { - pub fn new(send_interval: Duration) -> Self { + pub fn new(send_interval: Duration, ping_interval: Duration, handshake_pings: u8) -> Self { let mut handshake_timer = Timer::new(send_interval); handshake_timer.ring_manual(); @@ -44,6 +68,8 @@ impl HandshakeManager { pre_connection_digest: None, connection_state: HandshakeState::AwaitingChallengeResponse, auth_message: None, + ping_interval, + handshake_pings, } } @@ -64,30 +90,36 @@ impl HandshakeManager { self.handshake_timer.reset(); - match self.connection_state { - HandshakeState::Connected => { - // do nothing, not necessary - } + match &mut self.connection_state { HandshakeState::AwaitingChallengeResponse => { let mut writer = self.write_challenge_request(); - match io.send_writer(&mut writer) { - Ok(()) => {} - Err(_) => { - // TODO: pass this on and handle above - warn!("Client Error: Cannot send challenge request packet to Server"); - } + if io.send_writer(&mut writer).is_err() { + // TODO: pass this on and handle above + warn!("Client Error: Cannot send challenge request packet to Server"); } } - HandshakeState::AwaitingConnectResponse => { - let mut writer = self.write_connect_request(message_kinds); - match io.send_writer(&mut writer) { - Ok(()) => {} - Err(_) => { - // TODO: pass this on and handle above - warn!("Client Error: Cannot send connect request packet to Server"); - } + HandshakeState::AwaitingValidateResponse => { + let mut writer = self.write_validate_request(message_kinds); + if io.send_writer(&mut writer).is_err() { + // TODO: pass this on and handle above + warn!("Client Error: Cannot send validate request packet to Server"); } } + HandshakeState::TimeSync(time_manager) => { + // use time manager to send initial pings until client/server time is synced + // then, move state to AwaitingConnectResponse + time_manager.send_ping(io); + } + HandshakeState::AwaitingConnectResponse(_) => { + let mut writer = self.write_connect_request(); + if io.send_writer(&mut writer).is_err() { + // TODO: pass this on and handle above + warn!("Client Error: Cannot send connect request packet to Server"); + } + } + HandshakeState::Connected => { + // do nothing, not necessary + } } } } @@ -102,11 +134,49 @@ impl HandshakeManager { match header.packet_type { PacketType::ServerChallengeResponse => { self.recv_challenge_response(reader); - None + return None; + } + PacketType::ServerValidateResponse => { + if self.connection_state == HandshakeState::AwaitingValidateResponse { + self.recv_validate_response(); + } + return None; + } + PacketType::ServerConnectResponse => { + return self.recv_connect_response(); + } + PacketType::ServerRejectResponse => { + return Some(HandshakeResult::Rejected); + } + PacketType::Pong => { + // Time Manager should record incoming Pongs in order to sync time + let mut success = false; + if let HandshakeState::TimeSync(time_manager) = &mut self.connection_state { + let Ok(success_inner) = time_manager.read_pong(reader) else { + // TODO: bubble this up + warn!("Time Manager cannot process pong"); + return None; + }; + success = success_inner; + } + if success { + let HandshakeState::TimeSync(time_manager) = std::mem::replace(&mut self.connection_state, HandshakeState::Connected) else { + panic!("should be impossible due to check above"); + }; + self.connection_state = + HandshakeState::AwaitingConnectResponse(time_manager.finalize()); + } + return None; + } + PacketType::Data + | PacketType::Heartbeat + | PacketType::ClientChallengeRequest + | PacketType::ClientValidateRequest + | PacketType::ClientConnectRequest + | PacketType::Ping + | PacketType::Disconnect => { + return None; } - PacketType::ServerConnectResponse => self.recv_connect_response(), - PacketType::ServerRejectResponse => Some(HandshakeResult::Rejected), - _ => None, } } @@ -137,16 +207,16 @@ impl HandshakeManager { let digest_bytes = digest_bytes_result.unwrap(); self.pre_connection_digest = Some(digest_bytes); - self.connection_state = HandshakeState::AwaitingConnectResponse; + self.connection_state = HandshakeState::AwaitingValidateResponse; } } } // Step 3 of Handshake - pub fn write_connect_request(&self, message_kinds: &MessageKinds) -> BitWriter { + pub fn write_validate_request(&self, message_kinds: &MessageKinds) -> BitWriter { let mut writer = BitWriter::new(); - StandardHeader::new(PacketType::ClientConnectRequest, 0, 0, 0).ser(&mut writer); + StandardHeader::new(PacketType::ClientValidateRequest, 0, 0, 0).ser(&mut writer); // write timestamp & digest into payload self.write_signed_timestamp(&mut writer); @@ -166,15 +236,28 @@ impl HandshakeManager { } // Step 4 of Handshake - pub fn recv_connect_response(&mut self) -> Option { - let was_not_connected = self.connection_state != HandshakeState::Connected; + pub fn recv_validate_response(&mut self) { + self.connection_state = HandshakeState::TimeSync(HandshakeTimeManager::new( + self.ping_interval, + self.handshake_pings, + )); + } - self.connection_state = HandshakeState::Connected; + // Step 5 of Handshake + pub fn write_connect_request(&self) -> BitWriter { + let mut writer = BitWriter::new(); + StandardHeader::new(PacketType::ClientConnectRequest, 0, 0, 0).ser(&mut writer); - match was_not_connected { - true => Some(HandshakeResult::Connected), - false => None, - } + writer + } + + // Step 6 of Handshake + fn recv_connect_response(&mut self) -> Option { + let HandshakeState::AwaitingConnectResponse(time_manager) = std::mem::replace(&mut self.connection_state, HandshakeState::Connected) else { + return None; + }; + + return Some(HandshakeResult::Connected(time_manager)); } // Send 10 disconnect packets diff --git a/client/src/connection/handshake_time_manager.rs b/client/src/connection/handshake_time_manager.rs new file mode 100644 index 000000000..7b56ef639 --- /dev/null +++ b/client/src/connection/handshake_time_manager.rs @@ -0,0 +1,156 @@ +use std::time::Duration; + +use naia_shared::{BitReader, GameInstant, Serde, SerdeErr, Tick, GAME_TIME_LIMIT}; + +use crate::connection::{base_time_manager::BaseTimeManager, io::Io, time_manager::TimeManager}; + +pub struct HandshakeTimeManager { + base: BaseTimeManager, + handshake_pings: u8, + ping_interval: Duration, + pong_stats: Vec<(f32, f32)>, + server_tick: Tick, + server_tick_instant: GameInstant, + server_tick_duration_avg: f32, + server_speedup_potential: f32, +} + +impl HandshakeTimeManager { + pub fn new(ping_interval: Duration, handshake_pings: u8) -> Self { + let base = BaseTimeManager::new(); + let server_tick_instant = base.game_time_now(); + Self { + base, + ping_interval, + handshake_pings, + pong_stats: Vec::new(), + server_tick: 0, + server_tick_instant, + server_tick_duration_avg: 0.0, + server_speedup_potential: 0.0, + } + } + + pub(crate) fn send_ping(&mut self, io: &mut Io) { + self.base.send_ping(io); + } + + pub(crate) fn read_pong(&mut self, reader: &mut BitReader) -> Result { + // read server tick + let server_tick = Tick::de(reader)?; + + // read time since last tick + let server_tick_instant = GameInstant::de(reader)?; + + if let Some((duration_avg, speedup_potential, offset_millis, rtt_millis)) = + self.base.read_pong(reader)? + { + self.server_tick = server_tick; + self.server_tick_instant = server_tick_instant; + self.server_tick_duration_avg = duration_avg; + self.server_speedup_potential = speedup_potential; + + self.buffer_stats(offset_millis, rtt_millis); + if self.pong_stats.len() >= self.handshake_pings as usize { + return Ok(true); + } + } + + return Ok(false); + } + + fn buffer_stats(&mut self, time_offset_millis: i32, rtt_millis: u32) { + let time_offset_millis_f32 = time_offset_millis as f32; + let rtt_millis_f32 = rtt_millis as f32; + + self.pong_stats + .push((time_offset_millis_f32, rtt_millis_f32)); + } + + // This happens when a necessary # of handshake pongs have been recorded + pub fn finalize(mut self) -> TimeManager { + let sample_count = self.pong_stats.len() as f32; + + let pongs = std::mem::take(&mut self.pong_stats); + + // Find the Mean + let mut offset_mean = 0.0; + let mut rtt_mean = 0.0; + + for (time_offset_millis, rtt_millis) in &pongs { + offset_mean += *time_offset_millis; + rtt_mean += *rtt_millis; + } + + offset_mean /= sample_count; + rtt_mean /= sample_count; + + // Find the Variance + let mut offset_diff_mean = 0.0; + let mut rtt_diff_mean = 0.0; + + for (time_offset_millis, rtt_millis) in &pongs { + offset_diff_mean += (*time_offset_millis - offset_mean).powi(2); + rtt_diff_mean += (*rtt_millis - rtt_mean).powi(2); + } + + offset_diff_mean /= sample_count; + rtt_diff_mean /= sample_count; + + // Find the Standard Deviation + let offset_stdv = offset_diff_mean.sqrt(); + let rtt_stdv = rtt_diff_mean.sqrt(); + + // Prune out any pong values outside the standard deviation (mitigation) + let mut pruned_pongs = Vec::new(); + for (time_offset_millis, rtt_millis) in pongs { + let offset_diff = (time_offset_millis - offset_mean).abs(); + let rtt_diff = (rtt_millis - rtt_mean).abs(); + if offset_diff < offset_stdv && rtt_diff < rtt_stdv { + pruned_pongs.push((time_offset_millis, rtt_millis)); + } + } + + // Find the mean of the pruned pongs + let pruned_sample_count = pruned_pongs.len() as f32; + let mut pruned_offset_mean = 0.0; + let mut pruned_rtt_mean = 0.0; + + for (time_offset_millis, rtt_millis) in pruned_pongs { + pruned_offset_mean += time_offset_millis; + pruned_rtt_mean += rtt_millis; + } + + pruned_offset_mean /= pruned_sample_count; + pruned_rtt_mean /= pruned_sample_count; + + // Get values we were looking for + + // Set internal time to match offset + if pruned_offset_mean < 0.0 { + let offset_ms = (pruned_offset_mean * -1.0) as u32; + self.base.start_instant.subtract_millis(offset_ms); + } else { + let offset_ms = pruned_offset_mean as u32; + // start_instant should only be able to go BACK in time, otherwise `.elapsed()` might not work + self.base + .start_instant + .subtract_millis(GAME_TIME_LIMIT - offset_ms); + } + + // Clear out outstanding pings + self.base.sent_pings_clear(); + + TimeManager::from_parts( + self.ping_interval, + self.base, + self.server_tick, + self.server_tick_instant, + self.server_tick_duration_avg, + self.server_speedup_potential, + pruned_rtt_mean, + rtt_stdv, + offset_stdv, + ) + } +} diff --git a/client/src/connection/mod.rs b/client/src/connection/mod.rs index 91a4dd383..c9d7a3a83 100644 --- a/client/src/connection/mod.rs +++ b/client/src/connection/mod.rs @@ -1,4 +1,10 @@ +pub mod base_time_manager; +pub mod channel_tick_buffer_sender; #[allow(clippy::module_inception)] pub mod connection; pub mod handshake_manager; +pub mod handshake_time_manager; pub mod io; +pub mod tick_buffer_sender; +pub mod tick_queue; +pub mod time_manager; diff --git a/client/src/tick/tick_buffer_sender.rs b/client/src/connection/tick_buffer_sender.rs similarity index 95% rename from client/src/tick/tick_buffer_sender.rs rename to client/src/connection/tick_buffer_sender.rs index 92e536c6a..9b26e54b5 100644 --- a/client/src/tick/tick_buffer_sender.rs +++ b/client/src/connection/tick_buffer_sender.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, time::Duration}; +use std::collections::HashMap; use naia_shared::{ BitWrite, BitWriter, ChannelKind, ChannelKinds, ChannelMode, ChannelWriter, Message, @@ -14,14 +14,14 @@ pub struct TickBufferSender { } impl TickBufferSender { - pub fn new(channel_kinds: &ChannelKinds, tick_duration: &Duration) -> Self { + pub fn new(channel_kinds: &ChannelKinds) -> Self { // initialize senders let mut channel_senders = HashMap::new(); for (channel_index, channel) in channel_kinds.channels() { if let ChannelMode::TickBuffered(settings) = &channel.mode { channel_senders.insert( channel_index, - ChannelTickBufferSender::new(tick_duration, settings), + ChannelTickBufferSender::new(settings.clone()), ); } } diff --git a/client/src/tick/tick_queue.rs b/client/src/connection/tick_queue.rs similarity index 100% rename from client/src/tick/tick_queue.rs rename to client/src/connection/tick_queue.rs diff --git a/client/src/connection/time_manager.rs b/client/src/connection/time_manager.rs new file mode 100644 index 000000000..d7e640151 --- /dev/null +++ b/client/src/connection/time_manager.rs @@ -0,0 +1,501 @@ +use log::warn; +use std::time::Duration; + +use naia_shared::{ + sequence_greater_than, sequence_less_than, wrapping_diff, BitReader, GameDuration, GameInstant, + Instant, SerdeErr, Tick, Timer, +}; + +use crate::connection::{base_time_manager::BaseTimeManager, io::Io}; + +pub struct TimeManager { + base: BaseTimeManager, + ping_timer: Timer, + + // Stats + pruned_offset_avg: f32, + raw_offset_avg: f32, + offset_stdv: f32, + pruned_rtt_avg: f32, + raw_rtt_avg: f32, + rtt_stdv: f32, + + // Ticks + accumulator: f32, + + server_tick: Tick, + server_tick_instant: GameInstant, + server_tick_duration_avg: f32, + server_speedup_potential: f32, + + last_tick_check_instant: Instant, + pub client_receiving_tick: Tick, + pub client_sending_tick: Tick, + pub server_receivable_tick: Tick, + client_receiving_instant: GameInstant, + client_sending_instant: GameInstant, + server_receivable_instant: GameInstant, +} + +impl TimeManager { + pub fn from_parts( + ping_interval: Duration, + base: BaseTimeManager, + server_tick: Tick, + server_tick_instant: GameInstant, + server_tick_duration_avg: f32, + server_speedup_potential: f32, + pruned_rtt_avg: f32, + rtt_stdv: f32, + offset_stdv: f32, + ) -> Self { + let now = base.game_time_now(); + let latency_ms = (pruned_rtt_avg / 2.0) as u32; + let major_jitter_ms = (rtt_stdv / 2.0 * 3.0) as u32; + let tick_duration_ms = server_tick_duration_avg.round() as u32; + + let client_receiving_instant = + get_client_receiving_target(&now, latency_ms, major_jitter_ms, tick_duration_ms); + let client_sending_instant = + get_client_sending_target(&now, latency_ms, major_jitter_ms, tick_duration_ms, 1.0); + let server_receivable_instant = + get_server_receivable_target(&now, latency_ms, major_jitter_ms, tick_duration_ms); + + let client_receiving_tick = instant_to_tick( + &server_tick, + &server_tick_instant, + server_tick_duration_avg, + &client_receiving_instant, + ); + let client_sending_tick = instant_to_tick( + &server_tick, + &server_tick_instant, + server_tick_duration_avg, + &client_sending_instant, + ); + let server_receivable_tick = instant_to_tick( + &server_tick, + &server_tick_instant, + server_tick_duration_avg, + &server_receivable_instant, + ); + + Self { + base, + ping_timer: Timer::new(ping_interval), + + pruned_offset_avg: 0.0, + raw_offset_avg: 0.0, + offset_stdv, + + pruned_rtt_avg, + raw_rtt_avg: pruned_rtt_avg, + rtt_stdv, + + accumulator: 0.0, + + server_tick, + server_tick_instant, + server_tick_duration_avg, + server_speedup_potential, + + last_tick_check_instant: Instant::now(), + + client_receiving_tick, + client_sending_tick, + server_receivable_tick, + + client_receiving_instant, + client_sending_instant, + server_receivable_instant, + } + } + + // Base + + pub fn send_ping(&mut self, io: &mut Io) -> bool { + if self.ping_timer.ringing() { + self.ping_timer.reset(); + + self.base.send_ping(io); + + return true; + } + + return false; + } + + pub fn read_pong(&mut self, reader: &mut BitReader) -> Result<(), SerdeErr> { + if let Some((tick_duration_avg, speedup_potential, offset_millis, rtt_millis)) = + self.base.read_pong(reader)? + { + self.process_stats(offset_millis, rtt_millis); + self.recv_tick_duration_avg(tick_duration_avg, speedup_potential); + } + Ok(()) + } + + fn process_stats(&mut self, offset_millis: i32, rtt_millis: u32) { + let offset_sample = offset_millis as f32; + let rtt_sample = rtt_millis as f32; + + self.raw_offset_avg = (0.9 * self.raw_offset_avg) + (0.1 * offset_sample); + self.raw_rtt_avg = (0.9 * self.raw_rtt_avg) + (0.1 * rtt_sample); + + let offset_diff = offset_sample - self.raw_offset_avg; + let rtt_diff = rtt_sample - self.raw_rtt_avg; + + self.offset_stdv = ((0.9 * self.offset_stdv.powi(2)) + (0.1 * offset_diff.powi(2))).sqrt(); + self.rtt_stdv = ((0.9 * self.rtt_stdv.powi(2)) + (0.1 * rtt_diff.powi(2))).sqrt(); + + if offset_diff.abs() < self.offset_stdv && rtt_diff.abs() < self.rtt_stdv { + self.pruned_offset_avg = (0.9 * self.pruned_offset_avg) + (0.1 * offset_sample); + self.pruned_rtt_avg = (0.9 * self.pruned_rtt_avg) + (0.1 * rtt_sample); + } else { + // Pruned out sample + } + } + + // GameTime + + pub fn game_time_now(&self) -> GameInstant { + self.base.game_time_now() + } + + pub fn game_time_since(&self, previous_instant: &GameInstant) -> GameDuration { + self.base.game_time_since(previous_instant) + } + + // Tick + + pub(crate) fn recv_tick_instant( + &mut self, + server_tick: &Tick, + server_tick_instant: &GameInstant, + ) { + // only continue if this tick is the most recent + if !sequence_greater_than(*server_tick, self.server_tick) { + // We've already received the most recent tick + return; + } + + let prev_server_tick_instant = self.tick_to_instant(*server_tick); + let offset = prev_server_tick_instant.offset_from(&server_tick_instant); + + self.server_tick = *server_tick; + self.server_tick_instant = server_tick_instant.clone(); + + // Adjust tick instants to new incoming instant + self.client_receiving_instant = self.client_receiving_instant.add_signed_millis(offset); + self.client_sending_instant = self.client_sending_instant.add_signed_millis(offset); + self.server_receivable_instant = self.server_receivable_instant.add_signed_millis(offset); + } + + pub(crate) fn recv_tick_duration_avg( + &mut self, + server_tick_duration_avg: f32, + server_speedup_potential: f32, + ) { + let client_receiving_interp = + self.get_interp(self.client_receiving_tick, &self.client_receiving_instant); + let client_sending_interp = + self.get_interp(self.client_sending_tick, &self.client_sending_instant); + let server_receivable_interp = + self.get_interp(self.server_receivable_tick, &self.server_receivable_instant); + + self.server_tick_duration_avg = server_tick_duration_avg; + self.server_speedup_potential = server_speedup_potential; + + // Adjust tick instants to new incoming instant + self.client_receiving_instant = + self.instant_from_interp(self.client_receiving_tick, client_receiving_interp); + self.client_sending_instant = + self.instant_from_interp(self.client_sending_tick, client_sending_interp); + self.server_receivable_instant = + self.instant_from_interp(self.server_receivable_tick, server_receivable_interp); + } + + pub(crate) fn check_ticks(&mut self) -> (Option<(Tick, Tick)>, Option<(Tick, Tick)>) { + // updates client_receiving_tick + // returns (Some(start_tick, end_tick), None) if a client_receiving_tick has incremented + // returns (None, Some(start_tick, end_tick)) if a client_sending_tick or server_receivable_tick has incremented + let prev_client_receiving_tick = self.client_receiving_tick; + let prev_client_sending_tick = self.client_sending_tick; + + { + let time_elapsed = self.last_tick_check_instant.elapsed().as_secs_f32() * 1000.0; + self.last_tick_check_instant = Instant::now(); + self.accumulator += time_elapsed; + if self.accumulator < 1.0 { + return (None, None); + } + } + let millis_elapsed = self.accumulator.round() as u32; + let millis_elapsed_f32 = millis_elapsed as f32; + self.accumulator -= millis_elapsed_f32; + + // Target Instants + let now: GameInstant = self.game_time_now(); + let latency_ms: u32 = self.latency().round() as u32; + let major_jitter_ms: u32 = (self.jitter() * 3.0).round() as u32; + + let tick_duration_ms: u32 = self.server_tick_duration_avg.round() as u32; + + // find targets + let client_receiving_target = + get_client_receiving_target(&now, latency_ms, major_jitter_ms, tick_duration_ms); + + let client_sending_target = get_client_sending_target( + &now, + latency_ms, + major_jitter_ms, + tick_duration_ms, + self.server_speedup_potential, + ); + let server_receivable_target = + get_server_receivable_target(&now, latency_ms, major_jitter_ms, tick_duration_ms); + + // set default next instant + let client_receiving_default_next = + self.client_receiving_instant.add_millis(millis_elapsed); + let client_sending_default_next = self.client_sending_instant.add_millis(millis_elapsed); + let server_receivable_default_next = + self.server_receivable_instant.add_millis(millis_elapsed); + + // find speeds + let client_receiving_speed = + offset_to_speed(client_receiving_default_next.offset_from(&client_receiving_target)); + let client_sending_speed = + offset_to_speed(client_sending_default_next.offset_from(&client_sending_target)); + let server_receivable_speed = + offset_to_speed(server_receivable_default_next.offset_from(&server_receivable_target)); + + // apply speeds + self.client_receiving_instant = self + .client_receiving_instant + .add_millis((millis_elapsed_f32 * client_receiving_speed) as u32); + if self + .client_receiving_instant + .is_more_than(&client_receiving_target) + { + self.client_receiving_instant = client_receiving_target; + } + self.client_sending_instant = self + .client_sending_instant + .add_millis((millis_elapsed_f32 * client_sending_speed) as u32); + if self + .client_sending_instant + .is_more_than(&client_sending_target) + { + self.client_sending_instant = client_sending_target; + } + self.server_receivable_instant = self + .server_receivable_instant + .add_millis((millis_elapsed_f32 * server_receivable_speed) as u32); + if self + .server_receivable_instant + .is_more_than(&server_receivable_target) + { + self.server_receivable_instant = server_receivable_target; + } + + // convert current instants into ticks + let new_client_receiving_tick = instant_to_tick( + &self.server_tick, + &self.server_tick_instant, + self.server_tick_duration_avg, + &self.client_receiving_instant, + ); + let new_client_sending_tick = instant_to_tick( + &self.server_tick, + &self.server_tick_instant, + self.server_tick_duration_avg, + &self.client_sending_instant, + ); + let new_server_receivable_tick = instant_to_tick( + &self.server_tick, + &self.server_tick_instant, + self.server_tick_duration_avg, + &self.server_receivable_instant, + ); + + // make sure nothing ticks backwards + if sequence_less_than(new_client_receiving_tick, self.client_receiving_tick) { + warn!("Client Receiving Tick attempted to Tick Backwards"); + } else { + self.client_receiving_tick = new_client_receiving_tick; + } + if sequence_less_than(new_client_sending_tick, self.client_sending_tick) { + warn!("Client Sending Tick attempted to Tick Backwards"); + } else { + self.client_sending_tick = new_client_sending_tick; + } + if sequence_less_than(new_server_receivable_tick, self.server_receivable_tick) { + warn!("Server Receivable Tick attempted to Tick Backwards"); + } else { + self.server_receivable_tick = new_server_receivable_tick; + } + + let receiving_incremented = self.client_receiving_tick != prev_client_receiving_tick; + let sending_incremented = self.client_sending_tick != prev_client_sending_tick; + + let output_receiving = match receiving_incremented { + true => Some((prev_client_receiving_tick, self.client_receiving_tick)), + false => None, + }; + let output_sending = match sending_incremented { + true => Some((prev_client_sending_tick, self.client_sending_tick)), + false => None, + }; + + return (output_receiving, output_sending); + } + + // Stats + + pub(crate) fn client_interpolation(&self) -> f32 { + self.get_interp(self.client_sending_tick, &self.client_sending_instant) + } + + pub(crate) fn server_interpolation(&self) -> f32 { + self.get_interp(self.client_receiving_tick, &self.client_receiving_instant) + } + + pub(crate) fn rtt(&self) -> f32 { + self.pruned_rtt_avg + } + + pub(crate) fn jitter(&self) -> f32 { + self.rtt_stdv / 2.0 + } + + pub(crate) fn latency(&self) -> f32 { + self.pruned_rtt_avg / 2.0 + } + + pub(crate) fn tick_to_instant(&self, tick: Tick) -> GameInstant { + let tick_diff = wrapping_diff(self.server_tick, tick); + let tick_diff_duration = + ((tick_diff as f32) * self.server_tick_duration_avg).round() as i32; + return self + .server_tick_instant + .add_signed_millis(tick_diff_duration); + } + + pub(crate) fn get_interp(&self, tick: Tick, instant: &GameInstant) -> f32 { + let output = (self.tick_to_instant(tick).offset_from(&instant) as f32) + / self.server_tick_duration_avg; + output + } + + pub(crate) fn instant_from_interp(&self, tick: Tick, interp: f32) -> GameInstant { + let tick_length_interped = (interp * self.server_tick_duration_avg).round() as i32; + return self + .tick_to_instant(tick) + .add_signed_millis(tick_length_interped); + } +} + +fn instant_to_tick( + server_tick: &Tick, + server_tick_instant: &GameInstant, + server_tick_duration_avg: f32, + instant: &GameInstant, +) -> Tick { + let offset_ms = server_tick_instant.offset_from(instant); + let offset_ticks_f32 = (offset_ms as f32) / server_tick_duration_avg; + return server_tick + .clone() + .wrapping_add_signed(offset_ticks_f32 as i16); +} + +fn get_client_receiving_target( + now: &GameInstant, + latency: u32, + jitter: u32, + tick_duration: u32, +) -> GameInstant { + now.sub_millis(latency + jitter + tick_duration) +} + +fn get_client_sending_target( + now: &GameInstant, + latency: u32, + jitter: u32, + tick_duration: u32, + danger: f32, +) -> GameInstant { + let millis = + latency + jitter + (tick_duration * 4) + (tick_duration as f32 * danger).round() as u32; + now.add_millis(millis) +} + +fn get_server_receivable_target( + now: &GameInstant, + latency: u32, + jitter: u32, + tick_duration: u32, +) -> GameInstant { + let millis = (((latency + (tick_duration * 2)) as i32) - (jitter as i32)).max(0) as u32; + now.add_millis(millis) +} + +fn offset_to_speed(offset: i32) -> f32 { + if offset <= OFFSET_MIN { + let under = (OFFSET_MIN - offset) as f32; + return (RANGE_MIN / (under + RANGE_MIN)).max(SPEED_MIN); + } + + if offset >= OFFSET_MAX { + let over = (offset - OFFSET_MAX) as f32; + return (1.0 + (over / RANGE_MAX)).min(SPEED_MAX); + } + + return SAFE_SPEED; +} + +const OFFSET_MIN: i32 = -50; +const OFFSET_MAX: i32 = 50; +const SAFE_SPEED: f32 = 1.0; +const RANGE_MAX: f32 = 20.0; +const RANGE_MIN: f32 = 20.0; +const SPEED_MAX: f32 = 10.0; +const SPEED_MIN: f32 = 1.0 / SPEED_MAX; + +// Tests +#[cfg(test)] +mod offset_to_speed_tests { + use crate::connection::time_manager::{offset_to_speed, OFFSET_MAX, OFFSET_MIN, SAFE_SPEED}; + + #[test] + fn min_speed() { + assert_eq!(offset_to_speed(OFFSET_MIN), SAFE_SPEED); + } + + #[test] + fn max_speed() { + assert_eq!(offset_to_speed(OFFSET_MAX), SAFE_SPEED); + } + + #[test] + fn middle_speed() { + let middle_offset = ((OFFSET_MAX - OFFSET_MIN) / 2) + OFFSET_MIN; + assert_eq!(offset_to_speed(middle_offset), SAFE_SPEED); + } + + #[test] + fn over_max_speed() { + let offset = OFFSET_MAX + 5; + + // TODO: derive these values? + assert_eq!(offset_to_speed(offset), 1.5); + } + + #[test] + fn under_max_speed() { + let offset = OFFSET_MIN - 5; + + // TODO: derive these values? + assert_eq!(offset_to_speed(offset), 0.6666667); + } +} diff --git a/client/src/events.rs b/client/src/events.rs index de0b26589..43721f6dd 100644 --- a/client/src/events.rs +++ b/client/src/events.rs @@ -8,7 +8,8 @@ pub struct Events { connections: Vec, rejections: Vec, disconnections: Vec, - ticks: Vec<()>, + client_ticks: Vec, + server_ticks: Vec, errors: Vec, messages: HashMap>>>, spawns: Vec, @@ -31,7 +32,8 @@ impl Events { connections: Vec::new(), rejections: Vec::new(), disconnections: Vec::new(), - ticks: Vec::new(), + client_ticks: Vec::new(), + server_ticks: Vec::new(), errors: Vec::new(), messages: HashMap::new(), spawns: Vec::new(), @@ -105,8 +107,13 @@ impl Events { self.empty = false; } - pub(crate) fn push_tick(&mut self) { - self.ticks.push(()); + pub(crate) fn push_client_tick(&mut self, tick: Tick) { + self.client_ticks.push(tick); + self.empty = false; + } + + pub(crate) fn push_server_tick(&mut self, tick: Tick) { + self.server_ticks.push(tick); self.empty = false; } @@ -157,7 +164,8 @@ impl Events { self.connections.clear(); self.rejections.clear(); self.disconnections.clear(); - self.ticks.clear(); + self.client_ticks.clear(); + self.server_ticks.clear(); self.errors.clear(); self.messages.clear(); self.spawns.clear(); @@ -209,13 +217,24 @@ impl Event for DisconnectEvent { } } -// Tick Event -pub struct TickEvent; -impl Event for TickEvent { - type Iter = IntoIter<()>; +// Client Tick Event +pub struct ClientTickEvent; +impl Event for ClientTickEvent { + type Iter = IntoIter; + + fn iter(events: &mut Events) -> Self::Iter { + let list = std::mem::take(&mut events.client_ticks); + return IntoIterator::into_iter(list); + } +} + +// Server Tick Event +pub struct ServerTickEvent; +impl Event for ServerTickEvent { + type Iter = IntoIter; fn iter(events: &mut Events) -> Self::Iter { - let list = std::mem::take(&mut events.ticks); + let list = std::mem::take(&mut events.server_ticks); return IntoIterator::into_iter(list); } } diff --git a/client/src/lib.rs b/client/src/lib.rs index 4dc7116a2..16eacc315 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -19,19 +19,17 @@ mod connection; mod error; mod events; mod protocol; -mod tick; pub use client::Client; pub use client_config::ClientConfig; pub use command_history::CommandHistory; pub use error::NaiaClientError; pub use events::{ - ConnectEvent, DespawnEntityEvent, DisconnectEvent, ErrorEvent, Events, InsertComponentEvent, - MessageEvent, RejectEvent, RemoveComponentEvent, SpawnEntityEvent, TickEvent, - UpdateComponentEvent, + ClientTickEvent, ConnectEvent, DespawnEntityEvent, DisconnectEvent, ErrorEvent, Events, + InsertComponentEvent, MessageEvent, RejectEvent, RemoveComponentEvent, ServerTickEvent, + SpawnEntityEvent, UpdateComponentEvent, }; pub use protocol::entity_ref::EntityRef; -pub use tick::config::TickConfig; pub mod internal { pub use crate::connection::handshake_manager::{HandshakeManager, HandshakeState}; diff --git a/client/src/protocol/entity_manager.rs b/client/src/protocol/entity_manager.rs index c6ed29bb8..54d388142 100644 --- a/client/src/protocol/entity_manager.rs +++ b/client/src/protocol/entity_manager.rs @@ -169,9 +169,6 @@ impl EntityManager { for action in incoming_actions { match action { EntityAction::SpawnEntity(net_entity, components) => { - //let e_u16: u16 = net_entity.into(); - //info!("spawn entity: {}", e_u16); - if self.local_to_world_entity.contains_key(&net_entity) { panic!("attempted to insert duplicate entity"); } @@ -202,9 +199,6 @@ impl EntityManager { self.entity_records.insert(world_entity, entity_record); } EntityAction::DespawnEntity(net_entity) => { - //let e_u16: u16 = net_entity.into(); - //info!("despawn entity: {}", e_u16); - if let Some(world_entity) = self.local_to_world_entity.remove(&net_entity) { if self.entity_records.remove(&world_entity).is_none() { panic!("despawning an uninitialized entity"); @@ -228,9 +222,6 @@ impl EntityManager { } } EntityAction::InsertComponent(net_entity, component_kind) => { - //let e_u16: u16 = net_entity.into(); - //info!("insert component for: {}", e_u16); - let component = self .received_components .remove(&(net_entity, component_kind)) @@ -254,9 +245,6 @@ impl EntityManager { } } EntityAction::RemoveComponent(net_entity, component_kind) => { - //let e_u16: u16 = net_entity.into(); - //info!("remove component for: {}", e_u16); - let world_entity = self .local_to_world_entity .get_mut(&net_entity) diff --git a/client/src/tick/config.rs b/client/src/tick/config.rs deleted file mode 100644 index 9879fedca..000000000 --- a/client/src/tick/config.rs +++ /dev/null @@ -1,27 +0,0 @@ -#[derive(Copy, Clone)] -pub struct TickConfig { - /// The minimum of measured latency to the Server that the Client use to - /// ensure packets arrive in time. Should be fine if this is 0, - /// but you'll increase the chance that packets always arrive to be - /// processed by the Server with a higher number. This is especially - /// helpful early on in the connection, when estimates of latency are - /// less accurate. - pub minimum_latency_millis: f32, - /// Minimum size of the jitter buffer for packets received from the server. In ticks. - pub minimum_recv_jitter_buffer_size: u8, - /// Minimum size of the jitter buffer for packets sent to the server. In ticks. - pub minimum_send_jitter_buffer_size: u8, - /// Offset to use to compute the tick_offset_avg and tick_offset_speed_avg. Lower means more smoothing - pub tick_offset_smooth_factor: f32, -} - -impl Default for TickConfig { - fn default() -> Self { - Self { - minimum_latency_millis: 0.0, - minimum_recv_jitter_buffer_size: 0, - minimum_send_jitter_buffer_size: 0, - tick_offset_smooth_factor: 0.10, - } - } -} diff --git a/client/src/tick/mod.rs b/client/src/tick/mod.rs deleted file mode 100644 index dfc9ca507..000000000 --- a/client/src/tick/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod channel_tick_buffer_sender; -pub mod config; -pub mod tick_buffer_sender; -pub mod tick_manager; -pub mod tick_queue; diff --git a/client/src/tick/tick_manager.rs b/client/src/tick/tick_manager.rs deleted file mode 100644 index 86876286b..000000000 --- a/client/src/tick/tick_manager.rs +++ /dev/null @@ -1,208 +0,0 @@ -use std::time::Duration; - -use naia_shared::{wrapping_diff, Instant, Serde, Tick}; - -use crate::{ - client::{BitReader, BitWriter}, - TickConfig, -}; - -/// Manages the current tick for the host -pub struct TickManager { - config: TickConfig, - /// How much time in milliseconds does a tick last - tick_interval_millis: f32, - /// How much time in seconds does a tick last - tick_interval_seconds: f32, - /// Used to modify the tick interval. A value >1.0 means that the tick interval will be bigger - tick_speed_factor: f32, - /// Smoothed measure how fast the tick offset is varying - tick_offset_speed_avg: f32, - /// Smoothed measure of how much ahead the client tick is compared to the server tick - tick_offset_avg: f32, - /// current client tick - internal_tick: Tick, - client_sending_tick_adjust: f32, - server_receivable_tick_adjust: f32, - client_receiving_tick_adjust: f32, - last_tick_instant: Instant, - interpolation: f32, - accumulator: f32, - /// Last tick offset recorded - last_tick_offset: i16, - ticks_recorded: u8, -} - -impl TickManager { - /// Create a new TickManager with a given tick interval duration - pub fn new(tick_interval: Duration, config: TickConfig) -> Self { - let tick_interval_millis = tick_interval.as_millis() as f32; - - TickManager { - config, - tick_interval_millis, - tick_interval_seconds: tick_interval.as_nanos() as f32 / 1000000000.0, - tick_speed_factor: 1.0, - tick_offset_avg: 0.0, - tick_offset_speed_avg: 0.0, - internal_tick: 0, - client_sending_tick_adjust: 0.0, - client_receiving_tick_adjust: 0.0, - server_receivable_tick_adjust: 0.0, - last_tick_instant: Instant::now(), - accumulator: 0.0, - interpolation: 0.0, - last_tick_offset: 0, - ticks_recorded: 0, - } - } - - pub fn write_client_tick(&self, writer: &mut BitWriter) -> Tick { - let client_tick = self.client_sending_tick(); - client_tick.ser(writer); - client_tick - } - - /// Read server tick from any packet that includes it and updates - /// - /// # Panics - /// - /// If the incoming packet from the server doesn't contain the server tick - pub fn read_server_tick(&mut self, reader: &mut BitReader, rtt: f32, jitter: f32) -> Tick { - let server_tick = Tick::de(reader).expect("unable to read server tick from packet"); - - self.record_server_tick(server_tick, rtt, jitter); - - server_tick - } - - /// This should run every frame. Check if enough time has passed so that we move to the next tick - /// Also keeps track of internal state such as the interpolation percentage - pub fn recv_client_tick(&mut self) -> bool { - let mut ticked = false; - let tick_interval_seconds = self.tick_interval_seconds * self.tick_speed_factor; - - let frame_seconds = self.last_tick_instant.elapsed().as_nanos() as f32 / 1000000000.0; - // if frame_seconds > tick_interval_seconds { - // info!("big frame delta: {} ms", frame_seconds*1000.0); - // } - self.accumulator += frame_seconds.min(0.25); - self.last_tick_instant = Instant::now(); - - if self.accumulator >= tick_interval_seconds { - while self.accumulator >= tick_interval_seconds { - self.accumulator -= tick_interval_seconds; - self.internal_tick = self.internal_tick.wrapping_add(1); - } - // tick has occurred - ticked = true; - } - self.interpolation = self.accumulator / tick_interval_seconds; - ticked - } - - /// Return the current interpolation of the frame between the two surrounding ticks - /// 0.20 means that we are 20% of the way to the next tick - pub fn interpolation(&self) -> f32 { - self.interpolation - } - - /// Using information from the Server and RTT/Jitter measurements, determine - /// the appropriate future intended tick - fn record_server_tick(&mut self, server_tick: Tick, rtt_average: f32, jitter_average: f32) { - // make sure we only record server_ticks going FORWARD - - // tick diff - let tick_offset = wrapping_diff(self.internal_tick, server_tick); - let tick_offset_smoothing = self.config.tick_offset_smooth_factor; - let inv_tick_offset_smoothing = 1.0 - tick_offset_smoothing; - - if self.ticks_recorded <= 1 { - if self.ticks_recorded == 1 { - self.tick_offset_avg = tick_offset as f32; - } - - self.ticks_recorded += 1; - } else { - self.tick_offset_avg = (inv_tick_offset_smoothing * self.tick_offset_avg) - + (tick_offset_smoothing * (tick_offset as f32)); - let tick_offset_speed = (tick_offset - self.last_tick_offset) as f32; - self.tick_offset_speed_avg = (inv_tick_offset_smoothing * self.tick_offset_speed_avg) - + (tick_offset_smoothing * tick_offset_speed); - } - - self.last_tick_offset = tick_offset; - - if self.tick_offset_speed_avg > 1.0 { - self.tick_speed_factor -= 0.1; - self.tick_offset_speed_avg = 0.0; - } - if self.tick_offset_speed_avg < -1.0 { - self.tick_speed_factor += 0.1; - self.tick_offset_speed_avg = 0.0; - } - - // Calculate incoming & outgoing jitter buffer tick offsets - - let jitter_limit = jitter_average * 4.0; - self.client_receiving_tick_adjust = (jitter_limit / self.tick_interval_millis) - .max(self.config.minimum_recv_jitter_buffer_size as f32); - - // NOTE: I've struggled multiple times with why rtt_average instead of - // ping_average exists in this calculation, figured it out, then - // returned to struggle later. This is not a bug! - // Keep in mind that self.server_tick here is the tick we have RECEIVED from the - // Server which means that the real current server_tick is likely - // self.server_tick + ping_average / tick_interval. - // By using rtt_average here, we are correcting for our late (and - // lesser) self.server_tick value - let client_sending_adjust_millis = self - .config - .minimum_latency_millis - .max(rtt_average + jitter_limit); - self.client_sending_tick_adjust = (client_sending_adjust_millis - / self.tick_interval_millis) - .max(self.config.minimum_send_jitter_buffer_size as f32); - - // Calculate estimate of earliest tick Server could receive now - let server_receivable_adjust_millis = rtt_average - jitter_limit; - self.server_receivable_tick_adjust = - server_receivable_adjust_millis / self.tick_interval_millis; - } - - /// Gets the tick at which the Client is sending updates - pub fn client_sending_tick(&self) -> Tick { - let mut output = self.server_tick_estimate() + self.client_sending_tick_adjust; - wrap_f32(&mut output); - output.round() as Tick - } - - /// Gets the tick at which to receive messages from the Server (after jitter - /// buffer offset is applied) - pub fn client_receiving_tick(&self) -> Tick { - let mut output = self.server_tick_estimate() - self.client_receiving_tick_adjust; - wrap_f32(&mut output); - output.round() as Tick - } - - /// Gets the earliest tick the Server may be able to receive Client messages - pub fn server_receivable_tick(&self) -> Tick { - let mut output = self.server_tick_estimate() + self.server_receivable_tick_adjust; - wrap_f32(&mut output); - output.round() as Tick - } - - fn server_tick_estimate(&self) -> f32 { - (self.internal_tick as f32) + self.tick_offset_avg - } -} - -fn wrap_f32(val: &mut f32) { - const U16_MAX_AS_F32: f32 = u16::MAX as f32; - while *val < 0.0 { - *val += U16_MAX_AS_F32; - } - while *val > U16_MAX_AS_F32 { - *val -= U16_MAX_AS_F32; - } -} diff --git a/demos/basic/client/app/src/app.rs b/demos/basic/client/app/src/app.rs index 86bb7dc68..64d53c79d 100644 --- a/demos/basic/client/app/src/app.rs +++ b/demos/basic/client/app/src/app.rs @@ -7,9 +7,9 @@ cfg_if! { } use naia_client::{ - default_channels::UnorderedReliableChannel, Client as NaiaClient, ClientConfig, ConnectEvent, - DespawnEntityEvent, DisconnectEvent, ErrorEvent, MessageEvent, RejectEvent, - RemoveComponentEvent, SpawnEntityEvent, TickEvent, UpdateComponentEvent, + default_channels::UnorderedReliableChannel, Client as NaiaClient, ClientConfig, + ClientTickEvent, ConnectEvent, DespawnEntityEvent, DisconnectEvent, ErrorEvent, MessageEvent, + RejectEvent, RemoveComponentEvent, SpawnEntityEvent, UpdateComponentEvent, }; use naia_demo_world::{Entity, World}; @@ -100,7 +100,8 @@ impl App { if let Some(character) = self .client .entity(self.world.proxy(), &entity) - .component::() { + .component::() + { info!( "update of Character - x: {}, y: {}, name: {} {}", *character.x, @@ -119,7 +120,7 @@ impl App { (*character.fullname).last, ); } - for _ in events.read::() { + for _ in events.read::() { //info!("tick event"); } for error in events.read::() { diff --git a/demos/basic/server/src/app.rs b/demos/basic/server/src/app.rs index 18c639931..f7ced2b86 100644 --- a/demos/basic/server/src/app.rs +++ b/demos/basic/server/src/app.rs @@ -80,7 +80,7 @@ impl App { let mut events = self.server.receive(); if events.is_empty() { // If we don't sleep here, app will loop at 100% CPU until a new message comes in - sleep(Duration::from_millis(5)); + sleep(Duration::from_millis(1)); return; } else { for (user_key, auth) in events.read::>() { diff --git a/demos/bevy/client/src/app.rs b/demos/bevy/client/src/app.rs index 838dfe92e..107c01392 100644 --- a/demos/bevy/client/src/app.rs +++ b/demos/bevy/client/src/app.rs @@ -11,10 +11,10 @@ use bevy_transform::TransformPlugin; use bevy_window::WindowPlugin; use bevy_winit::WinitPlugin; -use naia_bevy_client::{ClientConfig, Plugin as ClientPlugin, Stage}; +use naia_bevy_client::{ClientConfig, Plugin as ClientPlugin}; use naia_bevy_demo_shared::protocol; -use crate::systems::{events, init, input, sync, tick}; +use crate::systems::{events, init, input, sync}; pub fn run() { App::default() @@ -43,10 +43,9 @@ pub fn run() { .add_system_to_stage(CoreStage::PreUpdate, events::insert_component_events) .add_system_to_stage(CoreStage::PreUpdate, events::update_component_events) .add_system_to_stage(CoreStage::PreUpdate, events::message_events) + .add_system_to_stage(CoreStage::PreUpdate, events::tick_events) .add_system_to_stage(CoreStage::Update, input) .add_system_to_stage(CoreStage::Update, sync) - // Gameplay Loop on Tick - .add_system_to_stage(Stage::Tick, tick) // Run App .run(); } diff --git a/demos/bevy/client/src/resources.rs b/demos/bevy/client/src/resources.rs index 79d3d6031..d0de451c6 100644 --- a/demos/bevy/client/src/resources.rs +++ b/demos/bevy/client/src/resources.rs @@ -2,7 +2,7 @@ use std::default::Default; use bevy_ecs::{entity::Entity, prelude::Resource}; -use naia_bevy_client::CommandHistory; +use naia_bevy_client::{CommandHistory, Tick}; use naia_bevy_demo_shared::messages::KeyCommand; @@ -25,4 +25,5 @@ pub struct Global { pub owned_entity: Option, pub queued_command: Option, pub command_history: CommandHistory, + pub last_client_tick: Tick, } diff --git a/demos/bevy/client/src/systems/events.rs b/demos/bevy/client/src/systems/events.rs index 4c0ea3260..ac8162443 100644 --- a/demos/bevy/client/src/systems/events.rs +++ b/demos/bevy/client/src/systems/events.rs @@ -10,17 +10,17 @@ use bevy_transform::components::Transform; use naia_bevy_client::{ events::{ - ConnectEvent, DisconnectEvent, InsertComponentEvents, MessageEvents, RejectEvent, - SpawnEntityEvent, UpdateComponentEvents, + ClientTickEvent, ConnectEvent, DisconnectEvent, InsertComponentEvents, MessageEvents, + RejectEvent, SpawnEntityEvent, UpdateComponentEvents, }, sequence_greater_than, Client, CommandsExt, Tick, }; use naia_bevy_demo_shared::{ behavior as shared_behavior, - channels::EntityAssignmentChannel, + channels::{EntityAssignmentChannel, PlayerCommandChannel}, components::{Color, ColorValue, Position}, - messages::EntityAssignment, + messages::{EntityAssignment, KeyCommand}, }; use crate::resources::{Global, OwnedEntity}; @@ -179,3 +179,46 @@ pub fn update_component_events( } } } + +pub fn tick_events( + mut tick_reader: EventReader, + mut global: ResMut, + mut client: Client, + mut position_query: Query<&mut Position>, +) { + if !client.is_connected() { + return; + } + let Some(command) = global.queued_command.take() else { + return; + }; + + let Some(predicted_entity) = global + .owned_entity + .as_ref() + .map(|owned_entity| owned_entity.predicted) else { + // No owned Entity + return; + }; + + for ClientTickEvent(tick) in tick_reader.iter() { + global.last_client_tick = *tick; + + //All game logic should happen here, on a tick event + if !global.command_history.can_insert(tick) { + // History is full + continue; + } + + // Record command + global.command_history.insert(*tick, command.clone()); + + // Send command + client.send_tick_buffer_message::(tick, &command); + + // Apply command + if let Ok(mut position) = position_query.get_mut(predicted_entity) { + shared_behavior::process_command(&command, &mut position); + } + } +} diff --git a/demos/bevy/client/src/systems/mod.rs b/demos/bevy/client/src/systems/mod.rs index 611583c04..c7aa30d59 100644 --- a/demos/bevy/client/src/systems/mod.rs +++ b/demos/bevy/client/src/systems/mod.rs @@ -3,9 +3,7 @@ pub mod events; mod init; mod input; mod sync; -mod tick; pub use init::init; pub use input::input; pub use sync::sync; -pub use tick::tick; diff --git a/demos/bevy/client/src/systems/tick.rs b/demos/bevy/client/src/systems/tick.rs deleted file mode 100644 index 1f3f08724..000000000 --- a/demos/bevy/client/src/systems/tick.rs +++ /dev/null @@ -1,41 +0,0 @@ -use bevy_ecs::system::{Query, ResMut}; - -use naia_bevy_client::Client; - -use naia_bevy_demo_shared::{ - behavior as shared_behavior, channels::PlayerCommandChannel, components::Position, - messages::KeyCommand, -}; - -use crate::resources::Global; - -pub fn tick( - mut global: ResMut, - mut client: Client, - mut position_query: Query<&mut Position>, -) { - //All game logic should happen here, on a tick event - - if let Some(command) = global.queued_command.take() { - if let Some(predicted_entity) = global - .owned_entity - .as_ref() - .map(|owned_entity| owned_entity.predicted) - { - if let Some(client_tick) = client.client_tick() { - if global.command_history.can_insert(&client_tick) { - // Record command - global.command_history.insert(client_tick, command.clone()); - - // Send command - client.send_message::(&command); - - // Apply command - if let Ok(mut position) = position_query.get_mut(predicted_entity) { - shared_behavior::process_command(&command, &mut position); - } - } - } - } - } -} diff --git a/demos/bevy/server/src/main.rs b/demos/bevy/server/src/main.rs index b12ffec8d..aa02b1c5e 100644 --- a/demos/bevy/server/src/main.rs +++ b/demos/bevy/server/src/main.rs @@ -1,14 +1,14 @@ -use bevy_app::{App, CoreStage, ScheduleRunnerPlugin}; +use bevy_app::{App, ScheduleRunnerPlugin}; use bevy_core::CorePlugin; use bevy_log::{info, LogPlugin}; use naia_bevy_demo_shared::protocol; -use naia_bevy_server::{Plugin as ServerPlugin, ServerConfig, Stage}; +use naia_bevy_server::{Plugin as ServerPlugin, ServerConfig}; mod resources; mod systems; -use systems::{events, init, tick}; +use systems::{events, init}; fn main() { info!("Naia Bevy Server Demo starting up"); @@ -23,13 +23,11 @@ fn main() { // Startup System .add_startup_system(init) // Receive Server Events - .add_system_to_stage(CoreStage::PreUpdate, events::auth_events) - .add_system_to_stage(CoreStage::PreUpdate, events::connect_events) - .add_system_to_stage(CoreStage::PreUpdate, events::disconnect_events) - .add_system_to_stage(CoreStage::PreUpdate, events::message_events) - .add_system_to_stage(CoreStage::PreUpdate, events::error_events) - // Gameplay Loop on Tick - .add_system_to_stage(Stage::Tick, tick) + .add_system(events::auth_events) + .add_system(events::connect_events) + .add_system(events::disconnect_events) + .add_system(events::error_events) + .add_system(events::tick_events) // Run App .run(); } diff --git a/demos/bevy/server/src/resources.rs b/demos/bevy/server/src/resources.rs index beb0efd42..5f39654ab 100644 --- a/demos/bevy/server/src/resources.rs +++ b/demos/bevy/server/src/resources.rs @@ -4,11 +4,8 @@ use bevy_ecs::{entity::Entity, prelude::Resource}; use naia_bevy_server::{RoomKey, UserKey}; -use naia_bevy_demo_shared::messages::KeyCommand; - #[derive(Resource)] pub struct Global { pub main_room_key: RoomKey, - pub user_to_prediction_map: HashMap, - pub player_last_command: HashMap, + pub user_to_entity_map: HashMap, } diff --git a/demos/bevy/server/src/systems/events.rs b/demos/bevy/server/src/systems/events.rs index e9f042191..771d385a2 100644 --- a/demos/bevy/server/src/systems/events.rs +++ b/demos/bevy/server/src/systems/events.rs @@ -1,12 +1,16 @@ -use bevy_ecs::{event::EventReader, system::ResMut}; +use bevy_ecs::{ + event::EventReader, + system::{Query, ResMut}, +}; use bevy_log::info; use naia_bevy_server::{ - events::{AuthEvents, ConnectEvent, DisconnectEvent, ErrorEvent, MessageEvents}, + events::{AuthEvents, ConnectEvent, DisconnectEvent, ErrorEvent, TickEvent}, Random, Server, }; use naia_bevy_demo_shared::{ + behavior as shared_behavior, channels::{EntityAssignmentChannel, PlayerCommandChannel}, components::{Color, ColorValue, Position}, messages::{Auth, EntityAssignment, KeyCommand}, @@ -75,7 +79,7 @@ pub fn connect_events<'world, 'state>( // return Entity id .id(); - global.user_to_prediction_map.insert(*user_key, entity); + global.user_to_entity_map.insert(*user_key, entity); // Send an Entity Assignment message to the User that owns the Square let mut assignment_message = EntityAssignment::new(true); @@ -96,7 +100,7 @@ pub fn disconnect_events( for DisconnectEvent(user_key, user) in event_reader.iter() { info!("Naia Server disconnected from: {:?}", user.address); - if let Some(entity) = global.user_to_prediction_map.remove(user_key) { + if let Some(entity) = global.user_to_entity_map.remove(user_key) { server .entity_mut(&entity) .leave_room(&global.main_room_key) @@ -105,24 +109,51 @@ pub fn disconnect_events( } } -pub fn message_events( - mut event_reader: EventReader, - mut global: ResMut, - server: Server, +pub fn error_events(mut event_reader: EventReader) { + for ErrorEvent(error) in event_reader.iter() { + info!("Naia Server Error: {:?}", error); + } +} + +pub fn tick_events( + mut server: Server, + mut position_query: Query<&mut Position>, + mut tick_reader: EventReader, ) { - for events in event_reader.iter() { - for (_user_key, key_command) in events.read::() { - if let Some(entity) = &key_command.entity.get(&server) { - global - .player_last_command - .insert(*entity, key_command.clone()); - } + let mut has_ticked = false; + + for TickEvent(server_tick) in tick_reader.iter() { + has_ticked = true; + + // All game logic should happen here, on a tick event + + let messages = server.receive_tick_buffer_messages(server_tick); + for (_user_key, key_command) in messages.read::() { + let Some(entity) = &key_command.entity.get(&server) else { + continue; + }; + let Ok(mut position) = position_query.get_mut(*entity) else { + continue; + }; + shared_behavior::process_command(&key_command, &mut position); } } -} -pub fn error_events(mut event_reader: EventReader) { - for ErrorEvent(error) in event_reader.iter() { - info!("Naia Server Error: {:?}", error); + if has_ticked { + // Update scopes of entities + for (_, user_key, entity) in server.scope_checks() { + // You'd normally do whatever checks you need to in here.. + // to determine whether each Entity should be in scope or not. + + // This indicates the Entity should be in this scope. + server.user_scope(&user_key).include(&entity); + + // And call this if Entity should NOT be in this scope. + // server.user_scope(..).exclude(..); + } + + // This is very important! Need to call this to actually send all update packets + // to all connected Clients! + server.send_all_updates(); } } diff --git a/demos/bevy/server/src/systems/init.rs b/demos/bevy/server/src/systems/init.rs index 983f34fb0..2b49ce5ec 100644 --- a/demos/bevy/server/src/systems/init.rs +++ b/demos/bevy/server/src/systems/init.rs @@ -32,7 +32,6 @@ pub fn init(mut commands: Commands, mut server: Server) { // Resources commands.insert_resource(Global { main_room_key, - user_to_prediction_map: HashMap::new(), - player_last_command: HashMap::new(), + user_to_entity_map: HashMap::new(), }) } diff --git a/demos/bevy/server/src/systems/mod.rs b/demos/bevy/server/src/systems/mod.rs index 7fc67b0c6..73828004f 100644 --- a/demos/bevy/server/src/systems/mod.rs +++ b/demos/bevy/server/src/systems/mod.rs @@ -1,7 +1,5 @@ pub mod events; mod init; -mod tick; pub use init::init; -pub use tick::tick; diff --git a/demos/bevy/server/src/systems/tick.rs b/demos/bevy/server/src/systems/tick.rs deleted file mode 100644 index f0613a842..000000000 --- a/demos/bevy/server/src/systems/tick.rs +++ /dev/null @@ -1,39 +0,0 @@ -use bevy_ecs::system::{Query, ResMut}; - -use naia_bevy_server::Server; - -use naia_bevy_demo_shared::{behavior as shared_behavior, components::Position}; - -use crate::resources::Global; - -pub fn tick( - mut global: ResMut, - mut server: Server, - mut position_query: Query<&mut Position>, -) { - // All game logic should happen here, on a tick event - //info!("tick"); - - // Update scopes of entities - for (_, user_key, entity) in server.scope_checks() { - // You'd normally do whatever checks you need to in here.. - // to determine whether each Entity should be in scope or not. - - // This indicates the Entity should be in this scope. - server.user_scope(&user_key).include(&entity); - - // And call this if Entity should NOT be in this scope. - // server.user_scope(..).exclude(..); - } - - // Process all received commands - for (entity, last_command) in global.player_last_command.drain() { - if let Ok(mut position) = position_query.get_mut(entity) { - shared_behavior::process_command(&last_command, &mut position); - } - } - - // This is very important! Need to call this to actually send all update packets - // to all connected Clients! - server.send_all_updates(); -} diff --git a/demos/bevy/shared/src/behavior/process_command.rs b/demos/bevy/shared/src/behavior/process_command.rs index 0fc85a476..154b92697 100644 --- a/demos/bevy/shared/src/behavior/process_command.rs +++ b/demos/bevy/shared/src/behavior/process_command.rs @@ -1,6 +1,6 @@ use crate::{components::Position, messages::KeyCommand}; -const SQUARE_SPEED: i16 = 3; +const SQUARE_SPEED: i16 = 2; pub fn process_command(key_command: &KeyCommand, position: &mut Position) { if key_command.w { diff --git a/demos/bevy/shared/src/protocol.rs b/demos/bevy/shared/src/protocol.rs index 7abb18d1b..1a72ce90b 100644 --- a/demos/bevy/shared/src/protocol.rs +++ b/demos/bevy/shared/src/protocol.rs @@ -8,8 +8,8 @@ use crate::{channels::ChannelsPlugin, components::ComponentsPlugin, messages::Me pub fn protocol() -> Protocol { Protocol::builder() // Config - .tick_interval(Duration::from_millis(25)) - .link_condition(LinkConditionerConfig::average_condition()) + .tick_interval(Duration::from_millis(16)) + .link_condition(LinkConditionerConfig::good_condition()) // Channels .add_plugin(ChannelsPlugin) // Messages diff --git a/demos/hecs/client/src/systems/events.rs b/demos/hecs/client/src/systems/events.rs index 8d63735dd..d9d536618 100644 --- a/demos/hecs/client/src/systems/events.rs +++ b/demos/hecs/client/src/systems/events.rs @@ -1,8 +1,8 @@ use log::info; use naia_hecs_client::{ - ConnectEvent, DespawnEntityEvent, DisconnectEvent, ErrorEvent, InsertComponentEvent, - RemoveComponentEvent, SpawnEntityEvent, TickEvent, + ClientTickEvent, ConnectEvent, DespawnEntityEvent, DisconnectEvent, ErrorEvent, + InsertComponentEvent, RemoveComponentEvent, SpawnEntityEvent, }; use naia_hecs_demo_shared::{Marker, Name, Position}; @@ -68,7 +68,7 @@ pub fn process_events(app: &mut App) { } // Tick Events - for _ in events.read::() { + for _ in events.read::() { app.tick(); } diff --git a/demos/hecs/server/src/systems/events.rs b/demos/hecs/server/src/systems/events.rs index 1fac9a8ab..a0dca8744 100644 --- a/demos/hecs/server/src/systems/events.rs +++ b/demos/hecs/server/src/systems/events.rs @@ -9,7 +9,7 @@ pub fn process_events(app: &mut App) { let mut events = app.server.receive(); if events.is_empty() { // If we don't sleep here, app will loop at 100% CPU until a new message comes in - sleep(Duration::from_millis(5)); + sleep(Duration::from_millis(1)); return; } else { for (user_key, auth) in events.read::>() { diff --git a/demos/macroquad/client/src/app.rs b/demos/macroquad/client/src/app.rs index 8121aa7ec..711a0c889 100644 --- a/demos/macroquad/client/src/app.rs +++ b/demos/macroquad/client/src/app.rs @@ -6,9 +6,9 @@ use macroquad::prelude::{ }; use naia_client::{ - Client as NaiaClient, ClientConfig, CommandHistory, ConnectEvent, DespawnEntityEvent, - DisconnectEvent, ErrorEvent, InsertComponentEvent, MessageEvent, RemoveComponentEvent, - SpawnEntityEvent, TickEvent, UpdateComponentEvent, + Client as NaiaClient, ClientConfig, ClientTickEvent, CommandHistory, ConnectEvent, + DespawnEntityEvent, DisconnectEvent, ErrorEvent, InsertComponentEvent, MessageEvent, + RemoveComponentEvent, SpawnEntityEvent, UpdateComponentEvent, }; use naia_demo_world::{Entity, World, WorldMutType, WorldRefType}; @@ -149,28 +149,28 @@ impl App { } } } - for _ in events.read::() { - if let Some(owned_entity) = &self.owned_entity { - if let Some(command) = self.queued_command.take() { - if let Some(client_tick) = self.client.client_tick() { - if self.command_history.can_insert(&client_tick) { - // Record command - self.command_history.insert(client_tick, command.clone()); + for client_tick in events.read::() { + let Some(owned_entity) = &self.owned_entity else { + continue; + }; + let Some(command) = self.queued_command.take() else { + continue; + }; + if self.command_history.can_insert(&client_tick) { + // Record command + self.command_history.insert(client_tick, command.clone()); - // Send command - self.client - .send_message::(&command); + // Send command + self.client + .send_tick_buffer_message::(&client_tick, &command); - // Apply command - if let Some(mut square_ref) = self - .world - .proxy_mut() - .component_mut::(&owned_entity.predicted) - { - shared_behavior::process_command(&command, &mut square_ref); - } - } - } + // Apply command + if let Some(mut square_ref) = self + .world + .proxy_mut() + .component_mut::(&owned_entity.predicted) + { + shared_behavior::process_command(&command, &mut square_ref); } } } diff --git a/demos/macroquad/server/src/app.rs b/demos/macroquad/server/src/app.rs index 954a624d3..7aac493f9 100644 --- a/demos/macroquad/server/src/app.rs +++ b/demos/macroquad/server/src/app.rs @@ -1,8 +1,8 @@ use std::{collections::HashMap, thread::sleep, time::Duration}; use naia_server::{ - AuthEvent, ConnectEvent, DisconnectEvent, ErrorEvent, MessageEvent, Random, RoomKey, - Server as NaiaServer, ServerAddrs, ServerConfig, TickEvent, UserKey, + AuthEvent, ConnectEvent, DisconnectEvent, ErrorEvent, Random, RoomKey, Server as NaiaServer, + ServerAddrs, ServerConfig, TickEvent, UserKey, }; use naia_demo_world::{Entity, World}; @@ -61,7 +61,7 @@ impl App { let mut events = self.server.receive(); if events.is_empty() { // If we don't sleep here, app will loop at 100% CPU until a new message comes in - sleep(Duration::from_millis(5)); + sleep(Duration::from_millis(1)); return; } for (user_key, auth) in events.read::>() { @@ -135,16 +135,30 @@ impl App { self.square_last_command.remove(&entity); } } - for (_user_key, key_command) in - events.read::>() - { - if let Some(entity) = &key_command.entity.get(&self.server) { - self.square_last_command.insert(*entity, key_command); + + let mut has_ticked = false; + + for server_tick in events.read::() { + has_ticked = true; + + // All game logic should happen here, on a tick event + + let messages = self.server.receive_tick_buffer_messages(&server_tick); + for (_user_key, key_command) in messages.read::() { + let Some(entity) = &key_command.entity.get(&self.server) else { + continue; + }; + if let Some(mut square) = self + .server + .entity_mut(self.world.proxy_mut(), &entity) + .component::() + { + shared_behavior::process_command(&key_command, &mut square); + } } } - for _ in events.read::() { - // All game logic should happen here, on a tick event + if has_ticked { // Check whether Entities are in/out of all possible Scopes for (_, user_key, entity) in self.server.scope_checks() { // You'd normally do whatever checks you need to in here.. @@ -157,21 +171,12 @@ impl App { // self.server.user_scope(..).exclude(..); } - for (entity, last_command) in self.square_last_command.drain() { - if let Some(mut square) = self - .server - .entity_mut(self.world.proxy_mut(), &entity) - .component::() - { - shared_behavior::process_command(&last_command, &mut square); - } - } - // VERY IMPORTANT! Calling this actually sends all update data // packets to all Clients that require it. If you don't call this // method, the Server will never communicate with it's connected Clients self.server.send_all_updates(self.world.proxy()); } + for error in events.read::() { info!("Naia Server error: {}", error); } diff --git a/demos/socket/server/src/app.rs b/demos/socket/server/src/app.rs index 35de3b7ab..3bdde97cd 100644 --- a/demos/socket/server/src/app.rs +++ b/demos/socket/server/src/app.rs @@ -57,7 +57,7 @@ impl App { } Ok(None) => { // If we don't sleep here, app will loop at 100% CPU until a new message comes in - sleep(Duration::from_millis(5)); + sleep(Duration::from_millis(1)); } Err(error) => { info!("Server Error: {}", error); diff --git a/server/src/tick/channel_tick_buffer_receiver.rs b/server/src/connection/channel_tick_buffer_receiver.rs similarity index 84% rename from server/src/tick/channel_tick_buffer_receiver.rs rename to server/src/connection/channel_tick_buffer_receiver.rs index 756579c7b..e73b8de6b 100644 --- a/server/src/tick/channel_tick_buffer_receiver.rs +++ b/server/src/connection/channel_tick_buffer_receiver.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, VecDeque}; use naia_shared::{ sequence_greater_than, BitReader, ChannelReader, Message, MessageKinds, Serde, SerdeErr, - ShortMessageIndex, Tick, UnsignedVariableInteger, + ShortMessageIndex, Tick, TickBufferSettings, UnsignedVariableInteger, }; /// Receive updates from the client and store them in a buffer along with the corresponding @@ -12,7 +12,7 @@ pub struct ChannelTickBufferReceiver { } impl ChannelTickBufferReceiver { - pub fn new() -> Self { + pub fn new(_settings: TickBufferSettings) -> Self { Self { incoming_messages: IncomingMessages::new(), } @@ -85,8 +85,7 @@ impl ChannelTickBufferReceiver { .incoming_messages .insert(host_tick, &remote_tick, message_index, new_message) { - //info!("failed command. server: {}, client: {}", - // server_tick, client_tick); + // Failed to Insert Command } } @@ -111,11 +110,6 @@ impl IncomingMessages { } } - // TODO: - // * add unit test? - // * should there be a maximum buffer size? - // * fasten client simulation if too many ticks are received too late (i.e. received client ticks are too old) ? - // * slow client simulation if ticks are received too in advance (buffer is too big) ? /// Insert a message from the client into the tick-buffer /// Will only insert messages that are from future ticks compared to the current server tick pub fn insert( @@ -125,6 +119,10 @@ impl IncomingMessages { message_index: ShortMessageIndex, new_message: Box, ) -> bool { + // TODO: + // * add unit test? + // * should there be a maximum buffer size? + if sequence_greater_than(*message_tick, *host_tick) { let mut index = self.buffer.len(); @@ -133,8 +131,6 @@ impl IncomingMessages { let mut map = HashMap::new(); map.insert(message_index, new_message); self.buffer.push_back((*message_tick, map)); - //info!("msg server_tick: {}, client_tick: {}, for entity: {} ... (empty q)", - // server_tick, client_tick, owned_entity); return true; } @@ -151,10 +147,7 @@ impl IncomingMessages { existing_messages.entry(message_index) { e.insert(new_message); - //info!("inserting command at tick: {}", client_tick); - //info!("msg server_tick: {}, client_tick: {}, for entity: {} ... (map - // xzist)", server_tick, client_tick, owned_entity); - // inserted command into existing tick + return true; } else { // TODO: log hash collisions? @@ -171,8 +164,6 @@ impl IncomingMessages { let mut new_messages = HashMap::new(); new_messages.insert(message_index, new_message); self.buffer.insert(index + 1, (*message_tick, new_messages)); - //info!("msg server_tick: {}, client_tick: {}, for entity: {} ... (midbck - // insrt)", server_tick, client_tick, owned_entity); return true; } @@ -181,8 +172,6 @@ impl IncomingMessages { let mut new_messages = HashMap::new(); new_messages.insert(message_index, new_message); self.buffer.push_front((*message_tick, new_messages)); - //info!("msg server_tick: {}, client_tick: {}, for entity: {} ... (front - // insrt)", server_tick, client_tick, owned_entity); return true; } } diff --git a/server/src/connection/connection.rs b/server/src/connection/connection.rs index beacd5eb1..862c015c0 100644 --- a/server/src/connection/connection.rs +++ b/server/src/connection/connection.rs @@ -1,40 +1,44 @@ -use log::warn; use std::{ hash::Hash, net::SocketAddr, sync::{Arc, RwLock}, }; +use log::warn; + use naia_shared::{ - sequence_greater_than, BaseConnection, BitReader, BitWriter, ChannelKinds, ConnectionConfig, - EntityConverter, HostType, Instant, PacketType, PingManager, Protocol, ProtocolIo, Serde, - SerdeErr, StandardHeader, Tick, WorldRefType, + BaseConnection, BitReader, BitWriter, ChannelKinds, ConnectionConfig, EntityConverter, + HostType, Instant, PacketType, Protocol, ProtocolIo, Serde, SerdeErr, StandardHeader, Tick, + WorldRefType, }; use crate::{ + connection::{ + ping_config::PingConfig, tick_buffer_messages::TickBufferMessages, + tick_buffer_receiver::TickBufferReceiver, time_manager::TimeManager, + }, protocol::{ entity_manager::EntityManager, global_diff_handler::GlobalDiffHandler, world_record::WorldRecord, }, - tick::{tick_buffer_receiver::TickBufferReceiver, tick_manager::TickManager}, user::UserKey, Events, }; -use super::io::Io; +use super::{io::Io, ping_manager::PingManager}; pub struct Connection { pub user_key: UserKey, pub base: BaseConnection, pub entity_manager: EntityManager, tick_buffer: TickBufferReceiver, - pub last_received_tick: Tick, pub ping_manager: PingManager, } impl Connection { pub fn new( connection_config: &ConnectionConfig, + ping_config: &PingConfig, user_address: SocketAddr, user_key: &UserKey, channel_kinds: &ChannelKinds, @@ -50,8 +54,7 @@ impl Connection { ), entity_manager: EntityManager::new(user_address, diff_handler), tick_buffer: TickBufferReceiver::new(channel_kinds), - ping_manager: PingManager::new(&connection_config.ping), - last_received_tick: 0, + ping_manager: PingManager::new(ping_config), } } @@ -62,18 +65,12 @@ impl Connection { .process_incoming_header(header, &mut Some(&mut self.entity_manager)); } - /// Update the last received tick tracker from the given client - pub fn recv_client_tick(&mut self, client_tick: Tick) { - if sequence_greater_than(client_tick, self.last_received_tick) { - self.last_received_tick = client_tick; - } - } - /// Read packet data received from a client pub fn process_incoming_data( &mut self, protocol: &Protocol, - server_and_client_tick_opt: Option<(Tick, Tick)>, + server_tick: Tick, + client_tick: Tick, reader: &mut BitReader, world_record: &WorldRecord, ) -> Result<(), SerdeErr> { @@ -82,15 +79,13 @@ impl Connection { // read tick-buffered messages { - if let Some((server_tick, client_tick)) = server_and_client_tick_opt { - self.tick_buffer.read_messages( - protocol, - &server_tick, - &client_tick, - &channel_reader, - reader, - )?; - } + self.tick_buffer.read_messages( + protocol, + &server_tick, + &client_tick, + &channel_reader, + reader, + )?; } // read messages @@ -110,11 +105,11 @@ impl Connection { } } - pub fn receive_tick_buffer_messages(&mut self, host_tick: &Tick, incoming_events: &mut Events) { - let channel_messages = self.tick_buffer.receive_messages(host_tick); + pub fn tick_buffer_messages(&mut self, tick: &Tick, messages: &mut TickBufferMessages) { + let channel_messages = self.tick_buffer.receive_messages(tick); for (channel_kind, received_messages) in channel_messages { for message in received_messages { - incoming_events.push_message(&self.user_key, &channel_kind, message); + messages.push(&self.user_key, &channel_kind, message); } } } @@ -127,14 +122,14 @@ impl Connection { io: &mut Io, world: &W, world_record: &WorldRecord, - tick_manager_opt: &Option, + time_manager: &TimeManager, rtt_millis: &f32, ) { self.collect_outgoing_messages(now, rtt_millis); let mut any_sent = false; loop { - if self.send_outgoing_packet(protocol, now, io, world, world_record, tick_manager_opt) { + if self.send_outgoing_packet(protocol, now, io, world, world_record, time_manager) { any_sent = true; } else { break; @@ -165,7 +160,7 @@ impl Connection { io: &mut Io, world: &W, world_record: &WorldRecord, - tick_manager_opt: &Option, + time_manager: &TimeManager, ) -> bool { if self.base.message_manager.has_outgoing_messages() || self.entity_manager.has_outgoing_messages() @@ -185,14 +180,10 @@ impl Connection { .write_outgoing_header(PacketType::Data, &mut bit_writer); // write server tick - if let Some(tick_manager) = tick_manager_opt { - tick_manager.write_server_tick(&mut bit_writer); - } + time_manager.current_tick().ser(&mut bit_writer); - // info!("-- packet: {} --", next_packet_index); - // if self.base.message_manager.has_outgoing_messages() { - // info!("writing some messages"); - // } + // write server tick instant + time_manager.current_tick_instant().ser(&mut bit_writer); let mut has_written = false; @@ -247,8 +238,6 @@ impl Connection { bit_writer.release_bits(1); } - //info!("--------------\n"); - // send packet match io.send_writer(&self.base.address, &mut bit_writer) { Ok(()) => {} diff --git a/server/src/connection/handshake_manager.rs b/server/src/connection/handshake_manager.rs index c473347c6..6eb0b561b 100644 --- a/server/src/connection/handshake_manager.rs +++ b/server/src/connection/handshake_manager.rs @@ -2,11 +2,10 @@ use std::{collections::HashMap, hash::Hash, net::SocketAddr}; use ring::{hmac, rand}; -use naia_shared::MessageKinds; pub use naia_shared::{ wrapping_diff, BaseConnection, BitReader, BitWriter, ConnectionConfig, FakeEntityConverter, - Instant, KeyGenerator, Message, PacketType, PropertyMutate, PropertyMutator, Replicate, Serde, - SerdeErr, StandardHeader, Timer, WorldMutType, WorldRefType, + Instant, KeyGenerator, Message, MessageKinds, PacketType, PropertyMutate, PropertyMutator, + Replicate, Serde, SerdeErr, StandardHeader, Timer, WorldMutType, WorldRefType, }; use crate::cache_map::CacheMap; @@ -71,7 +70,7 @@ impl HandshakeManager { } // Step 3 of Handshake - pub fn recv_connect_request( + pub fn recv_validate_request( &mut self, message_kinds: &MessageKinds, address: &SocketAddr, @@ -79,34 +78,39 @@ impl HandshakeManager { ) -> HandshakeResult { // Verify that timestamp hash has been written by this // server instance - if let Some(timestamp) = self.timestamp_validate(reader) { - // Timestamp hash is validated, now start configured auth process - if let Ok(has_auth) = bool::de(reader) { - if has_auth != self.require_auth { - return HandshakeResult::Invalid; - } + let Some(timestamp) = self.timestamp_validate(reader) else { + return HandshakeResult::Invalid; + }; + // Timestamp hash is validated, now start configured auth process + let Ok(has_auth) = bool::de(reader) else { + return HandshakeResult::Invalid; + }; + if has_auth != self.require_auth { + return HandshakeResult::Invalid; + } - self.address_to_timestamp_map.insert(*address, timestamp); + self.address_to_timestamp_map.insert(*address, timestamp); - if has_auth { - if let Ok(auth_message) = message_kinds.read(reader, &FakeEntityConverter) { - HandshakeResult::Success(Some(auth_message)) - } else { - HandshakeResult::Invalid - } - } else { - HandshakeResult::Success(None) - } - } else { - HandshakeResult::Invalid - } - } else { - HandshakeResult::Invalid + if !has_auth { + return HandshakeResult::Success(None); } + + let Ok(auth_message) = message_kinds.read(reader, &FakeEntityConverter) else { + return HandshakeResult::Invalid; + }; + + return HandshakeResult::Success(Some(auth_message)); } - // Step 3 of Handshake - pub fn write_connect_response(&self) -> BitWriter { + // Step 4 of Handshake + pub fn write_validate_response(&self) -> BitWriter { + let mut writer = BitWriter::new(); + StandardHeader::new(PacketType::ServerValidateResponse, 0, 0, 0).ser(&mut writer); + writer + } + + // Step 5 of Handshake + pub(crate) fn write_connect_response(&self) -> BitWriter { let mut writer = BitWriter::new(); StandardHeader::new(PacketType::ServerConnectResponse, 0, 0, 0).ser(&mut writer); writer diff --git a/server/src/connection/mod.rs b/server/src/connection/mod.rs index 4c8fb465c..002fe8d81 100644 --- a/server/src/connection/mod.rs +++ b/server/src/connection/mod.rs @@ -1,5 +1,10 @@ pub mod bandwidth_monitor; -#[allow(clippy::module_inception)] +pub mod channel_tick_buffer_receiver; pub mod connection; pub mod handshake_manager; pub mod io; +pub mod ping_config; +pub mod ping_manager; +pub mod tick_buffer_messages; +pub mod tick_buffer_receiver; +pub mod time_manager; diff --git a/shared/src/connection/ping_config.rs b/server/src/connection/ping_config.rs similarity index 100% rename from shared/src/connection/ping_config.rs rename to server/src/connection/ping_config.rs diff --git a/server/src/connection/ping_manager.rs b/server/src/connection/ping_manager.rs new file mode 100644 index 000000000..daa3e10ca --- /dev/null +++ b/server/src/connection/ping_manager.rs @@ -0,0 +1,63 @@ +use naia_shared::{BitReader, BitWriter, PingIndex, PingStore, Serde, Timer}; + +use crate::connection::ping_config::PingConfig; +use crate::connection::time_manager::TimeManager; + +/// Is responsible for sending regular ping messages between client/servers +/// and to estimate rtt/jitter +pub struct PingManager { + pub rtt_average: f32, + pub jitter_average: f32, + ping_timer: Timer, + sent_pings: PingStore, +} + +impl PingManager { + pub fn new(ping_config: &PingConfig) -> Self { + let rtt_average = ping_config.rtt_initial_estimate.as_secs_f32() * 1000.0; + let jitter_average = ping_config.jitter_initial_estimate.as_secs_f32() * 1000.0; + + PingManager { + rtt_average: rtt_average, + jitter_average: jitter_average, + ping_timer: Timer::new(ping_config.ping_interval), + sent_pings: PingStore::new(), + } + } + + /// Returns whether a ping message should be sent + pub fn should_send_ping(&self) -> bool { + self.ping_timer.ringing() + } + + /// Get an outgoing ping payload + pub fn write_ping(&mut self, writer: &mut BitWriter, time_manager: &TimeManager) { + self.ping_timer.reset(); + + let ping_index = self.sent_pings.push_new(time_manager.game_time_now()); + + // write index + ping_index.ser(writer); + } + + /// Process an incoming pong payload + pub fn process_pong(&mut self, time_manager: &TimeManager, reader: &mut BitReader) { + if let Ok(ping_index) = PingIndex::de(reader) { + match self.sent_pings.remove(ping_index) { + None => {} + Some(game_instant) => { + let rtt_millis = time_manager.game_time_since(&game_instant).as_millis(); + self.process_new_rtt(rtt_millis); + } + } + } + } + + /// Recompute rtt/jitter estimations + fn process_new_rtt(&mut self, rtt_millis: u32) { + let rtt_millis_f32 = rtt_millis as f32; + let new_jitter = ((rtt_millis_f32 - self.rtt_average) / 2.0).abs(); + self.jitter_average = (0.9 * self.jitter_average) + (0.1 * new_jitter); + self.rtt_average = (0.9 * self.rtt_average) + (0.1 * rtt_millis_f32); + } +} diff --git a/server/src/connection/tick_buffer_messages.rs b/server/src/connection/tick_buffer_messages.rs new file mode 100644 index 000000000..f5dc1f987 --- /dev/null +++ b/server/src/connection/tick_buffer_messages.rs @@ -0,0 +1,59 @@ +use std::{any::Any, collections::HashMap}; + +use naia_shared::{Channel, ChannelKind, Message, MessageKind}; + +use crate::UserKey; + +pub struct TickBufferMessages { + inner: HashMap)>>>, + empty: bool, +} + +impl TickBufferMessages { + pub fn new() -> Self { + Self { + inner: HashMap::new(), + empty: true, + } + } + + pub fn push( + &mut self, + user_key: &UserKey, + channel_kind: &ChannelKind, + message: Box, + ) { + if !self.inner.contains_key(&channel_kind) { + self.inner.insert(*channel_kind, HashMap::new()); + } + let channel_map = self.inner.get_mut(&channel_kind).unwrap(); + let message_type_id = message.kind(); + if !channel_map.contains_key(&message_type_id) { + channel_map.insert(message_type_id, Vec::new()); + } + let list = channel_map.get_mut(&message_type_id).unwrap(); + list.push((*user_key, message)); + self.empty = false; + } + + pub fn read(&self) -> Vec<(UserKey, M)> { + let mut output = Vec::new(); + + let channel_kind = ChannelKind::of::(); + if let Some(message_map) = self.inner.get(&channel_kind) { + let message_kind = MessageKind::of::(); + if let Some(messages) = message_map.get(&message_kind) { + for (user_key, boxed_message) in messages { + let boxed_any = boxed_message.clone_box().to_boxed_any(); + let message: M = Box::::downcast::(boxed_any) + .ok() + .map(|boxed_m| *boxed_m) + .unwrap(); + output.push((*user_key, message)); + } + } + } + + output + } +} diff --git a/server/src/tick/tick_buffer_receiver.rs b/server/src/connection/tick_buffer_receiver.rs similarity index 85% rename from server/src/tick/tick_buffer_receiver.rs rename to server/src/connection/tick_buffer_receiver.rs index bf3eb1985..a0d00c755 100644 --- a/server/src/tick/tick_buffer_receiver.rs +++ b/server/src/connection/tick_buffer_receiver.rs @@ -5,7 +5,7 @@ use naia_shared::{ SerdeErr, Tick, }; -use super::channel_tick_buffer_receiver::ChannelTickBufferReceiver; +use crate::connection::channel_tick_buffer_receiver::ChannelTickBufferReceiver; pub struct TickBufferReceiver { channel_receivers: HashMap, @@ -16,8 +16,11 @@ impl TickBufferReceiver { // initialize receivers let mut channel_receivers = HashMap::new(); for (channel_kind, channel_settings) in channel_kinds.channels() { - if let ChannelMode::TickBuffered(_) = channel_settings.mode { - channel_receivers.insert(channel_kind, ChannelTickBufferReceiver::new()); + if let ChannelMode::TickBuffered(settings) = channel_settings.mode { + channel_receivers.insert( + channel_kind, + ChannelTickBufferReceiver::new(settings.clone()), + ); } } diff --git a/server/src/connection/time_manager.rs b/server/src/connection/time_manager.rs new file mode 100644 index 000000000..d90eed065 --- /dev/null +++ b/server/src/connection/time_manager.rs @@ -0,0 +1,154 @@ +use std::time::Duration; + +use naia_shared::{ + wrapping_diff, BitReader, BitWriter, GameDuration, GameInstant, Instant, PacketType, PingIndex, + Serde, SerdeErr, StandardHeader, Tick, UnsignedVariableInteger, +}; + +/// Manages the current tick for the host +pub struct TimeManager { + start_instant: Instant, + current_tick: Tick, + last_tick_game_instant: GameInstant, + last_tick_instant: Instant, + tick_interval_millis: f32, + tick_duration_avg: f32, + tick_duration_avg_min: f32, + tick_duration_avg_max: f32, + tick_speedup_potential: f32, + client_diff_avg: f32, +} + +impl TimeManager { + /// Create a new TickManager with a given tick interval duration + pub fn new(tick_interval: Duration) -> Self { + let start_instant = Instant::now(); + let last_tick_instant = start_instant.clone(); + let last_tick_game_instant = GameInstant::new(&start_instant); + let tick_interval_millis = tick_interval.as_secs_f32() * 1000.0; + let tick_duration_avg = tick_interval_millis; + + Self { + start_instant, + current_tick: 0, + last_tick_game_instant, + last_tick_instant, + tick_interval_millis, + tick_duration_avg, + tick_duration_avg_min: tick_duration_avg, + tick_duration_avg_max: tick_duration_avg, + client_diff_avg: 0.0, + tick_speedup_potential: 0.0, + } + } + + pub(crate) fn duration_until_next_tick(&self) -> Duration { + let mut new_instant = self.last_tick_instant.clone(); + new_instant.add_millis(self.tick_interval_millis as u32); + return new_instant.until(); + } + + /// Whether or not we should emit a tick event + pub fn recv_server_tick(&mut self) -> bool { + let time_since_tick_ms = self.last_tick_instant.elapsed().as_secs_f32() * 1000.0; + + if time_since_tick_ms >= self.tick_interval_millis { + self.record_tick_duration(time_since_tick_ms); + self.last_tick_instant = Instant::now(); + self.last_tick_game_instant = self.game_time_now(); + self.current_tick = self.current_tick.wrapping_add(1); + return true; + } + return false; + } + + /// Gets the current tick of the Server + pub fn current_tick(&self) -> Tick { + self.current_tick + } + + pub fn current_tick_instant(&self) -> GameInstant { + self.last_tick_game_instant.clone() + } + + pub fn average_tick_duration(&self) -> Duration { + Duration::from_millis(self.tick_duration_avg.round() as u64) + } + + pub fn game_time_now(&self) -> GameInstant { + GameInstant::new(&self.start_instant) + } + + pub fn game_time_since(&self, previous_instant: &GameInstant) -> GameDuration { + self.game_time_now().time_since(previous_instant) + } + + pub fn record_tick_duration(&mut self, duration_ms: f32) { + self.tick_duration_avg = (0.9 * self.tick_duration_avg) + (0.1 * duration_ms); + + if self.tick_duration_avg < self.tick_duration_avg_min { + self.tick_duration_avg_min = self.tick_duration_avg; + } else { + self.tick_duration_avg_min = + (0.99999 * self.tick_duration_avg_min) + (0.00001 * self.tick_duration_avg); + } + + if self.tick_duration_avg > self.tick_duration_avg_max { + self.tick_duration_avg_max = self.tick_duration_avg; + } else { + self.tick_duration_avg_max = + (0.999 * self.tick_duration_avg_max) + (0.001 * self.tick_duration_avg); + } + + self.tick_speedup_potential = (((self.tick_duration_avg_max - self.tick_duration_avg_min) + / self.tick_duration_avg_min) + * 30.0) + .max(0.0) + .min(10.0); + } + + pub(crate) fn process_ping(&self, reader: &mut BitReader) -> Result { + let server_received_time = self.game_time_now(); + + // read incoming ping index + let ping_index = PingIndex::de(reader)?; + + // start packet writer + let mut writer = BitWriter::new(); + + // write pong payload + StandardHeader::new(PacketType::Pong, 0, 0, 0).ser(&mut writer); + + // write server tick + self.current_tick.ser(&mut writer); + + // write server tick instant + self.last_tick_game_instant.ser(&mut writer); + + // write index + ping_index.ser(&mut writer); + + // write received time + server_received_time.ser(&mut writer); + + // write average tick duration as microseconds + let tick_duration_avg = + UnsignedVariableInteger::<9>::new((self.tick_duration_avg * 1000.0).round() as i128); + tick_duration_avg.ser(&mut writer); + + let tick_speedup_potential = UnsignedVariableInteger::<9>::new( + (self.tick_speedup_potential * 1000.0).round() as i128, + ); + tick_speedup_potential.ser(&mut writer); + + // write send time + self.game_time_now().ser(&mut writer); + + Ok(writer) + } + + pub(crate) fn record_client_tick(&mut self, client_tick: Tick) { + let ticks_client_ahead_by = wrapping_diff(self.current_tick, client_tick) as f32; + self.client_diff_avg = (0.9 * self.client_diff_avg) + (0.1 * ticks_client_ahead_by); + } +} diff --git a/server/src/events.rs b/server/src/events.rs index 9d94bdf14..e4763db28 100644 --- a/server/src/events.rs +++ b/server/src/events.rs @@ -1,6 +1,6 @@ use std::{any::Any, collections::HashMap, marker::PhantomData, mem, vec::IntoIter}; -use naia_shared::{Channel, ChannelKind, Message, MessageKind}; +use naia_shared::{Channel, ChannelKind, Message, MessageKind, Tick}; use super::user::{User, UserKey}; use crate::NaiaServerError; @@ -8,7 +8,7 @@ use crate::NaiaServerError; pub struct Events { connections: Vec, disconnections: Vec<(UserKey, User)>, - ticks: Vec<()>, + ticks: Vec, errors: Vec, auths: HashMap)>>, messages: HashMap)>>>, @@ -97,8 +97,8 @@ impl Events { self.empty = false; } - pub(crate) fn push_tick(&mut self) { - self.ticks.push(()); + pub(crate) fn push_tick(&mut self, tick: Tick) { + self.ticks.push(tick); self.empty = false; } @@ -140,7 +140,7 @@ impl Event for DisconnectEvent { // Tick Event pub struct TickEvent; impl Event for TickEvent { - type Iter = IntoIter<()>; + type Iter = IntoIter; fn iter(events: &mut Events) -> Self::Iter { let list = std::mem::take(&mut events.ticks); diff --git a/server/src/lib.rs b/server/src/lib.rs index 112af5702..02bc98efc 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -23,10 +23,10 @@ mod room; mod sequence_list; mod server; mod server_config; -mod tick; mod user; mod user_scope; +pub use connection::tick_buffer_messages::TickBufferMessages; pub use error::NaiaServerError; pub use events::{ AuthEvent, ConnectEvent, DisconnectEvent, ErrorEvent, Events, MessageEvent, TickEvent, diff --git a/server/src/protocol/entity_manager.rs b/server/src/protocol/entity_manager.rs index 548eed62b..c8f92d4db 100644 --- a/server/src/protocol/entity_manager.rs +++ b/server/src/protocol/entity_manager.rs @@ -349,8 +349,6 @@ impl EntityManager { // if we are writing to this packet, add it to record if is_writing { - //info!("write SpawnEntity({})", action_id); - Self::record_action_written( &mut self.sent_action_packets, packet_index, @@ -370,8 +368,6 @@ impl EntityManager { // if we are writing to this packet, add it to record if is_writing { - //info!("write DespawnEntity({})", action_id); - Self::record_action_written( &mut self.sent_action_packets, packet_index, @@ -388,8 +384,6 @@ impl EntityManager { // if we are actually writing this packet if is_writing { - //info!("write Noop({})", action_id); - // add it to action record Self::record_action_written( &mut self.sent_action_packets, @@ -417,8 +411,6 @@ impl EntityManager { // if we are actually writing this packet if is_writing { - //info!("write InsertComponent({})", action_id); - // add it to action record Self::record_action_written( &mut self.sent_action_packets, @@ -435,8 +427,6 @@ impl EntityManager { // if we are actually writing this packet if is_writing { - //info!("write Noop({})", action_id); - // add it to action record Self::record_action_written( &mut self.sent_action_packets, @@ -459,8 +449,6 @@ impl EntityManager { // if we are writing to this packet, add it to record if is_writing { - //info!("write RemoveComponent({})", action_id); - Self::record_action_written( &mut self.sent_action_packets, packet_index, @@ -647,8 +635,6 @@ impl EntityManager { written_component_kinds.push(*component_kind); - //info!("writing UpdateComponent"); - // place diff mask in a special transmission record - like map self.last_update_packet_index = *packet_index; diff --git a/server/src/protocol/world_channel.rs b/server/src/protocol/world_channel.rs index 56e6b8bd8..9973de9e9 100644 --- a/server/src/protocol/world_channel.rs +++ b/server/src/protocol/world_channel.rs @@ -263,7 +263,9 @@ impl WorldChannel { pub fn remote_despawn_entity(&mut self, entity: &E) { if !self.remote_world.contains_key(entity) { - panic!("World Channel: should not be able to despawn non-existent entity in remote world"); + panic!( + "World Channel: should not be able to despawn non-existent entity in remote world" + ); } if let Some(EntityChannel::Despawning) = self.entity_channels.get(entity) { diff --git a/server/src/server.rs b/server/src/server.rs index 6cd83d68d..74ef5cf74 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -4,6 +4,7 @@ use std::{ net::SocketAddr, panic, sync::{Arc, RwLock}, + time::Duration, }; use log::warn; @@ -23,6 +24,8 @@ use crate::{ connection::Connection, handshake_manager::{HandshakeManager, HandshakeResult}, io::Io, + tick_buffer_messages::TickBufferMessages, + time_manager::TimeManager, }, protocol::{ entity_ref::{EntityMut, EntityRef}, @@ -30,7 +33,6 @@ use crate::{ global_diff_handler::GlobalDiffHandler, world_record::WorldRecord, }, - tick::tick_manager::TickManager, }; use super::{ @@ -59,6 +61,7 @@ pub struct Server { // Users users: BigMap, user_connections: HashMap>, + validated_users: HashMap, // Rooms rooms: BigMap>, // Entities @@ -69,7 +72,7 @@ pub struct Server { // Events incoming_events: Events, // Ticks - tick_manager: Option, + time_manager: TimeManager, } impl Server { @@ -80,7 +83,7 @@ impl Server { let socket = Socket::new(&protocol.socket); - let tick_manager = { protocol.tick_interval.map(TickManager::new) }; + let time_manager = TimeManager::new(protocol.tick_interval); let io = Io::new( &server_config.connection.bandwidth_measure_duration, @@ -96,11 +99,12 @@ impl Server { io, heartbeat_timer: Timer::new(server_config.connection.heartbeat_interval), timeout_timer: Timer::new(server_config.connection.disconnection_timeout_duration), - ping_timer: Timer::new(server_config.connection.ping.ping_interval), + ping_timer: Timer::new(server_config.ping.ping_interval), handshake_manager: HandshakeManager::new(server_config.require_auth), // Users users: BigMap::default(), user_connections: HashMap::new(), + validated_users: HashMap::new(), // Rooms rooms: BigMap::default(), // Entities @@ -111,7 +115,7 @@ impl Server { // Events incoming_events: Events::new(), // Ticks - tick_manager, + time_manager, } } @@ -128,6 +132,10 @@ impl Server { self.io.is_loaded() } + pub fn duration_until_next_tick(&self) -> Duration { + self.time_manager.duration_until_next_tick() + } + /// Must be called regularly, maintains connection to and receives messages /// from all Clients pub fn receive(&mut self) -> Events { @@ -136,11 +144,9 @@ impl Server { self.maintain_socket(); // tick event - let mut did_tick = false; - if let Some(tick_manager) = &mut self.tick_manager { - if tick_manager.recv_server_tick() { - did_tick = true; - } + if self.time_manager.recv_server_tick() { + self.incoming_events + .push_tick(self.time_manager.current_tick()); } // loop through all connections, receive Messages @@ -154,21 +160,6 @@ impl Server { connection.receive_messages(&mut self.incoming_events); } - // receive (retrieve from buffer) tick buffered messages for the current server tick - if did_tick { - // Receive Tick Buffered Messages - for user_address in &user_addresses { - let connection = self.user_connections.get_mut(user_address).unwrap(); - - connection.receive_tick_buffer_messages( - &self.tick_manager.as_ref().unwrap().server_tick(), - &mut self.incoming_events, - ); - } - - self.incoming_events.push_tick(); - } - // return all received messages and reset the buffer std::mem::take(&mut self.incoming_events) } @@ -178,33 +169,53 @@ impl Server { /// Accepts an incoming Client User, allowing them to establish a connection /// with the Server pub fn accept_connection(&mut self, user_key: &UserKey) { - if let Some(user) = self.users.get(user_key) { - let new_connection = Connection::new( - &self.server_config.connection, - user.address, - user_key, - &self.protocol.channel_kinds, - &self.diff_handler, + let Some(user) = self.users.get(user_key) else { + warn!("unknown user is finalizing connection..."); + return; + }; + + // send validate response + let mut writer = self.handshake_manager.write_validate_response(); + if self.io.send_writer(&user.address, &mut writer).is_err() { + // TODO: pass this on and handle above + warn!( + "Server Error: Cannot send validate response packet to {}", + &user.address ); - // send connectaccept response - let mut writer = self.handshake_manager.write_connect_response(); - match self.io.send_writer(&user.address, &mut writer) { - Ok(()) => {} - Err(_) => { - // TODO: pass this on and handle above - warn!( - "Server Error: Cannot send connect response packet to {}", - &user.address - ); - } - } - // - self.user_connections.insert(user.address, new_connection); - if self.io.bandwidth_monitor_enabled() { - self.io.register_client(&user.address); - } - self.incoming_events.push_connection(user_key); } + + self.validated_users.insert(user.address, *user_key); + } + + fn finalize_connection(&mut self, user_key: &UserKey) { + let Some(user) = self.users.get(user_key) else { + warn!("unknown user is finalizing connection..."); + return; + }; + let new_connection = Connection::new( + &self.server_config.connection, + &self.server_config.ping, + user.address, + user_key, + &self.protocol.channel_kinds, + &self.diff_handler, + ); + + // send connect response + let mut writer = self.handshake_manager.write_connect_response(); + if self.io.send_writer(&user.address, &mut writer).is_err() { + // TODO: pass this on and handle above + warn!( + "Server Error: Cannot send connect response packet to {}", + &user.address + ); + } + + self.user_connections.insert(user.address, new_connection); + if self.io.bandwidth_monitor_enabled() { + self.io.register_client(&user.address); + } + self.incoming_events.push_connection(user_key); } /// Rejects an incoming Client User, terminating their attempt to establish @@ -307,6 +318,15 @@ impl Server { .for_each(|user_key| self.send_message_inner(user_key, channel_kind, message.clone())) } + pub fn receive_tick_buffer_messages(&mut self, tick: &Tick) -> TickBufferMessages { + let mut tick_buffer_messages = TickBufferMessages::new(); + for (_user_address, connection) in self.user_connections.iter_mut() { + // receive messages from anyone + connection.tick_buffer_messages(tick, &mut tick_buffer_messages); + } + tick_buffer_messages + } + // Updates /// Used to evaluate whether, given a User & Entity that are in the @@ -350,7 +370,7 @@ impl Server { for user_address in user_addresses { let connection = self.user_connections.get_mut(&user_address).unwrap(); - let rtt = connection.ping_manager.rtt; + let rtt = connection.ping_manager.rtt_average; connection.send_outgoing_packets( &self.protocol, @@ -358,7 +378,7 @@ impl Server { &mut self.io, &world, &self.world_record, - &self.tick_manager, + &self.time_manager, &rtt, ); } @@ -511,22 +531,14 @@ impl Server { // Ticks - /// Gets the last received tick from the Client - pub fn client_tick(&self, user_key: &UserKey) -> Option { - if let Some(user) = self.users.get(user_key) { - if let Some(user_connection) = self.user_connections.get(&user.address) { - return Some(user_connection.last_received_tick); - } - } - None + /// Gets the current tick of the Server + pub fn current_tick(&self) -> Tick { + return self.time_manager.current_tick(); } - /// Gets the current tick of the Server - pub fn server_tick(&self) -> Option { - return self - .tick_manager - .as_ref() - .map(|tick_manager| tick_manager.server_tick()); + /// Gets the current average tick duration of the Server + pub fn average_tick_duration(&self) -> Duration { + self.time_manager.average_tick_duration() } // Bandwidth monitoring @@ -551,7 +563,7 @@ impl Server { pub fn rtt(&self, user_key: &UserKey) -> Option { if let Some(user) = self.users.get(user_key) { if let Some(user_connection) = self.user_connections.get(&user.address) { - return Some(user_connection.ping_manager.rtt); + return Some(user_connection.ping_manager.rtt_average); } } None @@ -562,7 +574,7 @@ impl Server { pub fn jitter(&self, user_key: &UserKey) -> Option { if let Some(user) = self.users.get(user_key) { if let Some(user_connection) = self.user_connections.get(&user.address) { - return Some(user_connection.ping_manager.jitter); + return Some(user_connection.ping_manager.jitter_average); } } None @@ -726,6 +738,7 @@ impl Server { pub(crate) fn user_delete(&mut self, user_key: &UserKey) -> Option { if let Some(user) = self.users.remove(user_key) { if self.user_connections.remove(&user.address).is_some() { + self.validated_users.remove(&user.address); self.entity_scope_map.remove_user(user_key); self.handshake_manager.delete_user(&user.address); @@ -925,20 +938,18 @@ impl Server { .write_outgoing_header(PacketType::Heartbeat, &mut writer); // write server tick - if let Some(tick_manager) = self.tick_manager.as_mut() { - tick_manager.write_server_tick(&mut writer); - } + self.time_manager.current_tick().ser(&mut writer); + + // write server tick instant + self.time_manager.current_tick_instant().ser(&mut writer); // send packet - match self.io.send_writer(user_address, &mut writer) { - Ok(()) => {} - Err(_) => { - // TODO: pass this on and handle above - warn!( - "Server Error: Cannot send heartbeat packet to {}", - user_address - ); - } + if self.io.send_writer(user_address, &mut writer).is_err() { + // TODO: pass this on and handle above + warn!( + "Server Error: Cannot send heartbeat packet to {}", + user_address + ); } connection.base.mark_sent(); } @@ -959,21 +970,21 @@ impl Server { .base .write_outgoing_header(PacketType::Ping, &mut writer); - // write client tick - if let Some(tick_manager) = self.tick_manager.as_mut() { - tick_manager.write_server_tick(&mut writer); - } + // write server tick + self.time_manager.current_tick().ser(&mut writer); + + // write server tick instant + self.time_manager.current_tick_instant().ser(&mut writer); // write body - connection.ping_manager.write_ping(&mut writer); + connection + .ping_manager + .write_ping(&mut writer, &self.time_manager); // send packet - match self.io.send_writer(user_address, &mut writer) { - Ok(()) => {} - Err(_) => { - // TODO: pass this on and handle above - warn!("Server Error: Cannot send ping packet to {}", user_address); - } + if self.io.send_writer(user_address, &mut writer).is_err() { + // TODO: pass this on and handle above + warn!("Server Error: Cannot send ping packet to {}", user_address); } connection.base.mark_sent(); } @@ -987,13 +998,11 @@ impl Server { let mut reader = owned_reader.borrow(); // Read header - let header_result = StandardHeader::de(&mut reader); - if header_result.is_err() { + let Ok(header) = StandardHeader::de(&mut reader) else { // Received a malformed packet // TODO: increase suspicion against packet sender continue; - } - let header = header_result.unwrap(); + }; // Handshake stuff match header.packet_type { @@ -1001,35 +1010,31 @@ impl Server { if let Ok(mut writer) = self.handshake_manager.recv_challenge_request(&mut reader) { - match self.io.send_writer(&address, &mut writer) { - Ok(()) => {} - Err(_) => { - // TODO: pass this on and handle above - warn!("Server Error: Cannot send challenge response packet to {}", &address); - } - }; + if self.io.send_writer(&address, &mut writer).is_err() { + // TODO: pass this on and handle above + warn!( + "Server Error: Cannot send challenge response packet to {}", + &address + ); + } } continue; } - PacketType::ClientConnectRequest => { - match self.handshake_manager.recv_connect_request( + PacketType::ClientValidateRequest => { + match self.handshake_manager.recv_validate_request( &self.protocol.message_kinds, &address, &mut reader, ) { HandshakeResult::Success(auth_message_opt) => { - if self.user_connections.contains_key(&address) { - // send connectaccept response + if self.validated_users.contains_key(&address) { + // send validate response let mut writer = - self.handshake_manager.write_connect_response(); - match self.io.send_writer(&address, &mut writer) { - Ok(()) => {} - Err(_) => { - // TODO: pass this on and handle above - warn!("Server Error: Cannot send connect success response packet to {}", &address); - } + self.handshake_manager.write_validate_response(); + if self.io.send_writer(&address, &mut writer).is_err() { + // TODO: pass this on and handle above + warn!("Server Error: Cannot send validate success response packet to {}", &address); }; - // } else { let user = User::new(address); let user_key = self.users.insert(user); @@ -1047,6 +1052,36 @@ impl Server { } continue; } + PacketType::Ping => { + let mut response = self.time_manager.process_ping(&mut reader).unwrap(); + // send packet + if self.io.send_writer(&address, &mut response).is_err() { + // TODO: pass this on and handle above + warn!("Server Error: Cannot send pong packet to {}", &address); + }; + if let Some(user_connection) = self.user_connections.get_mut(&address) { + user_connection.base.mark_sent(); + } + continue; + } + PacketType::ClientConnectRequest => { + if self.user_connections.contains_key(&address) { + // send connect response + let mut writer = self.handshake_manager.write_connect_response(); + if self.io.send_writer(&address, &mut writer).is_err() { + // TODO: pass this on and handle above + warn!("Server Error: Cannot send connect success response packet to {}", &address); + }; + // + } else { + let user_key = *self + .validated_users + .get(&address) + .expect("should be a user by now, from validation step"); + self.finalize_connection(&user_key); + } + continue; + } _ => {} } @@ -1061,41 +1096,32 @@ impl Server { match header.packet_type { PacketType::Data => { // read client tick - let server_and_client_tick_opt = { - if let Some(tick_manager) = self.tick_manager.as_ref() { - //// - let client_tick_result = - tick_manager.read_client_tick(&mut reader); - if client_tick_result.is_err() { - // Received a malformed packet - // TODO: increase suspicion against packet sender - continue; - } - let client_tick = client_tick_result.unwrap(); - user_connection.recv_client_tick(client_tick); - //// + let Ok(client_tick) = Tick::de(&mut reader) else { + // Received a malformed packet + // TODO: increase suspicion against packet sender + continue; + }; - let server_tick = tick_manager.server_tick(); + self.time_manager.record_client_tick(client_tick); - Some((server_tick, client_tick)) - } else { - None - } - }; + let server_tick = self.time_manager.current_tick(); // process data - let data_result = user_connection.process_incoming_data( - &self.protocol, - server_and_client_tick_opt, - &mut reader, - &self.world_record, - ); - if data_result.is_err() { + if user_connection + .process_incoming_data( + &self.protocol, + server_tick, + client_tick, + &mut reader, + &self.world_record, + ) + .is_err() + { // Received a malformed packet // TODO: increase suspicion against packet sender warn!("Error reading incoming packet!"); continue; - } + }; } PacketType::Disconnect => { if self @@ -1107,88 +1133,13 @@ impl Server { } } PacketType::Heartbeat => { - // read client tick, don't need to do anything else - if let Some(tick_manager) = self.tick_manager.as_ref() { - //// - let client_tick_result = - tick_manager.read_client_tick(&mut reader); - if client_tick_result.is_err() { - // Received a malformed packet - // TODO: increase suspicion against packet sender - continue; - } - let client_tick = client_tick_result.unwrap(); - user_connection.recv_client_tick(client_tick); - //// - } - } - PacketType::Ping => { - // read client tick - if let Some(tick_manager) = self.tick_manager.as_ref() { - //// - let client_tick_result = - tick_manager.read_client_tick(&mut reader); - if client_tick_result.is_err() { - // Received a malformed packet - // TODO: increase suspicion against packet sender - continue; - } - let client_tick = client_tick_result.unwrap(); - user_connection.recv_client_tick(client_tick); - //// - } - - // read incoming ping index - let ping_index = u16::de(&mut reader).unwrap(); - - // write pong payload - let mut writer = BitWriter::new(); - - // write header - user_connection - .base - .write_outgoing_header(PacketType::Pong, &mut writer); - - // write server tick - if let Some(tick_manager) = self.tick_manager.as_ref() { - tick_manager.write_server_tick(&mut writer); - } - - // write index - ping_index.ser(&mut writer); - - // send packet - match self.io.send_writer(&address, &mut writer) { - Ok(()) => {} - Err(_) => { - // TODO: pass this on and handle above - warn!( - "Server Error: Cannot send pong packet to {}", - &address - ); - } - }; - user_connection.base.mark_sent(); + // already marked heard above } PacketType::Pong => { // read client tick - if let Some(tick_manager) = self.tick_manager.as_ref() { - //// - let client_tick_result = - tick_manager.read_client_tick(&mut reader); - if client_tick_result.is_err() { - // Received a malformed packet - // TODO: increase suspicion against packet sender - continue; - } - let client_tick = client_tick_result.unwrap(); - user_connection.recv_client_tick(client_tick); - //// - } - - // TODO: send a message to client with a recommendation on how - // to speedup/slowdown simulation? - user_connection.ping_manager.process_pong(&mut reader); + user_connection + .ping_manager + .process_pong(&self.time_manager, &mut reader); } _ => {} } diff --git a/server/src/server_config.rs b/server/src/server_config.rs index e6b194bf8..f767f5504 100644 --- a/server/src/server_config.rs +++ b/server/src/server_config.rs @@ -2,6 +2,8 @@ use std::default::Default; use naia_shared::ConnectionConfig; +use crate::connection::ping_config::PingConfig; + /// Contains Config properties which will be used by the Server #[derive(Clone)] pub struct ServerConfig { @@ -10,6 +12,8 @@ pub struct ServerConfig { /// Determines whether to require that the Client send some auth message /// in order to connect. pub require_auth: bool, + /// Configuration used to monitor the ping & jitter on the network + pub ping: PingConfig, } impl Default for ServerConfig { @@ -17,6 +21,7 @@ impl Default for ServerConfig { Self { connection: ConnectionConfig::default(), require_auth: true, + ping: PingConfig::default(), } } } diff --git a/server/src/tick/mod.rs b/server/src/tick/mod.rs deleted file mode 100644 index b8f557605..000000000 --- a/server/src/tick/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod channel_tick_buffer_receiver; -pub mod tick_buffer_receiver; -pub mod tick_manager; diff --git a/server/src/tick/tick_manager.rs b/server/src/tick/tick_manager.rs deleted file mode 100644 index b8bc3fe0c..000000000 --- a/server/src/tick/tick_manager.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::time::Duration; - -use naia_shared::{BitReader, BitWriter, Serde, SerdeErr, Tick, Timer}; - -/// Manages the current tick for the host -pub struct TickManager { - current_tick: Tick, - timer: Timer, -} - -impl TickManager { - /// Create a new TickManager with a given tick interval duration - pub fn new(tick_interval: Duration) -> Self { - TickManager { - current_tick: 0, - timer: Timer::new(tick_interval), - } - } - - pub fn write_server_tick(&self, writer: &mut BitWriter) { - self.current_tick.ser(writer); - } - - pub fn read_client_tick(&self, reader: &mut BitReader) -> Result { - Tick::de(reader) - } - - /// Whether or not we should emit a tick event - pub fn recv_server_tick(&mut self) -> bool { - if self.timer.ringing() { - self.timer.reset(); - self.current_tick = self.current_tick.wrapping_add(1); - return true; - } - false - } - - /// Gets the current tick on the host - pub fn server_tick(&self) -> Tick { - self.current_tick - } -} diff --git a/shared/src/connection/connection_config.rs b/shared/src/connection/connection_config.rs index d29ecdf95..afcf8462f 100644 --- a/shared/src/connection/connection_config.rs +++ b/shared/src/connection/connection_config.rs @@ -1,4 +1,3 @@ -use crate::PingConfig; use std::{default::Default, time::Duration}; /// Contains Config properties which will be used by a Server or Client @@ -13,8 +12,6 @@ pub struct ConnectionConfig { /// The duration over which to measure bandwidth. Set to None to avoid /// measure bandwidth at all. pub bandwidth_measure_duration: Option, - /// Configuration used to monitor the ping & jitter on the network - pub ping: PingConfig, } impl ConnectionConfig { @@ -23,13 +20,11 @@ impl ConnectionConfig { disconnection_timeout_duration: Duration, heartbeat_interval: Duration, bandwidth_measure_duration: Option, - ping: PingConfig, ) -> Self { ConnectionConfig { disconnection_timeout_duration, heartbeat_interval, bandwidth_measure_duration, - ping, } } } @@ -40,7 +35,6 @@ impl Default for ConnectionConfig { disconnection_timeout_duration: Duration::from_secs(30), heartbeat_interval: Duration::from_secs(4), bandwidth_measure_duration: None, - ping: PingConfig::default(), } } } diff --git a/shared/src/connection/mod.rs b/shared/src/connection/mod.rs index ea92a21a6..8a5d51941 100644 --- a/shared/src/connection/mod.rs +++ b/shared/src/connection/mod.rs @@ -7,7 +7,6 @@ pub mod decoder; pub mod encoder; pub mod packet_notifiable; pub mod packet_type; -pub mod ping_config; -pub mod ping_manager; +pub mod ping_store; pub mod sequence_buffer; pub mod standard_header; diff --git a/shared/src/connection/packet_type.rs b/shared/src/connection/packet_type.rs index 3c283f9fa..21b6da299 100644 --- a/shared/src/connection/packet_type.rs +++ b/shared/src/connection/packet_type.rs @@ -13,6 +13,10 @@ pub enum PacketType { ClientChallengeRequest, // The Server's response to the Client's initial handshake message ServerChallengeResponse, + // The handshake message validating the Client + ClientValidateRequest, + // The Server's response to the Client's validation request + ServerValidateResponse, // The final handshake message sent by the Client ClientConnectRequest, // The final handshake message sent by the Server, indicating that the @@ -46,12 +50,14 @@ impl Serde for PacketType { PacketType::Heartbeat => 0, PacketType::ClientChallengeRequest => 1, PacketType::ServerChallengeResponse => 2, - PacketType::ClientConnectRequest => 3, - PacketType::ServerConnectResponse => 4, - PacketType::ServerRejectResponse => 5, - PacketType::Ping => 6, - PacketType::Pong => 7, - PacketType::Disconnect => 8, + PacketType::ClientValidateRequest => 3, + PacketType::ServerValidateResponse => 4, + PacketType::ClientConnectRequest => 5, + PacketType::ServerConnectResponse => 6, + PacketType::ServerRejectResponse => 7, + PacketType::Ping => 8, + PacketType::Pong => 9, + PacketType::Disconnect => 10, }; UnsignedInteger::<4>::new(index).ser(writer); @@ -67,12 +73,14 @@ impl Serde for PacketType { 0 => Ok(PacketType::Heartbeat), 1 => Ok(PacketType::ClientChallengeRequest), 2 => Ok(PacketType::ServerChallengeResponse), - 3 => Ok(PacketType::ClientConnectRequest), - 4 => Ok(PacketType::ServerConnectResponse), - 5 => Ok(PacketType::ServerRejectResponse), - 6 => Ok(PacketType::Ping), - 7 => Ok(PacketType::Pong), - 8 => Ok(PacketType::Disconnect), + 3 => Ok(PacketType::ClientValidateRequest), + 4 => Ok(PacketType::ServerValidateResponse), + 5 => Ok(PacketType::ClientConnectRequest), + 6 => Ok(PacketType::ServerConnectResponse), + 7 => Ok(PacketType::ServerRejectResponse), + 8 => Ok(PacketType::Ping), + 9 => Ok(PacketType::Pong), + 10 => Ok(PacketType::Disconnect), _ => panic!("shouldn't happen, caught above"), } } diff --git a/shared/src/connection/ping_manager.rs b/shared/src/connection/ping_manager.rs deleted file mode 100644 index e57017573..000000000 --- a/shared/src/connection/ping_manager.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::collections::VecDeque; - -use naia_serde::{BitReader, BitWriter, Serde}; - -use naia_socket_shared::Instant; - -use crate::{backends::Timer, wrapping_number::sequence_greater_than}; - -use super::ping_config::PingConfig; - -/// Is responsible for sending regular ping messages between client/servers -/// and to estimate rtt/jitter -pub struct PingManager { - ping_timer: Timer, - sent_pings: SentPings, - pub rtt: f32, - pub jitter: f32, - rtt_smoothing_factor: f32, - rtt_smoothing_factor_inv: f32, -} - -impl PingManager { - pub fn new(ping_config: &PingConfig) -> Self { - let rtt_average = ping_config.rtt_initial_estimate.as_secs_f32() * 1000.0; - let jitter_average = ping_config.jitter_initial_estimate.as_secs_f32() * 1000.0; - - PingManager { - ping_timer: Timer::new(ping_config.ping_interval), - sent_pings: SentPings::new(), - rtt: rtt_average, - jitter: jitter_average, - rtt_smoothing_factor: ping_config.rtt_smoothing_factor, - rtt_smoothing_factor_inv: 1.0 - ping_config.rtt_smoothing_factor, - } - } - - /// Returns whether a ping message should be sent - pub fn should_send_ping(&self) -> bool { - self.ping_timer.ringing() - } - - /// Get an outgoing ping payload - pub fn write_ping(&mut self, writer: &mut BitWriter) { - self.ping_timer.reset(); - - let ping_index = self.sent_pings.push_new(); - - // write index - ping_index.ser(writer); - } - - /// Process an incoming pong payload - pub fn process_pong(&mut self, reader: &mut BitReader) { - if let Ok(ping_index) = PingIndex::de(reader) { - match self.sent_pings.remove(ping_index) { - None => {} - Some(ping_instant) => { - let rtt_millis = &ping_instant.elapsed().as_secs_f32() * 1000.0; - self.process_new_rtt(rtt_millis); - } - } - } - } - - /// Recompute rtt/jitter estimations - fn process_new_rtt(&mut self, rtt_millis: f32) { - let new_jitter = ((rtt_millis - self.rtt) / 2.0).abs(); - self.jitter = (self.rtt_smoothing_factor_inv * self.jitter) - + (self.rtt_smoothing_factor * new_jitter); - - self.rtt = - (self.rtt_smoothing_factor_inv * self.rtt) + (self.rtt_smoothing_factor * rtt_millis); - } -} - -pub type PingIndex = u16; -const SENT_PINGS_HISTORY_SIZE: u16 = 32; - -struct SentPings { - ping_index: PingIndex, - // front big, back small - // front recent, back past - buffer: VecDeque<(PingIndex, Instant)>, -} - -impl SentPings { - pub fn new() -> Self { - SentPings { - ping_index: 0, - buffer: VecDeque::new(), - } - } - - pub fn push_new(&mut self) -> PingIndex { - // save current ping index and add a new ping instant associated with it - let ping_index = self.ping_index; - self.ping_index = self.ping_index.wrapping_add(1); - self.buffer.push_front((ping_index, Instant::now())); - - // a good time to prune down the size of this buffer - while self.buffer.len() > SENT_PINGS_HISTORY_SIZE.into() { - self.buffer.pop_back(); - //info!("pruning sent_pings buffer cause it got too big"); - } - - ping_index - } - - pub fn remove(&mut self, ping_index: PingIndex) -> Option { - let mut vec_index = self.buffer.len(); - let mut found = false; - - loop { - vec_index -= 1; - - if let Some((old_index, _)) = self.buffer.get(vec_index) { - if *old_index == ping_index { - //found it! - found = true; - } else { - // if old_index is bigger than ping_index, give up, it's only getting - // bigger - if sequence_greater_than(*old_index, ping_index) { - return None; - } - } - } - - if found { - let (_, ping_instant) = self.buffer.remove(vec_index).unwrap(); - //info!("found and removed ping: {}", index); - return Some(ping_instant); - } - - // made it to the front - if vec_index == 0 { - return None; - } - } - } -} diff --git a/shared/src/connection/ping_store.rs b/shared/src/connection/ping_store.rs new file mode 100644 index 000000000..cf333ba43 --- /dev/null +++ b/shared/src/connection/ping_store.rs @@ -0,0 +1,78 @@ +use std::collections::VecDeque; + +use crate::{sequence_greater_than, GameInstant}; + +pub type PingIndex = u16; + +const SENT_PINGS_HISTORY_SIZE: u16 = 32; + +pub struct PingStore { + ping_index: PingIndex, + // front big, back small + // front recent, back past + buffer: VecDeque<(PingIndex, GameInstant)>, +} + +impl PingStore { + pub fn new() -> Self { + PingStore { + ping_index: 0, + buffer: VecDeque::new(), + } + } + + pub fn push_new(&mut self, now: GameInstant) -> PingIndex { + // save current ping index and add a new ping instant associated with it + let ping_index = self.ping_index; + self.ping_index = self.ping_index.wrapping_add(1); + self.buffer.push_front((ping_index, now)); + + // a good time to prune down the size of this buffer + while self.buffer.len() > SENT_PINGS_HISTORY_SIZE.into() { + self.buffer.pop_back(); + } + + ping_index + } + + pub fn remove(&mut self, ping_index: PingIndex) -> Option { + let mut vec_index = self.buffer.len(); + + if vec_index == 0 { + return None; + } + + let mut found = false; + + loop { + vec_index -= 1; + + if let Some((old_index, _)) = self.buffer.get(vec_index) { + if *old_index == ping_index { + //found it! + found = true; + } else { + // if old_index is bigger than ping_index, give up, it's only getting + // bigger + if sequence_greater_than(*old_index, ping_index) { + return None; + } + } + } + + if found { + let (_, ping_instant) = self.buffer.remove(vec_index).unwrap(); + return Some(ping_instant); + } + + // made it to the front + if vec_index == 0 { + return None; + } + } + } + + pub fn clear(&mut self) { + self.buffer.clear(); + } +} diff --git a/shared/src/constants.rs b/shared/src/constants.rs index f4818a1e3..8b1378917 100644 --- a/shared/src/constants.rs +++ b/shared/src/constants.rs @@ -1,2 +1 @@ -// Number of messages to keep in tick buffer -pub const MESSAGE_HISTORY_SIZE: u16 = 64; + diff --git a/shared/src/game_time.rs b/shared/src/game_time.rs new file mode 100644 index 000000000..5773c35e7 --- /dev/null +++ b/shared/src/game_time.rs @@ -0,0 +1,267 @@ +use naia_serde::{BitReader, BitWrite, Serde, SerdeErr, UnsignedInteger}; +use naia_socket_shared::Instant; + +pub const GAME_TIME_LIMIT: u32 = 4194304; // 2^22 +const GAME_TIME_LIMIT_U128: u128 = GAME_TIME_LIMIT as u128; +const GAME_TIME_MAX: u32 = 4194303; // 2^22 - 1 +const TIME_OFFSET_MAX: i32 = 2097151; // 2^21 - 1 +const TIME_OFFSET_MIN: i32 = -2097152; // 2^21 * -1 + +// GameInstant measures the # of milliseconds since the start of the Server +// GameInstant wraps around at 2^22 milliseconds (around one hour) +#[derive(PartialEq, Clone)] +pub struct GameInstant { + millis: u32, +} + +impl GameInstant { + pub fn new(start_instant: &Instant) -> Self { + let millis = (start_instant.elapsed().as_millis() % GAME_TIME_LIMIT_U128) as u32; + + // start_instant should mark the initialization of the Server's TimeManager + Self { millis } + } + + // This method assumes that `previous_instant` is known to be from the past + pub fn time_since(&self, previous_instant: &GameInstant) -> GameDuration { + let previous_millis = previous_instant.millis; + let current_millis = self.millis; + + if previous_millis == current_millis { + return GameDuration { millis: 0 }; + } + + if previous_millis < current_millis { + return GameDuration::from_millis(current_millis - previous_millis); + } else { + return GameDuration::from_millis(GAME_TIME_MAX - previous_millis + current_millis); + } + } + + // Returns offset to target time, in milliseconds (possibly negative) + // 10.offset_from(12) = 2 + // 12.offset_from(10) = -2 + pub fn offset_from(&self, other: &GameInstant) -> i32 { + const MAX: i32 = TIME_OFFSET_MAX; + const MIN: i32 = TIME_OFFSET_MIN; + const ADJUST: i32 = GAME_TIME_LIMIT as i32; + + let a: i32 = self.millis as i32; + let b: i32 = other.millis as i32; + + let mut result = b - a; + if (MIN..=MAX).contains(&result) { + result + } else if b > a { + result = b - (a + ADJUST); + if (MIN..=MAX).contains(&result) { + result + } else { + panic!("integer overflow, this shouldn't happen"); + } + } else { + result = (b + ADJUST) - a; + if (MIN..=MAX).contains(&result) { + result + } else { + panic!("integer overflow, this shouldn't happen"); + } + } + } + + pub fn is_more_than(&self, other: &GameInstant) -> bool { + return self.offset_from(other) < 0; + } + + pub fn as_millis(&self) -> u32 { + self.millis + } + + pub fn add_millis(&self, millis: u32) -> Self { + Self { + millis: (self.millis + millis) % GAME_TIME_LIMIT, + } + } + + pub fn sub_millis(&self, millis: u32) -> Self { + if self.millis >= millis { + Self { + millis: self.millis - millis, + } + } else { + // my millis is less than your millis + let delta = millis - self.millis; + Self { + millis: GAME_TIME_LIMIT - delta, + } + } + } + + pub fn add_signed_millis(&self, millis: i32) -> Self { + if millis >= 0 { + return self.add_millis(millis as u32); + } else { + return self.sub_millis((millis * -1) as u32); + } + } +} + +impl Serde for GameInstant { + fn ser(&self, writer: &mut dyn BitWrite) { + let integer = UnsignedInteger::<22>::new(self.millis as u64); + integer.ser(writer); + } + + fn de(reader: &mut BitReader) -> Result { + let integer = UnsignedInteger::<22>::de(reader)?; + let millis = integer.get() as u32; + Ok(Self { millis }) + } +} + +// GameDuration measures the duration between two GameInstants, in milliseconds +#[derive(PartialEq, PartialOrd, Eq, Clone)] +pub struct GameDuration { + millis: u32, +} + +impl GameDuration { + pub fn from_millis(millis: u32) -> Self { + Self { millis } + } + + pub fn as_millis(&self) -> u32 { + return self.millis; + } + + pub fn add_millis(&self, millis: u32) -> Self { + Self { + millis: self.millis + millis, + } + } + + pub fn sub_millis(&self, millis: u32) -> Self { + Self { + millis: self.millis - millis, + } + } +} + +// Tests +#[cfg(test)] +mod wrapping_diff_tests { + use super::GameInstant; + use crate::game_time::{GAME_TIME_LIMIT, GAME_TIME_MAX}; + + #[test] + fn simple() { + let a = GameInstant { millis: 10 }; + let b = GameInstant { millis: 12 }; + + let result = a.offset_from(&b); + + assert_eq!(result, 2); + } + + #[test] + fn simple_backwards() { + let a = GameInstant { millis: 10 }; + let b = GameInstant { millis: 12 }; + + let result = b.offset_from(&a); + + assert_eq!(result, -2); + } + + #[test] + fn max_wrap() { + let a = GameInstant { + millis: GAME_TIME_MAX, + }; + let b = a.add_millis(2); + + let result = a.offset_from(&b); + + assert_eq!(result, 2); + } + + #[test] + fn min_wrap() { + let a = GameInstant { millis: 0 }; + let b = a.sub_millis(2); + + let result = a.offset_from(&b); + + assert_eq!(result, -2); + } + + #[test] + fn max_wrap_backwards() { + let a = GameInstant { + millis: GAME_TIME_MAX, + }; + let b = a.add_millis(2); + + let result = b.offset_from(&a); + + assert_eq!(result, -2); + } + + #[test] + fn min_wrap_backwards() { + let a = GameInstant { millis: 0 }; + let b = a.sub_millis(2); + + let result = b.offset_from(&a); + + assert_eq!(result, 2); + } + + #[test] + fn medium_min_wrap() { + let diff = (GAME_TIME_LIMIT / 2); + let a = GameInstant { millis: 0 }; + let b = a.sub_millis(diff); + + let result = a.offset_from(&b); + + assert_eq!(result as i64, -i64::from(diff)); + } + + #[test] + fn medium_min_wrap_backwards() { + let diff = (GAME_TIME_LIMIT / 2) - 1; + let a = GameInstant { millis: 0 }; + let b = a.sub_millis(diff); + + let result = b.offset_from(&a); + + assert_eq!(result as i64, i64::from(diff)); + } + + #[test] + fn medium_max_wrap() { + let diff = (GAME_TIME_LIMIT / 2) - 1; + let a = GameInstant { + millis: GAME_TIME_MAX, + }; + let b = a.add_millis(diff); + + let result = a.offset_from(&b); + + assert_eq!(result as i64, i64::from(diff)); + } + + #[test] + fn medium_max_wrap_backwards() { + let diff = (GAME_TIME_LIMIT / 2); + let a = GameInstant { + millis: GAME_TIME_MAX, + }; + let b = a.add_millis(diff); + + let result = b.offset_from(&a); + + assert_eq!(result as i64, -i64::from(diff)); + } +} diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 74c5edfc5..5b1ed13d0 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -33,6 +33,7 @@ mod messages; mod bigmap; mod constants; +mod game_time; mod key_generator; mod protocol; mod types; @@ -65,8 +66,7 @@ pub use connection::{ encoder::Encoder, packet_notifiable::PacketNotifiable, packet_type::PacketType, - ping_config::PingConfig, - ping_manager::{PingIndex, PingManager}, + ping_store::{PingIndex, PingStore}, standard_header::StandardHeader, }; pub use entity::{ @@ -100,7 +100,7 @@ pub use naia_serde::{ }; pub use bigmap::{BigMap, BigMapKey}; -pub use constants::MESSAGE_HISTORY_SIZE; +pub use game_time::{GameDuration, GameInstant, GAME_TIME_LIMIT}; pub use key_generator::KeyGenerator; pub use protocol::{Protocol, ProtocolPlugin}; pub use types::{HostType, MessageIndex, PacketIndex, ShortMessageIndex, Tick}; diff --git a/shared/src/messages/channel.rs b/shared/src/messages/channel.rs index b88151a6b..4f2ea03b7 100644 --- a/shared/src/messages/channel.rs +++ b/shared/src/messages/channel.rs @@ -64,13 +64,15 @@ impl ReliableSettings { #[derive(Clone)] pub struct TickBufferSettings { - pub tick_resend_factor: u8, + /// Describes a maximum of messages that may be kept in the buffer. + /// Oldest messages are pruned out first. + pub message_capacity: usize, } impl TickBufferSettings { pub const fn default() -> Self { Self { - tick_resend_factor: 1, + message_capacity: 64, } } } diff --git a/shared/src/messages/default_channels.rs b/shared/src/messages/default_channels.rs index b52edafc0..cee006775 100644 --- a/shared/src/messages/default_channels.rs +++ b/shared/src/messages/default_channels.rs @@ -1,7 +1,5 @@ -use crate::{ - Channel, ChannelDirection, ChannelMode, Protocol, ProtocolPlugin, ReliableSettings, - TickBufferSettings, -}; +use crate::messages::channel::TickBufferSettings; +use crate::{Channel, ChannelDirection, ChannelMode, Protocol, ProtocolPlugin, ReliableSettings}; #[derive(Channel)] pub struct UnorderedUnreliableChannel; diff --git a/shared/src/protocol.rs b/shared/src/protocol.rs index 5fa262998..924ff6dbf 100644 --- a/shared/src/protocol.rs +++ b/shared/src/protocol.rs @@ -1,4 +1,3 @@ - use std::time::Duration; use naia_socket_shared::{LinkConditionerConfig, SocketConfig}; @@ -28,7 +27,7 @@ pub struct Protocol { /// Used to configure the underlying socket pub socket: SocketConfig, /// The duration between each tick - pub tick_interval: Option, + pub tick_interval: Duration, /// Configuration used to control compression parameters pub compression: Option, locked: bool, @@ -41,7 +40,7 @@ impl Default for Protocol { message_kinds: MessageKinds::new(), component_kinds: ComponentKinds::new(), socket: SocketConfig::new(None, None), - tick_interval: None, + tick_interval: Duration::from_millis(50), compression: None, locked: false, } @@ -73,7 +72,7 @@ impl Protocol { pub fn tick_interval(&mut self, duration: Duration) -> &mut Self { self.check_lock(); - self.tick_interval = Some(duration); + self.tick_interval = duration; self } diff --git a/shared/tests/derive_replicate.rs b/shared/tests/derive_replicate.rs index b3153dd08..4206ada66 100644 --- a/shared/tests/derive_replicate.rs +++ b/shared/tests/derive_replicate.rs @@ -112,7 +112,9 @@ fn read_write_unit_replica() { #[test] fn read_write_named_replica() { // Protocol - let protocol = Protocol::builder().add_component::().build(); + let protocol = Protocol::builder() + .add_component::() + .build(); let component_kinds = protocol.component_kinds; // Write @@ -145,7 +147,9 @@ fn read_write_named_replica() { #[test] fn read_write_tuple_replica() { // Protocol - let protocol = Protocol::builder().add_component::().build(); + let protocol = Protocol::builder() + .add_component::() + .build(); let component_kinds = protocol.component_kinds; // Write @@ -201,7 +205,9 @@ fn read_write_entity_replica() { } // Protocol - let protocol = Protocol::builder().add_component::().build(); + let protocol = Protocol::builder() + .add_component::() + .build(); let component_kinds = protocol.component_kinds; // Write @@ -232,7 +238,9 @@ fn read_write_entity_replica() { #[test] fn read_write_nonreplicated_replica() { // Protocol - let protocol = Protocol::builder().add_component::().build(); + let protocol = Protocol::builder() + .add_component::() + .build(); let component_kinds = protocol.component_kinds; // Write diff --git a/socket/client/src/backends/wasm_bindgen/packet_sender.rs b/socket/client/src/backends/wasm_bindgen/packet_sender.rs index a0aa24ea5..d59fc6fb5 100644 --- a/socket/client/src/backends/wasm_bindgen/packet_sender.rs +++ b/socket/client/src/backends/wasm_bindgen/packet_sender.rs @@ -37,4 +37,4 @@ impl PacketSender { } unsafe impl Send for PacketSender {} -unsafe impl Sync for PacketSender {} \ No newline at end of file +unsafe impl Sync for PacketSender {} diff --git a/socket/shared/src/link_condition_logic.rs b/socket/shared/src/link_condition_logic.rs index 327acfef3..6fcc4ff05 100644 --- a/socket/shared/src/link_condition_logic.rs +++ b/socket/shared/src/link_condition_logic.rs @@ -14,7 +14,6 @@ pub fn process_packet( ) { if Random::gen_range_f32(0.0, 1.0) <= config.incoming_loss { // drop the packet - //info!("link conditioner: packet lost"); return; } let mut latency: u32 = config.incoming_latency; diff --git a/socket/shared/src/link_conditioner_config.rs b/socket/shared/src/link_conditioner_config.rs index 87095a77f..b6c96df21 100644 --- a/socket/shared/src/link_conditioner_config.rs +++ b/socket/shared/src/link_conditioner_config.rs @@ -26,9 +26,9 @@ impl LinkConditionerConfig { /// good condition pub fn good_condition() -> Self { LinkConditionerConfig { - incoming_latency: 50, - incoming_jitter: 10, - incoming_loss: 0.01, + incoming_latency: 40, + incoming_jitter: 6, + incoming_loss: 0.002, } } @@ -36,9 +36,9 @@ impl LinkConditionerConfig { /// average condition pub fn average_condition() -> Self { LinkConditionerConfig { - incoming_latency: 200, - incoming_jitter: 20, - incoming_loss: 0.055, + incoming_latency: 170, + incoming_jitter: 45, + incoming_loss: 0.02, } } @@ -46,9 +46,9 @@ impl LinkConditionerConfig { /// poor condition pub fn poor_condition() -> Self { LinkConditionerConfig { - incoming_latency: 350, - incoming_jitter: 30, - incoming_loss: 0.1, + incoming_latency: 300, + incoming_jitter: 84, + incoming_loss: 0.04, } } } diff --git a/test/tests/handshake.rs b/test/tests/handshake.rs index 0955bd928..3235cbd6c 100644 --- a/test/tests/handshake.rs +++ b/test/tests/handshake.rs @@ -52,13 +52,13 @@ fn end_to_end_handshake_w_auth() { client.recv_challenge_response(&mut reader); assert_eq!( client.connection_state, - HandshakeState::AwaitingConnectResponse + HandshakeState::AwaitingValidateResponse ); } // 5. Client send connect request { - writer = client.write_connect_request(&message_kinds); + writer = client.write_validate_request(&message_kinds); let (length, buffer) = writer.flush(); message_length = length; message_buffer = buffer; @@ -69,7 +69,7 @@ fn end_to_end_handshake_w_auth() { reader = BitReader::new(&message_buffer[..message_length]); StandardHeader::de(&mut reader).expect("unable to read standard header from stream"); let address = "127.0.0.1:4000".parse().unwrap(); - let result = server.recv_connect_request(&message_kinds, &address, &mut reader); + let result = server.recv_validate_request(&message_kinds, &address, &mut reader); if let HandshakeResult::Success(Some(auth_message)) = result { let boxed_any = auth_message.to_boxed_any(); let auth_replica = boxed_any @@ -92,7 +92,7 @@ fn end_to_end_handshake_w_auth() { // 7. Server send connect response { - let header = StandardHeader::new(PacketType::ServerConnectResponse, 0, 0, 0); + let header = StandardHeader::new(PacketType::ServerValidateResponse, 0, 0, 0); writer = BitWriter::new(); header.ser(&mut writer); let (length, buffer) = writer.flush(); @@ -104,6 +104,6 @@ fn end_to_end_handshake_w_auth() { { reader = BitReader::new(&message_buffer[..message_length]); StandardHeader::de(&mut reader).expect("unable to read standard header from stream"); - client.recv_connect_response(); + client.recv_validate_response(); } }