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(), ®istry) {
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(), ®istry) else {
warn!("Cannot serialize material {}", id);
continue;
};
result.push(Message::StandardMaterialUpdated {
uuid: id,
material: bin,
});
}
}
}