From d859659fd7e5a08495a0b22ca72ca4ff6f263363 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Sat, 6 Sep 2025 12:38:36 +0300 Subject: [PATCH 1/9] Add `_fn` for built system to avoid shadowing function names --- src/client/event.rs | 20 ++++++++++---------- src/server/event.rs | 16 ++++++++-------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/client/event.rs b/src/client/event.rs index 9d13512e..6cd34dfd 100644 --- a/src/client/event.rs +++ b/src/client/event.rs @@ -32,7 +32,7 @@ impl Plugin for ClientEventPlugin { .remove_resource::() .expect("event registry should be initialized on app build"); - let send = ( + let send_fn = ( FilteredResourcesParamBuilder::new(|builder| { for event in event_registry.iter_all_client() { builder.add_read_by_id(event.events_id()); @@ -51,7 +51,7 @@ impl Plugin for ClientEventPlugin { .build_state(app.world_mut()) .build_system(send); - let receive = ( + let receive_fn = ( FilteredResourcesMutParamBuilder::new(|builder| { for event in event_registry.iter_all_server() { builder.add_write_by_id(event.events_id()); @@ -71,7 +71,7 @@ impl Plugin for ClientEventPlugin { .build_state(app.world_mut()) .build_system(receive); - let trigger = ( + let trigger_fn = ( FilteredResourcesMutParamBuilder::new(|builder| { for trigger in event_registry.iter_server_triggers() { builder.add_write_by_id(trigger.event().events_id()); @@ -83,7 +83,7 @@ impl Plugin for ClientEventPlugin { .build_state(app.world_mut()) .build_system(trigger); - let resend_locally = ( + let resend_locally_fn = ( FilteredResourcesMutParamBuilder::new(|builder| { for event in event_registry.iter_all_client() { builder.add_write_by_id(event.client_events_id()); @@ -99,7 +99,7 @@ impl Plugin for ClientEventPlugin { .build_state(app.world_mut()) .build_system(resend_locally); - let reset = ( + let reset_fn = ( FilteredResourcesMutParamBuilder::new(|builder| { for event in event_registry.iter_all_client() { builder.add_write_by_id(event.events_id()); @@ -119,12 +119,12 @@ impl Plugin for ClientEventPlugin { .add_systems( PreUpdate, ( - reset.in_set(ClientSet::ResetEvents), + reset_fn.in_set(ClientSet::ResetEvents), ( - receive + receive_fn .after(super::receive_replication) .run_if(client_connected), - trigger, + trigger_fn, ) .chain() .in_set(ClientSet::Receive), @@ -133,8 +133,8 @@ impl Plugin for ClientEventPlugin { .add_systems( PostUpdate, ( - send.run_if(client_connected), - resend_locally.run_if(server_or_singleplayer), + send_fn.run_if(client_connected), + resend_locally_fn.run_if(server_or_singleplayer), ) .chain() .in_set(ClientSet::Send), diff --git a/src/server/event.rs b/src/server/event.rs index 8135761c..ccbc2a71 100644 --- a/src/server/event.rs +++ b/src/server/event.rs @@ -33,7 +33,7 @@ impl Plugin for ServerEventPlugin { .remove_resource::() .expect("event registry should be initialized on app build"); - let send_or_buffer = ( + let send_or_buffer_fn = ( FilteredResourcesParamBuilder::new(|builder| { for event in event_registry.iter_all_server() { builder.add_read_by_id(event.server_events_id()); @@ -48,7 +48,7 @@ impl Plugin for ServerEventPlugin { .build_state(app.world_mut()) .build_system(send_or_buffer); - let receive = ( + let receive_fn = ( FilteredResourcesMutParamBuilder::new(|builder| { for event in event_registry.iter_all_client() { builder.add_write_by_id(event.client_events_id()); @@ -61,7 +61,7 @@ impl Plugin for ServerEventPlugin { .build_state(app.world_mut()) .build_system(receive); - let trigger = ( + let trigger_fn = ( FilteredResourcesMutParamBuilder::new(|builder| { for trigger in event_registry.iter_client_triggers() { builder.add_write_by_id(trigger.event().client_events_id()); @@ -73,7 +73,7 @@ impl Plugin for ServerEventPlugin { .build_state(app.world_mut()) .build_system(trigger); - let resend_locally = ( + let resend_locally_fn = ( FilteredResourcesMutParamBuilder::new(|builder| { for event in event_registry.iter_all_server() { builder.add_write_by_id(event.server_events_id()); @@ -93,8 +93,8 @@ impl Plugin for ServerEventPlugin { .add_systems( PreUpdate, ( - receive.run_if(server_running), - trigger.run_if(server_or_singleplayer), + receive_fn.run_if(server_running), + trigger_fn.run_if(server_or_singleplayer), ) .chain() .in_set(ServerSet::Receive), @@ -102,11 +102,11 @@ impl Plugin for ServerEventPlugin { .add_systems( PostUpdate, ( - send_or_buffer.run_if(server_running), + send_or_buffer_fn.run_if(server_running), send_buffered .run_if(server_running) .run_if(resource_changed::), - resend_locally.run_if(server_or_singleplayer), + resend_locally_fn.run_if(server_or_singleplayer), ) .chain() .after(super::send_replication) From 837e1bd7dfcaf5adf19484cbfc2f289122d5c362 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Sat, 6 Sep 2025 12:46:43 +0300 Subject: [PATCH 2/9] Simplify system ordering --- src/client/event.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/client/event.rs b/src/client/event.rs index 6cd34dfd..0aeed3a9 100644 --- a/src/client/event.rs +++ b/src/client/event.rs @@ -120,13 +120,9 @@ impl Plugin for ClientEventPlugin { PreUpdate, ( reset_fn.in_set(ClientSet::ResetEvents), - ( - receive_fn - .after(super::receive_replication) - .run_if(client_connected), - trigger_fn, - ) + (receive_fn.run_if(client_connected), trigger_fn) .chain() + .after(super::receive_replication) .in_set(ClientSet::Receive), ), ) From 181cf740466a734c8e7cbb1b17887d2dd63daf12 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Fri, 5 Sep 2025 23:43:51 +0300 Subject: [PATCH 3/9] Add `StatesPlugin` everywhere --- Cargo.toml | 4 ++- benches/related_entities.rs | 16 +++++----- benches/replication.rs | 5 ++- bevy_replicon_example_backend/Cargo.toml | 1 - .../tests/backend.rs | 8 ++++- src/lib.rs | 31 ++++++++++--------- src/scene.rs | 4 +-- src/server/client_visibility.rs | 6 ++-- src/server/related_entities.rs | 3 +- src/shared.rs | 3 +- src/shared/event/client_event.rs | 3 +- src/shared/event/server_event.rs | 3 +- src/shared/protocol.rs | 4 +-- src/shared/replication/command_markers.rs | 3 +- src/shared/replication/registry/test_fns.rs | 4 +-- src/shared/replication/rules.rs | 30 +++++++++++------- src/test_app.rs | 3 +- tests/bidirectional_event.rs | 5 +-- tests/client_event.rs | 9 ++++-- tests/client_trigger.rs | 10 ++++-- tests/connection.rs | 15 ++++++--- tests/despawn.rs | 6 +++- tests/fns.rs | 30 +++++++++--------- tests/insertion.rs | 16 +++++++++- tests/mutate_ticks.rs | 5 ++- tests/mutations.rs | 22 ++++++++++++- tests/priority.rs | 4 ++- tests/removal.rs | 13 +++++++- tests/scene.rs | 10 +++--- tests/server_event.rs | 15 +++++++++ tests/server_trigger.rs | 9 +++++- tests/spawn.rs | 9 +++++- tests/stats.rs | 3 +- tests/visibility.rs | 8 ++++- 34 files changed, 226 insertions(+), 94 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fe360c99..81005bf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,9 @@ all-features = true members = ["bevy_replicon_example_backend"] [dependencies] -bevy = { version = "0.16.0", default-features = false } +bevy = { version = "0.16.0", default-features = false, features = [ + "bevy_state", +] } log = "0.4" # Directly depend on `log` like other `no_std` Bevy crates, since `bevy_log` currently requires `std`. petgraph = { version = "0.8", default-features = false, features = [ "stable_graph", diff --git a/benches/related_entities.rs b/benches/related_entities.rs index 1969703b..9d43331c 100644 --- a/benches/related_entities.rs +++ b/benches/related_entities.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::prelude::*; use criterion::{Criterion, criterion_group, criterion_main}; @@ -11,13 +11,14 @@ fn hierarchy_spawning(c: &mut Criterion) { group.bench_function("regular", |b| { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)).finish(); + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) + .finish(); b.iter(|| spawn_then_despawn(&mut app)); }); group.bench_function("related_without_server", |b| { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .sync_related_entities::() .finish(); @@ -25,7 +26,7 @@ fn hierarchy_spawning(c: &mut Criterion) { }); group.bench_function("related", |b| { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .sync_related_entities::() .finish(); @@ -47,7 +48,8 @@ fn hierarchy_changes(c: &mut Criterion) { group.bench_function("regular", |b| { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)).finish(); + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) + .finish(); spawn_hierarchy(app.world_mut()); @@ -55,7 +57,7 @@ fn hierarchy_changes(c: &mut Criterion) { }); group.bench_function("related_without_server", |b| { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .sync_related_entities::() .finish(); @@ -65,7 +67,7 @@ fn hierarchy_changes(c: &mut Criterion) { }); group.bench_function("related", |b| { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .sync_related_entities::() .finish(); diff --git a/benches/replication.rs b/benches/replication.rs index f0a03b5c..64f45deb 100644 --- a/benches/replication.rs +++ b/benches/replication.rs @@ -1,6 +1,8 @@ use core::time::Duration; -use bevy::{ecs::component::Mutable, platform::time::Instant, prelude::*}; +use bevy::{ + ecs::component::Mutable, platform::time::Instant, prelude::*, state::app::StatesPlugin, +}; use bevy_replicon::{prelude::*, test_app::ServerTestAppExt}; use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use serde::{Deserialize, Serialize, de::DeserializeOwned}; @@ -184,6 +186,7 @@ fn create_app() -> App { let mut app = App::new(); app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() diff --git a/bevy_replicon_example_backend/Cargo.toml b/bevy_replicon_example_backend/Cargo.toml index 01bb7db2..ebdd5730 100644 --- a/bevy_replicon_example_backend/Cargo.toml +++ b/bevy_replicon_example_backend/Cargo.toml @@ -29,7 +29,6 @@ fastrand-contrib = "0.1" [dev-dependencies] bevy = { version = "0.16.0", default-features = false, features = [ "bevy_gizmos", - "bevy_state", "bevy_text", "bevy_ui_picking_backend", "bevy_ui", diff --git a/bevy_replicon_example_backend/tests/backend.rs b/bevy_replicon_example_backend/tests/backend.rs index f5bd195e..b2fa46f4 100644 --- a/bevy_replicon_example_backend/tests/backend.rs +++ b/bevy_replicon_example_backend/tests/backend.rs @@ -1,6 +1,6 @@ use std::{io, net::Ipv4Addr}; -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::prelude::*; use bevy_replicon_example_backend::{ExampleClient, ExampleServer, RepliconExampleBackendPlugins}; use serde::{Deserialize, Serialize}; @@ -13,6 +13,7 @@ fn connect_disconnect() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -55,6 +56,7 @@ fn disconnect_request() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -107,6 +109,7 @@ fn server_stop() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -154,6 +157,7 @@ fn replication() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -181,6 +185,7 @@ fn server_event() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -212,6 +217,7 @@ fn client_event() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() diff --git a/src/lib.rs b/src/lib.rs index b0e4d19c..8740f704 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,12 +16,12 @@ We provide a [`prelude`] module, which exports most of the typically used traits Add [`RepliconPlugins`] and plugins for your chosen messaging backend to your app: ``` -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::prelude::*; # use bevy::app::PluginGroupBuilder; let mut app = App::new(); -app.add_plugins((MinimalPlugins, RepliconPlugins, MyMessagingPlugins)); +app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins, MyMessagingPlugins)); # struct MyMessagingPlugins; # impl PluginGroup for MyMessagingPlugins { # fn build(self) -> PluginGroupBuilder { @@ -71,9 +71,10 @@ You can use [`TickPolicy::Manual`] and then add the [`increment_tick`](server::i system to [`FixedUpdate`]: ``` -# use bevy::prelude::*; +# use bevy::{prelude::*, state::app::StatesPlugin}; # use bevy_replicon::prelude::*; # let mut app = App::new(); +# app.add_plugins(StatesPlugin); app.add_plugins( RepliconPlugins .build() @@ -107,11 +108,11 @@ By default no components are replicated, you need to define rules for it. Use [`AppRuleExt::replicate`] to create a replication rule for a single component: ``` -# use bevy::prelude::*; +# use bevy::{prelude::*, state::app::StatesPlugin}; # use bevy_replicon::prelude::*; # use serde::{Deserialize, Serialize}; # let mut app = App::new(); -# app.add_plugins(RepliconPlugins); +# app.add_plugins((StatesPlugin, RepliconPlugins)); app.replicate::(); #[derive(Component, Deserialize, Serialize)] @@ -146,11 +147,11 @@ necessary to send over the network. Components that can be calculated on the cli be inserted using Bevy's required components feature. ``` -# use bevy::prelude::*; +# use bevy::{prelude::*, state::app::StatesPlugin}; # use bevy_replicon::prelude::*; # use serde::{Deserialize, Serialize}; # let mut app = App::new(); -# app.add_plugins(RepliconPlugins); +# app.add_plugins((StatesPlugin, RepliconPlugins)); // Replicate only transform and player marker. app.replicate::() .replicate::() @@ -249,11 +250,11 @@ These events will appear on server as [`FromClient`] wrapper event that contains sender ID and the sent event. ``` -# use bevy::prelude::*; +# use bevy::{prelude::*, state::app::StatesPlugin}; # use bevy_replicon::prelude::*; # use serde::{Deserialize, Serialize}; # let mut app = App::new(); -# app.add_plugins(RepliconPlugins); +# app.add_plugins((StatesPlugin, RepliconPlugins)); app.add_client_event::(Channel::Ordered) .add_systems( PreUpdate, @@ -293,11 +294,11 @@ Alternatively, you can use triggers with a similar API. First, you need to regis using [`ClientTriggerAppExt::add_client_trigger`], and then use [`ClientTriggerExt::client_trigger`]. ``` -# use bevy::prelude::*; +# use bevy::{prelude::*, state::app::StatesPlugin}; # use bevy_replicon::prelude::*; # use serde::{Deserialize, Serialize}; # let mut app = App::new(); -# app.add_plugins(RepliconPlugins); +# app.add_plugins((StatesPlugin, RepliconPlugins)); app.add_client_trigger::(Channel::Ordered) .add_observer(receive_events) .add_systems(Update, send_events.run_if(client_connected)); @@ -327,11 +328,11 @@ and send it from server using [`ToClients`]. This wrapper contains send paramete and the event itself. ``` -# use bevy::prelude::*; +# use bevy::{prelude::*, state::app::StatesPlugin}; # use bevy_replicon::prelude::*; # use serde::{Deserialize, Serialize}; # let mut app = App::new(); -# app.add_plugins(RepliconPlugins); +# app.add_plugins((StatesPlugin, RepliconPlugins)); app.add_server_event::(Channel::Ordered) .add_systems( PreUpdate, @@ -368,11 +369,11 @@ Trigger-based API available for server events as well. First, you need to regist with [`ServerTriggerAppExt::add_server_trigger`] and then use [`ServerTriggerExt::server_trigger`]: ``` -# use bevy::prelude::*; +# use bevy::{prelude::*, state::app::StatesPlugin}; # use bevy_replicon::prelude::*; # use serde::{Deserialize, Serialize}; # let mut app = App::new(); -# app.add_plugins(RepliconPlugins); +# app.add_plugins((StatesPlugin, RepliconPlugins)); app.add_server_trigger::(Channel::Ordered) .add_observer(receive_events) .add_systems(Update, send_events.run_if(server_running)); diff --git a/src/scene.rs b/src/scene.rs index 611cde2a..0ce6e1d6 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -15,11 +15,11 @@ So on deserialization you need to insert it back if you want entities to continu # Examples ``` -use bevy::{prelude::*, asset::ron, scene::serde::SceneDeserializer}; +use bevy::{asset::ron, prelude::*, state::app::StatesPlugin, scene::serde::SceneDeserializer}; use bevy_replicon::{prelude::*, scene}; use serde::de::DeserializeSeed; # let mut app = App::new(); -# app.add_plugins(RepliconPlugins); +# app.add_plugins((StatesPlugin, RepliconPlugins)); // Serialization let registry = app.world().resource::(); diff --git a/src/server/client_visibility.rs b/src/server/client_visibility.rs index 335c3a68..08ff407c 100644 --- a/src/server/client_visibility.rs +++ b/src/server/client_visibility.rs @@ -12,11 +12,13 @@ use bevy::{ /// # Examples /// /// ``` -/// # use bevy::prelude::*; -/// # use bevy_replicon::prelude::*; +/// use bevy::{prelude::*, state::app::StatesPlugin}; +/// use bevy_replicon::prelude::*; +/// /// # let mut app = App::new(); /// app.add_plugins(( /// MinimalPlugins, +/// StatesPlugin, /// RepliconPlugins.set(ServerPlugin { /// visibility_policy: VisibilityPolicy::Whitelist, // Makes all entities invisible for clients by default. /// ..Default::default() diff --git a/src/server/related_entities.rs b/src/server/related_entities.rs index 8fe1d054..72097263 100644 --- a/src/server/related_entities.rs +++ b/src/server/related_entities.rs @@ -32,11 +32,12 @@ pub trait SyncRelatedAppExt { /// /// # Examples /// ``` + /// # use bevy::state::app::StatesPlugin; /// use bevy::prelude::*; /// use bevy_replicon::prelude::*; /// /// # let mut app = App::new(); - /// # app.add_plugins(RepliconPlugins); + /// # app.add_plugins((StatesPlugin, RepliconPlugins)); /// app.sync_related_entities::(); /// /// // Changes to any replicated components on these diff --git a/src/shared.rs b/src/shared.rs index 9bf69c18..7a2fe253 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -31,13 +31,14 @@ pub struct RepliconSharedPlugin { only with [`AuthMethod::ProtocolCheck`], but it could be any event. ``` - use bevy::prelude::*; + use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::prelude::*; use serde::{Deserialize, Serialize}; let mut app = App::new(); app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(RepliconSharedPlugin { auth_method: AuthMethod::Custom, }), diff --git a/src/shared/event/client_event.rs b/src/shared/event/client_event.rs index fe255a53..f410ae5c 100644 --- a/src/shared/event/client_event.rs +++ b/src/shared/event/client_event.rs @@ -71,6 +71,7 @@ pub trait ClientEventAppExt { use bevy::{ prelude::*, reflect::serde::{ReflectDeserializer, ReflectSerializer}, + state::app::StatesPlugin, }; use bevy_replicon::{ bytes::Bytes, @@ -82,7 +83,7 @@ pub trait ClientEventAppExt { use serde::{de::DeserializeSeed, Serialize}; let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)); + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)); app.add_client_event_with(Channel::Ordered, serialize_reflect, deserialize_reflect); fn serialize_reflect( diff --git a/src/shared/event/server_event.rs b/src/shared/event/server_event.rs index 496c46c3..8ebc4ae3 100644 --- a/src/shared/event/server_event.rs +++ b/src/shared/event/server_event.rs @@ -80,6 +80,7 @@ pub trait ServerEventAppExt { use bevy::{ prelude::*, reflect::serde::{ReflectDeserializer, ReflectSerializer}, + state::app::StatesPlugin, }; use bevy_replicon::{ bytes::Bytes, @@ -91,7 +92,7 @@ pub trait ServerEventAppExt { use serde::{de::DeserializeSeed, Serialize}; let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)); + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)); app.add_server_event_with(Channel::Ordered, serialize_reflect, deserialize_reflect); fn serialize_reflect( diff --git a/src/shared/protocol.rs b/src/shared/protocol.rs index 1173e64d..2590dbc8 100644 --- a/src/shared/protocol.rs +++ b/src/shared/protocol.rs @@ -34,10 +34,10 @@ impl ProtocolHasher { /// Include a game version. /// /// ``` - /// use bevy::prelude::*; + /// use bevy::{prelude::*, state::app::StatesPlugin}; /// use bevy_replicon::prelude::*; /// let mut app = App::new(); - /// app.add_plugins((MinimalPlugins, RepliconPlugins)); + /// app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)); /// /// // Should be called before `app.run()` or `app.finish()`. /// // Can also be done inside your game's plugin. diff --git a/src/shared/replication/command_markers.rs b/src/shared/replication/command_markers.rs index 9c04cde8..45ee4248 100644 --- a/src/shared/replication/command_markers.rs +++ b/src/shared/replication/command_markers.rs @@ -48,6 +48,7 @@ pub trait AppMarkerExt { Then `Health` updates after that will be inserted to the history. ``` + # use bevy::state::app::StatesPlugin; use bevy::{ecs::system::EntityCommands, ecs::component::Mutable, prelude::*, platform::collections::HashMap}; use bevy_replicon::{ bytes::Bytes, @@ -67,7 +68,7 @@ pub trait AppMarkerExt { use serde::{Serialize, Deserialize}; # let mut app = App::new(); - # app.add_plugins(RepliconPlugins); + # app.add_plugins((StatesPlugin, RepliconPlugins)); app.register_marker_with::(MarkerConfig { need_history: true, // Enable writing for values that are older than the last received value. ..Default::default() diff --git a/src/shared/replication/registry/test_fns.rs b/src/shared/replication/registry/test_fns.rs index 62a68bbe..2bef6763 100644 --- a/src/shared/replication/registry/test_fns.rs +++ b/src/shared/replication/registry/test_fns.rs @@ -26,7 +26,7 @@ See also [`ReplicationRegistry::register_rule_fns`]. This example shows how to call registered functions on an entity: ``` -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{ shared::{ replication::registry::{ @@ -39,7 +39,7 @@ use bevy_replicon::{ use serde::{Deserialize, Serialize}; let mut app = App::new(); -app.add_plugins((MinimalPlugins, RepliconPlugins)); +app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)); let tick = RepliconTick::default(); diff --git a/src/shared/replication/rules.rs b/src/shared/replication/rules.rs index 60387cfe..1e1a242c 100644 --- a/src/shared/replication/rules.rs +++ b/src/shared/replication/rules.rs @@ -46,11 +46,11 @@ pub trait AppRuleExt { /// # Examples /// /// ``` - /// # use bevy::prelude::*; + /// # use bevy::{prelude::*, state::app::StatesPlugin}; /// # use bevy_replicon::prelude::*; /// # use serde::{Deserialize, Serialize}; /// # let mut app = App::new(); - /// # app.add_plugins(RepliconPlugins); + /// # app.add_plugins((StatesPlugin, RepliconPlugins)); /// app.replicate_filtered::>() // Replicate `Transform` only for players. /// .replicate_filtered::, With)>>() // Replicate `Health` only for player and enemies. /// .replicate_filtered::, Without)>(); // Replicate only active and non-moving platforms. @@ -119,6 +119,7 @@ pub trait AppRuleExt { Pass [`RuleFns`] to ser/de only specific field: ``` + # use bevy::state::app::StatesPlugin; use bevy::prelude::*; use bevy_replicon::{ bytes::Bytes, @@ -131,7 +132,7 @@ pub trait AppRuleExt { }; # let mut app = App::new(); - # app.add_plugins(RepliconPlugins); + # app.add_plugins((StatesPlugin, RepliconPlugins)); // We override in-place as well to apply only translation when the component is already inserted. app.replicate_with( RuleFns::new(serialize_translation, deserialize_translation) @@ -177,12 +178,13 @@ pub trait AppRuleExt { A rule with multiple components: ``` + # use bevy::state::app::StatesPlugin; use bevy::prelude::*; use bevy_replicon::prelude::*; use serde::{Deserialize, Serialize}; # let mut app = App::new(); - # app.add_plugins(RepliconPlugins); + # app.add_plugins((StatesPlugin, RepliconPlugins)); app.replicate_with(( // You can also use `replicate_bundle` if you don't want // to tweak functions or send rate. @@ -208,6 +210,7 @@ pub trait AppRuleExt { Ser/de with compression: ``` + # use bevy::state::app::StatesPlugin; use bevy::prelude::*; use bevy_replicon::{ bytes::Bytes, @@ -223,7 +226,7 @@ pub trait AppRuleExt { use serde::{Deserialize, Serialize}; # let mut app = App::new(); - # app.add_plugins(RepliconPlugins); + # app.add_plugins((StatesPlugin, RepliconPlugins)); app.replicate_with(RuleFns::new( serialize_big_component, deserialize_big_component, @@ -275,6 +278,7 @@ pub trait AppRuleExt { Custom ser/de with entity mapping: ``` + # use bevy::state::app::StatesPlugin; use bevy::prelude::*; use bevy_replicon::{ bytes::Bytes, @@ -288,8 +292,8 @@ pub trait AppRuleExt { }; use serde::{Deserialize, Serialize}; - let mut app = App::new(); - app.add_plugins(RepliconPlugins); + # let mut app = App::new(); + # app.add_plugins((StatesPlugin, RepliconPlugins)); app.replicate_with(RuleFns::new( serialize_mapped_component, deserialize_mapped_component, @@ -330,6 +334,7 @@ pub trait AppRuleExt { Component with [`Box`]: ``` + # use bevy::state::app::StatesPlugin; use bevy::{ prelude::*, reflect::serde::{ReflectDeserializer, ReflectSerializer}, @@ -346,8 +351,8 @@ pub trait AppRuleExt { }; use serde::{de::DeserializeSeed, Serialize}; - let mut app = App::new(); - app.add_plugins(RepliconPlugins); + # let mut app = App::new(); + # app.add_plugins((StatesPlugin, RepliconPlugins)); app.replicate_with(RuleFns::new(serialize_reflect, deserialize_reflect)); fn serialize_reflect( @@ -388,11 +393,11 @@ pub trait AppRuleExt { /// # Examples /// /// ``` - /// # use bevy::prelude::*; + /// # use bevy::{prelude::*, state::app::StatesPlugin}; /// # use bevy_replicon::prelude::*; /// # use serde::{Deserialize, Serialize}; /// # let mut app = App::new(); - /// # app.add_plugins(RepliconPlugins); + /// # app.add_plugins((StatesPlugin, RepliconPlugins)); /// app.replicate_with_filtered::<_, With>(( /// RuleFns::::default(), /// (RuleFns::::default(), ReplicationMode::Once), @@ -444,6 +449,7 @@ pub trait AppRuleExt { # Examples ``` + # use bevy::state::app::StatesPlugin; use bevy::prelude::*; use bevy_replicon::{ bytes::Bytes, @@ -459,7 +465,7 @@ pub trait AppRuleExt { use serde::{Deserialize, Serialize}; # let mut app = App::new(); - # app.add_plugins(RepliconPlugins); + # app.add_plugins((StatesPlugin, RepliconPlugins)); app.replicate_bundle::<(Name, City)>() // Tuple of components is also a bundle! .replicate_bundle::(); diff --git a/src/test_app.rs b/src/test_app.rs index a818551d..60f1a983 100644 --- a/src/test_app.rs +++ b/src/test_app.rs @@ -8,7 +8,7 @@ Extension for [`App`] to communicate with other instances like it's a server. # Example ``` -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{prelude::*, test_app::ServerTestAppExt}; let mut server_app = App::new(); @@ -16,6 +16,7 @@ let mut client_app = App::new(); for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, // No messaging library plugin required. RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, // To tick each app update. diff --git a/tests/bidirectional_event.rs b/tests/bidirectional_event.rs index be46fcc4..44aad3a8 100644 --- a/tests/bidirectional_event.rs +++ b/tests/bidirectional_event.rs @@ -1,4 +1,4 @@ -use bevy::{ecs::event::Events, prelude::*}; +use bevy::{ecs::event::Events, prelude::*, state::app::StatesPlugin}; use bevy_replicon::{prelude::*, test_app::ServerTestAppExt}; use serde::{Deserialize, Serialize}; use test_log::test; @@ -8,7 +8,7 @@ fn event() { let mut server_app = App::new(); let mut client_app = App::new(); for app in [&mut server_app, &mut client_app] { - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .add_client_event::(Channel::Ordered) .add_server_event::(Channel::Ordered) .finish(); @@ -46,6 +46,7 @@ fn trigger() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() diff --git a/tests/client_event.rs b/tests/client_event.rs index f7c29b8d..2736c0d5 100644 --- a/tests/client_event.rs +++ b/tests/client_event.rs @@ -1,6 +1,7 @@ use bevy::{ ecs::{entity::MapEntities, event::Events}, prelude::*, + state::app::StatesPlugin, time::TimePlugin, }; use bevy_replicon::{ @@ -18,6 +19,7 @@ fn channels() { let mut app = App::new(); app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -37,7 +39,7 @@ fn regular() { let mut server_app = App::new(); let mut client_app = App::new(); for app in [&mut server_app, &mut client_app] { - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .add_client_event::(Channel::Ordered) .finish(); } @@ -63,6 +65,7 @@ fn mapped() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -111,6 +114,7 @@ fn without_plugins() { server_app .add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .build() .disable::() @@ -121,6 +125,7 @@ fn without_plugins() { client_app .add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .build() .disable::() @@ -146,7 +151,7 @@ fn without_plugins() { #[test] fn local_resending() { let mut app = App::new(); - app.add_plugins((TimePlugin, RepliconPlugins)) + app.add_plugins((TimePlugin, StatesPlugin, RepliconPlugins)) .add_client_event::(Channel::Ordered) .finish(); diff --git a/tests/client_trigger.rs b/tests/client_trigger.rs index 64d11001..53280d40 100644 --- a/tests/client_trigger.rs +++ b/tests/client_trigger.rs @@ -1,4 +1,4 @@ -use bevy::{ecs::entity::MapEntities, prelude::*, time::TimePlugin}; +use bevy::{ecs::entity::MapEntities, prelude::*, state::app::StatesPlugin, time::TimePlugin}; use bevy_replicon::{ prelude::*, shared::server_entity_map::ServerEntityMap, test_app::ServerTestAppExt, }; @@ -10,7 +10,7 @@ fn regular() { let mut server_app = App::new(); let mut client_app = App::new(); for app in [&mut server_app, &mut client_app] { - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .add_client_trigger::(Channel::Ordered) .finish(); } @@ -35,6 +35,7 @@ fn with_target() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -79,6 +80,7 @@ fn mapped() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -124,6 +126,7 @@ fn without_plugins() { server_app .add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .build() .disable::() @@ -134,6 +137,7 @@ fn without_plugins() { client_app .add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .build() .disable::() @@ -158,7 +162,7 @@ fn without_plugins() { #[test] fn local_resending() { let mut app = App::new(); - app.add_plugins((TimePlugin, RepliconPlugins)) + app.add_plugins((TimePlugin, StatesPlugin, RepliconPlugins)) .add_client_trigger::(Channel::Ordered) .finish(); app.init_resource::>(); diff --git a/tests/connection.rs b/tests/connection.rs index be1d34d1..3a486af3 100644 --- a/tests/connection.rs +++ b/tests/connection.rs @@ -1,6 +1,6 @@ use core::marker::PhantomData; -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{ prelude::*, server::server_tick::ServerTick, @@ -15,7 +15,8 @@ fn client_connect_disconnect() { let mut server_app = App::new(); let mut client_app = App::new(); for app in [&mut server_app, &mut client_app] { - app.add_plugins((MinimalPlugins, RepliconPlugins)).finish(); + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) + .finish(); } server_app.connect_client(&mut client_app); @@ -37,6 +38,7 @@ fn server_start_stop() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .set(RepliconSharedPlugin { auth_method: AuthMethod::Custom, @@ -72,12 +74,12 @@ fn protocol_mismatch() { let mut server_app = App::new(); let mut client_app = App::new(); server_app - .add_plugins((MinimalPlugins, RepliconPlugins)) + .add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .add_client_event::(Channel::Ordered) .finish(); client_app .init_resource::>() - .add_plugins((MinimalPlugins, RepliconPlugins)) + .add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .finish(); server_app.connect_client(&mut client_app); @@ -100,6 +102,7 @@ fn custom_auth() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(RepliconSharedPlugin { auth_method: AuthMethod::Custom, }), @@ -120,6 +123,7 @@ fn disabled_auth() { let mut app = App::new(); app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(RepliconSharedPlugin { auth_method: AuthMethod::None, }), @@ -133,7 +137,8 @@ fn disabled_auth() { #[test] fn network_id_map() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)).finish(); + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) + .finish(); let client_entity = app.world_mut().spawn(NetworkId::new(0)).id(); assert_eq!(app.world().resource::().len(), 1); diff --git a/tests/despawn.rs b/tests/despawn.rs index a81998ef..b8f1aec5 100644 --- a/tests/despawn.rs +++ b/tests/despawn.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{ prelude::*, shared::server_entity_map::ServerEntityMap, test_app::ServerTestAppExt, }; @@ -12,6 +12,7 @@ fn single() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -56,6 +57,7 @@ fn with_relations() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -95,6 +97,7 @@ fn after_spawn() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -127,6 +130,7 @@ fn hidden() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, visibility_policy: VisibilityPolicy::Whitelist, // Hide all spawned entities by default. diff --git a/tests/fns.rs b/tests/fns.rs index 7aa731ca..91959494 100644 --- a/tests/fns.rs +++ b/tests/fns.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{ prelude::*, shared::{ @@ -22,7 +22,7 @@ use test_log::test; #[should_panic] fn serialize_missing_component() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)); + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)); let tick = RepliconTick::default(); let (_, fns_id) = @@ -38,7 +38,7 @@ fn serialize_missing_component() { #[test] fn write() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)); + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)); let tick = RepliconTick::default(); let (_, fns_id) = @@ -57,7 +57,7 @@ fn write() { #[test] fn remove() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)); + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)); let tick = RepliconTick::default(); let (_, fns_id) = @@ -74,7 +74,7 @@ fn remove() { #[test] fn write_with_command() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .set_command_fns(replace, command_fns::default_remove::); let tick = RepliconTick::default(); @@ -93,7 +93,7 @@ fn write_with_command() { #[test] fn remove_with_command() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .set_command_fns(replace, command_fns::default_remove::); let tick = RepliconTick::default(); @@ -111,7 +111,7 @@ fn remove_with_command() { #[test] fn write_without_marker() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .register_marker::() .set_marker_fns::( replace, @@ -135,7 +135,7 @@ fn write_without_marker() { #[test] fn remove_without_marker() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .register_marker::() .set_marker_fns::( replace, @@ -157,7 +157,7 @@ fn remove_without_marker() { #[test] fn write_with_marker() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .register_marker::() .set_marker_fns::( replace, @@ -180,7 +180,7 @@ fn write_with_marker() { #[test] fn remove_with_marker() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .register_marker::() .set_marker_fns::( replace, @@ -202,7 +202,7 @@ fn remove_with_marker() { #[test] fn write_with_multiple_markers() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .register_marker::() .register_marker::() .set_marker_fns::( @@ -235,7 +235,7 @@ fn write_with_multiple_markers() { #[test] fn remove_with_multiple_markers() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .register_marker::() .register_marker::() .set_marker_fns::( @@ -267,7 +267,7 @@ fn remove_with_multiple_markers() { #[test] fn write_with_priority_marker() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .register_marker_with::(MarkerConfig { priority: 1, ..Default::default() @@ -300,7 +300,7 @@ fn write_with_priority_marker() { #[test] fn remove_with_priority_marker() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)) + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)) .register_marker_with::(MarkerConfig { priority: 1, ..Default::default() @@ -332,7 +332,7 @@ fn remove_with_priority_marker() { #[test] fn despawn() { let mut app = App::new(); - app.add_plugins((MinimalPlugins, RepliconPlugins)); + app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins)); let mut registry = app.world_mut().resource_mut::(); registry.despawn = mark_despawned; diff --git a/tests/insertion.rs b/tests/insertion.rs index d0640f46..b2eb62c6 100644 --- a/tests/insertion.rs +++ b/tests/insertion.rs @@ -1,4 +1,4 @@ -use bevy::{ecs::system::SystemState, prelude::*}; +use bevy::{ecs::system::SystemState, prelude::*, state::app::StatesPlugin}; use bevy_replicon::{ client::confirm_history::{ConfirmHistory, EntityReplicated}, prelude::*, @@ -23,6 +23,7 @@ fn table_storage() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -61,6 +62,7 @@ fn sparse_set_storage() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -99,6 +101,7 @@ fn immutable() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -150,6 +153,7 @@ fn mapped_existing_entity() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -200,6 +204,7 @@ fn mapped_new_entity() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -246,6 +251,7 @@ fn multiple_components() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -292,6 +298,7 @@ fn command_fns() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -333,6 +340,7 @@ fn marker() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -383,6 +391,7 @@ fn group() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -421,6 +430,7 @@ fn not_replicated() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -455,6 +465,7 @@ fn after_removal() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -504,6 +515,7 @@ fn before_started_replication() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .set(RepliconSharedPlugin { auth_method: AuthMethod::Custom, @@ -554,6 +566,7 @@ fn after_started_replication() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .set(RepliconSharedPlugin { auth_method: AuthMethod::Custom, @@ -598,6 +611,7 @@ fn confirm_history() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() diff --git a/tests/mutate_ticks.rs b/tests/mutate_ticks.rs index 5dc7d4da..d3ed3d7e 100644 --- a/tests/mutate_ticks.rs +++ b/tests/mutate_ticks.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{ client::server_mutate_ticks::{MutateTickReceived, ServerMutateTicks}, prelude::*, @@ -16,6 +16,7 @@ fn without_changes() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -51,6 +52,7 @@ fn one_message() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -118,6 +120,7 @@ fn multiple_messages() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() diff --git a/tests/mutations.rs b/tests/mutations.rs index f74b3c24..a5812885 100644 --- a/tests/mutations.rs +++ b/tests/mutations.rs @@ -1,7 +1,7 @@ use core::time::Duration; use test_log::test; -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{ client::{ ServerUpdateTick, @@ -29,6 +29,7 @@ fn small_component() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -76,6 +77,7 @@ fn package_size_component() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -129,6 +131,7 @@ fn many_components() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -185,6 +188,7 @@ fn once() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -232,6 +236,7 @@ fn filtered() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -291,6 +296,7 @@ fn related() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -337,6 +343,7 @@ fn command_fns() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -386,6 +393,7 @@ fn marker() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -446,6 +454,7 @@ fn marker_with_history() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -522,6 +531,7 @@ fn marker_with_history_consume() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -612,6 +622,7 @@ fn marker_with_history_old_update() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -689,6 +700,7 @@ fn many_entities() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -742,6 +754,7 @@ fn with_insertion() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -787,6 +800,7 @@ fn with_removal() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -832,6 +846,7 @@ fn with_despawn() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -881,6 +896,7 @@ fn buffering() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -939,6 +955,7 @@ fn old_ignored() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -1006,6 +1023,7 @@ fn acknowledgment() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, mutations_timeout: Duration::ZERO, // Will cause dropping updates after each frame. @@ -1084,6 +1102,7 @@ fn confirm_history() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -1150,6 +1169,7 @@ fn after_disconnect() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() diff --git a/tests/priority.rs b/tests/priority.rs index 085f83d7..72b8f755 100644 --- a/tests/priority.rs +++ b/tests/priority.rs @@ -1,6 +1,6 @@ use test_log::test; -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{ prelude::*, test_app::{ServerTestAppExt, TestClientEntity}, @@ -14,6 +14,7 @@ fn regular() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -73,6 +74,7 @@ fn with_miss() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() diff --git a/tests/removal.rs b/tests/removal.rs index 1ed4eb72..735561a5 100644 --- a/tests/removal.rs +++ b/tests/removal.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{ client::confirm_history::{ConfirmHistory, EntityReplicated}, prelude::*, @@ -20,6 +20,7 @@ fn single() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -60,6 +61,7 @@ fn multiple() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -108,6 +110,7 @@ fn command_fns() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -149,6 +152,7 @@ fn marker() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -198,6 +202,7 @@ fn group() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -243,6 +248,7 @@ fn not_replicated() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -294,6 +300,7 @@ fn after_insertion() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -336,6 +343,7 @@ fn with_spawn() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -366,6 +374,7 @@ fn with_despawn() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -408,6 +417,7 @@ fn confirm_history() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -474,6 +484,7 @@ fn hidden() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, visibility_policy: VisibilityPolicy::Whitelist, // Hide all spawned entities by default. diff --git a/tests/scene.rs b/tests/scene.rs index b4a5f951..c7a322c8 100644 --- a/tests/scene.rs +++ b/tests/scene.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{prelude::*, scene}; use serde::{Deserialize, Serialize}; use test_log::test; @@ -6,7 +6,7 @@ use test_log::test; #[test] fn replicated_entity() { let mut app = App::new(); - app.add_plugins(RepliconPlugins) + app.add_plugins((StatesPlugin, RepliconPlugins)) .register_type::() .register_type::() .replicate::() @@ -42,7 +42,7 @@ fn replicated_entity() { #[test] fn empty_entity() { let mut app = App::new(); - app.add_plugins(RepliconPlugins).finish(); + app.add_plugins((StatesPlugin, RepliconPlugins)).finish(); let entity = app.world_mut().spawn(Replicated).id(); @@ -61,7 +61,7 @@ fn empty_entity() { #[test] fn not_replicated_entity() { let mut app = App::new(); - app.add_plugins(RepliconPlugins) + app.add_plugins((StatesPlugin, RepliconPlugins)) .register_type::() .replicate::() .finish(); @@ -78,7 +78,7 @@ fn not_replicated_entity() { #[test] fn entity_update() { let mut app = App::new(); - app.add_plugins(RepliconPlugins) + app.add_plugins((StatesPlugin, RepliconPlugins)) .register_type::() .replicate::() .register_type::() diff --git a/tests/server_event.rs b/tests/server_event.rs index bafb5a9f..ec37c71b 100644 --- a/tests/server_event.rs +++ b/tests/server_event.rs @@ -1,6 +1,7 @@ use bevy::{ ecs::{entity::MapEntities, event::Events}, prelude::*, + state::app::StatesPlugin, time::TimePlugin, }; use bevy_replicon::{ @@ -20,6 +21,7 @@ fn channels() { let mut app = App::new(); app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -41,6 +43,7 @@ fn regular() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -86,6 +89,7 @@ fn mapped() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -131,6 +135,7 @@ fn without_plugins() { server_app .add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .build() .set(ServerPlugin { @@ -145,6 +150,7 @@ fn without_plugins() { client_app .add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .build() .disable::() @@ -187,6 +193,7 @@ fn local_resending() { let mut app = App::new(); app.add_plugins(( TimePlugin, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -229,6 +236,7 @@ fn server_buffering() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::Manual, // To artificially delay replication after sending. ..Default::default() @@ -275,6 +283,7 @@ fn client_queue() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -325,6 +334,7 @@ fn client_queue_and_mapping() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -388,6 +398,7 @@ fn multiple_client_queues() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -450,6 +461,7 @@ fn independent() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -521,6 +533,7 @@ fn before_started_replication() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, @@ -564,6 +577,7 @@ fn independent_before_started_replication() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, @@ -613,6 +627,7 @@ fn different_ticks() { for app in [&mut server_app, &mut client_app1, &mut client_app2] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() diff --git a/tests/server_trigger.rs b/tests/server_trigger.rs index 78d65502..c532ef07 100644 --- a/tests/server_trigger.rs +++ b/tests/server_trigger.rs @@ -1,4 +1,4 @@ -use bevy::{ecs::entity::MapEntities, prelude::*, time::TimePlugin}; +use bevy::{ecs::entity::MapEntities, prelude::*, state::app::StatesPlugin, time::TimePlugin}; use bevy_replicon::{ client::ServerUpdateTick, prelude::*, shared::server_entity_map::ServerEntityMap, test_app::ServerTestAppExt, @@ -13,6 +13,7 @@ fn regular() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -45,6 +46,7 @@ fn with_target() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -89,6 +91,7 @@ fn mapped() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -131,6 +134,7 @@ fn without_plugins() { server_app .add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .build() .set(ServerPlugin { @@ -145,6 +149,7 @@ fn without_plugins() { client_app .add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins .build() .disable::() @@ -174,6 +179,7 @@ fn local_resending() { let mut app = App::new(); app.add_plugins(( TimePlugin, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -204,6 +210,7 @@ fn independent() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() diff --git a/tests/spawn.rs b/tests/spawn.rs index 29f0e07a..efb6cd9b 100644 --- a/tests/spawn.rs +++ b/tests/spawn.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{ client::confirm_history::ConfirmHistory, prelude::*, @@ -15,6 +15,7 @@ fn empty() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -57,6 +58,7 @@ fn with_component() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -85,6 +87,7 @@ fn with_multiple_components() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -121,6 +124,7 @@ fn with_old_component() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -164,6 +168,7 @@ fn before_connection() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -192,6 +197,7 @@ fn pre_spawn() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() @@ -258,6 +264,7 @@ fn after_despawn() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() diff --git a/tests/stats.rs b/tests/stats.rs index 8b3208c0..d9bfe62a 100644 --- a/tests/stats.rs +++ b/tests/stats.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{ prelude::*, test_app::{ServerTestAppExt, TestClientEntity}, @@ -13,6 +13,7 @@ fn client_stats() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, ..Default::default() diff --git a/tests/visibility.rs b/tests/visibility.rs index 44ec0787..ccc20980 100644 --- a/tests/visibility.rs +++ b/tests/visibility.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{prelude::*, state::app::StatesPlugin}; use bevy_replicon::{ prelude::*, test_app::{ServerTestAppExt, TestClientEntity}, @@ -13,6 +13,7 @@ fn empty_blacklist() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, visibility_policy: VisibilityPolicy::Blacklist, @@ -44,6 +45,7 @@ fn blacklist() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, visibility_policy: VisibilityPolicy::Blacklist, @@ -100,6 +102,7 @@ fn blacklist_with_despawn() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, visibility_policy: VisibilityPolicy::Blacklist, @@ -140,6 +143,7 @@ fn empty_whitelist() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, visibility_policy: VisibilityPolicy::Whitelist, @@ -173,6 +177,7 @@ fn whitelist() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, visibility_policy: VisibilityPolicy::Whitelist, @@ -232,6 +237,7 @@ fn whitelist_with_despawn() { for app in [&mut server_app, &mut client_app] { app.add_plugins(( MinimalPlugins, + StatesPlugin, RepliconPlugins.set(ServerPlugin { tick_policy: TickPolicy::EveryFrame, visibility_policy: VisibilityPolicy::Whitelist, From 79e4764ed29434f41deb9dbeeb889039d23ba605 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Sat, 6 Sep 2025 13:35:58 +0300 Subject: [PATCH 4/9] Migrate to Bevy states This allows users to utilize things like `StateScoped` and run systems more efficiently using `OnEnter` or `OnExit` without evaluating conditions every frame. As a result, we now require `StatesPlugin` to be present. It's included by default in `DefaultPlugins`, but with `MinimalPlugins` you have to add it manually. In tests, I had to add `StatesPlugin` everywhere. --- CHANGELOG.md | 2 + benches/related_entities.rs | 10 +- .../examples/authoritative_rts.rs | 7 +- .../examples/tic_tac_toe.rs | 18 +-- bevy_replicon_example_backend/src/client.rs | 27 ++-- bevy_replicon_example_backend/src/server.rs | 27 ++-- .../tests/backend.rs | 23 +-- src/client.rs | 59 +++---- src/client/diagnostics.rs | 6 +- src/client/event.rs | 38 ++++- src/lib.rs | 102 +++++++++--- src/server.rs | 50 +++--- src/server/client_visibility.rs | 2 +- src/server/event.rs | 10 +- src/server/related_entities.rs | 153 +++++++----------- src/shared.rs | 5 +- src/shared/backend.rs | 41 ++++- src/shared/backend/replicon_client.rs | 135 +--------------- src/shared/backend/replicon_server.rs | 96 +---------- src/shared/common_conditions.rs | 88 ---------- src/shared/event/client_event.rs | 2 +- src/shared/event/client_trigger.rs | 4 +- src/test_app.rs | 26 ++- tests/connection.rs | 5 +- 24 files changed, 368 insertions(+), 568 deletions(-) delete mode 100644 src/shared/common_conditions.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e3b912f9..3323b1cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Rename `RepliconClientStatus` to `ClientState` and `RepliconServerStatus` to `ServerState`. They are now regular Bevy states. As result, we now require `StatesPlugin` to be added. It's present by default in `DefaultPlugins`, but with `MinimalPlugins` you have to add it manually. - Make custom entity ser/de compatible with `serde` attributes. - All contexts now store `AppTypeRegistry` instead of `TypeRegistry`. To get `TypeRegistry`, call `AppTypeRegistry::read`. - All events now use `ClientId` wrapper instead of `Entity`. @@ -43,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `entity_serde::serialize_entity` and `entity_serde::deserialize_entity`. Use `postcard_utils::entity_to_extend_mut` and `postcard_utils::entity_from_buf` respectively; just swap the argument order. - `SERVER`. Use `ClientId::Server` instead. - `ReplicationMode::Periodic`. Use `PriorityMap` instead. +- All provided run conditions. Just use `in_state` or `OnEnter`/`OnExit` with `ServerState` and `ClientState` instead. `server_or_singleplayer` is just `in_state(ClientState::Disconnected)`. ## [0.34.4] - 2025-07-29 diff --git a/benches/related_entities.rs b/benches/related_entities.rs index 9d43331c..f933846e 100644 --- a/benches/related_entities.rs +++ b/benches/related_entities.rs @@ -30,8 +30,9 @@ fn hierarchy_spawning(c: &mut Criterion) { .sync_related_entities::() .finish(); - let mut server = app.world_mut().resource_mut::(); - server.set_running(true); + app.world_mut() + .resource_mut::>() + .set(ServerState::Running); b.iter(|| spawn_then_despawn(&mut app)); }); @@ -73,8 +74,9 @@ fn hierarchy_changes(c: &mut Criterion) { spawn_hierarchy(app.world_mut()); - let mut server = app.world_mut().resource_mut::(); - server.set_running(true); + app.world_mut() + .resource_mut::>() + .set(ServerState::Running); b.iter(|| trigger_hierarchy_change(&mut app)); }); diff --git a/bevy_replicon_example_backend/examples/authoritative_rts.rs b/bevy_replicon_example_backend/examples/authoritative_rts.rs index 63ec1c5f..8877c216 100644 --- a/bevy_replicon_example_backend/examples/authoritative_rts.rs +++ b/bevy_replicon_example_backend/examples/authoritative_rts.rs @@ -63,11 +63,14 @@ fn main() { .add_observer(trigger_units_move) .add_observer(apply_units_move) .add_systems(Startup, setup) - .add_systems(FixedUpdate, move_units.run_if(server_or_singleplayer)) + .add_systems(OnEnter(ClientState::Connected), trigger_team_request) + .add_systems( + FixedUpdate, + move_units.run_if(in_state(ClientState::Disconnected)), + ) .add_systems( Update, ( - trigger_team_request.run_if(client_just_connected), draw_selection.run_if(|r: Res| r.active), draw_selected, ), diff --git a/bevy_replicon_example_backend/examples/tic_tac_toe.rs b/bevy_replicon_example_backend/examples/tic_tac_toe.rs index dafe1160..990fffd8 100644 --- a/bevy_replicon_example_backend/examples/tic_tac_toe.rs +++ b/bevy_replicon_example_backend/examples/tic_tac_toe.rs @@ -56,19 +56,17 @@ fn main() { .add_systems(OnEnter(GameState::Winner), show_winner_text) .add_systems(OnEnter(GameState::Tie), show_tie_text) .add_systems(OnEnter(GameState::Disconnected), stop_networking) + .add_systems(OnEnter(ClientState::Connected), client_start) + .add_systems(OnEnter(ClientState::Connecting), show_connecting_text) + .add_systems(OnExit(ClientState::Connected), disconnect_by_server) + .add_systems(OnEnter(ServerState::Running), show_waiting_client_text) .add_systems( Update, ( - show_connecting_text.run_if(resource_added::), - show_waiting_client_text.run_if(resource_added::), - client_start.run_if(client_just_connected), - ( - disconnect_by_server.run_if(client_just_disconnected), - update_buttons_background.run_if(local_player_turn), - show_turn_symbol.run_if(resource_changed::), - ) - .run_if(in_state(GameState::InGame)), - ), + update_buttons_background.run_if(local_player_turn), + show_turn_symbol.run_if(resource_changed::), + ) + .run_if(in_state(GameState::InGame)), ) .run(); } diff --git a/bevy_replicon_example_backend/src/client.rs b/bevy_replicon_example_backend/src/client.rs index 1bd977dc..87bc86a1 100644 --- a/bevy_replicon_example_backend/src/client.rs +++ b/bevy_replicon_example_backend/src/client.rs @@ -20,32 +20,31 @@ impl Plugin for RepliconExampleClientPlugin { app.add_systems( PreUpdate, ( + ( + receive_packets.run_if(resource_exists::), + // Run after since the resource might be removed after receiving packets. + set_disconnected.run_if(resource_removed::), + ) + .chain(), set_connected.run_if(resource_added::), - receive_packets.run_if(resource_exists::), ) - .chain() .in_set(ClientSet::ReceivePackets), ) .add_systems( PostUpdate, - ( - set_disconnected - .in_set(ClientSet::PrepareSend) - .run_if(resource_removed::), - send_packets - .in_set(ClientSet::SendPackets) - .run_if(resource_exists::), - ), + send_packets + .run_if(resource_exists::) + .in_set(ClientSet::SendPackets), ); } } -fn set_disconnected(mut replicon_client: ResMut) { - replicon_client.set_status(RepliconClientStatus::Disconnected); +fn set_connected(mut state: ResMut>) { + state.set(ClientState::Connected); } -fn set_connected(mut replicon_client: ResMut) { - replicon_client.set_status(RepliconClientStatus::Connected); +fn set_disconnected(mut state: ResMut>) { + state.set(ClientState::Disconnected); } fn receive_packets( diff --git a/bevy_replicon_example_backend/src/server.rs b/bevy_replicon_example_backend/src/server.rs index 9693eec5..98a3bc3b 100644 --- a/bevy_replicon_example_backend/src/server.rs +++ b/bevy_replicon_example_backend/src/server.rs @@ -20,32 +20,31 @@ impl Plugin for RepliconExampleServerPlugin { app.add_systems( PreUpdate, ( + ( + receive_packets.run_if(resource_exists::), + // Run after since the resource might be removed after receiving packets. + set_stopped.run_if(resource_removed::), + ) + .chain(), set_running.run_if(resource_added::), - receive_packets.run_if(resource_exists::), ) - .chain() .in_set(ServerSet::ReceivePackets), ) .add_systems( PostUpdate, - ( - set_stopped - .in_set(ServerSet::PrepareSend) - .run_if(resource_removed::), - send_packets - .in_set(ServerSet::SendPackets) - .run_if(resource_exists::), - ), + send_packets + .run_if(resource_exists::) + .in_set(ServerSet::SendPackets), ); } } -fn set_stopped(mut server: ResMut) { - server.set_running(false); +fn set_running(mut state: ResMut>) { + state.set(ServerState::Running); } -fn set_running(mut server: ResMut) { - server.set_running(true); +fn set_stopped(mut state: ResMut>) { + state.set(ServerState::Stopped); } fn receive_packets( diff --git a/bevy_replicon_example_backend/tests/backend.rs b/bevy_replicon_example_backend/tests/backend.rs index b2fa46f4..5336b7ae 100644 --- a/bevy_replicon_example_backend/tests/backend.rs +++ b/bevy_replicon_example_backend/tests/backend.rs @@ -25,15 +25,16 @@ fn connect_disconnect() { setup(&mut server_app, &mut client_app).unwrap(); - assert!(server_app.world().resource::().is_running()); + let server_state = server_app.world().resource::>(); + assert_eq!(*server_state, ServerState::Running); let mut clients = server_app .world_mut() .query::<(&ConnectedClient, &AuthorizedClient)>(); assert_eq!(clients.iter(server_app.world()).len(), 1); - let replicon_client = client_app.world().resource::(); - assert!(replicon_client.is_connected()); + let client_state = client_app.world().resource::>(); + assert_eq!(*client_state, ClientState::Connected); let renet_client = client_app.world().resource::(); assert!(renet_client.is_connected()); @@ -45,8 +46,8 @@ fn connect_disconnect() { assert_eq!(clients.iter(server_app.world()).len(), 0); - let replicon_client = client_app.world().resource::(); - assert!(replicon_client.is_disconnected()); + let client_state = client_app.world().resource::>(); + assert_eq!(*client_state, ClientState::Disconnected); } #[test] @@ -88,8 +89,8 @@ fn disconnect_request() { assert_eq!(clients.iter(server_app.world()).len(), 0); - let client = client_app.world().resource::(); - assert!(client.is_disconnected()); + let client_state = client_app.world().resource::>(); + assert_eq!(*client_state, ClientState::Disconnected); let events = client_app.world().resource::>(); assert_eq!(events.len(), 1, "last event should be received"); @@ -134,10 +135,12 @@ fn server_stop() { let mut clients = server_app.world_mut().query::<&ConnectedClient>(); assert_eq!(clients.iter(server_app.world()).len(), 0); - assert!(!server_app.world().resource::().is_running()); - let client = client_app.world().resource::(); - assert!(client.is_disconnected()); + let server_state = server_app.world().resource::>(); + assert_eq!(*server_state, ServerState::Stopped); + + let client_state = client_app.world().resource::>(); + assert_eq!(*client_state, ClientState::Disconnected); let events = client_app.world().resource::>(); assert!(events.is_empty(), "event after stop shouldn't be received"); diff --git a/src/client.rs b/src/client.rs index aaeb305e..93c0d0e3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -48,40 +48,45 @@ impl Plugin for ClientPlugin { PreUpdate, ( ClientSet::ReceivePackets, - ( - ClientSet::ResetEvents.run_if(client_just_connected), - ClientSet::Reset.run_if(client_just_disconnected), - ), ClientSet::Receive, ClientSet::Diagnostics, ) .chain(), ) .configure_sets( - PostUpdate, + OnEnter(ClientState::Connected), ( - ClientSet::PrepareSend, - ClientSet::Send, - ClientSet::SendPackets, + ClientSet::ResetEvents, + ClientSet::Receive, + ClientSet::Diagnostics, ) .chain(), ) + .configure_sets( + PostUpdate, + (ClientSet::Send, ClientSet::SendPackets).chain(), + ) .add_systems( PreUpdate, receive_replication .in_set(ClientSet::Receive) - .run_if(client_connected), + .run_if(in_state(ClientState::Connected)), + ) + .add_systems( + OnEnter(ClientState::Connected), + receive_replication.in_set(ClientSet::Receive), ) - .add_systems(PreUpdate, reset.in_set(ClientSet::Reset)); + .add_systems( + OnExit(ClientState::Connected), + reset.in_set(ClientSet::Reset), + ); let auth_method = *app.world().resource::(); debug!("using authorization method `{auth_method:?}`"); if auth_method == AuthMethod::ProtocolCheck { app.add_observer(log_protocol_error).add_systems( - PreUpdate, - send_protocol_hash - .in_set(ClientSet::Receive) - .run_if(client_just_connected), + OnEnter(ClientState::Connected), + send_protocol_hash.in_set(ClientSet::SendHash), ); } } @@ -165,12 +170,14 @@ pub(super) fn receive_replication( } fn reset( + mut client: ResMut, mut update_tick: ResMut, mut entity_map: ResMut, mut buffered_mutations: ResMut, mutate_ticks: Option>, stats: Option>, ) { + client.clear(); *update_tick = Default::default(); entity_map.clear(); buffered_mutations.clear(); @@ -744,7 +751,7 @@ struct ReceiveParams<'a> { /// Set with replication and event systems related to client. #[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)] pub enum ClientSet { - /// Systems that receive packets from the messaging backend. + /// Systems that receive packets from the messaging backend and update [`ClientState`]. /// /// Used by messaging backend implementations. /// @@ -752,24 +759,18 @@ pub enum ClientSet { ReceivePackets, /// Systems that receive data from [`RepliconClient`]. /// - /// Used by `bevy_replicon`. - /// - /// Runs in [`PreUpdate`]. + /// Runs in [`PreUpdate`] and [`OnEnter`] for [`ClientState::Connected`] (to avoid 1 frame delay). Receive, /// Systems that populate Bevy's [`Diagnostics`](bevy::diagnostic::Diagnostics). /// - /// Used by `bevy_replicon`. - /// - /// Runs in [`PreUpdate`]. + /// Runs in [`PreUpdate`] and [`OnEnter`] for [`ClientState::Connected`] (to avoid 1 frame delay). Diagnostics, - /// Systems that prepare for sending data to [`RepliconClient`]. + /// System that sends [`ProtocolHash`]. /// - /// Can be used by backends to add custom logic before sending data, such as transition to a disconnected or connecting state. - PrepareSend, + /// Runs in [`OnEnter`] for [`ClientState::Connected`]. + SendHash, /// Systems that send data to [`RepliconClient`]. /// - /// Used by `bevy_replicon`. - /// /// Runs in [`PostUpdate`]. Send, /// Systems that send packets to the messaging backend. @@ -780,13 +781,13 @@ pub enum ClientSet { SendPackets, /// Systems that reset queued server events. /// - /// Runs in [`PreUpdate`] immediately after the client connects to ensure client sessions have a fresh start. - /// /// This is a separate set from [`ClientSet::Reset`] to avoid sending events that were sent before the connection. + /// + /// Runs in [`OnEnter`] for [`ClientState::Connected`]. ResetEvents, /// Systems that reset the client. /// - /// Runs in [`PreUpdate`] when the client just disconnected. + /// Runs in [`OnExit`] for [`ClientState::Connected`]. Reset, } diff --git a/src/client/diagnostics.rs b/src/client/diagnostics.rs index 9e9d1c33..a865f499 100644 --- a/src/client/diagnostics.rs +++ b/src/client/diagnostics.rs @@ -18,7 +18,11 @@ impl Plugin for ClientDiagnosticsPlugin { PreUpdate, add_measurements .in_set(ClientSet::Diagnostics) - .run_if(client_connected), + .run_if(in_state(ClientState::Connected)), + ) + .add_systems( + OnEnter(ClientState::Connected), + add_measurements.in_set(ClientSet::Diagnostics), ) .register_diagnostic( Diagnostic::new(RTT) diff --git a/src/client/event.rs b/src/client/event.rs index 0aeed3a9..b6c68fcf 100644 --- a/src/client/event.rs +++ b/src/client/event.rs @@ -51,7 +51,7 @@ impl Plugin for ClientEventPlugin { .build_state(app.world_mut()) .build_system(send); - let receive_fn = ( + let receive_builder = ( FilteredResourcesMutParamBuilder::new(|builder| { for event in event_registry.iter_all_server() { builder.add_write_by_id(event.events_id()); @@ -67,11 +67,18 @@ impl Plugin for ClientEventPlugin { ParamBuilder, ParamBuilder, ParamBuilder, - ) + ); + + let receive_fn = receive_builder + .clone() + .build_state(app.world_mut()) + .build_system(receive); + + let enter_receive_fn = receive_builder .build_state(app.world_mut()) .build_system(receive); - let trigger_fn = ( + let trigger_builder = ( FilteredResourcesMutParamBuilder::new(|builder| { for trigger in event_registry.iter_server_triggers() { builder.add_write_by_id(trigger.event().events_id()); @@ -79,7 +86,14 @@ impl Plugin for ClientEventPlugin { }), ParamBuilder, ParamBuilder, - ) + ); + + let trigger_fn = trigger_builder + .clone() + .build_state(app.world_mut()) + .build_system(trigger); + + let enter_trigger_fn = trigger_builder .build_state(app.world_mut()) .build_system(trigger); @@ -118,9 +132,19 @@ impl Plugin for ClientEventPlugin { app.insert_resource(event_registry) .add_systems( PreUpdate, + ( + receive_fn.run_if(in_state(ClientState::Connected)), + trigger_fn, + ) + .chain() + .after(super::receive_replication) + .in_set(ClientSet::Receive), + ) + .add_systems( + OnEnter(ClientState::Connected), ( reset_fn.in_set(ClientSet::ResetEvents), - (receive_fn.run_if(client_connected), trigger_fn) + (enter_receive_fn, enter_trigger_fn) .chain() .after(super::receive_replication) .in_set(ClientSet::Receive), @@ -129,8 +153,8 @@ impl Plugin for ClientEventPlugin { .add_systems( PostUpdate, ( - send_fn.run_if(client_connected), - resend_locally_fn.run_if(server_or_singleplayer), + send_fn.run_if(in_state(ClientState::Connected)), + resend_locally_fn.run_if(in_state(ClientState::Disconnected)), ) .chain() .in_set(ClientSet::Send), diff --git a/src/lib.rs b/src/lib.rs index 8740f704..603c830b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,14 +30,14 @@ app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins, MyMessagingPlugi # } ``` +If you use [`MinimalPlugins`], you need to add [`StatesPlugin`](bevy::state::app::StatesPlugin) +manually. It's included by default with [`DefaultPlugins`]. + ## Server and client creation This part is specific to your messaging backend. For `bevy_replicon_renet`, see [this section](https://docs.rs/bevy_replicon_renet#server-and-client-creation). -Backends manage [`RepliconServer`] and [`RepliconClient`] resources. They can be used -to obtain things like state or statistic in backend-independent way. - On server connected clients represented as entities with [`ConnectedClient`] component. Their data represented as components, such as [`NetworkStats`]. Users can also attach their own metadata to them or even replicate these entiteis back to clients. @@ -49,6 +49,57 @@ to disconnect after sending messages. You can use [`Trigger`] to react to new connections, or use backend-provided events if you need the disconnect reason. +## States + +We provide [`ClientState`] and [`ServerState`], which are Bevy [`States`]. +These are managed by your messaging backend, and you can use them to control when your systems run. + +For systems that should run continuously while in a specific state, use [`IntoScheduleConfigs::run_if`] +with the [`in_state`] run condition: + +``` +# use bevy::prelude::*; +# use bevy_replicon::prelude::*; +# let mut app = App::new(); +app.add_systems( + Update, + ( + apply_damage.run_if(in_state(ServerState::Running)), // Runs every frame on the server. + display_vfx.run_if(in_state(ClientState::Connected)), // Runs every frame on the client. + ), +); +# fn apply_damage() {} +# fn display_vfx() {} +``` + +To run systems when entering or exiting a state, use the [`OnEnter`] or [`OnExit`] schedules: + +``` +# use bevy::prelude::*; +# use bevy_replicon::prelude::*; +# let mut app = App::new(); +app.add_systems(OnEnter(ClientState::Connecting), display_connection_message) // Runs when the client starts connecting. + .add_systems(OnExit(ClientState::Connected), show_disconnected_message) // Runs when the client disconnects. + .add_systems(OnEnter(ServerState::Running), initialize_match); // Runs when the server starts. +# fn display_connection_message() {} +# fn show_disconnected_message() {} +# fn initialize_match() {} +``` + +You can also use these states with [`StateScoped`], but you'll need to call +[`AppExtStates::enable_state_scoped_entities`] in your app: + +``` +# use bevy::prelude::*; +# use bevy_replicon::prelude::*; +# let mut app = App::new(); +app.enable_state_scoped_entities::() + .enable_state_scoped_entities::(); +``` + +Read more about system patterns in the [Abstracting over configurations](#abstracting-over-configurations) +section. + ## Replication It's a process of exchanging data in order to keep the world in sync. Replicon @@ -260,11 +311,11 @@ app.add_client_event::(Channel::Ordered) PreUpdate, receive_events .after(ServerSet::Receive) - .run_if(server_running), + .run_if(in_state(ServerState::Running)), ) .add_systems( PostUpdate, - send_events.before(ClientSet::Send).run_if(client_connected), + send_events.before(ClientSet::Send).run_if(in_state(ClientState::Connected)), ); fn send_events(mut events: EventWriter) { @@ -301,7 +352,7 @@ using [`ClientTriggerAppExt::add_client_trigger`], and then use [`ClientTriggerE # app.add_plugins((StatesPlugin, RepliconPlugins)); app.add_client_trigger::(Channel::Ordered) .add_observer(receive_events) - .add_systems(Update, send_events.run_if(client_connected)); + .add_systems(Update, send_events.run_if(in_state(ClientState::Connected))); fn send_events(mut commands: Commands) { commands.client_trigger(ExampleEvent); @@ -338,11 +389,11 @@ app.add_server_event::(Channel::Ordered) PreUpdate, receive_events .after(ClientSet::Receive) - .run_if(client_connected), + .run_if(in_state(ClientState::Connected)), ) .add_systems( PostUpdate, - send_events.before(ServerSet::Send).run_if(server_running), + send_events.before(ServerSet::Send).run_if(in_state(ServerState::Running)), ); fn send_events(mut events: EventWriter>) { @@ -376,7 +427,7 @@ with [`ServerTriggerAppExt::add_server_trigger`] and then use [`ServerTriggerExt # app.add_plugins((StatesPlugin, RepliconPlugins)); app.add_server_trigger::(Channel::Ordered) .add_observer(receive_events) - .add_systems(Update, send_events.run_if(server_running)); + .add_systems(Update, send_events.run_if(in_state(ServerState::Running))); fn send_events(mut commands: Commands) { commands.server_trigger(ToClients { @@ -435,30 +486,32 @@ without actually running them: - Listen server configuration runs only the server and both logics. - Singleplayer configuration doesn't run the client or server but runs both logics. -To achieve this, just use provided [run conditions](shared::common_conditions): +To achieve this, use the provided [`ClientState`] and [`ServerState`] states: -- Use [`server_or_singleplayer`] for systems that require server authority. For example, systems that - apply damage or send server events. -- Use client or server conditions like [`client_connecting`], [`client_connected`], [`server_running`], etc. +- Use [`ClientState::Disconnected`] for systems that require server authority. + For example, systems that apply damage or send server events. This basically means "run when not a client", + which applies to both server **and** singleplayer. +- Use [`ClientState::Connecting`], [`ClientState::Connected`], [`ServerState::Running`], etc. **only** for miscellaneous things, like display a connection message or a menu to kick connected players (things that actually require server or client running) -- For everything else don't use Replicon's conditions. - -We also provide [`ClientSet`] and [`ServerSet`] to schedule your system at specific time in the frame. -For example, you can run your systems right after receive using [`ClientSet::Receive`] or [`ServerSet::Receive`]. +- For everything else don't use Replicon's states. Everything else is done automatically by the crate. All provided [examples](https://github.com/simgine/bevy_replicon/tree/master/bevy_replicon_example_backend/examples) use this approach. -Internally we run replication sending system only if [`server_running`] and replication receiving -only if [`client_connected`]. This way for singleplayer replication systems won't run at all and +Internally we run replication sending system only in [`ServerState::Running`] and replication receiving +only in [`ClientState::Connected`]. This way for singleplayer replication systems won't run at all and for listen server replication will only be sending (server world is already in the correct state). -For events it's a bit trickier. For all client events we internally drain events as `E` and re-emit -them as [`FromClient`] locally with [`ClientId::Server`] if [`server_or_singleplayer`] is `true`. +For events, it's a bit trickier. For all client events, we internally drain events as `E` and re-emit +them as [`FromClient`] locally with [`ClientId::Server`] in [`ClientState::Disconnected`]. For server events we drain [`ToClients`] and, if the [`ClientId::Server`] is the recipient of the event, -re-emit it as `E` locally. +re-emit it as `E` locally. This emulates event receiving for both server and singleplayer without actually +transmitting data over the network. + +We also provide [`ClientSet`] and [`ServerSet`] to schedule your system at specific time in the frame. +For example, you can run your systems right after receive using [`ClientSet::Receive`] or [`ServerSet::Receive`]. ## Organizing your game code @@ -656,14 +709,13 @@ pub mod prelude { shared::{ AuthMethod, RepliconSharedPlugin, backend::{ - DisconnectRequest, + ClientState, DisconnectRequest, ServerState, channels::{Channel, RepliconChannels}, connected_client::{ConnectedClient, NetworkStats}, - replicon_client::{RepliconClient, RepliconClientStatus}, + replicon_client::RepliconClient, replicon_server::RepliconServer, }, client_id::ClientId, - common_conditions::*, event::{ client_event::{ClientEventAppExt, FromClient}, client_trigger::{ClientTriggerAppExt, ClientTriggerExt}, diff --git a/src/server.rs b/src/server.rs index 3373733c..74863bc2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -92,12 +92,7 @@ impl Plugin for ServerPlugin { ) .configure_sets( PostUpdate, - ( - ServerSet::PrepareSend, - ServerSet::Send, - ServerSet::SendPackets, - ) - .chain(), + (ServerSet::Send, ServerSet::SendPackets).chain(), ) .add_observer(handle_connects) .add_observer(handle_disconnects) @@ -110,20 +105,18 @@ impl Plugin for ServerPlugin { ) .chain() .in_set(ServerSet::Receive) - .run_if(server_running), + .run_if(in_state(ServerState::Running)), ) + .add_systems(OnExit(ServerState::Running), reset) .add_systems( PostUpdate, ( - ( - buffer_removals, - send_replication.run_if(resource_changed::), - ) - .chain() - .in_set(ServerSet::Send) - .run_if(server_running), - reset.run_if(server_just_stopped), - ), + buffer_removals, + send_replication.run_if(resource_changed::), + ) + .chain() + .in_set(ServerSet::Send) + .run_if(in_state(ServerState::Running)), ); debug!("using tick policy `{:?}`", self.tick_policy); @@ -134,7 +127,7 @@ impl Plugin for ServerPlugin { PostUpdate, increment_tick .before(send_replication) - .run_if(server_running) + .run_if(in_state(ServerState::Running)) .run_if(on_timer(tick_time)), ); } @@ -143,7 +136,7 @@ impl Plugin for ServerPlugin { PostUpdate, increment_tick .before(send_replication) - .run_if(server_running), + .run_if(in_state(ServerState::Running)), ); } TickPolicy::Manual => (), @@ -279,9 +272,9 @@ fn receive_acks( fn buffer_despawns( trigger: Trigger, mut despawn_buffer: ResMut, - server: Res, + state: Res>, ) { - if server.is_running() { + if *state == ServerState::Running { despawn_buffer.push(trigger.target()); } } @@ -371,11 +364,13 @@ fn send_replication( fn reset( mut commands: Commands, + mut server: ResMut, mut server_tick: ResMut, mut related_entities: ResMut, clients: Query>, mut buffered_events: ResMut, ) { + server.clear(); *server_tick = Default::default(); buffered_events.clear(); related_entities.clear(); @@ -797,7 +792,7 @@ fn write_tick_cached( /// Set with replication and event systems related to server. #[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)] pub enum ServerSet { - /// Systems that receive packets from the messaging backend. + /// Systems that receive packets from the messaging backend and update [`ServerState`]. /// /// Used by the messaging backend. /// @@ -805,17 +800,16 @@ pub enum ServerSet { ReceivePackets, /// Systems that receive data from [`RepliconServer`]. /// - /// Used by `bevy_replicon`. - /// /// Runs in [`PreUpdate`]. Receive, - /// Systems that prepare for sending data to [`RepliconServer`]. + /// Systems that build the initial graph with all related entities registered via + /// [`SyncRelatedAppExt::sync_related_entities`]. /// - /// Can be used by backends to add custom logic before sending data, such as transition to a stopped state. - PrepareSend, - /// Systems that send data to [`RepliconServer`]. + /// The graph is kept in sync with observers. /// - /// Used by `bevy_replicon`. + /// Runs in [`OnEnter`] for [`ServerState::Running`]. + ReadRelations, + /// Systems that send data to [`RepliconServer`]. /// /// Runs in [`PostUpdate`] on server tick, see [`TickPolicy`]. Send, diff --git a/src/server/client_visibility.rs b/src/server/client_visibility.rs index 08ff407c..699466d6 100644 --- a/src/server/client_visibility.rs +++ b/src/server/client_visibility.rs @@ -24,7 +24,7 @@ use bevy::{ /// ..Default::default() /// }), /// )) -/// .add_systems(Update, update_visibility.run_if(server_running)); +/// .add_systems(Update, update_visibility.run_if(in_state(ServerState::Running))); /// /// /// Disables the visibility of other players' entities that are further away than the visible distance. /// fn update_visibility( diff --git a/src/server/event.rs b/src/server/event.rs index ccbc2a71..353f49af 100644 --- a/src/server/event.rs +++ b/src/server/event.rs @@ -93,8 +93,8 @@ impl Plugin for ServerEventPlugin { .add_systems( PreUpdate, ( - receive_fn.run_if(server_running), - trigger_fn.run_if(server_or_singleplayer), + receive_fn.run_if(in_state(ServerState::Running)), + trigger_fn.run_if(in_state(ClientState::Disconnected)), ) .chain() .in_set(ServerSet::Receive), @@ -102,11 +102,11 @@ impl Plugin for ServerEventPlugin { .add_systems( PostUpdate, ( - send_or_buffer_fn.run_if(server_running), + send_or_buffer_fn.run_if(in_state(ServerState::Running)), send_buffered - .run_if(server_running) + .run_if(in_state(ServerState::Running)) .run_if(resource_changed::), - resend_locally_fn.run_if(server_or_singleplayer), + resend_locally_fn.run_if(in_state(ClientState::Disconnected)), ) .chain() .after(super::send_replication) diff --git a/src/server/related_entities.rs b/src/server/related_entities.rs index 72097263..48dc0f6f 100644 --- a/src/server/related_entities.rs +++ b/src/server/related_entities.rs @@ -59,11 +59,8 @@ impl SyncRelatedAppExt for App { C: Relationship + Component, { self.add_systems( - PostUpdate, - read_relations:: - .before(super::send_replication) - .in_set(ServerSet::Send) - .run_if(server_just_started), + OnEnter(ServerState::Running), + read_relations::.in_set(ServerSet::ReadRelations), ) .add_observer(add_relation::) .add_observer(remove_relation::) @@ -251,11 +248,11 @@ fn read_relations( fn add_relation( trigger: Trigger, - server: Res, mut related_entities: ResMut, + state: Res>, components: Query<&C, With>, ) { - if server.is_running() + if *state == ServerState::Running && let Ok(relationship) = components.get(trigger.target()) { related_entities.add_relation::(trigger.target(), relationship.get()); @@ -264,11 +261,11 @@ fn add_relation( fn remove_relation( trigger: Trigger, - server: Res, mut related_entities: ResMut, + state: Res>, relationships: Query<&C, With>, ) { - if server.is_running() + if *state == ServerState::Running && let Ok(relationship) = relationships.get(trigger.target()) { related_entities.remove_relation::(trigger.target(), relationship.get()); @@ -277,11 +274,11 @@ fn remove_relation( fn start_replication( trigger: Trigger, - server: Res, mut related_entities: ResMut, + state: Res>, components: Query<&C, With>, ) { - if server.is_running() + if *state == ServerState::Running && let Ok(relationship) = components.get(trigger.target()) { related_entities.add_relation::(trigger.target(), relationship.get()); @@ -290,11 +287,11 @@ fn start_replication( fn stop_replication( trigger: Trigger, - server: Res, mut related_entities: ResMut, + state: Res>, relationships: Query<&C, With>, ) { - if server.is_running() + if *state == ServerState::Running && let Ok(relationship) = relationships.get(trigger.target()) { related_entities.remove_relation::(trigger.target(), relationship.get()); @@ -303,6 +300,7 @@ fn stop_replication( #[cfg(test)] mod tests { + use bevy::state::app::StatesPlugin; use test_log::test; use super::*; @@ -310,13 +308,11 @@ mod tests { #[test] fn orphan() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let entity1 = app .world_mut() .spawn((Replicated, Children::default())) @@ -336,13 +332,11 @@ mod tests { #[test] fn single() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let child1 = app.world_mut().spawn(Replicated).id(); let child2 = app.world_mut().spawn(Replicated).id(); let root = app @@ -362,13 +356,11 @@ mod tests { #[test] fn disjoint() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let child1 = app.world_mut().spawn(Replicated).id(); let root1 = app.world_mut().spawn(Replicated).add_child(child1).id(); let child2 = app.world_mut().spawn(Replicated).id(); @@ -386,13 +378,11 @@ mod tests { #[test] fn nested() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let grandchild = app.world_mut().spawn(Replicated).id(); let child = app.world_mut().spawn(Replicated).add_child(grandchild).id(); let root = app.world_mut().spawn(Replicated).add_child(child).id(); @@ -408,13 +398,11 @@ mod tests { #[test] fn split() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let grandgrandchild = app.world_mut().spawn(Replicated).id(); let grandchild = app .world_mut() @@ -438,13 +426,11 @@ mod tests { #[test] fn join() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let child1 = app.world_mut().spawn(Replicated).id(); let root1 = app.world_mut().spawn(Replicated).add_child(child1).id(); let child2 = app.world_mut().spawn(Replicated).id(); @@ -464,13 +450,11 @@ mod tests { #[test] fn reparent() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let child1 = app.world_mut().spawn(Replicated).id(); let root1 = app.world_mut().spawn(Replicated).add_child(child1).id(); let child2 = app.world_mut().spawn(Replicated).id(); @@ -490,13 +474,11 @@ mod tests { #[test] fn orphan_after_split() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let child = app.world_mut().spawn(Replicated).id(); let root = app.world_mut().spawn(Replicated).add_child(child).id(); @@ -512,13 +494,11 @@ mod tests { #[test] fn despawn() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let child1 = app.world_mut().spawn(Replicated).id(); let child2 = app.world_mut().spawn(Replicated).id(); let root = app @@ -540,14 +520,12 @@ mod tests { #[test] fn intersection() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let child = app.world_mut().spawn(Replicated).id(); let root1 = app.world_mut().spawn(Replicated).add_child(child).id(); let root2 = app @@ -567,14 +545,12 @@ mod tests { #[test] fn overlap() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let child = app.world_mut().spawn(Replicated).id(); let root = app .world_mut() @@ -593,14 +569,12 @@ mod tests { #[test] fn overlap_removal() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let child = app.world_mut().spawn(Replicated).id(); let root = app .world_mut() @@ -621,14 +595,12 @@ mod tests { #[test] fn connected() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let grandchild = app.world_mut().spawn(Replicated).id(); let child = app.world_mut().spawn(Replicated).add_child(grandchild).id(); let root = app @@ -648,14 +620,12 @@ mod tests { #[test] fn replication_start() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let child = app.world_mut().spawn_empty().id(); let root = app.world_mut().spawn_empty().add_child(child).id(); @@ -672,14 +642,12 @@ mod tests { #[test] fn replication_stop() { let mut app = App::new(); - app.init_resource::() + app.add_plugins(StatesPlugin) + .insert_state(ServerState::Running) + .init_resource::() .sync_related_entities::() .sync_related_entities::(); - let mut server = RepliconServer::default(); - server.set_running(true); - app.insert_resource(server); - let child = app.world_mut().spawn(Replicated).id(); let root = app .world_mut() @@ -700,8 +668,9 @@ mod tests { #[test] fn runs_only_with_server() { let mut app = App::new(); - app.init_resource::() - .init_resource::() + app.add_plugins(StatesPlugin) + .init_state::() + .init_resource::() .sync_related_entities::(); let child1 = app.world_mut().spawn(Replicated).id(); @@ -720,8 +689,8 @@ mod tests { assert_eq!(related.graph_index(child2), None); app.world_mut() - .resource_mut::() - .set_running(true); + .resource_mut::>() + .set(ServerState::Running); app.update(); diff --git a/src/shared.rs b/src/shared.rs index 7a2fe253..ad85066b 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -1,6 +1,5 @@ pub mod backend; pub mod client_id; -pub mod common_conditions; pub mod event; pub mod protocol; pub mod replication; @@ -47,7 +46,7 @@ pub struct RepliconSharedPlugin { .add_server_trigger::(Channel::Unreliable) .make_trigger_independent::() // Let client receive it without replication. .add_observer(start_game) - .add_systems(Update, send_info.run_if(client_just_connected)); + .add_systems(OnEnter(ClientState::Connected), send_info); fn send_info( mut commands: Commands, @@ -135,6 +134,8 @@ impl Plugin for RepliconSharedPlugin { .register_type::() .register_type::() .register_type::() + .init_state::() + .init_state::() .init_resource::() .init_resource::() .init_resource::() diff --git a/src/shared/backend.rs b/src/shared/backend.rs index 2b70d540..4f9ff3ae 100644 --- a/src/shared/backend.rs +++ b/src/shared/backend.rs @@ -4,6 +4,7 @@ //! //! - Create channels defined in the [`RepliconChannels`](channels::RepliconChannels) resource. //! This can be done via an extension trait that provides a conversion which the user needs to call manually to get channels for the backend. +//! - Manage the [`ClientState`] and [`ServerState`] states. //! - Update the [`RepliconServer`](replicon_server::RepliconServer) and [`RepliconClient`](replicon_client::RepliconClient) resources. //! - Spawn and despawn entities with [`ConnectedClient`](connected_client::ConnectedClient) component. //! - React on [`DisconnectRequest`] event. @@ -27,6 +28,42 @@ pub mod replicon_server; use bevy::prelude::*; +/// Connection state of the client. +/// +///
+/// +/// Should only be changed from the messaging backend when the client changes its state +/// in [`ClientSet::ReceivePackets`](crate::client::ClientSet::ReceivePackets). +/// +///
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)] +pub enum ClientState { + /// Not connected or trying to connect. + #[default] + Disconnected, + /// Trying to connect to the server. + Connecting, + /// Connected to the server. + Connected, +} + +/// Connection state of the server. +/// +///
+/// +/// Should only be changed from the messaging backend when the server changes its state +/// in [`ServerSet::ReceivePackets`](crate::server::ServerSet::ReceivePackets). +/// +///
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)] +pub enum ServerState { + /// Inactive. + #[default] + Stopped, + /// Accepting and handling client connections. + Running, +} + /// An event for the messaging backend to queue a disconnection /// for a specific client on the server. /// @@ -53,7 +90,6 @@ mod tests { let channels = RepliconChannels::default(); let mut client = RepliconClient::default(); client.setup_server_channels(channels.server_channels().len()); - client.set_status(RepliconClientStatus::Connected); const MESSAGES: &[&[u8]] = &[&[0], &[1]]; for &message in MESSAGES { @@ -62,7 +98,6 @@ mod tests { let mut server = RepliconServer::default(); server.setup_client_channels(channels.client_channels().len()); - server.set_running(true); for (channel_id, message) in client.drain_sent() { server.insert_received(Entity::PLACEHOLDER, channel_id, message); @@ -80,7 +115,6 @@ mod tests { let channels = RepliconChannels::default(); let mut server = RepliconServer::default(); server.setup_client_channels(channels.client_channels().len()); - server.set_running(true); const MESSAGES: &[&[u8]] = &[&[0], &[1]]; for &message in MESSAGES { @@ -89,7 +123,6 @@ mod tests { let mut client = RepliconClient::default(); client.setup_server_channels(channels.server_channels().len()); - client.set_status(RepliconClientStatus::Connected); for (_, channel_id, message) in server.drain_sent() { client.insert_received(channel_id, message); diff --git a/src/shared/backend/replicon_client.rs b/src/shared/backend/replicon_client.rs index d1f05625..e7e75b78 100644 --- a/src/shared/backend/replicon_client.rs +++ b/src/shared/backend/replicon_client.rs @@ -1,20 +1,12 @@ use bevy::prelude::*; use bytes::Bytes; -use log::{debug, trace, warn}; +use log::trace; use crate::prelude::*; /// Stores information about a client independent from the messaging backend. /// /// The messaging backend is responsible for updating this resource: -/// - When the messaging client changes its status, [`Self::set_status`] should be used to -/// reflect this change: -/// - [`RepliconClientStatus::Connecting`] and [`RepliconClientStatus::Connected`] should -/// be set in [`ClientSet::ReceivePackets`] to allow the client to process received -/// messages immediately. -/// - [`RepliconClientStatus::Disconnected`] should be set before [`ClientSet::Send`] -/// to allow the client to process -/// all received messages before disconnecting. /// - For receiving messages, [`Self::insert_received`] should be to used. /// A system to forward backend messages to Replicon should run in [`ClientSet::ReceivePackets`]. /// - For sending messages, [`Self::drain_sent`] should be used to drain all sent messages. @@ -24,9 +16,6 @@ use crate::prelude::*; /// Inserted as resource by [`ClientPlugin`]. #[derive(Resource, Default)] pub struct RepliconClient { - /// Client connection status. - status: RepliconClientStatus, - /// List of received messages for each channel. /// /// Top index is channel ID. @@ -65,11 +54,6 @@ impl RepliconClient { &mut self, channel_id: I, ) -> impl Iterator + '_ { - if !self.is_connected() { - // We can't return here because we need to return an empty iterator. - warn!("trying to receive a message when the client is not connected"); - } - let channel_id = channel_id.into(); let channel_messages = self .received_messages @@ -98,11 +82,6 @@ impl RepliconClient { /// /// pub fn send, B: Into>(&mut self, channel_id: I, message: B) { - if !self.is_connected() { - warn!("trying to send a message when the client is not connected"); - return; - } - let channel_id = channel_id.into(); let message: Bytes = message.into(); @@ -111,61 +90,13 @@ impl RepliconClient { self.sent_messages.push((channel_id, message)); } - /// Sets the client connection status. - /// - /// Discards all messages if the state changes from [`RepliconClientStatus::Connected`]. - /// See also [`Self::status`]. - /// - ///
- /// - /// Should only be called from the messaging backend when the client status changes. - /// - ///
- pub fn set_status(&mut self, status: RepliconClientStatus) { - debug!("changing status to `{status:?}`"); - - if self.is_connected() && status != RepliconClientStatus::Connected { - for channel_messages in &mut self.received_messages { - channel_messages.clear(); - } - self.sent_messages.clear(); - - self.stats = Default::default(); + pub(crate) fn clear(&mut self) { + for channel_messages in &mut self.received_messages { + channel_messages.clear(); } + self.sent_messages.clear(); - self.status = status; - } - - /// Returns the current client status. - /// - /// See also [`Self::set_status`]. - #[inline] - pub fn status(&self) -> RepliconClientStatus { - self.status - } - - /// Returns `true` if the client is disconnected. - /// - /// See also [`Self::status`]. - #[inline] - pub fn is_disconnected(&self) -> bool { - self.status == RepliconClientStatus::Disconnected - } - - /// Returns `true` if the client is connecting. - /// - /// See also [`Self::status`]. - #[inline] - pub fn is_connecting(&self) -> bool { - self.status == RepliconClientStatus::Connecting - } - - /// Returns `true` if the client is connected. - /// - /// See also [`Self::status`]. - #[inline] - pub fn is_connected(&self) -> bool { - self.status == RepliconClientStatus::Connected + self.stats = Default::default(); } /// Removes all sent messages, returning them as an iterator with channel. @@ -187,11 +118,6 @@ impl RepliconClient { /// /// pub fn insert_received, B: Into>(&mut self, channel_id: I, message: B) { - if !self.is_connected() { - warn!("trying to insert a received message when the client is not connected"); - return; - } - let channel_id = channel_id.into(); let channel_messages = self .received_messages @@ -217,52 +143,3 @@ impl RepliconClient { &mut self.stats } } - -/// Connection status of the [`RepliconClient`]. -#[derive(Clone, Copy, PartialEq, Debug, Default)] -pub enum RepliconClientStatus { - /// Not connected or trying to connect. - #[default] - Disconnected, - /// Trying to connect to the server. - Connecting, - /// Connected to the server. - Connected, -} - -#[cfg(test)] -mod tests { - use test_log::test; - - use super::*; - use crate::shared::backend::channels::{ClientChannel, ServerChannel}; - - #[test] - fn cleanup_on_disconnect() { - let channels = RepliconChannels::default(); - let mut client = RepliconClient::default(); - client.setup_server_channels(channels.server_channels().len()); - client.set_status(RepliconClientStatus::Connected); - - client.send(ClientChannel::MutationAcks, Vec::new()); - client.insert_received(ServerChannel::Mutations, Vec::new()); - - client.set_status(RepliconClientStatus::Disconnected); - - assert_eq!(client.drain_sent().count(), 0); - assert_eq!(client.receive(ServerChannel::Mutations).count(), 0); - } - - #[test] - fn disconnected() { - let channels = RepliconChannels::default(); - let mut client = RepliconClient::default(); - client.setup_server_channels(channels.server_channels().len()); - - client.send(ClientChannel::MutationAcks, Vec::new()); - client.insert_received(ServerChannel::Mutations, Vec::new()); - - assert_eq!(client.drain_sent().count(), 0); - assert_eq!(client.receive(ServerChannel::Mutations).count(), 0); - } -} diff --git a/src/shared/backend/replicon_server.rs b/src/shared/backend/replicon_server.rs index df19a91b..bc073a12 100644 --- a/src/shared/backend/replicon_server.rs +++ b/src/shared/backend/replicon_server.rs @@ -1,15 +1,10 @@ use bevy::prelude::*; use bytes::Bytes; -use log::{debug, trace, warn}; +use log::trace; /// Stores information about the server independent from the messaging backend. /// /// The messaging backend is responsible for updating this resource: -/// - When the server starts or stops, [`Self::set_running`] should be used to update its status: -/// - Set to `true` in [`ServerSet::ReceivePackets`](crate::server::ServerSet::ReceivePackets) to -/// allow immediate processing of received messages. -/// - Set to `false` before [`ServerSet::Send`](crate::server::ServerSet::Send) to ensure -/// the server stops immediately on the same frame. /// - For receiving messages, [`Self::insert_received`] should be used. /// A system to forward messages from the backend to Replicon should run in [`ServerSet::ReceivePackets`](crate::server::ServerSet::ReceivePackets). /// - For sending messages, [`Self::drain_sent`] should be used to drain all sent messages. @@ -18,11 +13,6 @@ use log::{debug, trace, warn}; /// Inserted as resource by [`ServerPlugin`](crate::server::ServerPlugin). #[derive(Resource, Default)] pub struct RepliconServer { - /// Indicates if the server is open for connections. - /// - /// By default set to `false`. - running: bool, - /// List of received messages for each channel. /// /// Top index is channel ID. @@ -54,11 +44,6 @@ impl RepliconServer { &mut self, channel_id: I, ) -> impl Iterator + '_ { - if !self.running { - // We can't return here because we need to return an empty iterator. - warn!("trying to receive a message when the server is not running"); - } - let channel_id = channel_id.into(); let channel_messages = self .received_messages @@ -92,11 +77,6 @@ impl RepliconServer { channel_id: I, message: B, ) { - if !self.running { - warn!("trying to send a message when the server is not running"); - return; - } - let channel_id = channel_id.into(); let message: Bytes = message.into(); @@ -105,32 +85,6 @@ impl RepliconServer { self.sent_messages.push((client, channel_id, message)); } - /// Marks the server as running or stopped. - /// - ///
- /// - /// Should only be called from the messaging backend when the server changes its state. - /// - ///
- pub fn set_running(&mut self, running: bool) { - debug!("changing running status to `{running}`"); - - if !running { - for receive_channel in &mut self.received_messages { - receive_channel.clear(); - } - self.sent_messages.clear(); - } - - self.running = running; - } - - /// Returns `true` if the server is running. - #[inline] - pub fn is_running(&self) -> bool { - self.running - } - /// Retains only the messages specified by the predicate. /// /// Used for testing. @@ -165,11 +119,6 @@ impl RepliconServer { channel_id: I, message: B, ) { - if !self.running { - warn!("trying to insert a received message when the server is not running"); - return; - } - let channel_id = channel_id.into(); let receive_channel = self .received_messages @@ -178,44 +127,11 @@ impl RepliconServer { receive_channel.push((client, message.into())); } -} - -#[cfg(test)] -mod tests { - use test_log::test; - - use super::*; - use crate::{ - prelude::*, - shared::backend::channels::{ClientChannel, ServerChannel}, - }; - - #[test] - fn cleanup_on_stop() { - let channels = RepliconChannels::default(); - let mut server = RepliconServer::default(); - server.setup_client_channels(channels.client_channels().len()); - server.set_running(true); - - server.send(Entity::PLACEHOLDER, ServerChannel::Mutations, Vec::new()); - server.insert_received(Entity::PLACEHOLDER, ClientChannel::MutationAcks, Vec::new()); - - server.set_running(false); - - assert_eq!(server.drain_sent().count(), 0); - assert_eq!(server.receive(ClientChannel::MutationAcks).count(), 0); - } - #[test] - fn inactive() { - let channels = RepliconChannels::default(); - let mut server = RepliconServer::default(); - server.setup_client_channels(channels.client_channels().len()); - - server.send(Entity::PLACEHOLDER, ServerChannel::Mutations, Vec::new()); - server.insert_received(Entity::PLACEHOLDER, ClientChannel::MutationAcks, Vec::new()); - - assert_eq!(server.drain_sent().count(), 0); - assert_eq!(server.receive(ClientChannel::MutationAcks).count(), 0); + pub(crate) fn clear(&mut self) { + for receive_channel in &mut self.received_messages { + receive_channel.clear(); + } + self.sent_messages.clear(); } } diff --git a/src/shared/common_conditions.rs b/src/shared/common_conditions.rs deleted file mode 100644 index 4bb152e8..00000000 --- a/src/shared/common_conditions.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! System conditions for [`RepliconClient`] and [`RepliconServer`] resources. - -use bevy::prelude::*; - -use crate::prelude::*; - -/// Returns `true` if the server is running. -pub fn server_running(server: Option>) -> bool { - server.is_some_and(|server| server.is_running()) -} - -/// Returns `true` if there is no client or if the existing client is disconnected. -/// -/// Can be used instead of the regular [`server_running`] to seamlessly support -/// singleplayer or listen-server mode (where server is also a player). -pub fn server_or_singleplayer(client: Option>) -> bool { - client.is_none_or(|client| client.is_disconnected()) -} - -/// Returns `true` when the client is connecting. -pub fn client_connecting(client: Option>) -> bool { - client.is_some_and(|client| client.is_connecting()) -} - -/// Returns `true` when the client is connected. -pub fn client_connected(client: Option>) -> bool { - client.is_some_and(|client| client.is_connected()) -} - -/// Returns `true` if the server stopped on this tick. -pub fn server_just_stopped( - mut last_running: Local, - server: Option>, -) -> bool { - let running = server.is_some_and(|server| server.is_running()); - - let just_stopped = *last_running && !running; - *last_running = running; - just_stopped -} - -/// Returns `true` if the server started on this tick. -pub fn server_just_started( - mut last_running: Local, - server: Option>, -) -> bool { - let running = server.is_some_and(|server| server.is_running()); - - let just_started = !*last_running && running; - *last_running = running; - just_started -} - -/// Returns `true` when the client just started connecting on this tick. -pub fn client_started_connecting( - mut last_connecting: Local, - client: Option>, -) -> bool { - let connecting = client.is_some_and(|client| client.is_connecting()); - - let started_connecting = !*last_connecting && connecting; - *last_connecting = connecting; - started_connecting -} - -/// Returns `true` when the client is connected on this tick. -pub fn client_just_connected( - mut last_connected: Local, - client: Option>, -) -> bool { - let connected = client.is_some_and(|client| client.is_connected()); - - let just_connected = !*last_connected && connected; - *last_connected = connected; - just_connected -} - -/// Returns `true` when the client is disconnected on this tick. -pub fn client_just_disconnected( - mut last_not_disconnected: Local, - client: Option>, -) -> bool { - let disconnected = client.is_some_and(|client| client.is_disconnected()); - - let just_disconnected = *last_not_disconnected && disconnected; - *last_not_disconnected = !disconnected; - just_disconnected -} diff --git a/src/shared/event/client_event.rs b/src/shared/event/client_event.rs index f410ae5c..9a4e683e 100644 --- a/src/shared/event/client_event.rs +++ b/src/shared/event/client_event.rs @@ -24,7 +24,7 @@ pub trait ClientEventAppExt { /// /// After emitting `E` event on the client, [`FromClient`] event will be emitted on the server. /// - /// If [`ServerEventPlugin`] is enabled and [`RepliconClient`] is inactive, the event will be drained + /// If [`ServerEventPlugin`] is enabled and the client state is [`ClientState::Disconnected`], the event will be drained /// right after sending and re-emitted locally as [`FromClient`] event with [`FromClient::client_id`] /// equal to [`ClientId::Server`]. /// diff --git a/src/shared/event/client_trigger.rs b/src/shared/event/client_trigger.rs index 6a9737ba..7e0ec9be 100644 --- a/src/shared/event/client_trigger.rs +++ b/src/shared/event/client_trigger.rs @@ -23,7 +23,7 @@ pub trait ClientTriggerAppExt { /// /// After triggering `E` event on the client, [`FromClient`] event will be triggered on the server. /// - /// If [`ServerEventPlugin`] is enabled and [`RepliconClient`] is inactive, the event will also be triggered + /// If [`ServerEventPlugin`] is enabled and the client state is [`ClientState::Disconnected`], the event will also be triggered /// locally as [`FromClient`] event with [`FromClient::client_id`] equal to [`ClientId::Server`]. /// /// See also the [corresponding section](../index.html#from-client-to-server) from the quick start guide. @@ -186,7 +186,7 @@ fn trigger_deserialize<'a, E>( /// /// See also [`ClientTriggerAppExt`]. pub trait ClientTriggerExt { - /// Like [`Commands::trigger`], but triggers [`FromClient`] on server and locally if [`RepliconClient`] is inactive. + /// Like [`Commands::trigger`], but triggers [`FromClient`] on server and locally if the client state is [`ClientState::Disconnected`]. fn client_trigger(&mut self, event: impl Event); /// Like [`Self::client_trigger`], but allows you to specify target entities, similar to [`Commands::trigger_targets`]. diff --git a/src/test_app.rs b/src/test_app.rs index 60f1a983..6a9da79c 100644 --- a/src/test_app.rs +++ b/src/test_app.rs @@ -84,19 +84,26 @@ pub trait ServerTestAppExt { impl ServerTestAppExt for App { fn connect_client(&mut self, client_app: &mut App) { - let mut server = self.world_mut().resource_mut::(); - server.set_running(true); + self.world_mut() + .resource_mut::>() + .set(ServerState::Running); + let client_entity = self .world_mut() .spawn(ConnectedClient { max_size: 1200 }) .id(); - let mut client = client_app.world_mut().resource_mut::(); - assert!( - client.is_disconnected(), + assert_eq!( + *client_app.world_mut().resource::>(), + ClientState::Disconnected, "client can't be connected multiple times" ); - client.set_status(RepliconClientStatus::Connected); + + client_app + .world_mut() + .resource_mut::>() + .set(ClientState::Connected); + client_app .world_mut() .insert_resource(TestClientEntity(client_entity)); @@ -110,8 +117,11 @@ impl ServerTestAppExt for App { } fn disconnect_client(&mut self, client_app: &mut App) { - let mut client = client_app.world_mut().resource_mut::(); - client.set_status(RepliconClientStatus::Disconnected); + client_app + .world_mut() + .resource_mut::>() + .set(ClientState::Disconnected); + let client_entity = *client_app .world_mut() .remove_resource::() diff --git a/tests/connection.rs b/tests/connection.rs index 3a486af3..50efeecc 100644 --- a/tests/connection.rs +++ b/tests/connection.rs @@ -61,8 +61,9 @@ fn server_start_stop() { server_app .world_mut() - .resource_mut::() - .set_running(false); + .resource_mut::>() + .set(ServerState::Stopped); + server_app.update(); assert_eq!(clients.iter(server_app.world()).len(), 0); From 8c49c0916c8aed3026c4f156ddfc71d30eb3aea1 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Sat, 6 Sep 2025 13:55:04 +0300 Subject: [PATCH 5/9] Move `NetworkStats` to `shared::backend` It's associated with both connected client on the server and the client itself. --- CHANGELOG.md | 1 + src/lib.rs | 4 ++-- src/shared/backend.rs | 25 ++++++++++++++++++++++ src/shared/backend/connected_client.rs | 29 +++----------------------- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3323b1cf..5c07ee70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rename `replication_rules` module into `rules`. - Rename `replication_registry` module into `registry`. - Rename `IntoComponentRule::register_component` to `IntoComponentRule::into_rule` and move it to the `shared::replication::rules::component` module. +- Move `NetworkStats` from `shared::backend::connected_client` to `shared::backend`. - Replace `IntoReplicationRule` with `IntoComponentRules`, which returns only `Vec`. - Replace `ReplicationBundle` with `BundleRules`, which returns only `Vec` and uses an associated constant to customize the priority. - Hide `ServerEntityMap` mutation methods from public API. diff --git a/src/lib.rs b/src/lib.rs index 603c830b..eeac74d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -709,9 +709,9 @@ pub mod prelude { shared::{ AuthMethod, RepliconSharedPlugin, backend::{ - ClientState, DisconnectRequest, ServerState, + ClientState, DisconnectRequest, NetworkStats, ServerState, channels::{Channel, RepliconChannels}, - connected_client::{ConnectedClient, NetworkStats}, + connected_client::ConnectedClient, replicon_client::RepliconClient, replicon_server::RepliconServer, }, diff --git a/src/shared/backend.rs b/src/shared/backend.rs index 4f9ff3ae..d83a48fc 100644 --- a/src/shared/backend.rs +++ b/src/shared/backend.rs @@ -75,6 +75,31 @@ pub struct DisconnectRequest { pub client: Entity, } +/// Statistic associated with [`RepliconClient`](replicon_client::RepliconClient) or +/// [`ConnectedClient`](connected_client::ConnectedClient). +/// +/// All values can be zero if not provided by the backend. +/// +///
+/// +/// Should only be modified from the messaging backend. +/// +///
+#[derive(Component, Debug, Clone, Copy, Default, Reflect)] +pub struct NetworkStats { + /// Round-time trip in seconds for the connection. + pub rtt: f64, + + /// Packet loss % for the connection. + pub packet_loss: f64, + + /// Bytes sent per second for the connection. + pub sent_bps: f64, + + /// Bytes received per second for the connection. + pub received_bps: f64, +} + #[cfg(test)] mod tests { use test_log::test; diff --git a/src/shared/backend/connected_client.rs b/src/shared/backend/connected_client.rs index 8afe400e..78b1f27c 100644 --- a/src/shared/backend/connected_client.rs +++ b/src/shared/backend/connected_client.rs @@ -6,6 +6,8 @@ use bevy::{ use log::error; use serde::{Deserialize, Serialize}; +use crate::prelude::*; + /// Marker for a connected client. /// /// Backends should spawn and despawn entities with this component on connect and disconnect @@ -24,7 +26,7 @@ use serde::{Deserialize, Serialize}; /// /// /// -/// See also [`AuthorizedClient`](crate::server::AuthorizedClient). +/// See also [`AuthorizedClient`]. #[derive(Component, Reflect)] #[require(Name::new("Connected client"), NetworkStats)] pub struct ConnectedClient { @@ -105,28 +107,3 @@ fn on_id_remove(mut world: DeferredWorld, ctx: HookContext) { let mut network_map = world.resource_mut::(); network_map.0.remove(&network_id); } - -/// Statistic associated with [`RepliconClient`](super::replicon_client::RepliconClient) or -/// [`ConnectedClient`]. -/// -/// All values can be zero if not provided by the backend. -/// -///
-/// -/// Should only be modified from the messaging backend. -/// -///
-#[derive(Component, Debug, Clone, Copy, Default, Reflect)] -pub struct NetworkStats { - /// Round-time trip in seconds for the connection. - pub rtt: f64, - - /// Packet loss % for the connection. - pub packet_loss: f64, - - /// Bytes sent per second for the connection. - pub sent_bps: f64, - - /// Bytes received per second for the connection. - pub received_bps: f64, -} From b6286ee118c6a83de87c2142279125b718cbd4f9 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Sat, 6 Sep 2025 14:13:33 +0300 Subject: [PATCH 6/9] Move `NetworkStats` from `RepliconClient` into a separate resource --- CHANGELOG.md | 2 +- src/client.rs | 9 +++++--- src/client/diagnostics.rs | 32 +++++++++++++++------------ src/shared/backend.rs | 8 ++++--- src/shared/backend/replicon_client.rs | 31 +++++--------------------- 5 files changed, 35 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c07ee70..481c8a55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rename `replication_rules` module into `rules`. - Rename `replication_registry` module into `registry`. - Rename `IntoComponentRule::register_component` to `IntoComponentRule::into_rule` and move it to the `shared::replication::rules::component` module. -- Move `NetworkStats` from `shared::backend::connected_client` to `shared::backend`. +- Move `NetworkStats` from `shared::backend::connected_client` to `shared::backend`. It's now a separate resource from `RepliconClient`. - Replace `IntoReplicationRule` with `IntoComponentRules`, which returns only `Vec`. - Replace `ReplicationBundle` with `BundleRules`, which returns only `Vec` and uses an associated constant to customize the priority. - Hide `ServerEntityMap` mutation methods from public API. diff --git a/src/client.rs b/src/client.rs index 93c0d0e3..6949d739 100644 --- a/src/client.rs +++ b/src/client.rs @@ -39,6 +39,7 @@ pub struct ClientPlugin; impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { app.init_resource::() + .init_resource::() .init_resource::() .init_resource::() .init_resource::() @@ -171,21 +172,23 @@ pub(super) fn receive_replication( fn reset( mut client: ResMut, + mut stats: ResMut, mut update_tick: ResMut, mut entity_map: ResMut, mut buffered_mutations: ResMut, mutate_ticks: Option>, - stats: Option>, + replication_stats: Option>, ) { client.clear(); + *stats = Default::default(); *update_tick = Default::default(); entity_map.clear(); buffered_mutations.clear(); if let Some(mut mutate_ticks) = mutate_ticks { mutate_ticks.clear(); } - if let Some(mut stats) = stats { - *stats = Default::default(); + if let Some(mut replication_stats) = replication_stats { + *replication_stats = Default::default(); } } diff --git a/src/client/diagnostics.rs b/src/client/diagnostics.rs index a865f499..e0eceb83 100644 --- a/src/client/diagnostics.rs +++ b/src/client/diagnostics.rs @@ -107,28 +107,32 @@ pub const DIAGNOSTIC_HISTORY_LEN: usize = 60; fn add_measurements( mut diagnostics: Diagnostics, - stats: Res, - mut last_stats: Local, - client: Res, + mut last_replication_stats: Local, + replication_stats: Res, + stats: Res, ) { - diagnostics.add_measurement(&RTT, || client.stats().rtt); - diagnostics.add_measurement(&PACKET_LOSS, || client.stats().packet_loss); - diagnostics.add_measurement(&SENT_BPS, || client.stats().sent_bps); - diagnostics.add_measurement(&RECEIVED_BPS, || client.stats().received_bps); + diagnostics.add_measurement(&RTT, || stats.rtt); + diagnostics.add_measurement(&PACKET_LOSS, || stats.packet_loss); + diagnostics.add_measurement(&SENT_BPS, || stats.sent_bps); + diagnostics.add_measurement(&RECEIVED_BPS, || stats.received_bps); diagnostics.add_measurement(&ENTITIES_CHANGED, || { - (stats.entities_changed - last_stats.entities_changed) as f64 + (replication_stats.entities_changed - last_replication_stats.entities_changed) as f64 }); diagnostics.add_measurement(&COMPONENTS_CHANGED, || { - (stats.components_changed - last_stats.components_changed) as f64 + (replication_stats.components_changed - last_replication_stats.components_changed) as f64 + }); + diagnostics.add_measurement(&MAPPINGS, || { + (replication_stats.mappings - last_replication_stats.mappings) as f64 + }); + diagnostics.add_measurement(&DESPAWNS, || { + (replication_stats.despawns - last_replication_stats.despawns) as f64 }); - diagnostics.add_measurement(&MAPPINGS, || (stats.mappings - last_stats.mappings) as f64); - diagnostics.add_measurement(&DESPAWNS, || (stats.despawns - last_stats.despawns) as f64); diagnostics.add_measurement(&REPLICATION_MESSAGES, || { - (stats.messages - last_stats.messages) as f64 + (replication_stats.messages - last_replication_stats.messages) as f64 }); diagnostics.add_measurement(&REPLICATION_BYTES, || { - (stats.bytes - last_stats.bytes) as f64 + (replication_stats.bytes - last_replication_stats.bytes) as f64 }); - *last_stats = *stats; + *last_replication_stats = *replication_stats; } diff --git a/src/shared/backend.rs b/src/shared/backend.rs index d83a48fc..68d706c6 100644 --- a/src/shared/backend.rs +++ b/src/shared/backend.rs @@ -8,6 +8,7 @@ //! - Update the [`RepliconServer`](replicon_server::RepliconServer) and [`RepliconClient`](replicon_client::RepliconClient) resources. //! - Spawn and despawn entities with [`ConnectedClient`](connected_client::ConnectedClient) component. //! - React on [`DisconnectRequest`] event. +//! - Optionally update statistic in [`NetworkStats`] resource and components. //! //! This way, integrations can be provided as separate crates without requiring us or crate authors to maintain them under a feature. //! See the documentation on types in this module for details. @@ -75,8 +76,9 @@ pub struct DisconnectRequest { pub client: Entity, } -/// Statistic associated with [`RepliconClient`](replicon_client::RepliconClient) or -/// [`ConnectedClient`](connected_client::ConnectedClient). +/// Statistic for the current client when used as a resource, +/// or for a connected client when used as a component +/// on connected entities on the server. /// /// All values can be zero if not provided by the backend. /// @@ -85,7 +87,7 @@ pub struct DisconnectRequest { /// Should only be modified from the messaging backend. /// /// -#[derive(Component, Debug, Clone, Copy, Default, Reflect)] +#[derive(Resource, Component, Debug, Clone, Copy, Default, Reflect)] pub struct NetworkStats { /// Round-time trip in seconds for the connection. pub rtt: f64, diff --git a/src/shared/backend/replicon_client.rs b/src/shared/backend/replicon_client.rs index e7e75b78..43824da1 100644 --- a/src/shared/backend/replicon_client.rs +++ b/src/shared/backend/replicon_client.rs @@ -2,18 +2,17 @@ use bevy::prelude::*; use bytes::Bytes; use log::trace; -use crate::prelude::*; - /// Stores information about a client independent from the messaging backend. /// /// The messaging backend is responsible for updating this resource: /// - For receiving messages, [`Self::insert_received`] should be to used. -/// A system to forward backend messages to Replicon should run in [`ClientSet::ReceivePackets`]. +/// A system to forward backend messages to Replicon should run in +/// [`ClientSet::ReceivePackets`](crate::prelude::ClientSet::ReceivePackets). /// - For sending messages, [`Self::drain_sent`] should be used to drain all sent messages. -/// A system to forward Replicon messages to the backend should run in [`ClientSet::SendPackets`]. -/// - Optionally update statistic using [`Self::stats_mut`]. +/// A system to forward Replicon messages to the backend should run in +/// [`ClientSet::SendPackets`](crate::prelude::ClientSet::SendPackets). /// -/// Inserted as resource by [`ClientPlugin`]. +/// Inserted as resource by [`ClientPlugin`](crate::prelude::ClientPlugin). #[derive(Resource, Default)] pub struct RepliconClient { /// List of received messages for each channel. @@ -24,8 +23,6 @@ pub struct RepliconClient { /// List of sent messages and their channels since the last tick. sent_messages: Vec<(usize, Bytes)>, - - stats: NetworkStats, } impl RepliconClient { @@ -95,8 +92,6 @@ impl RepliconClient { channel_messages.clear(); } self.sent_messages.clear(); - - self.stats = Default::default(); } /// Removes all sent messages, returning them as an iterator with channel. @@ -126,20 +121,4 @@ impl RepliconClient { channel_messages.push(message.into()); } - - /// Returns network statistic. - pub fn stats(&self) -> &NetworkStats { - &self.stats - } - - /// Returns a mutable reference to set network statistic. - /// - ///
- /// - /// Should only be called from the messaging backend. - /// - ///
- pub fn stats_mut(&mut self) -> &mut NetworkStats { - &mut self.stats - } } From b069f991c3f231b869bfa97a1f3f8bba5861770c Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Sat, 6 Sep 2025 14:19:24 +0300 Subject: [PATCH 7/9] Rename `NetworkStats` into `ClientStats` Makes it more clear that it's only for clients. --- CHANGELOG.md | 2 +- src/client.rs | 4 ++-- src/client/diagnostics.rs | 2 +- src/lib.rs | 4 ++-- src/shared.rs | 2 +- src/shared/backend.rs | 4 ++-- src/shared/backend/connected_client.rs | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 481c8a55..82b6f9cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rename `replication_rules` module into `rules`. - Rename `replication_registry` module into `registry`. - Rename `IntoComponentRule::register_component` to `IntoComponentRule::into_rule` and move it to the `shared::replication::rules::component` module. -- Move `NetworkStats` from `shared::backend::connected_client` to `shared::backend`. It's now a separate resource from `RepliconClient`. +- Rename `NetworkStats` to `ClientStats` and move it from `shared::backend::connected_client` to `shared::backend`. It's also now a separate resource from `RepliconClient`. - Replace `IntoReplicationRule` with `IntoComponentRules`, which returns only `Vec`. - Replace `ReplicationBundle` with `BundleRules`, which returns only `Vec` and uses an associated constant to customize the priority. - Hide `ServerEntityMap` mutation methods from public API. diff --git a/src/client.rs b/src/client.rs index 6949d739..6e908328 100644 --- a/src/client.rs +++ b/src/client.rs @@ -39,7 +39,7 @@ pub struct ClientPlugin; impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { app.init_resource::() - .init_resource::() + .init_resource::() .init_resource::() .init_resource::() .init_resource::() @@ -172,7 +172,7 @@ pub(super) fn receive_replication( fn reset( mut client: ResMut, - mut stats: ResMut, + mut stats: ResMut, mut update_tick: ResMut, mut entity_map: ResMut, mut buffered_mutations: ResMut, diff --git a/src/client/diagnostics.rs b/src/client/diagnostics.rs index e0eceb83..488c7912 100644 --- a/src/client/diagnostics.rs +++ b/src/client/diagnostics.rs @@ -109,7 +109,7 @@ fn add_measurements( mut diagnostics: Diagnostics, mut last_replication_stats: Local, replication_stats: Res, - stats: Res, + stats: Res, ) { diagnostics.add_measurement(&RTT, || stats.rtt); diagnostics.add_measurement(&PACKET_LOSS, || stats.packet_loss); diff --git a/src/lib.rs b/src/lib.rs index eeac74d6..a9b7cb01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,7 +39,7 @@ This part is specific to your messaging backend. For `bevy_replicon_renet`, see [this section](https://docs.rs/bevy_replicon_renet#server-and-client-creation). On server connected clients represented as entities with [`ConnectedClient`] component. -Their data represented as components, such as [`NetworkStats`]. Users can also attach their +Their data represented as components, such as [`ClientStats`]. Users can also attach their own metadata to them or even replicate these entiteis back to clients. These entities are automatically spawned and despawned by the messaging backend. You can @@ -709,7 +709,7 @@ pub mod prelude { shared::{ AuthMethod, RepliconSharedPlugin, backend::{ - ClientState, DisconnectRequest, NetworkStats, ServerState, + ClientState, ClientStats, DisconnectRequest, ServerState, channels::{Channel, RepliconChannels}, connected_client::ConnectedClient, replicon_client::RepliconClient, diff --git a/src/shared.rs b/src/shared.rs index ad85066b..a163ed32 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -133,7 +133,7 @@ impl Plugin for RepliconSharedPlugin { app.register_type::() .register_type::() .register_type::() - .register_type::() + .register_type::() .init_state::() .init_state::() .init_resource::() diff --git a/src/shared/backend.rs b/src/shared/backend.rs index 68d706c6..901cec97 100644 --- a/src/shared/backend.rs +++ b/src/shared/backend.rs @@ -8,7 +8,7 @@ //! - Update the [`RepliconServer`](replicon_server::RepliconServer) and [`RepliconClient`](replicon_client::RepliconClient) resources. //! - Spawn and despawn entities with [`ConnectedClient`](connected_client::ConnectedClient) component. //! - React on [`DisconnectRequest`] event. -//! - Optionally update statistic in [`NetworkStats`] resource and components. +//! - Optionally update statistic in [`ClientStats`] resource and components. //! //! This way, integrations can be provided as separate crates without requiring us or crate authors to maintain them under a feature. //! See the documentation on types in this module for details. @@ -88,7 +88,7 @@ pub struct DisconnectRequest { /// /// #[derive(Resource, Component, Debug, Clone, Copy, Default, Reflect)] -pub struct NetworkStats { +pub struct ClientStats { /// Round-time trip in seconds for the connection. pub rtt: f64, diff --git a/src/shared/backend/connected_client.rs b/src/shared/backend/connected_client.rs index 78b1f27c..b6a789de 100644 --- a/src/shared/backend/connected_client.rs +++ b/src/shared/backend/connected_client.rs @@ -11,7 +11,7 @@ use crate::prelude::*; /// Marker for a connected client. /// /// Backends should spawn and despawn entities with this component on connect and disconnect -/// and optionally update the [`NetworkStats`] component. +/// and optionally update the [`ClientStats`] component. /// /// If the MTU of the connected client is dynamic, it's required for the backend to update /// [`Self::max_size`] to ensure message splitting works properly. @@ -28,7 +28,7 @@ use crate::prelude::*; /// /// See also [`AuthorizedClient`]. #[derive(Component, Reflect)] -#[require(Name::new("Connected client"), NetworkStats)] +#[require(Name::new("Connected client"), ClientStats)] pub struct ConnectedClient { /// Maximum size of a message that can be transferred over unreliable channel without /// splitting into multiple packets. From f5181375d61753bea37b4dbe77fa9d1b55e5e625 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Sat, 6 Sep 2025 14:54:41 +0300 Subject: [PATCH 8/9] Rename `RepliconClient` into `ClientMessages` Because it holds only messages now. --- CHANGELOG.md | 1 + bevy_replicon_example_backend/src/client.rs | 8 +++--- src/client.rs | 28 +++++++++---------- src/client/event.rs | 8 +++--- src/lib.rs | 2 +- src/shared/backend.rs | 20 ++++++------- ...{replicon_client.rs => client_messages.rs} | 12 ++++---- src/shared/event/client_event.rs | 10 +++---- src/shared/event/server_event.rs | 10 +++---- src/test_app.rs | 6 ++-- tests/mutations.rs | 4 +-- 11 files changed, 54 insertions(+), 55 deletions(-) rename src/shared/backend/{replicon_client.rs => client_messages.rs} (89%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b6f9cd..bd5f0a42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rename `replication_registry` module into `registry`. - Rename `IntoComponentRule::register_component` to `IntoComponentRule::into_rule` and move it to the `shared::replication::rules::component` module. - Rename `NetworkStats` to `ClientStats` and move it from `shared::backend::connected_client` to `shared::backend`. It's also now a separate resource from `RepliconClient`. +- Rename `RepliconClient` into `ClientMessages`. It holds only messages now. - Replace `IntoReplicationRule` with `IntoComponentRules`, which returns only `Vec`. - Replace `ReplicationBundle` with `BundleRules`, which returns only `Vec` and uses an associated constant to customize the priority. - Hide `ServerEntityMap` mutation methods from public API. diff --git a/bevy_replicon_example_backend/src/client.rs b/bevy_replicon_example_backend/src/client.rs index 87bc86a1..e9ececdf 100644 --- a/bevy_replicon_example_backend/src/client.rs +++ b/bevy_replicon_example_backend/src/client.rs @@ -50,7 +50,7 @@ fn set_disconnected(mut state: ResMut>) { fn receive_packets( mut commands: Commands, mut client: ResMut, - mut replicon_client: ResMut, + mut messages: ResMut, config: Option>, ) { let now = Instant::now(); @@ -77,16 +77,16 @@ fn receive_packets( } while let Some((channel_id, message)) = client.conditioner.pop(now) { - replicon_client.insert_received(channel_id, message); + messages.insert_received(channel_id, message); } } fn send_packets( mut commands: Commands, mut client: ResMut, - mut replicon_client: ResMut, + mut messages: ResMut, ) { - for (channel_id, message) in replicon_client.drain_sent() { + for (channel_id, message) in messages.drain_sent() { if let Err(e) = tcp::send_message(&mut client.stream, channel_id, &message) { error!("disconnecting due message write error: {e}"); commands.remove_resource::(); diff --git a/src/client.rs b/src/client.rs index 6e908328..a82e7719 100644 --- a/src/client.rs +++ b/src/client.rs @@ -38,7 +38,7 @@ pub struct ClientPlugin; impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { - app.init_resource::() + app.init_resource::() .init_resource::() .init_resource::() .init_resource::() @@ -98,9 +98,9 @@ impl Plugin for ClientPlugin { } app.world_mut() - .resource_scope(|world, mut client: Mut| { + .resource_scope(|world, mut messages: Mut| { let channels = world.resource::(); - client.setup_server_channels(channels.server_channels().len()); + messages.setup_server_channels(channels.server_channels().len()); }); } } @@ -126,7 +126,7 @@ pub(super) fn receive_replication( mut changes: Local, mut entity_markers: Local, ) { - world.resource_scope(|world, mut client: Mut| { + world.resource_scope(|world, mut messages: Mut| { world.resource_scope(|world, mut entity_map: Mut| { world.resource_scope(|world, mut buffered_mutations: Mut| { world.resource_scope(|world, command_markers: Mut| { @@ -151,7 +151,7 @@ pub(super) fn receive_replication( apply_replication( world, &mut params, - &mut client, + &mut messages, &mut buffered_mutations, ); @@ -171,7 +171,7 @@ pub(super) fn receive_replication( } fn reset( - mut client: ResMut, + mut messages: ResMut, mut stats: ResMut, mut update_tick: ResMut, mut entity_map: ResMut, @@ -179,7 +179,7 @@ fn reset( mutate_ticks: Option>, replication_stats: Option>, ) { - client.clear(); + messages.clear(); *stats = Default::default(); *update_tick = Default::default(); entity_map.clear(); @@ -209,10 +209,10 @@ fn log_protocol_error(_trigger: Trigger) { fn apply_replication( world: &mut World, params: &mut ReceiveParams, - client: &mut RepliconClient, + messages: &mut ClientMessages, buffered_mutations: &mut BufferedMutations, ) { - for mut message in client.receive(ServerChannel::Updates) { + for mut message in messages.receive(ServerChannel::Updates) { if let Err(e) = apply_update_message(world, params, &mut message) { error!("unable to apply update message: {e}"); } @@ -225,15 +225,15 @@ fn apply_replication( // (unless user requested history via marker). let update_tick = *world.resource::(); let acks_size = - MutateIndex::POSTCARD_MAX_SIZE * client.received_count(ServerChannel::Mutations); + MutateIndex::POSTCARD_MAX_SIZE * messages.received_count(ServerChannel::Mutations); if acks_size != 0 { let mut acks = Vec::with_capacity(acks_size); - for message in client.receive(ServerChannel::Mutations) { + for message in messages.receive(ServerChannel::Mutations) { if let Err(e) = buffer_mutate_message(params, buffered_mutations, message, &mut acks) { error!("unable to buffer mutate message: {e}"); } } - client.send(ClientChannel::MutationAcks, acks); + messages.send(ClientChannel::MutationAcks, acks); } apply_mutate_messages(world, params, buffered_mutations, update_tick); @@ -760,7 +760,7 @@ pub enum ClientSet { /// /// Runs in [`PreUpdate`]. ReceivePackets, - /// Systems that receive data from [`RepliconClient`]. + /// Systems that read data from [`ClientMessages`]. /// /// Runs in [`PreUpdate`] and [`OnEnter`] for [`ClientState::Connected`] (to avoid 1 frame delay). Receive, @@ -772,7 +772,7 @@ pub enum ClientSet { /// /// Runs in [`OnEnter`] for [`ClientState::Connected`]. SendHash, - /// Systems that send data to [`RepliconClient`]. + /// Systems that write data to [`ClientMessages`]. /// /// Runs in [`PostUpdate`]. Send, diff --git a/src/client/event.rs b/src/client/event.rs index b6c68fcf..6b8b8c4b 100644 --- a/src/client/event.rs +++ b/src/client/event.rs @@ -165,7 +165,7 @@ impl Plugin for ClientEventPlugin { fn send( events: FilteredResources, mut readers: FilteredResourcesMut, - mut client: ResMut, + mut messages: ResMut, type_registry: Res, entity_map: Res, event_registry: Res, @@ -186,7 +186,7 @@ fn send( // SAFETY: passed pointers were obtained using this event data. unsafe { - event.send(&mut ctx, &events, reader.into_inner(), &mut client); + event.send(&mut ctx, &events, reader.into_inner(), &mut messages); } } } @@ -194,7 +194,7 @@ fn send( fn receive( mut events: FilteredResourcesMut, mut queues: FilteredResourcesMut, - mut client: ResMut, + mut messages: ResMut, type_registry: Res, entity_map: Res, event_registry: Res, @@ -220,7 +220,7 @@ fn receive( &mut ctx, events.into_inner(), queue.into_inner(), - &mut client, + &mut messages, **update_tick, ) }; diff --git a/src/lib.rs b/src/lib.rs index a9b7cb01..3eca161c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -711,8 +711,8 @@ pub mod prelude { backend::{ ClientState, ClientStats, DisconnectRequest, ServerState, channels::{Channel, RepliconChannels}, + client_messages::ClientMessages, connected_client::ConnectedClient, - replicon_client::RepliconClient, replicon_server::RepliconServer, }, client_id::ClientId, diff --git a/src/shared/backend.rs b/src/shared/backend.rs index 901cec97..8685c1c6 100644 --- a/src/shared/backend.rs +++ b/src/shared/backend.rs @@ -5,7 +5,7 @@ //! - Create channels defined in the [`RepliconChannels`](channels::RepliconChannels) resource. //! This can be done via an extension trait that provides a conversion which the user needs to call manually to get channels for the backend. //! - Manage the [`ClientState`] and [`ServerState`] states. -//! - Update the [`RepliconServer`](replicon_server::RepliconServer) and [`RepliconClient`](replicon_client::RepliconClient) resources. +//! - Update the [`RepliconServer`](replicon_server::RepliconServer) and [`ClientMessages`](client_messages::ClientMessages) resources. //! - Spawn and despawn entities with [`ConnectedClient`](connected_client::ConnectedClient) component. //! - React on [`DisconnectRequest`] event. //! - Optionally update statistic in [`ClientStats`] resource and components. @@ -23,8 +23,8 @@ //! which we maintain. pub mod channels; +pub mod client_messages; pub mod connected_client; -pub mod replicon_client; pub mod replicon_server; use bevy::prelude::*; @@ -115,18 +115,18 @@ mod tests { #[test] fn client_to_server() { let channels = RepliconChannels::default(); - let mut client = RepliconClient::default(); - client.setup_server_channels(channels.server_channels().len()); + let mut client_messages = ClientMessages::default(); + client_messages.setup_server_channels(channels.server_channels().len()); const MESSAGES: &[&[u8]] = &[&[0], &[1]]; for &message in MESSAGES { - client.send(ClientChannel::MutationAcks, message); + client_messages.send(ClientChannel::MutationAcks, message); } let mut server = RepliconServer::default(); server.setup_client_channels(channels.client_channels().len()); - for (channel_id, message) in client.drain_sent() { + for (channel_id, message) in client_messages.drain_sent() { server.insert_received(Entity::PLACEHOLDER, channel_id, message); } @@ -148,14 +148,14 @@ mod tests { server.send(Entity::PLACEHOLDER, ServerChannel::Mutations, message); } - let mut client = RepliconClient::default(); - client.setup_server_channels(channels.server_channels().len()); + let mut client_messages = ClientMessages::default(); + client_messages.setup_server_channels(channels.server_channels().len()); for (_, channel_id, message) in server.drain_sent() { - client.insert_received(channel_id, message); + client_messages.insert_received(channel_id, message); } - let messages: Vec<_> = client.receive(ServerChannel::Mutations).collect(); + let messages: Vec<_> = client_messages.receive(ServerChannel::Mutations).collect(); assert_eq!(messages, MESSAGES); } } diff --git a/src/shared/backend/replicon_client.rs b/src/shared/backend/client_messages.rs similarity index 89% rename from src/shared/backend/replicon_client.rs rename to src/shared/backend/client_messages.rs index 43824da1..2c36763a 100644 --- a/src/shared/backend/replicon_client.rs +++ b/src/shared/backend/client_messages.rs @@ -2,19 +2,17 @@ use bevy::prelude::*; use bytes::Bytes; use log::trace; -/// Stores information about a client independent from the messaging backend. +/// Sent and received messages for exchange between Replicon and the messaging backend. /// /// The messaging backend is responsible for updating this resource: -/// - For receiving messages, [`Self::insert_received`] should be to used. -/// A system to forward backend messages to Replicon should run in +/// - Received messages should be forwarded to Replicon via [`Self::insert_received`] in /// [`ClientSet::ReceivePackets`](crate::prelude::ClientSet::ReceivePackets). -/// - For sending messages, [`Self::drain_sent`] should be used to drain all sent messages. -/// A system to forward Replicon messages to the backend should run in +/// - Replicon messages needs to be forwarded to the backend via [`Self::drain_sent`] in /// [`ClientSet::SendPackets`](crate::prelude::ClientSet::SendPackets). /// /// Inserted as resource by [`ClientPlugin`](crate::prelude::ClientPlugin). #[derive(Resource, Default)] -pub struct RepliconClient { +pub struct ClientMessages { /// List of received messages for each channel. /// /// Top index is channel ID. @@ -25,7 +23,7 @@ pub struct RepliconClient { sent_messages: Vec<(usize, Bytes)>, } -impl RepliconClient { +impl ClientMessages { /// Changes the size of the receive messages storage according to the number of server channels. pub(crate) fn setup_server_channels(&mut self, channels_count: usize) { self.received_messages.resize(channels_count, Vec::new()); diff --git a/src/shared/event/client_event.rs b/src/shared/event/client_event.rs index 9a4e683e..c61d7e45 100644 --- a/src/shared/event/client_event.rs +++ b/src/shared/event/client_event.rs @@ -229,9 +229,9 @@ impl ClientEvent { ctx: &mut ClientSendCtx, events: &Ptr, reader: PtrMut, - client: &mut RepliconClient, + messages: &mut ClientMessages, ) { - unsafe { (self.send)(self, ctx, events, reader, client) }; + unsafe { (self.send)(self, ctx, events, reader, messages) }; } /// Typed version of [`Self::send`]. @@ -245,7 +245,7 @@ impl ClientEvent { ctx: &mut ClientSendCtx, events: &Ptr, reader: PtrMut, - client: &mut RepliconClient, + messages: &mut ClientMessages, ) { let reader: &mut ClientEventReader = unsafe { reader.deref_mut() }; let events = unsafe { events.deref() }; @@ -260,7 +260,7 @@ impl ClientEvent { } debug!("sending event `{}`", any::type_name::()); - client.send(self.channel_id, message); + messages.send(self.channel_id, message); } } @@ -418,7 +418,7 @@ impl ClientEvent { } /// Signature of client event sending functions. -type SendFn = unsafe fn(&ClientEvent, &mut ClientSendCtx, &Ptr, PtrMut, &mut RepliconClient); +type SendFn = unsafe fn(&ClientEvent, &mut ClientSendCtx, &Ptr, PtrMut, &mut ClientMessages); /// Signature of client event receiving functions. type ReceiveFn = unsafe fn(&ClientEvent, &mut ServerReceiveCtx, PtrMut, &mut RepliconServer); diff --git a/src/shared/event/server_event.rs b/src/shared/event/server_event.rs index 8ebc4ae3..68b661ab 100644 --- a/src/shared/event/server_event.rs +++ b/src/shared/event/server_event.rs @@ -436,10 +436,10 @@ impl ServerEvent { ctx: &mut ClientReceiveCtx, events: PtrMut, queue: PtrMut, - client: &mut RepliconClient, + messages: &mut ClientMessages, update_tick: RepliconTick, ) { - unsafe { (self.receive)(self, ctx, events, queue, client, update_tick) } + unsafe { (self.receive)(self, ctx, events, queue, messages, update_tick) } } /// Typed version of [`ServerEvent::receive`]. @@ -453,7 +453,7 @@ impl ServerEvent { ctx: &mut ClientReceiveCtx, events: PtrMut, queue: PtrMut, - client: &mut RepliconClient, + messages: &mut ClientMessages, update_tick: RepliconTick, ) { let events: &mut Events = unsafe { events.deref_mut() }; @@ -477,7 +477,7 @@ impl ServerEvent { } } - for mut message in client.receive(self.channel_id) { + for mut message in messages.receive(self.channel_id) { if !self.independent { let tick = match postcard_utils::from_buf(&mut message) { Ok(tick) => tick, @@ -644,7 +644,7 @@ type ReceiveFn = unsafe fn( &mut ClientReceiveCtx, PtrMut, PtrMut, - &mut RepliconClient, + &mut ClientMessages, RepliconTick, ); diff --git a/src/test_app.rs b/src/test_app.rs index 6a9da79c..b4c130fc 100644 --- a/src/test_app.rs +++ b/src/test_app.rs @@ -135,16 +135,16 @@ impl ServerTestAppExt for App { fn exchange_with_client(&mut self, client_app: &mut App) { let client_entity = **client_app.world().resource::(); - let mut client = client_app.world_mut().resource_mut::(); + let mut client_messages = client_app.world_mut().resource_mut::(); let mut server = self.world_mut().resource_mut::(); - for (channel_id, message) in client.drain_sent() { + for (channel_id, message) in client_messages.drain_sent() { server.insert_received(client_entity, channel_id, message) } server.retain_sent(|(entity, channel_id, message)| { if *entity == client_entity { - client.insert_received(*channel_id, message.clone()); + client_messages.insert_received(*channel_id, message.clone()); false } else { true diff --git a/tests/mutations.rs b/tests/mutations.rs index a5812885..740a6dcd 100644 --- a/tests/mutations.rs +++ b/tests/mutations.rs @@ -1061,8 +1061,8 @@ fn acknowledgment() { let tick1 = component.last_changed(); // Take and drop ack message. - let mut client = client_app.world_mut().resource_mut::(); - assert_eq!(client.drain_sent().count(), 1); + let mut messages = client_app.world_mut().resource_mut::(); + assert_eq!(messages.drain_sent().count(), 1); server_app.update(); server_app.exchange_with_client(&mut client_app); From dc5c38669fa16b2a03a66c35b531dd48dbe8c286 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Sat, 6 Sep 2025 15:10:18 +0300 Subject: [PATCH 9/9] Rename `RepliconServer` into `ServerMessages` Because it holds only messages now. --- CHANGELOG.md | 1 + bevy_replicon_example_backend/src/server.rs | 8 ++--- src/lib.rs | 2 +- src/server.rs | 32 +++++++++---------- src/server/event.rs | 12 +++---- src/server/replication_messages/mutations.rs | 8 ++--- src/server/replication_messages/updates.rs | 4 +-- src/shared/backend.rs | 20 ++++++------ ...{replicon_server.rs => server_messages.rs} | 14 ++++---- src/shared/event/client_event.rs | 10 +++--- src/shared/event/server_event.rs | 32 ++++++++++--------- src/test_app.rs | 6 ++-- tests/priority.rs | 4 +-- 13 files changed, 78 insertions(+), 75 deletions(-) rename src/shared/backend/{replicon_server.rs => server_messages.rs} (87%) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd5f0a42..a8fbd383 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rename `IntoComponentRule::register_component` to `IntoComponentRule::into_rule` and move it to the `shared::replication::rules::component` module. - Rename `NetworkStats` to `ClientStats` and move it from `shared::backend::connected_client` to `shared::backend`. It's also now a separate resource from `RepliconClient`. - Rename `RepliconClient` into `ClientMessages`. It holds only messages now. +- Rename `RepliconServer` into `ServerMessages`. It holds only messages now. - Replace `IntoReplicationRule` with `IntoComponentRules`, which returns only `Vec`. - Replace `ReplicationBundle` with `BundleRules`, which returns only `Vec` and uses an associated constant to customize the priority. - Hide `ServerEntityMap` mutation methods from public API. diff --git a/bevy_replicon_example_backend/src/server.rs b/bevy_replicon_example_backend/src/server.rs index 98a3bc3b..5ac9f922 100644 --- a/bevy_replicon_example_backend/src/server.rs +++ b/bevy_replicon_example_backend/src/server.rs @@ -49,8 +49,8 @@ fn set_stopped(mut state: ResMut>) { fn receive_packets( mut commands: Commands, + mut messages: ResMut, server: Res, - mut replicon_server: ResMut, mut clients: Query<(Entity, &mut ExampleConnection, Option<&ConditionerConfig>)>, global_config: Option>, ) { @@ -116,7 +116,7 @@ fn receive_packets( } while let Some((channel_id, message)) = connection.conditioner.pop(now) { - replicon_server.insert_received(client, channel_id, message) + messages.insert_received(client, channel_id, message) } } } @@ -124,10 +124,10 @@ fn receive_packets( fn send_packets( mut commands: Commands, mut disconnect_events: EventReader, - mut replicon_server: ResMut, + mut messages: ResMut, mut clients: Query<&mut ExampleConnection>, ) { - for (client, channel_id, message) in replicon_server.drain_sent() { + for (client, channel_id, message) in messages.drain_sent() { let mut connection = clients .get_mut(client) .expect("all connected clients should have streams"); diff --git a/src/lib.rs b/src/lib.rs index 3eca161c..addec053 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -713,7 +713,7 @@ pub mod prelude { channels::{Channel, RepliconChannels}, client_messages::ClientMessages, connected_client::ConnectedClient, - replicon_server::RepliconServer, + server_messages::ServerMessages, }, client_id::ClientId, event::{ diff --git a/src/server.rs b/src/server.rs index 74863bc2..8a12bb82 100644 --- a/src/server.rs +++ b/src/server.rs @@ -81,7 +81,7 @@ impl Plugin for ServerPlugin { fn build(&self, app: &mut App) { app.init_resource::() .init_resource::() - .init_resource::() + .init_resource::() .init_resource::() .init_resource::() .init_resource::() @@ -171,9 +171,9 @@ impl Plugin for ServerPlugin { fn finish(&self, app: &mut App) { app.world_mut() - .resource_scope(|world, mut server: Mut| { + .resource_scope(|world, mut messages: Mut| { let channels = world.resource::(); - server.setup_client_channels(channels.client_channels().len()); + messages.setup_client_channels(channels.client_channels().len()); }); } } @@ -194,10 +194,10 @@ fn handle_connects( fn handle_disconnects( trigger: Trigger, - mut server: ResMut, + mut messages: ResMut, ) { debug!("client `{}` disconnected", trigger.target()); - server.remove_client(trigger.target()); + messages.remove_client(trigger.target()); } fn check_protocol( @@ -242,11 +242,11 @@ fn cleanup_acks( fn receive_acks( change_tick: SystemChangeTick, - mut server: ResMut, + mut messages: ResMut, mut clients: Query<&mut ClientTicks>, mut entity_buffer: ResMut, ) { - for (client, mut message) in server.receive(ClientChannel::MutationAcks) { + for (client, mut message) in messages.receive(ClientChannel::MutationAcks) { while message.has_remaining() { match postcard_utils::from_buf(&mut message) { Ok(mutate_index) => { @@ -316,7 +316,7 @@ fn send_replication( mut removal_buffer: ResMut, mut entity_buffer: ResMut, mut despawn_buffer: ResMut, - mut server: ResMut, + mut messages: ResMut, track_mutate_messages: Res, registry: Res, type_registry: Res, @@ -349,7 +349,7 @@ fn send_replication( send_messages( &mut clients, - &mut server, + &mut messages, **server_tick, **track_mutate_messages, &mut serialized, @@ -364,13 +364,13 @@ fn send_replication( fn reset( mut commands: Commands, - mut server: ResMut, + mut messages: ResMut, mut server_tick: ResMut, mut related_entities: ResMut, clients: Query>, mut buffered_events: ResMut, ) { - server.clear(); + messages.clear(); *server_tick = Default::default(); buffered_events.clear(); related_entities.clear(); @@ -391,7 +391,7 @@ fn send_messages( &mut PriorityMap, &mut ClientVisibility, )>, - server: &mut RepliconServer, + messages: &mut ServerMessages, server_tick: RepliconTick, track_mutate_messages: bool, serialized: &mut SerializedData, @@ -407,7 +407,7 @@ fn send_messages( let server_tick_range = write_tick_cached(&mut server_tick_range, serialized, server_tick)?; - updates.send(server, client_entity, serialized, server_tick_range)?; + updates.send(messages, client_entity, serialized, server_tick_range)?; } if !mutations.is_empty() || track_mutate_messages { @@ -415,7 +415,7 @@ fn send_messages( write_tick_cached(&mut server_tick_range, serialized, server_tick)?; mutations.send( - server, + messages, client_entity, &mut ticks, entity_buffer, @@ -798,7 +798,7 @@ pub enum ServerSet { /// /// Runs in [`PreUpdate`]. ReceivePackets, - /// Systems that receive data from [`RepliconServer`]. + /// Systems that read data from [`ServerMessages`]. /// /// Runs in [`PreUpdate`]. Receive, @@ -809,7 +809,7 @@ pub enum ServerSet { /// /// Runs in [`OnEnter`] for [`ServerState::Running`]. ReadRelations, - /// Systems that send data to [`RepliconServer`]. + /// Systems that write data to [`ServerMessages`]. /// /// Runs in [`PostUpdate`] on server tick, see [`TickPolicy`]. Send, diff --git a/src/server/event.rs b/src/server/event.rs index 353f49af..5fb83402 100644 --- a/src/server/event.rs +++ b/src/server/event.rs @@ -117,7 +117,7 @@ impl Plugin for ServerEventPlugin { fn send_or_buffer( server_events: FilteredResources, - mut server: ResMut, + mut messages: ResMut, mut buffered_events: ResMut, type_registry: Res, event_registry: Res, @@ -138,7 +138,7 @@ fn send_or_buffer( event.send_or_buffer( &mut ctx, &server_events, - &mut server, + &mut messages, &clients, &mut buffered_events, ); @@ -147,18 +147,18 @@ fn send_or_buffer( } fn send_buffered( - mut server: ResMut, + mut messages: ResMut, mut buffered_events: ResMut, clients: Query<(Entity, Option<&ClientTicks>), With>, ) { buffered_events - .send_all(&mut server, &clients) + .send_all(&mut messages, &clients) .expect("buffered server events should send"); } fn receive( mut client_events: FilteredResourcesMut, - mut server: ResMut, + mut messages: ResMut, type_registry: Res, event_registry: Res, ) { @@ -172,7 +172,7 @@ fn receive( .expect("client events resource should be accessible"); // SAFETY: passed pointer was obtained using this event data. - unsafe { event.receive(&mut ctx, client_events.into_inner(), &mut server) }; + unsafe { event.receive(&mut ctx, client_events.into_inner(), &mut messages) }; } } diff --git a/src/server/replication_messages/mutations.rs b/src/server/replication_messages/mutations.rs index c5d3df92..7cc8946e 100644 --- a/src/server/replication_messages/mutations.rs +++ b/src/server/replication_messages/mutations.rs @@ -154,7 +154,7 @@ impl Mutations { /// using the last up-to-date mutations to avoid re-sending old values. pub(crate) fn send( &mut self, - server: &mut RepliconServer, + messages: &mut ServerMessages, client: Entity, ticks: &mut ClientTicks, entity_buffer: &mut EntityBuffer, @@ -246,7 +246,7 @@ impl Mutations { debug_assert_eq!(message.len(), message_size); - server.send(client, ServerChannel::Mutations, message); + messages.send(client, ServerChannel::Mutations, message); } Ok(self.messages.len()) @@ -446,7 +446,7 @@ mod tests { track_mutate_messages: bool, ) -> usize { let mut serialized = SerializedData::default(); - let mut server = RepliconServer::default(); + let mut messages = ServerMessages::default(); let mut mutations = Mutations::default(); mutations.resize_related(related.len()); @@ -463,7 +463,7 @@ mod tests { mutations .send( - &mut server, + &mut messages, Entity::PLACEHOLDER, &mut Default::default(), &mut Default::default(), diff --git a/src/server/replication_messages/updates.rs b/src/server/replication_messages/updates.rs index af502b48..5a8efec0 100644 --- a/src/server/replication_messages/updates.rs +++ b/src/server/replication_messages/updates.rs @@ -181,7 +181,7 @@ impl Updates { /// on deserialization just consume all remaining bytes. pub(crate) fn send( &self, - server: &mut RepliconServer, + messages: &mut ServerMessages, client: Entity, serialized: &SerializedData, server_tick_range: Range, @@ -272,7 +272,7 @@ impl Updates { debug_assert_eq!(message.len(), message_size); - server.send(client, ServerChannel::Updates, message); + messages.send(client, ServerChannel::Updates, message); Ok(()) } diff --git a/src/shared/backend.rs b/src/shared/backend.rs index 8685c1c6..d66fd2e3 100644 --- a/src/shared/backend.rs +++ b/src/shared/backend.rs @@ -5,7 +5,7 @@ //! - Create channels defined in the [`RepliconChannels`](channels::RepliconChannels) resource. //! This can be done via an extension trait that provides a conversion which the user needs to call manually to get channels for the backend. //! - Manage the [`ClientState`] and [`ServerState`] states. -//! - Update the [`RepliconServer`](replicon_server::RepliconServer) and [`ClientMessages`](client_messages::ClientMessages) resources. +//! - Update the [`ServerMessages`](server_messages::ServerMessages) and [`ClientMessages`](client_messages::ClientMessages) resources. //! - Spawn and despawn entities with [`ConnectedClient`](connected_client::ConnectedClient) component. //! - React on [`DisconnectRequest`] event. //! - Optionally update statistic in [`ClientStats`] resource and components. @@ -25,7 +25,7 @@ pub mod channels; pub mod client_messages; pub mod connected_client; -pub mod replicon_server; +pub mod server_messages; use bevy::prelude::*; @@ -123,14 +123,14 @@ mod tests { client_messages.send(ClientChannel::MutationAcks, message); } - let mut server = RepliconServer::default(); - server.setup_client_channels(channels.client_channels().len()); + let mut server_messages = ServerMessages::default(); + server_messages.setup_client_channels(channels.client_channels().len()); for (channel_id, message) in client_messages.drain_sent() { - server.insert_received(Entity::PLACEHOLDER, channel_id, message); + server_messages.insert_received(Entity::PLACEHOLDER, channel_id, message); } - let messages: Vec<_> = server + let messages: Vec<_> = server_messages .receive(ClientChannel::MutationAcks) .map(|(_, message)| message) .collect(); @@ -140,18 +140,18 @@ mod tests { #[test] fn server_to_client() { let channels = RepliconChannels::default(); - let mut server = RepliconServer::default(); - server.setup_client_channels(channels.client_channels().len()); + let mut server_messages = ServerMessages::default(); + server_messages.setup_client_channels(channels.client_channels().len()); const MESSAGES: &[&[u8]] = &[&[0], &[1]]; for &message in MESSAGES { - server.send(Entity::PLACEHOLDER, ServerChannel::Mutations, message); + server_messages.send(Entity::PLACEHOLDER, ServerChannel::Mutations, message); } let mut client_messages = ClientMessages::default(); client_messages.setup_server_channels(channels.server_channels().len()); - for (_, channel_id, message) in server.drain_sent() { + for (_, channel_id, message) in server_messages.drain_sent() { client_messages.insert_received(channel_id, message); } diff --git a/src/shared/backend/replicon_server.rs b/src/shared/backend/server_messages.rs similarity index 87% rename from src/shared/backend/replicon_server.rs rename to src/shared/backend/server_messages.rs index bc073a12..1e922953 100644 --- a/src/shared/backend/replicon_server.rs +++ b/src/shared/backend/server_messages.rs @@ -2,17 +2,17 @@ use bevy::prelude::*; use bytes::Bytes; use log::trace; -/// Stores information about the server independent from the messaging backend. +/// Sent and received messages for exchange between Replicon and the messaging backend. /// /// The messaging backend is responsible for updating this resource: -/// - For receiving messages, [`Self::insert_received`] should be used. -/// A system to forward messages from the backend to Replicon should run in [`ServerSet::ReceivePackets`](crate::server::ServerSet::ReceivePackets). -/// - For sending messages, [`Self::drain_sent`] should be used to drain all sent messages. -/// A system to forward messages from Replicon to the backend should run in [`ServerSet::SendPackets`](crate::server::ServerSet::SendPackets). +/// - Received messages should be forwarded to Replicon via [`Self::insert_received`] in +/// [`ServerSet::ReceivePackets`](crate::prelude::ServerSet::ReceivePackets). +/// - Replicon messages needs to be forwarded to the backend via [`Self::drain_sent`] in +/// [`ServerSet::SendPackets`](crate::prelude::ServerSet::SendPackets). /// /// Inserted as resource by [`ServerPlugin`](crate::server::ServerPlugin). #[derive(Resource, Default)] -pub struct RepliconServer { +pub struct ServerMessages { /// List of received messages for each channel. /// /// Top index is channel ID. @@ -23,7 +23,7 @@ pub struct RepliconServer { sent_messages: Vec<(Entity, usize, Bytes)>, } -impl RepliconServer { +impl ServerMessages { /// Changes the size of the receive messages storage according to the number of client channels. pub(crate) fn setup_client_channels(&mut self, channels_count: usize) { self.received_messages.resize(channels_count, Vec::new()); diff --git a/src/shared/event/client_event.rs b/src/shared/event/client_event.rs index c61d7e45..74f3d7b9 100644 --- a/src/shared/event/client_event.rs +++ b/src/shared/event/client_event.rs @@ -274,9 +274,9 @@ impl ClientEvent { &self, ctx: &mut ServerReceiveCtx, client_events: PtrMut, - server: &mut RepliconServer, + messages: &mut ServerMessages, ) { - unsafe { (self.receive)(self, ctx, client_events, server) } + unsafe { (self.receive)(self, ctx, client_events, messages) } } /// Typed version of [`Self::receive`]. @@ -289,10 +289,10 @@ impl ClientEvent { &self, ctx: &mut ServerReceiveCtx, client_events: PtrMut, - server: &mut RepliconServer, + messages: &mut ServerMessages, ) { let client_events: &mut Events> = unsafe { client_events.deref_mut() }; - for (client, mut message) in server.receive(self.channel_id) { + for (client, mut message) in messages.receive(self.channel_id) { match unsafe { self.deserialize::(ctx, &mut message) } { Ok(event) => { debug!( @@ -421,7 +421,7 @@ impl ClientEvent { type SendFn = unsafe fn(&ClientEvent, &mut ClientSendCtx, &Ptr, PtrMut, &mut ClientMessages); /// Signature of client event receiving functions. -type ReceiveFn = unsafe fn(&ClientEvent, &mut ServerReceiveCtx, PtrMut, &mut RepliconServer); +type ReceiveFn = unsafe fn(&ClientEvent, &mut ServerReceiveCtx, PtrMut, &mut ServerMessages); /// Signature of client event resending functions. type ResendLocallyFn = unsafe fn(PtrMut, PtrMut); diff --git a/src/shared/event/server_event.rs b/src/shared/event/server_event.rs index 68b661ab..36ded204 100644 --- a/src/shared/event/server_event.rs +++ b/src/shared/event/server_event.rs @@ -304,11 +304,13 @@ impl ServerEvent { &self, ctx: &mut ServerSendCtx, server_events: &Ptr, - server: &mut RepliconServer, + messages: &mut ServerMessages, clients: &Query>, buffered_events: &mut BufferedServerEvents, ) { - unsafe { (self.send_or_buffer)(self, ctx, server_events, server, clients, buffered_events) } + unsafe { + (self.send_or_buffer)(self, ctx, server_events, messages, clients, buffered_events) + } } /// Typed version of [`Self::send_or_buffer`]. @@ -321,7 +323,7 @@ impl ServerEvent { &self, ctx: &mut ServerSendCtx, server_events: &Ptr, - server: &mut RepliconServer, + messages: &mut ServerMessages, clients: &Query>, buffered_events: &mut BufferedServerEvents, ) { @@ -333,7 +335,7 @@ impl ServerEvent { if self.independent { unsafe { - self.send_independent_event::(ctx, event, mode, server, clients) + self.send_independent_event::(ctx, event, mode, messages, clients) .expect("independent server event should be serializable"); } } else { @@ -357,7 +359,7 @@ impl ServerEvent { ctx: &mut ServerSendCtx, event: &E, mode: &SendMode, - server: &mut RepliconServer, + messages: &mut ServerMessages, clients: &Query>, ) -> Result<()> { let mut message = Vec::new(); @@ -367,19 +369,19 @@ impl ServerEvent { match *mode { SendMode::Broadcast => { for client_entity in clients { - server.send(client_entity, self.channel_id, message.clone()); + messages.send(client_entity, self.channel_id, message.clone()); } } SendMode::BroadcastExcept(ignored_id) => { for client in clients { if ignored_id != client.into() { - server.send(client, self.channel_id, message.clone()); + messages.send(client, self.channel_id, message.clone()); } } } SendMode::Direct(client_id) => { if let ClientId::Client(client) = client_id { - server.send(client, self.channel_id, message.clone()); + messages.send(client, self.channel_id, message.clone()); } } } @@ -633,7 +635,7 @@ type SendOrBufferFn = unsafe fn( &ServerEvent, &mut ServerSendCtx, &Ptr, - &mut RepliconServer, + &mut ServerMessages, &Query>, &mut BufferedServerEvents, ); @@ -725,12 +727,12 @@ struct BufferedServerEvent { impl BufferedServerEvent { fn send( &mut self, - server: &mut RepliconServer, + messages: &mut ServerMessages, client_entity: Entity, client: &ClientTicks, ) -> Result<()> { let message = self.message.get_bytes(client.update_tick())?; - server.send(client_entity, self.channel_id, message); + messages.send(client_entity, self.channel_id, message); Ok(()) } } @@ -794,7 +796,7 @@ impl BufferedServerEvents { pub(crate) fn send_all( &mut self, - server: &mut RepliconServer, + messages: &mut ServerMessages, clients: &Query<(Entity, Option<&ClientTicks>), With>, ) -> Result<()> { for mut set in self.buffer.drain(..) { @@ -805,7 +807,7 @@ impl BufferedServerEvents { clients.iter().filter(|(e, _)| !set.excluded.contains(e)) { if let Some(ticks) = ticks { - event.send(server, client, ticks)?; + event.send(messages, client, ticks)?; } else { debug!( "ignoring broadcast for channel {} for non-authorized client `{client}`", @@ -823,7 +825,7 @@ impl BufferedServerEvents { } if let Some(ticks) = ticks { - event.send(server, client, ticks)?; + event.send(messages, client, ticks)?; } else { debug!( "ignoring broadcast except `{ignored_id}` for channel {} for non-authorized client `{client}`", @@ -838,7 +840,7 @@ impl BufferedServerEvents { && !set.excluded.contains(&client) { if let Some(ticks) = ticks { - event.send(server, client, ticks)?; + event.send(messages, client, ticks)?; } else { error!( "ignoring direct event for non-authorized client `{client}`, \ diff --git a/src/test_app.rs b/src/test_app.rs index b4c130fc..9349140a 100644 --- a/src/test_app.rs +++ b/src/test_app.rs @@ -137,12 +137,12 @@ impl ServerTestAppExt for App { let client_entity = **client_app.world().resource::(); let mut client_messages = client_app.world_mut().resource_mut::(); - let mut server = self.world_mut().resource_mut::(); + let mut server_messages = self.world_mut().resource_mut::(); for (channel_id, message) in client_messages.drain_sent() { - server.insert_received(client_entity, channel_id, message) + server_messages.insert_received(client_entity, channel_id, message) } - server.retain_sent(|(entity, channel_id, message)| { + server_messages.retain_sent(|(entity, channel_id, message)| { if *entity == client_entity { client_messages.insert_received(*channel_id, message.clone()); false diff --git a/tests/priority.rs b/tests/priority.rs index 72b8f755..007613e8 100644 --- a/tests/priority.rs +++ b/tests/priority.rs @@ -122,8 +122,8 @@ fn with_miss() { server_app.update(); // Take and drop the mutation message. - let mut server = server_app.world_mut().resource_mut::(); - assert_eq!(server.drain_sent().count(), 1); + let mut messages = server_app.world_mut().resource_mut::(); + assert_eq!(messages.drain_sent().count(), 1); server_app.exchange_with_client(&mut client_app); client_app.update();