bevy_sync 0.19.0

Plugin for synchronizing entities and components between server and its clients.
Documentation
use bevy_ecs::name::Name;
use bevy_reflect::DynamicTypePath;
use bevy_reflect::PartialReflect;
use std::{any::TypeId, collections::HashSet, error::Error};
use tracing::warn;

use crate::{
    SyncEntity,
    binreflect::reflect_to_bin,
    lib_priv::{SkinnedMeshSyncMapper, SyncTrackerRes},
    proto::Message,
};
use bevy_asset::{AssetId, Assets};
use bevy_ecs::{
    entity::Entity,
    hierarchy::ChildOf,
    reflect::{AppTypeRegistry, ReflectComponent},
    world::World,
};
use bevy_entity_uuid::EntityLookup;
use bevy_mesh::skinning::{SkinnedMesh, SkinnedMeshInverseBindposes};
use bevy_pbr::StandardMaterial;
use tracing::debug;

pub(crate) fn build_full_sync(world: &mut World) -> Result<Vec<Message>, Box<dyn Error>> {
    let mut result: Vec<Message> = Vec::new();
    check_entity_components(world, &mut result)?;
    check_parents(world, &mut result)?;
    check_materials(world, &mut result);
    Ok(result)
}

fn check_entity_components(world: &World, result: &mut Vec<Message>) -> Result<(), Box<dyn Error>> {
    let mut entity_ids_sent: HashSet<Entity> = HashSet::new();
    let track = world.resource::<SyncTrackerRes>();
    let entity_map = world.resource::<EntityLookup>();
    let registry = world.resource::<AppTypeRegistry>();
    let registry = registry.read();
    let sync_down_id = world
        .component_id::<SyncEntity>()
        .ok_or("SyncDown is not registered")?;
    for arch in world
        .archetypes()
        .iter()
        .filter(|arch| arch.contains(sync_down_id))
    {
        for arch_entity in arch.entities() {
            let entity = world.entity(arch_entity.id());
            let e_id = entity.id();
            if let Some(uuid) = entity_map.entity_to_uuid(&e_id)
                && !entity_ids_sent.contains(&e_id)
            {
                result.push(Message::EntitySpawn { uuid });
                entity_ids_sent.insert(e_id);
            }
        }

        for c_id in arch
            .components()
            .iter()
            .filter(|&c_id| track.registered_componets_for_sync.contains(c_id))
        {
            let c_exclude_id = track
                .sync_exclude_cid_of_component_cid
                .get(c_id)
                .ok_or("Sync component not setup correctly, missing SyncExclude<T>")?;
            if arch.contains(*c_exclude_id) {
                continue;
            }
            let c_info = world
                .components()
                .get_info(*c_id)
                .ok_or("component not found")?;
            let registration = registry
                .get(c_info.type_id().ok_or("not registered")?)
                .ok_or("not registered")?;
            let reflect_component = registration
                .data::<ReflectComponent>()
                .ok_or("missing #[reflect(Component)]")?;
            for arch_entity in arch.entities() {
                let entity = world.entity(arch_entity.id());
                let e_id = entity.id();
                let component = reflect_component.reflect(entity).ok_or("not registered")?;
                if component.type_id() == TypeId::of::<Name>() {
                    if let Some(uuid) = entity_map.entity_to_uuid(&e_id) {
                        let name = component.downcast_ref::<Name>().unwrap();
                        result.push(Message::ComponentNameUpdated {
                            uuid,
                            data: name.to_string(),
                        });
                    }
                    continue;
                }
                let type_name = if component.type_id() == TypeId::of::<SkinnedMesh>() {
                    SkinnedMeshSyncMapper::default()
                        .reflect_type_path()
                        .to_string()
                } else {
                    let refl = component.as_partial_reflect();
                    refl.get_represented_type_info().unwrap().type_path().into()
                };
                let component = if component.type_id() == TypeId::of::<SkinnedMesh>() {
                    debug!("Initial sync: Converting SkinnedMesh to SkinnedMeshSyncMapper");
                    SyncTrackerRes::to_skinned_mapper(
                        world.resource::<Assets<SkinnedMeshInverseBindposes>>(),
                        entity_map,
                        component.downcast_ref::<SkinnedMesh>().unwrap(),
                    )
                    .to_dynamic()
                } else {
                    component.to_dynamic()
                };
                let compo_bin = match reflect_to_bin(component.as_ref(), &registry) {
                    Ok(compo_bin) => compo_bin,
                    Err(e) => {
                        debug!(
                            "Initial sync: Could not send component {:?}, {:?}",
                            type_name, e
                        );
                        continue;
                    }
                };
                if let Some(uuid) = entity_map.entity_to_uuid(&e_id) {
                    result.push(Message::ComponentUpdated {
                        uuid,
                        name: type_name,
                        data: compo_bin,
                    });
                }
            }
        }
    }

    Ok(())
}

fn check_parents(world: &World, result: &mut Vec<Message>) -> Result<(), Box<dyn Error>> {
    let entity_map = world.resource::<EntityLookup>();
    let sync_down_id = world
        .component_id::<SyncEntity>()
        .ok_or("SyncDown is not registered")?;
    let parent_component_id = world
        .component_id::<SyncEntity>()
        .ok_or("Parent is not registered")?;
    for arch in world
        .archetypes()
        .iter()
        .filter(|arch| arch.contains(sync_down_id))
    {
        for _ in arch
            .components()
            .iter()
            .filter(|&c_id| *c_id == parent_component_id)
        {
            for arch_entity in arch.entities() {
                let entity = world.entity(arch_entity.id());
                let e_id = entity.id();
                let Some(parent) = entity.get::<ChildOf>() else {
                    continue;
                };
                if let Some(sid) = entity_map.entity_to_uuid(&e_id)
                    && let Some(pid) = entity_map.entity_to_uuid(&parent.parent())
                {
                    result.push(Message::EntityParented {
                        entity_uuid: sid,
                        parent_uuid: pid,
                    });
                }
            }
        }
    }
    Ok(())
}

fn check_materials(world: &World, result: &mut Vec<Message>) {
    let track = world.resource::<SyncTrackerRes>();
    let registry = world.resource::<AppTypeRegistry>();
    let registry = registry.read();
    if track.sync_materials {
        let materials = world.resource::<Assets<StandardMaterial>>();
        for (id, material) in materials.iter() {
            let AssetId::Uuid { uuid: id } = id else {
                continue;
            };
            let Ok(bin) = reflect_to_bin(material.as_partial_reflect(), &registry) else {
                warn!("Cannot serialize material {}", id);
                continue;
            };
            result.push(Message::StandardMaterialUpdated {
                uuid: id,
                material: bin,
            });
        }
    }
}