bevy_replicon/lib.rs
1/*!
2A server-authoritative replication crate for [Bevy](https://bevyengine.org).
3
4# Quick start
5
6The library doesn't provide any I/O, so you need to add a
7[messaging backend](https://github.com/simgine/bevy_replicon#messaging-backends).
8If you want to write an integration yourself, see [`shared::backend`] module.
9
10## Prelude
11
12We provide a [`prelude`] module, which exports most of the typically used traits and types.
13
14## Plugins
15
16Add [`RepliconPlugins`] and plugins for your chosen messaging backend to your app:
17
18```
19use bevy::{prelude::*, state::app::StatesPlugin};
20use bevy_replicon::prelude::*;
21# use bevy::app::PluginGroupBuilder;
22
23let mut app = App::new();
24app.add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins, MyMessagingPlugins));
25# struct MyMessagingPlugins;
26# impl PluginGroup for MyMessagingPlugins {
27# fn build(self) -> PluginGroupBuilder {
28# PluginGroupBuilder::start::<Self>()
29# }
30# }
31```
32
33If you use [`MinimalPlugins`], you need to add [`StatesPlugin`](bevy::state::app::StatesPlugin)
34manually. It's included by default with [`DefaultPlugins`].
35
36## Server and client creation
37
38This part is specific to your messaging backend. For `bevy_replicon_renet`,
39see [this section](https://docs.rs/bevy_replicon_renet#server-and-client-creation).
40
41On server connected clients represented as entities with [`ConnectedClient`] component.
42Their data represented as components, such as [`ClientStats`]. Users can also attach their
43own metadata to them or even replicate these entities back to clients.
44
45These entities are automatically spawned and despawned by the messaging backend. You can
46also despawn them yourself to trigger a disconnect or use the [`DisconnectRequest`] message
47to disconnect after sending messages.
48
49You can use [`On<Add, ConnectedClient>`] to react to new connections,
50or use backend-provided events if you need the disconnect reason.
51
52## States
53
54We provide [`ClientState`] and [`ServerState`], which are Bevy [`States`].
55These are managed by your messaging backend, and you can use them to control when your systems run.
56
57For systems that should run continuously while in a specific state, use [`IntoScheduleConfigs::run_if`]
58with the [`in_state`] run condition:
59
60```
61# use bevy::prelude::*;
62# use bevy_replicon::prelude::*;
63# let mut app = App::new();
64app.add_systems(
65 Update,
66 (
67 apply_damage.run_if(in_state(ServerState::Running)), // Runs every frame on the server.
68 display_vfx.run_if(in_state(ClientState::Connected)), // Runs every frame on the client.
69 ),
70);
71# fn apply_damage() {}
72# fn display_vfx() {}
73```
74
75To run systems when entering or exiting a state, use the [`OnEnter`] or [`OnExit`] schedules:
76
77```
78# use bevy::prelude::*;
79# use bevy_replicon::prelude::*;
80# let mut app = App::new();
81app.add_systems(OnEnter(ClientState::Connecting), display_connection_message) // Runs when the client starts connecting.
82 .add_systems(OnExit(ClientState::Connected), show_disconnected_message) // Runs when the client disconnects.
83 .add_systems(OnEnter(ServerState::Running), initialize_match); // Runs when the server starts.
84# fn display_connection_message() {}
85# fn show_disconnected_message() {}
86# fn initialize_match() {}
87```
88
89You can also use these states with [`StateScoped`], but you'll need to call
90[`AppExtStates::enable_state_scoped_entities`] in your app:
91
92```
93# use bevy::prelude::*;
94# use bevy_replicon::prelude::*;
95# let mut app = App::new();
96app.enable_state_scoped_entities::<ClientState>()
97 .enable_state_scoped_entities::<ServerState>();
98```
99
100Read more about system patterns in the [Abstracting over configurations](#abstracting-over-configurations)
101section.
102
103## Replication
104
105It's a process of exchanging data in order to keep the world in sync. Replicon
106provides a high-level API to automate server-authoritative replication.
107
108Replication happens only from server to clients. It's necessary to prevent cheating.
109If you need to send information from clients to the server, use
110[messages](#network-messages-and-events).
111
112For implementation details see [`ServerChannel`](shared::backend::channels::ServerChannel).
113
114### Tick rate
115
116By default, updates are not sent every frame in order to save bandwidth. Replication runs
117in [`ServerSystems::Send`] whenever the [`ServerTick`](server::server_tick::ServerTick) resource
118changes and if the state is [`ServerState::Running`].
119
120By default, the tick is incremented in [`FixedPostUpdate`] each time [`FixedMain`](bevy::app::FixedMain)
121runs. You can configure how often it runs by inserting [`Time<Fixed>`], which is 64 Hz by default.
122
123```rust
124use bevy::{prelude::*, state::app::StatesPlugin};
125use bevy_replicon::prelude::*;
126
127# let mut app = App::new();
128// `Time::from_hz` is implemented only for `Fixed`, so the type is inferred.
129app.insert_resource(Time::from_hz(30.0)) // Run 30 times per second.
130 .add_plugins((MinimalPlugins, StatesPlugin, RepliconPlugins));
131```
132
133You can also configure the schedule via [`ServerPlugin::tick_schedule`].
134
135### Entities
136
137By default no entities are replicated. Add the [`Replicated`] marker
138component on the server for entities you want to replicate.
139
140On clients [`Replicated`] will be automatically inserted to newly-replicated entities.
141
142If you remove the [`Replicated`] component from an entity on the server, it will be despawned on all clients.
143
144Entity IDs differ between clients and server. As a result, clients maps server entities to local entities
145on receive. These mappings are stored in the [`ServerEntityMap`](shared::server_entity_map::ServerEntityMap)
146resource.
147
148### Components
149
150Components will be replicated only on entities marked for replication.
151By default no components are replicated, you need to define rules for it.
152
153Use [`AppRuleExt::replicate`] to create a replication rule for a single component:
154
155```
156# use bevy::{prelude::*, state::app::StatesPlugin};
157# use bevy_replicon::prelude::*;
158# use serde::{Deserialize, Serialize};
159# let mut app = App::new();
160# app.add_plugins((StatesPlugin, RepliconPlugins));
161app.replicate::<ExampleComponent>();
162
163#[derive(Component, Deserialize, Serialize)]
164struct ExampleComponent;
165```
166
167If your component contains an entity, it cannot be deserialized as is
168because entities inside components also need to be mapped. Therefore,
169to properly replicate such components, mark fields containing entities with
170`#[entities]`. See [`Component::map_entities`] for details.
171
172By default all components are serialized with [`postcard`].
173In order to serialize Bevy components you need to enable the `serialize` feature on Bevy.
174
175If your component doesn't implement serde traits or you want to customize the serialization
176(for example, quantize, skip some fields or apply compression), you can use
177[`AppRuleExt::replicate_as`] or [`AppRuleExt::replicate_with`].
178
179You can also create a rule for multiple components. Use [`AppRuleExt::replicate_bundle`],
180or pass a tuple of [`RuleFns`] to [`AppRuleExt::replicate_with`]. The components will only
181be replicated if all of them are present on the entity. This also allows you to specialize
182serialization and deserialization based on specific entity components.
183
184All functions also have `_filtered` variants that additionally accept archetypal filters,
185similar to [`Query`]. They are separate functions because Rust doesn't allow default generics
186for functions. For more details, see [`AppRuleExt::replicate_filtered`].
187
188#### Required components
189
190You don't want to replicate all components because not all of them are
191necessary to send over the network. Components that can be calculated on the client can
192be inserted using Bevy's required components feature.
193
194```
195# use bevy::{prelude::*, state::app::StatesPlugin};
196# use bevy_replicon::prelude::*;
197# use serde::{Deserialize, Serialize};
198# let mut app = App::new();
199# app.add_plugins((StatesPlugin, RepliconPlugins));
200// Replicate only transform and player marker.
201app.replicate::<Transform>()
202 .replicate::<Player>()
203 .add_observer(init_player_mesh);
204
205fn init_player_mesh(
206 add: On<Add, Mesh2d>,
207 mut meshes: ResMut<Assets<Mesh>>,
208 mut players: Query<&mut Mesh2d>,
209) {
210 let mut mesh = players.get_mut(add.entity).unwrap();
211 **mesh = meshes.add(Capsule2d::default());
212}
213
214/// Main player component.
215///
216/// [`NotReplicatedComponent`] and [`Replicated`] will be implicitly inserted after spawning on
217/// both the server and clients.
218///
219/// [`Replicated`] is always inserted on the client after replication, regardless of whether it is marked
220/// as required. However, it may still be useful to mark it as required if you want to avoid
221/// inserting it explicitly on the server.
222#[derive(Component, Deserialize, Serialize)]
223#[require(Replicated, NotReplicatedComponent, Mesh2d)]
224struct Player;
225
226#[derive(Default, Component)]
227struct NotReplicatedComponent;
228```
229
230This pairs nicely with server state serialization and keeps saves clean.
231You can use [`scene::replicate_into`] to fill [`DynamicScene`] with replicated entities and their components.
232On deserialization all missing required components will be inserted, and initialization
233systems will restore the correct game state.
234
235If a component can't be used with the required components due to the inability to insert it
236without world access, you can create an observer for a replicated marker and insert the actual
237component inside it. However, it's preferred to use required components when possible. For example,
238it's better to require a [`Handle<T>`] with a default value that doesn't point to any asset
239and initialize it later in a hook or observer. This way you avoid archetype moves in ECS.
240
241#### Mutability
242
243There are two ways to change a component value on an entity: re-inserting it or mutating it.
244
245We use Bevy’s change detection to track and send changes. However, it does not distinguish between modifications
246and re-insertions. This is why we simply send the list of changes and decide how to apply them on the client.
247By default, this behavior is based on [`Component::Mutability`].
248
249When a component is [`Mutable`](bevy::ecs::component::Mutable), we check whether it already exists on the entity.
250If it doesn’t, we insert it. If it does, we mutate it. This means that if you insert a component into an entity
251that already has it on the server, the client will treat it as a mutation. As a result, triggers may behave
252differently on the client and server. If your game logic relies on this semantic, mark your component as
253[`Immutable`](bevy::ecs::component::Immutable). For such components, replication will always be applied via insertion.
254
255This behavior is also configurable via [client markers](#client-markers).
256
257#### Component relations
258
259Some components depend on each other. For example, [`ChildOf`] and [`Children`]. You can enable
260replication only for [`ChildOf`] and [`Children`] will be updated automatically on insertion.
261This will emit a [`B0004`](https://bevy.org/learn/errors/b0004) warning which can be safely ignored.
262See [#19776](https://github.com/bevyengine/bevy/issues/19776) for more details.
263
264You can also ensure that their mutations arrive in sync by using [`SyncRelatedAppExt::sync_related_entities`].
265
266#### Deterministic replication
267
268Up until now, we've covered only authoritative replication (AR), where the server is the source of truth
269and continuously sends changes. However, sometimes you may want to send data only once and simulate independently,
270relying on determinism. This approach is called deterministic replication (DR).
271
272For example, you might use AR for things like player health, and DR for moving platform positions to reduce
273network traffic.
274
275Use [`AppRuleExt::replicate_once`] to replicate only the initial value of a component.
276You can configure this per-component within a replication rule using [`AppRuleExt::replicate_with`].
277
278See also [server messages](#from-server-to-client), which are also useful for DR.
279
280## Network messages and events
281
282This replaces RPCs (remote procedure calls) in other engines and,
283unlike replication, can be sent both from server to clients and from clients to
284server.
285
286### From client to server
287
288To send a message from client to server, you need to register the message
289with [`ClientMessageAppExt::add_client_message`] instead of [`App::add_message`].
290
291Messages include [`Channel`] to configure delivery guarantees (reliability and
292ordering).
293
294These messages will appear on server as [`FromClient`] wrapper message that
295contains sender ID and the message.
296
297```
298# use bevy::{prelude::*, state::app::StatesPlugin};
299# use bevy_replicon::prelude::*;
300# use serde::{Deserialize, Serialize};
301# let mut app = App::new();
302# app.add_plugins((StatesPlugin, RepliconPlugins));
303app.add_client_message::<Ping>(Channel::Ordered)
304 .add_systems(
305 PreUpdate,
306 receive
307 .after(ServerSystems::Receive)
308 .run_if(in_state(ServerState::Running)),
309 )
310 .add_systems(
311 PostUpdate,
312 send.before(ClientSystems::Send).run_if(in_state(ClientState::Connected)),
313 );
314
315fn send(mut ping: MessageWriter<Ping>) {
316 ping.write(Ping);
317}
318
319fn receive(mut pings: MessageReader<FromClient<Ping>>) {
320 for ping in pings.read() {
321 info!("received ping from client `{}`", ping.client_id);
322 }
323}
324
325#[derive(Message, Deserialize, Serialize)]
326struct Ping;
327```
328
329If a message contains an entity, implement
330[`MapEntities`](bevy::ecs::entity::MapEntities) for it and use use
331[`ClientMessageAppExt::add_mapped_client_message`] instead.
332
333There is also [`ClientMessageAppExt::add_client_message_with`] to register a message with special serialization and
334deserialization functions. This could be used for sending messages that contain [`Box<dyn PartialReflect>`], which
335require access to the [`AppTypeRegistry`] resource. Don't forget to validate the contents of every
336[`Box<dyn PartialReflect>`] from a client, it could be anything!
337
338Alternatively, you can use events with a similar API. First, you need to register the event
339using [`ClientEventAppExt::add_client_event`], and then use [`ClientTriggerExt::client_trigger`].
340
341```
342# use bevy::{prelude::*, state::app::StatesPlugin};
343# use bevy_replicon::prelude::*;
344# use serde::{Deserialize, Serialize};
345# let mut app = App::new();
346# app.add_plugins((StatesPlugin, RepliconPlugins));
347app.add_client_event::<Ping>(Channel::Ordered)
348 .add_observer(receive)
349 .add_systems(Update, send.run_if(in_state(ClientState::Connected)));
350
351fn send(mut commands: Commands) {
352 commands.client_trigger(Ping);
353}
354
355fn receive(ping: On<FromClient<Ping>>) {
356 info!("received ping from client `{}`", ping.client_id);
357}
358# #[derive(Event, Deserialize, Serialize)]
359# struct Ping;
360```
361
362For events with entities inside use [`ClientEventAppExt::add_mapped_client_event`].
363Similar to messages, serialization can also be customized with [`ClientEventAppExt::add_client_event_with`].
364
365### From server to client
366
367A similar technique is used to send messages from server to clients. To do this,
368register a message with [`ServerMessageAppExt::add_server_message`]
369and send it from server using [`ToClients`]. This wrapper contains send parameters
370and the message itself.
371
372```
373# use bevy::{prelude::*, state::app::StatesPlugin};
374# use bevy_replicon::prelude::*;
375# use serde::{Deserialize, Serialize};
376# let mut app = App::new();
377# app.add_plugins((StatesPlugin, RepliconPlugins));
378app.add_server_message::<Pong>(Channel::Ordered)
379 .add_systems(
380 PreUpdate,
381 receive
382 .after(ClientSystems::Receive)
383 .run_if(in_state(ClientState::Connected)),
384 )
385 .add_systems(
386 PostUpdate,
387 send.before(ServerSystems::Send).run_if(in_state(ServerState::Running)),
388 );
389
390fn send(mut pongs: MessageWriter<ToClients<Pong>>) {
391 pongs.write(ToClients {
392 mode: SendMode::Broadcast,
393 message: Pong,
394 });
395}
396
397fn receive(mut pongs: MessageReader<Pong>) {
398 for pong in pongs.read() {
399 info!("received pong from the server");
400 }
401}
402
403#[derive(Message, Deserialize, Serialize)]
404struct Pong;
405```
406
407Just like for client messages, we provide [`ServerMessageAppExt::add_mapped_server_message`]
408and [`ServerMessageAppExt::add_server_message_with`].
409
410Event API is available. First, you need to register an event with [`ServerEventAppExt::add_server_event`]
411and then use [`ServerTriggerExt::server_trigger`]:
412
413```
414# use bevy::{prelude::*, state::app::StatesPlugin};
415# use bevy_replicon::prelude::*;
416# use serde::{Deserialize, Serialize};
417# let mut app = App::new();
418# app.add_plugins((StatesPlugin, RepliconPlugins));
419app.add_server_event::<Pong>(Channel::Ordered)
420 .add_observer(receive)
421 .add_systems(Update, send.run_if(in_state(ServerState::Running)));
422
423fn send(mut commands: Commands) {
424 commands.server_trigger(ToClients {
425 mode: SendMode::Broadcast,
426 message: Pong,
427 });
428}
429
430fn receive(_on: On<Pong>) {
431 info!("received pong from the server");
432}
433# #[derive(Event, Deserialize, Serialize)]
434# struct Pong;
435```
436
437And just like for client events, we provide [`ServerEventAppExt::add_mapped_server_event`]
438and [`ServerEventAppExt::add_server_event_with`].
439
440We guarantee that clients will never receive events or messages that point to an entity or require specific
441component to be presentt which client haven't received yet. For more details see the documentation on
442[`ServerMessageAppExt::make_message_independent`].
443
444## Abstracting over configurations
445
446Depending on the game, you may need to support some of these configurations:
447
448- Client
449- Dedicated (headless) server
450- Listen server (where the server is also a client)
451- Singleplayer
452
453There are 2 ways to support multiple configurations at the same time.
454
455### Classic way
456
457Just split client and server logic. Then for listen server and singleplayer run both the server and client,
458just don't accept outside connections for singleplayer.
459
460However, **running the client and server in a single app is not supported**. We rely on change detection to
461decide on which data to send, and since the world is shared, applying replication will trigger changes.
462To avoid this, you can use one of the following workarounds:
463
464- Two Bevy apps inside a single process, running in separate threads.
465- Two executables. After starting the client app, the server starts in the background.
466
467It's not easy to set up and requires more resources due to the synchronization between two worlds.
468This is why, while it's possible to use Replicon this way, we recommend a different approach.
469
470### The recommended way
471
472Instead of recreating full client-server logic, we provide a way to emulate client and server functionality
473without actually running them:
474
475- Client configuration runs only the client and its logic.
476- Dedicated server configuration runs only the server and its logic (all client logic usually compiled out).
477- Listen server configuration runs only the server and both logics.
478- Singleplayer configuration doesn't run the client or server but runs both logics.
479
480To achieve this, use the provided [`ClientState`] and [`ServerState`] states:
481
482- Use [`ClientState::Disconnected`] for systems that require server authority.
483 For example, systems that apply damage or send server events. This basically means "run when not a client",
484 which applies to both server **and** singleplayer.
485- Use [`ClientState::Connecting`], [`ClientState::Connected`], [`ServerState::Running`], etc.
486 **only** for miscellaneous things, like display a connection message or a menu to kick connected players
487 (things that actually require server or client running)
488- For everything else don't use Replicon's states.
489
490Everything else is done automatically by the crate. All provided
491[examples](https://github.com/simgine/bevy_replicon/tree/master/bevy_replicon_example_backend/examples)
492use this approach.
493
494Internally we run replication sending system only in [`ServerState::Running`] and replication receiving
495only in [`ClientState::Connected`]. This way for singleplayer replication systems won't run at all and
496for listen server replication will only be sending (server world is already in the correct state).
497
498For messages, it's a bit trickier. For all client messages, we internally drain messages as `E` and send
499them as [`FromClient<E>`] locally with [`ClientId::Server`] in [`ClientState::Disconnected`].
500For server messages we drain [`ToClients<E>`] and, if the [`ClientId::Server`] is the recipient of the message,
501re-emit it as `E` locally. The same applies to the event API. This emulates message receiving for both server
502and singleplayer without actually transmitting data over the network.
503
504We also provide [`ClientSystems`] and [`ServerSystems`] to schedule your system at specific time in the frame.
505For example, you can run your systems right after receive using [`ClientSystems::Receive`] or [`ServerSystems::Receive`].
506
507## Organizing your game code
508
509If you support a dedicated server, it's recommended to split your game logic into "client", "server", and "shared"
510crates. This way, you can compile out all unnecessary code for the dedicated server configuration.
511Alternatively, you can use [Cargo features](https://doc.rust-lang.org/cargo/reference/features.html) and split
512the logic into modules.
513
514We provide `client` and `server` features to disable unneeded functionality for this use case.
515You will need to disable similar features on your messaging backend crate too.
516
517<div class="warning">
518
519Make sure that the component, event and message registration order is the same on the client and server. Simply put all
520registration code in your "shared" crate.
521
522</div>
523
524If you don't need to support a dedicated server configuration, it's easier to keep the logic grouped together.
525Splitting your game into crates is still useful, but it should be done logically rather than on server/client
526basis.
527
528## Advanced features
529
530### Authorization
531
532Connected clients are considered authorized when they have the [`AuthorizedClient`] component. Without this
533component, clients can only receive events and messages marked as independent via [`ServerMessageAppExt::make_message_independent`]
534or [`ServerEventAppExt::make_event_independent`]. Additionally, some components on connected clients are only present
535after replication starts. See the required components for [`AuthorizedClient`] for details.
536
537By default, this component is automatically inserted when the client and server [`ProtocolHash`] matches.
538This behavior can be customized via [`RepliconSharedPlugin::auth_method`].
539
540### Client visibility
541
542You can control which parts of the world are visible for each client by setting visibility policy
543in [`ServerPlugin`] to [`VisibilityPolicy::Whitelist`] or [`VisibilityPolicy::Blacklist`].
544
545To set which entity is visible, you need to use the [`ClientVisibility`] component
546on authorized clients.
547
548Check also the [corresponding section](https://github.com/simgine/bevy_replicon#visibility)
549in our README for more high-level abstractions.
550
551### Prioritization
552
553By default, all unacknowledged mutations are sent every tick. This can be expensive if you
554have many entities with mutating components. The [`PriorityMap`] component lets you configure
555how often mutations are sent for each entity on authorized clients. See its documentation for
556more details.
557
558In addition, [`ClientVisibility`] can be used to further reduce bandwidth by hiding entities
559that are irrelevant to a given client.
560
561### Interpolation and/or client-side prediction
562
563Due to network round-trip time and [tick rate](#tick-rate), you may notice that the state isn't updated
564immediately. This might be fine depending on your type of game. However, sometimes this needs to be visually hidden.
565
566To make value updates look smooth, you can just to interpolate the received state. If the input delay doesn't matter
567for your type of game, it can be enough.
568
569But if your game is fast-paced, waiting for server to receive your inputs and replicate the state back might
570might be unacceptable. The solution is to predict simulation on the client.
571
572However, it introduces another problem - misspredictions. For example, player 1 might predict movement to point X,
573while player 2 might have stunned it, the player 1 just didn't receive it yet. To solve this, client must apply
574the received state from server and replay its inputs.
575
576How much to predict also depends on the game. Common approaches are:
577
578- Predict individual entities. Common for shooters or games where you don't have many entities. In the case of
579 a misprediction or non-deterministic mismatch, the state will be corrected. Determinism is important for this
580 approach to reduce the number of rollbacks.
581- Predict the entire world. Common for physics-based games or games with many entities. With this approach,
582 all predicted entities are rolled back to the oldest received tick. For example, if one entity have confirmed tick 1
583 and another entity have confirmed tick 2, both entities are rolled back to tick 1. This approach is usually more expensive
584 but produces better results for physics. Additionally, if there are many predicted entities, it might even be faster
585 since there's no need to check each entity for misprediction. The more entities you predict, the more likely it is
586 that at least one will trigger a world rollback. So with this approach client usually just always rollbacks.
587
588We don't have these features built-in, but we provide a low-level API to implement these abstractions on top.
589Check the [corresponding section](https://github.com/simgine/bevy_replicon#interpolation-andor-rollback)
590in our README for existing implementations.
591
592### Predicting spawns
593
594Sometimes it's necessary to spawn an entity on the client before receiving replication from the server.
595A common example is projectiles. By default, the client will end up with two entities: one locally spawned
596and one replicated from the server.
597
598We provide the [`Signature`] component, which allows replicated entities to be matched with entities
599previously spawned on the client.
600
601This is also useful for synchronizing scenes. Both the client and the server can load a level independently
602and then match level entities to synchronize certain things, such as opened doors.
603
604#### Client markers
605
606To apply interpolation or store value history for client-side prediction, you need to override how components are
607written. However, the server knows nothing about archetypes on the client, and while some entities need to be predicted,
608others might need to be interpolated.
609
610This is why writing functions are marker-based. First, you register a marker using [`AppMarkerExt::register_marker<M>`].
611Then you can override how specific component is written and removed using [`AppMarkerExt::set_marker_fns<M, C>`].
612
613You can control marker priority or enable processing of old values using [`AppMarkerExt::register_marker_with<M>`].
614
615### Ticks information
616
617This requires an understanding of how replication works. See the documentation on
618[`ServerChannel`](shared::backend::channels::ServerChannel) and [this section](#eventual-consistency) for more details.
619
620To get information about confirmed ticks for individual entities, we provide
621[`ConfirmHistory`](client::confirm_history::ConfirmHistory) along with the [`EntityReplicated`](client::confirm_history::ConfirmHistory)
622trigger. This component is updated when any replication for its entity is received. However, we don't update this component if an entity
623hasn't changed for performance reasons.
624
625This means that to check if a tick is confirmed for an entity, you also need to check the received messages for this
626tick. The [`ServerUpdateTick`](client::ServerUpdateTick) resource stores the last received tick from an update message.
627The [`ServerMutateTicks`](client::server_mutate_ticks::ServerMutateTicks) resource and
628[`MutateTickReceived`](client::server_mutate_ticks::MutateTickReceived) trigger provide information about received mutate
629messages for the past 64 ticks.
630
631A tick for an entity is confirmed if one of the following is true:
632- [`ServerUpdateTick`](client::ServerUpdateTick) is greater than the tick.
633- [`ConfirmHistory`](client::confirm_history::ConfirmHistory) is greater than the tick.
634- [`ServerMutateTicks`](client::server_mutate_ticks::ServerMutateTicks) reports that for at least one of the next ticks, all update
635 messages have been received.
636
637### Optimizing entity serialization
638
639Serialization of [`Entity`] is optimized for use in scenes, but it’s not very efficient for networking. Because of this, we use our own implementation.
640See the [`compact_entity`] module if you want to apply this optimization to entities in your own types. You can also use
641[`postcard_utils::entity_to_extend_mut`] and [`postcard_utils::entity_from_buf`] for manual writing.
642
643# Eventual consistency
644
645All events, messages, inserts, removals and despawns will be applied to clients in the same order as on the server.
646
647However, if you insert/mutate a component and immediately remove it, the client will only receive the removal because the component value
648won't exist in the [`World`] during the replication process. But removal followed by insertion will work as expected since we buffer removals.
649
650Entity component mutations may be applied to clients in a different order than on the server.
651For example, if two entities are spawned in tick 1 on the server and their components are mutated in tick 2,
652then the client is guaranteed to see the spawns at the same tick, but the component mutations may appear later (but not earlier).
653
654If a component is dependent on other data, mutations to the component will only be applied to the client when that data has arrived.
655So if your component references another entity, mutations to that component will only be applied when the referenced entity has been spawned on the client.
656
657Mutations for despawned entities will be discarded automatically, but events, messages or components may reference despawned entities and should be handled with that in mind.
658
659Clients should never assume their world state is the same as the server's on any given tick value-wise.
660World state on the client is only "eventually consistent" with the server's.
661
662# Troubleshooting
663
664If you face any issue, try to enable logging to see what is going on.
665To enable logging, you can temporarily set `RUST_LOG` environment variable to `bevy_replicon=debug`
666(or `bevy_replicon=trace` for more noisy output) like this:
667
668```bash
669RUST_LOG=bevy_replicon=debug cargo run
670```
671
672You can also filter by specific module like this:
673
674```bash
675RUST_LOG=bevy_replicon::shared::protocol=debug cargo run
676```
677
678The exact method depends on the OS shell.
679
680Alternatively you can configure `LogPlugin` from Bevy to make it permanent.
681
682For deserialization errors on client we use `error` level which should be visible by default.
683But on server we use `debug` for it to avoid flooding server logs with errors caused by clients.
684*/
685#![cfg_attr(docsrs, feature(doc_cfg))]
686#![no_std]
687
688extern crate alloc;
689
690#[cfg(feature = "client")]
691pub mod client;
692pub mod compact_entity;
693pub mod postcard_utils;
694#[cfg(feature = "scene")]
695pub mod scene;
696#[cfg(feature = "server")]
697pub mod server;
698pub mod shared;
699#[cfg(all(feature = "server", feature = "client"))]
700pub mod test_app;
701
702pub mod prelude {
703 pub use super::{
704 RepliconPlugins,
705 shared::{
706 AuthMethod, RepliconSharedPlugin,
707 backend::{
708 ClientState, ClientStats, DisconnectRequest, ServerState,
709 channels::{Channel, RepliconChannels},
710 client_messages::ClientMessages,
711 connected_client::ConnectedClient,
712 server_messages::ServerMessages,
713 },
714 client_id::ClientId,
715 message::{
716 client_event::{ClientEventAppExt, ClientTriggerExt},
717 client_message::{ClientMessageAppExt, FromClient},
718 server_event::{ServerEventAppExt, ServerTriggerExt},
719 server_message::{SendMode, ServerMessageAppExt, ToClients},
720 },
721 protocol::{ProtocolHash, ProtocolHasher, ProtocolMismatch},
722 replication::{
723 Replicated,
724 command_markers::AppMarkerExt,
725 registry::rule_fns::RuleFns,
726 rules::{AppRuleExt, component::ReplicationMode},
727 signature::Signature,
728 },
729 replicon_tick::RepliconTick,
730 },
731 };
732
733 #[cfg(feature = "client")]
734 pub use super::client::{
735 ClientPlugin, ClientReplicationStats, ClientSystems, message::ClientMessagePlugin,
736 };
737
738 #[cfg(feature = "server")]
739 pub use super::server::{
740 AuthorizedClient, PriorityMap, ServerPlugin, ServerSystems, VisibilityPolicy,
741 client_visibility::ClientVisibility, message::ServerMessagePlugin,
742 related_entities::SyncRelatedAppExt,
743 };
744
745 #[cfg(feature = "client_diagnostics")]
746 pub use super::client::diagnostics::ClientDiagnosticsPlugin;
747}
748
749pub use bytes;
750pub use postcard;
751
752use bevy::{app::PluginGroupBuilder, prelude::*};
753use prelude::*;
754
755/// Plugin group for all replicon plugins.
756///
757/// Contains the following:
758/// * [`RepliconSharedPlugin`].
759/// * [`ServerPlugin`] - with feature `server`.
760/// * [`ServerMessagePlugin`] - with feature `server`.
761/// * [`ClientPlugin`] - with feature `client`.
762/// * [`ClientMessagePlugin`] - with feature `client`.
763/// * [`ClientDiagnosticsPlugin`] - with feature `client_diagnostics`.
764pub struct RepliconPlugins;
765
766impl PluginGroup for RepliconPlugins {
767 fn build(self) -> PluginGroupBuilder {
768 let mut group = PluginGroupBuilder::start::<Self>();
769 group = group.add(RepliconSharedPlugin::default());
770
771 #[cfg(feature = "server")]
772 {
773 group = group.add(ServerPlugin::default()).add(ServerMessagePlugin);
774 }
775
776 #[cfg(feature = "client")]
777 {
778 group = group.add(ClientPlugin).add(ClientMessagePlugin);
779 }
780
781 #[cfg(feature = "client_diagnostics")]
782 {
783 group = group.add(ClientDiagnosticsPlugin);
784 }
785
786 group
787 }
788}