bevy_sync 0.19.1

Plugin for synchronizing entities and components between server and its clients.
Documentation
use bevy_app::{App, Plugin, FixedUpdate};
use bevy_connect::{events::MessageReceivedEvent, prelude::Channel};
use bevy_ecs::message::MessageWriter;
use bevy_ecs::prelude::resource_changed;
use bevy_ecs::prelude::resource_exists;
use bevy_ecs::prelude::resource_removed;
use bevy_ecs::schedule::IntoScheduleConfigs;
use bevy_ecs::schedule::SystemCondition;
use bevy_ecs::schedule::common_conditions::on_message;
use bevy_ecs::{
    schedule::common_conditions::resource_added,
    system::{Res, ResMut},
};
use bevy_state::{condition::in_state, state::NextState};
use tracing::info;

use crate::{
    InitialSyncFinished, ServerState, proto::Message, server::initial_sync::send_initial_sync,
};

mod initial_sync;
mod receiver;

pub(crate) struct ServerSyncPlugin;

impl Plugin for ServerSyncPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(
            FixedUpdate,
            server_connected.run_if(
                in_state(ServerState::Disconnected).and_then(resource_added::<Channel<Message>>),
            ),
        );
        app.add_systems(
            FixedUpdate,
            server_disconnected
                .run_if(in_state(ServerState::Connected).and_then(resource_removed::<Channel<Message>>)),
        );
        app.add_systems(
            FixedUpdate,
            server_update_state.run_if(
                resource_exists::<Channel<Message>>.and_then(resource_changed::<Channel<Message>>),
            ),
        );

        app.add_systems(
            FixedUpdate,
            receiver::process_messages.run_if(
                in_state(ServerState::Connected)
                    .and_then(resource_exists::<Channel<Message>>)
                    .and_then(on_message::<MessageReceivedEvent<Message>>),
            ),
        );
    }
}

fn server_disconnected(mut state: ResMut<NextState<ServerState>>) {
    info!("Server is shut down.");
    state.set(ServerState::Disconnected);
}

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

#[allow(clippy::needless_pass_by_value)]
fn server_connected(
    channel: Res<Channel<Message>>,
    mut state: ResMut<NextState<ServerState>>,
    mut event: MessageWriter<InitialSyncFinished>,
) {
    if !channel.is_host() {
        state.set(ServerState::Disconnected);
        return;
    }
    info!("Server ready to accept connections.");
    state.set(ServerState::Connected);
    // Server is always 'ready' so it's finished from the start
    event.write(InitialSyncFinished);
}