use bevy_app::{App, Plugin};
use bevy_asset::{Assets, Handle};
use bevy_color::Color;
use bevy_connect::events::MessageReceivedEvent;
use bevy_connect::prelude::{Channel, SessionDisconnectCommand, SessionPromoteToHostCommand};
use bevy_connect::{ClientId, SessionPlugin};
use bevy_ecs::component::{Component, ComponentId};
use bevy_ecs::entity::Entity;
use bevy_ecs::message::Messages;
use bevy_ecs::prelude::ReflectComponent;
use bevy_ecs::resource::Resource;
use bevy_ecs::system::{Command, Res};
use bevy_ecs::world::{Mut, World};
use bevy_entity_uuid::{EntityLookup, EntityUuidPlugin};
use bevy_image::Image;
use bevy_light::{DirectionalLight, PointLight, SpotLight};
use bevy_math::Mat4;
use bevy_mesh::Mesh;
use bevy_mesh::skinning::{SkinnedMesh, SkinnedMeshInverseBindposes};
use bevy_pbr::{ParallaxMappingMethod, StandardMaterial};
use bevy_reflect::prelude::ReflectDefault;
use bevy_reflect::{DynamicTypePath, FromReflect, GetTypeRegistration, ReflectFromReflect};
use bevy_reflect::{Reflect, TypePath};
use bevy_state::app::AppExtStates;
use std::any::TypeId;
use std::collections::{HashMap, HashSet};
use tracing::{debug, warn};
use uuid::Uuid;
use crate::networking::assets::{AssetMessage, SyncAssetPlugin};
use crate::proto::Message;
use crate::track::SyncTrackPlugin;
use crate::{
ClientState, InitialSyncFinished, ServerState, SyncComponent, SyncExclude, SyncMark,
SyncPlugin, bundle_fix::BundleFixPlugin, client::ClientSyncPlugin,
handle_uuid_fix::HandleUUIDFixPlugin, server::ServerSyncPlugin,
track::setup_tracking_of_component,
};
use crate::{ConnectCommand, DisconnectCommand, PromoteToHostCommand, SyncWorld};
#[allow(clippy::struct_excessive_bools)]
#[derive(Resource, Default)]
pub(crate) struct SyncTrackerRes {
pub(crate) registered_componets_for_sync: HashSet<ComponentId>,
pub(crate) sync_exclude_cid_of_component_cid: HashMap<ComponentId, ComponentId>,
pub(crate) pushed_handles_from_network: HashSet<Uuid>,
pub(crate) sync_materials: bool,
pub(crate) sync_meshes: bool,
pub(crate) sync_audios: bool,
pub(crate) host_promotion_in_progress: bool,
}
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn sync_material_enabled(tracker: Res<SyncTrackerRes>) -> bool {
tracker.sync_materials
}
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn sync_mesh_enabled(tracker: Res<SyncTrackerRes>) -> bool {
tracker.sync_meshes
}
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn sync_audio_enabled(tracker: Res<SyncTrackerRes>) -> bool {
tracker.sync_audios
}
impl SyncTrackerRes {
pub(crate) fn skip_network_handle_change(&mut self, id: Uuid) -> bool {
if self.pushed_handles_from_network.contains(&id) {
debug!(
"Debouncing network handle change, was already pushed. {:?}",
id
);
self.pushed_handles_from_network.remove(&id);
return true;
}
false
}
pub(crate) fn to_skinned_mapper(
assets: &Assets<SkinnedMeshInverseBindposes>,
entity_map: &EntityLookup,
component: &SkinnedMesh,
) -> SkinnedMeshSyncMapper {
let mut joints_uuid = Vec::<Uuid>::new();
for e in &component.joints {
if let Some(uuid) = entity_map.entity_to_uuid(e) {
joints_uuid.push(uuid);
}
}
let poses = assets.get(component.inverse_bindposes.id()).unwrap();
let poses = (**poses).to_vec();
SkinnedMeshSyncMapper {
inverse_bindposes: poses,
joints: joints_uuid,
}
}
pub(crate) fn to_skinned_mesh(world: &mut World, mapper: SkinnedMeshSyncMapper) -> SkinnedMesh {
world.resource_scope(
|world, mut assets: Mut<Assets<SkinnedMeshInverseBindposes>>| {
let entity_map = world.resource::<EntityLookup>();
let mut joints = Vec::<Entity>::new();
for uuid in &mapper.joints {
if let Some(e) = entity_map.uuid_to_entity(uuid) {
joints.push(e);
}
}
let poses = mapper.inverse_bindposes;
let poses: SkinnedMeshInverseBindposes = poses.into();
let handle = assets.add(poses);
SkinnedMesh {
inverse_bindposes: handle,
joints,
}
},
)
}
}
impl SyncComponent for App {
fn sync_component<T>(&mut self) -> &mut Self
where
T: Component
+ TypePath
+ DynamicTypePath
+ Reflect
+ FromReflect
+ GetTypeRegistration
+ Clone,
{
if self.world().get_resource::<SyncTrackerRes>().is_none() {
warn!("Trying to register sync_component in bevy_sync, but bevy_sync is not enabled.");
return self;
}
self.register_type::<T>();
self.register_type_data::<T, ReflectFromReflect>();
let c_id = self.world_mut().register_component::<T>();
let c_exclude_id = self.world_mut().register_component::<SyncExclude<T>>();
let mut track = self.world_mut().resource_mut::<SyncTrackerRes>();
track.registered_componets_for_sync.insert(c_id);
track
.sync_exclude_cid_of_component_cid
.insert(c_id, c_exclude_id);
setup_tracking_of_component::<T>(self);
setup_cascade_registrations::<T>(self);
self
}
fn sync_materials(&mut self, enable: bool) {
let mut tracker = self.world_mut().resource_mut::<SyncTrackerRes>();
tracker.sync_materials = enable;
}
fn sync_meshes(&mut self, enable: bool) {
let mut tracker = self.world_mut().resource_mut::<SyncTrackerRes>();
tracker.sync_meshes = enable;
}
fn sync_audios(&mut self, enable: bool) {
let mut tracker = self.world_mut().resource_mut::<SyncTrackerRes>();
tracker.sync_audios = enable;
}
}
#[derive(Component, Debug, Clone, Reflect, Default)]
#[reflect(Component, Default)]
pub(crate) struct SkinnedMeshSyncMapper {
pub inverse_bindposes: Vec<Mat4>,
pub joints: Vec<Uuid>,
}
fn setup_cascade_registrations<T: Component + Reflect + FromReflect + GetTypeRegistration>(
app: &mut App,
) {
if TypeId::of::<T>() == TypeId::of::<SkinnedMesh>() {
app.register_type::<SkinnedMeshSyncMapper>();
}
if TypeId::of::<T>() == TypeId::of::<Mesh>() {
app.register_type::<Image>();
app.register_type::<Handle<Image>>();
}
if TypeId::of::<T>() == TypeId::of::<Handle<StandardMaterial>>() {
app.register_type_data::<StandardMaterial, ReflectFromReflect>();
app.register_type::<Color>();
app.register_type::<Image>();
app.register_type::<Handle<Image>>();
app.register_type::<Option<Handle<Image>>>();
app.register_type::<ParallaxMappingMethod>();
}
if TypeId::of::<T>() == TypeId::of::<PointLight>() {
app.register_type::<Color>();
}
if TypeId::of::<T>() == TypeId::of::<SpotLight>() {
app.register_type::<Color>();
}
if TypeId::of::<T>() == TypeId::of::<DirectionalLight>() {
app.register_type::<Color>();
}
}
impl Plugin for SyncPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(EntityUuidPlugin);
app.add_plugins(SessionPlugin::<Message>::default());
app.add_plugins(SessionPlugin::<AssetMessage>::default());
app.add_message::<InitialSyncFinished>();
app.register_type::<SyncMark>();
app.init_resource::<SyncTrackerRes>();
app.init_state::<ServerState>();
app.init_state::<ClientState>();
app.add_plugins(ServerSyncPlugin);
app.add_plugins(ClientSyncPlugin);
app.add_plugins(SyncTrackPlugin);
app.add_plugins(SyncAssetPlugin);
app.add_plugins(BundleFixPlugin);
app.add_plugins(HandleUUIDFixPlugin);
app.add_plugins(crate::metrics::MetricsPlugin);
}
}
impl Command for ConnectCommand {
type Out = ();
fn apply(self, world: &mut World) {
world.insert_resource(self.config.clone());
if self.is_host {
crate::networking::setup_server(&mut world.commands(), &self.config.clone());
} else {
crate::networking::setup_client(&mut world.commands(), &self.config.clone());
}
}
}
impl Command for DisconnectCommand {
type Out = ();
fn apply(self, world: &mut World) {
world
.commands()
.queue(SessionDisconnectCommand::<Message>::default());
}
}
impl Command for PromoteToHostCommand {
type Out = ();
fn apply(self, world: &mut World) {
let promotion = SessionPromoteToHostCommand::<Message>::new(self.0, self.1);
world.commands().queue(promotion);
}
}
impl SyncWorld for &World {
fn sync_host_uuid(&self) -> Option<ClientId> {
self.resource::<Channel<Message>>().host_uuid()
}
fn sync_self_uuid(&self) -> ClientId {
self.resource::<Channel<Message>>().uuid()
}
fn sync_all_client_uuids(&self) -> HashSet<ClientId> {
self.resource::<Channel<Message>>().destinations()
}
fn events_queue_count(&self) -> usize {
let events = self.resource::<Messages<MessageReceivedEvent<Message>>>();
events.len()
}
}