bevy_sync 0.18.5

Plugin for synchronizing entities and components between server and its clients.
Documentation
use crate::{ClientState, full_sync, lib_priv::SyncTrackerRes, proto::Message};
use bevy_app::{App, Plugin, Update};
use bevy_connect::{events::MessageReceivedEvent, prelude::Channel};
use bevy_ecs::schedule::IntoScheduleConfigs;
use bevy_ecs::schedule::SystemCondition;
use bevy_ecs::schedule::common_conditions::on_message;
use bevy_ecs::{
    resource::Resource,
    schedule::common_conditions::{
        resource_added, resource_changed, resource_exists, resource_removed,
    },
    system::{Commands, Res, ResMut},
    world::World,
};
use bevy_state::{condition::in_state, state::NextState};
use tracing::info;
use tracing::warn;

mod receiver;

#[derive(Resource, Default)]
struct ClientPresendInitialSync {
    messages: Vec<Message>,
}

pub(crate) struct ClientSyncPlugin;
impl Plugin for ClientSyncPlugin {
    fn build(&self, app: &mut App) {
        app.init_resource::<ClientPresendInitialSync>();
        app.add_systems(
            Update,
            client_connected.run_if(
                resource_added::<Channel<Message>>.and(in_state(ClientState::Disconnected)),
            ),
        );
        app.add_systems(
            Update,
            set_client_to_disconnected
                .run_if(resource_removed::<Channel<Message>>.and(in_state(ClientState::Connected))),
        );

        app.add_systems(
            Update,
            client_update_state.run_if(
                resource_exists::<Channel<Message>>.and(resource_changed::<Channel<Message>>),
            ),
        );

        app.add_systems(
            Update,
            receiver::process_messages.chain().run_if(
                resource_exists::<Channel<Message>>
                    .and(in_state(ClientState::Connected))
                    .and(on_message::<MessageReceivedEvent<Message>>),
            ),
        );
    }
}

fn set_client_to_disconnected(mut client_state: ResMut<NextState<ClientState>>) {
    info!("Disconnected from server.");
    client_state.set(ClientState::Disconnected);
}

#[allow(clippy::needless_pass_by_value)]
fn client_update_state(channel: Res<Channel<Message>>, mut state: ResMut<NextState<ClientState>>) {
    if channel.is_host() {
        state.set(ClientState::Disconnected);
        return;
    }
    state.set(ClientState::Connected);
}

#[allow(clippy::needless_pass_by_value)]
fn client_connected(
    mut cmd: Commands,
    channel: Res<Channel<Message>>,
    mut client_state: ResMut<NextState<ClientState>>,
    mut tracker: ResMut<SyncTrackerRes>,
) {
    if channel.is_host() {
        client_state.set(ClientState::Disconnected);
        return;
    }
    info!("Connected to server.");
    client_state.set(ClientState::Connected);
    if tracker.host_promotion_in_progress {
        // Since no initial sync is being sent and connection completed,
        // now reset back as if promotin never happened.
        tracker.host_promotion_in_progress = false;
        info!("Promotion: Reconnected after host promotion, not requesting initial sync.");
    } else {
        cmd.queue(|world: &mut World| {
            info!("Starting new client session and requesting initial sync.");
            world.resource_mut::<ClientPresendInitialSync>().messages =
                full_sync::build_full_sync(world).unwrap_or_default();
            let mut channel = world.resource_mut::<Channel<Message>>();
            let Some(host_uuid) = channel.host_uuid() else {
                warn!("No host uuid known");
                return;
            };
            let self_uuid = channel.uuid();
            channel.send_to(
                host_uuid,
                Message::RequestInitialSync {
                    from_uuid: self_uuid.0,
                },
            );
        });
    }
}