diff --git a/Cargo.toml b/Cargo.toml index 5e41be2..9776ea0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,12 +16,13 @@ include = ["/src", "/LICENSE-MIT", "/LICENSE-APACHE"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bevy = { version = "0.11.0" } -bevy_renet = "0.0.9" +bevy = { version = "0.12" } +bevy_renet = { git = "https://github.com/lucaspoffo/renet.git" } bincode = "1.3.3" serde = { version = "1.0.160", features = ["derive"] } [dev-dependencies] portpicker = "0.1.1" serial_test = "2.0.0" -bevy_editor_pls = { git = "https://github.com/jakobhellermann/bevy_editor_pls.git", branch = "main" } +bevy_editor_pls = { git = "https://github.com/jakobhellermann/bevy_editor_pls" } +uuid = "1.5.0" diff --git a/README.md b/README.md index d2e4279..43759b8 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Current state is in development. - [ ] Asset: Textures - [ ] Asset: Audio +**Asset are synchronized only if they are added to bevy by uuid.** + ## Examples Run both examples so the they connect to each other: diff --git a/examples/host.rs b/examples/host.rs index 2ae4529..d4dd0a3 100644 --- a/examples/host.rs +++ b/examples/host.rs @@ -3,12 +3,9 @@ use std::{ net::{IpAddr, Ipv4Addr}, }; -use bevy::{ - pbr::wireframe::{Wireframe, WireframeConfig}, - prelude::*, - render::primitives::Aabb, -}; +use bevy::{prelude::*, render::primitives::Aabb}; use bevy_sync::{ServerPlugin, SyncComponent, SyncExclude, SyncMark, SyncPlugin}; +use uuid::Uuid; fn main() { if env::var("RUST_LOG").is_err() { @@ -27,7 +24,6 @@ fn main() { host.sync_component::(); host.sync_component::(); host.sync_component::(); - host.sync_component::(); host.sync_component::(); host.sync_component::>(); host.sync_component::>(); @@ -38,17 +34,28 @@ fn main() { host.run(); } +trait AddByUuid { + fn addu(&mut self, asset: A) -> Handle; +} +impl AddByUuid for Assets { + fn addu(&mut self, asset: A) -> Handle { + let id = AssetId::Uuid { + uuid: Uuid::new_v4(), + }; + self.insert(id, asset); + Handle::::Weak(id) + } +} + fn load_world( mut commands: Commands, - mut wireframe_config: ResMut, mut meshes: ResMut>, mut materials: ResMut>, ) { - wireframe_config.global = false; commands.spawn(( PbrBundle { - mesh: meshes.add(shape::Plane::from_size(5.0).into()), - material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), + mesh: meshes.addu(shape::Plane::from_size(5.0).into()), + material: materials.addu(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }, SyncMark, @@ -57,12 +64,11 @@ fn load_world( )); commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), - material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + mesh: meshes.addu(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.addu(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() }, - Wireframe, SyncMark, Name::new("Cube"), )); diff --git a/src/bundle_fix.rs b/src/bundle_fix.rs index e9a1322..dcd0949 100644 --- a/src/bundle_fix.rs +++ b/src/bundle_fix.rs @@ -20,16 +20,17 @@ impl Plugin for BundleFixPlugin { fn fix_computed_visibility( mut cmd: Commands, - query: Query, Without)>, + query: Query, Without)>, ) { for e in query.iter() { - cmd.entity(e).insert(ComputedVisibility::default()); + cmd.entity(e).insert(ViewVisibility::default()); } } +#[allow(clippy::type_complexity)] fn fix_missing_global_transforms( mut cmd: Commands, - query: Query<(Entity, &Transform), Without>, + query: Query<(Entity, &Transform), (Added, Without)>, ) { for (e, &t) in query.iter() { cmd.entity(e).insert(GlobalTransform::from(t)); @@ -38,7 +39,7 @@ fn fix_missing_global_transforms( fn fix_missing_cubemap_frusta( mut cmd: Commands, - query: Query, Without)>, + query: Query, Without)>, ) { for e in query.iter() { cmd.entity(e).insert(CubemapFrusta::default()); @@ -47,7 +48,7 @@ fn fix_missing_cubemap_frusta( fn fix_missing_cubemap_visible_entities( mut cmd: Commands, - query: Query, Without)>, + query: Query, Without)>, ) { for e in query.iter() { cmd.entity(e).insert(CubemapVisibleEntities::default()); diff --git a/src/client/mod.rs b/src/client/mod.rs index f0c0221..ed2aceb 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -27,13 +27,11 @@ impl Plugin for ClientSyncPlugin { Update, client_connected .run_if(state_exists_and_equals(ClientState::Connecting)) - .run_if(bevy_renet::transport::client_connected()), ); app.add_systems( Update, client_connecting .run_if(state_exists_and_equals(ClientState::Disconnected)) - .run_if(bevy_renet::transport::client_connecting()), ); app.add_systems( Update, diff --git a/src/client/receiver.rs b/src/client/receiver.rs index 68851c3..06df62f 100644 --- a/src/client/receiver.rs +++ b/src/client/receiver.rs @@ -1,3 +1,5 @@ +use crate::logging::{log_message_received, Who}; + use super::*; pub(crate) fn poll_for_messages( @@ -22,13 +24,9 @@ fn receive_as_client( } fn client_received_a_message(msg: Message, track: &mut ResMut, cmd: &mut Commands) { + log_message_received(Who::Client, &msg); match msg { Message::EntitySpawn { id } => { - debug!( - "Client received of type EntitySpawn for server entity {}v{}", - id.index(), - id.generation() - ); if let Some(e_id) = track.server_to_client_entities.get(&id) { if cmd.get_entity(*e_id).is_some() { return; @@ -46,11 +44,6 @@ fn client_received_a_message(msg: Message, track: &mut ResMut, c server_entity_id: id, client_entity_id: back_id, } => { - debug!( - "Client received of type EntitySpawnBack for server entity {}v{}", - id.index(), - id.generation() - ); if let Some(mut e) = cmd.get_entity(back_id) { e.remove::().insert(SyncUp { server_entity_id: id, @@ -61,31 +54,35 @@ fn client_received_a_message(msg: Message, track: &mut ResMut, c server_entity_id: e_id, server_parent_id: p_id, } => { - let Some(&c_e_id) = track.server_to_client_entities.get(&e_id) else {return}; - let Some(&c_p_id) = track.server_to_client_entities.get(&p_id) else {return}; + let Some(&c_e_id) = track.server_to_client_entities.get(&e_id) else { + return; + }; + let Some(&c_p_id) = track.server_to_client_entities.get(&p_id) else { + return; + }; cmd.add(move |world: &mut World| { let mut entity = world.entity_mut(c_e_id); let opt_parent = entity.get::(); if opt_parent.is_none() || opt_parent.unwrap().get() != c_p_id { - entity.set_parent(p_id); + entity.set_parent(c_p_id); world.entity_mut(c_p_id).add_child(c_e_id); } }); } Message::EntityDelete { id } => { - debug!( - "Client received of type EntityDelete for server entity {}v{}", - id.index(), - id.generation() - ); - let Some(&e_id) = track.server_to_client_entities.get(&id) else {return}; - let Some(mut e) = cmd.get_entity(e_id) else {return}; + let Some(&e_id) = track.server_to_client_entities.get(&id) else { + return; + }; + let Some(mut e) = cmd.get_entity(e_id) else { + return; + }; e.despawn(); } Message::ComponentUpdated { id, name, data } => { - let Some(&e_id) = track.server_to_client_entities.get(&id) else {return}; - let mut entity = cmd.entity(e_id); - entity.add(move |_: Entity, world: &mut World| { + let Some(&e_id) = track.server_to_client_entities.get(&id) else { + return; + }; + cmd.add(move |world: &mut World| { SyncTrackerRes::apply_component_change_from_network(e_id, name, &data, world); }); } diff --git a/src/client/track.rs b/src/client/track.rs index c8ef43b..92d63ce 100644 --- a/src/client/track.rs +++ b/src/client/track.rs @@ -35,7 +35,9 @@ pub(crate) fn entity_parented_on_client( query_parent: Query<(Entity, &SyncUp), With>, ) { for (p, sup) in query.iter() { - let Ok(parent) = query_parent.get_component::(p.get()) else {return}; + let Ok(parent) = query_parent.get_component::(p.get()) else { + return; + }; client.send_message( DefaultChannel::ReliableOrdered, bincode::serialize(&Message::EntityParented { @@ -77,17 +79,13 @@ pub(crate) fn react_on_changed_components( mut track: ResMut, ) { let registry = registry.read(); - while let Some(change) = track.changed_components.pop_front() { - debug!( - "Component was changed on client: {}", - change.data.type_name() - ); - client.send_message( + while let Some(change) = track.changed_components_to_send.pop_front() { + client.send_message( DefaultChannel::ReliableOrdered, bincode::serialize(&Message::ComponentUpdated { id: change.change_id.id, name: change.change_id.name, - data: compo_to_bin(change.data.clone_value(), ®istry), + data: compo_to_bin(change.data.as_reflect(), ®istry), }) .unwrap(), ); @@ -102,23 +100,29 @@ pub(crate) fn react_on_changed_materials( mut events: EventReader>, ) { let registry = registry.read(); - for event in &mut events { + for event in &mut events.read() { match event { - AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { - let Some(material) = materials.get(handle) else { return; }; - if track.skip_network_handle_change(handle.id()) { + AssetEvent::Added { id } | AssetEvent::Modified { id } => { + let Some(material) = materials.get(*id) else { + return; + }; + let AssetId::Uuid { uuid: id } = id else { + return; + }; + if track.skip_network_handle_change(*id) { return; } client.send_message( DefaultChannel::ReliableOrdered, bincode::serialize(&Message::StandardMaterialUpdated { - id: handle.id(), - material: compo_to_bin(material.clone_value(), ®istry), + id: *id, + material: compo_to_bin(material.as_reflect(), ®istry), }) .unwrap(), ); } - AssetEvent::Removed { handle: _ } => {} + AssetEvent::Removed { id: _ } => {} + _ => (), } } } @@ -129,23 +133,29 @@ pub(crate) fn react_on_changed_meshes( assets: Res>, mut events: EventReader>, ) { - for event in &mut events { + for event in &mut events.read() { match event { - AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { - let Some(mesh) = assets.get(handle) else { return; }; - if track.skip_network_handle_change(handle.id()) { + AssetEvent::Added { id } | AssetEvent::Modified { id } => { + let Some(mesh) = assets.get(*id) else { + return; + }; + let AssetId::Uuid { uuid: id } = id else { + return; + }; + if track.skip_network_handle_change(*id) { return; } client.send_message( DefaultChannel::ReliableOrdered, bincode::serialize(&Message::MeshUpdated { - id: handle.id(), + id: *id, mesh: mesh_to_bin(mesh), }) .unwrap(), ); } - AssetEvent::Removed { handle: _ } => {} + AssetEvent::Removed { id: _ } => {} + _ => (), } } } diff --git a/src/lib.rs b/src/lib.rs index 0857bc2..0b19b16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,11 +24,11 @@ let mut app = App::new(); app.add_plugins(MinimalPlugins); // Either one of these two, if being server or client -app.add_plugin(ServerPlugin { ip: Ipv4Addr::LOCALHOST.into(), port: 5555 }); +app.add_plugins(ServerPlugin { ip: Ipv4Addr::LOCALHOST.into(), port: 5555 }); //app.add_plugin(ClientPlugin { ip: Ipv4Addr::LOCALHOST.into(), port: 5555 }); // Setup sync mechanics and which components will be synced -app.add_plugin(SyncPlugin); +app.add_plugins(SyncPlugin); app.sync_component::(); // Mark entity for sync with SyncMark component @@ -45,6 +45,7 @@ mod networking; mod proto; mod proto_serde; mod server; +mod logging; pub mod prelude { pub use super::{ @@ -106,7 +107,9 @@ pub struct SyncUp { } pub trait SyncComponent { - fn sync_component( + fn sync_component< + T: Component + TypePath + DynamicTypePath + Reflect + FromReflect + GetTypeRegistration, + >( &mut self, ) -> &mut Self; fn sync_materials(&mut self, enable: bool); diff --git a/src/lib_priv.rs b/src/lib_priv.rs index 725bc24..ea934ab 100644 --- a/src/lib_priv.rs +++ b/src/lib_priv.rs @@ -1,15 +1,15 @@ use std::{any::TypeId, collections::VecDeque}; use bevy::{ - asset::HandleId, ecs::component::ComponentId, prelude::*, - reflect::{FromReflect, GetTypeRegistration, Reflect, ReflectFromReflect}, - utils::{HashMap, HashSet}, + reflect::{DynamicTypePath, FromReflect, GetTypeRegistration, Reflect, ReflectFromReflect}, + utils::{HashMap, HashSet}, pbr::OpaqueRendererMethod, }; +use bevy_renet::renet::ClientId; use crate::{ - bundle_fix::BundleFixPlugin, client::ClientSyncPlugin, mesh_serde::bin_to_mesh, + bundle_fix::BundleFixPlugin, client::ClientSyncPlugin, mesh_serde::bin_to_mesh, proto::AssId, proto_serde::bin_to_compo, server::ServerSyncPlugin, ClientPlugin, ServerPlugin, SyncComponent, SyncDown, SyncExclude, SyncMark, SyncPlugin, SyncUp, }; @@ -25,37 +25,46 @@ pub(crate) struct ComponentChange { pub(crate) data: Box, } -// Keeps mapping of server entity ids to client entity ids. -// Key: server entity id. -// Value: client entity id. -// For servers, the map contains same key & value. #[derive(Resource, Default)] pub(crate) struct SyncTrackerRes { + /// Mapping of entity ids between server and clients. key: server, value: client pub(crate) server_to_client_entities: HashMap, - pub(crate) sync_components: HashSet, - pub(crate) exclude_components: HashMap, - pub(crate) changed_components: VecDeque, - pushed_component_from_network: HashSet, - pushed_handles_from_network: HashSet, - material_handles: HashMap>, - mesh_handles: HashMap>, - sync_materials: bool, - sync_meshes: bool, + + pub(crate) registered_componets_for_sync: HashSet, + /// Tracks SyncExcludes for component T. key: component id of T, value: component id of SyncExcdlude + pub(crate) sync_exclude_cid_of_component_cid: HashMap, + /// Queue of component changes to be sent over network + pub(crate) changed_components_to_send: VecDeque, + /// Pushed references (component and handle) that came from network and were applied in world, + /// so that in the next detect step they will be skipped and avoid ensless loop. + pub(crate) pushed_component_from_network: HashSet, + pub(crate) pushed_handles_from_network: HashSet, + + pub(crate) sync_materials: bool, + pub(crate) sync_meshes: bool, +} + +pub(crate) fn sync_material_enabled(tracker: Res) -> bool { + tracker.sync_materials +} + +pub(crate) fn sync_mesh_enabled(tracker: Res) -> bool { + tracker.sync_meshes } impl SyncTrackerRes { pub(crate) fn signal_component_changed(&mut self, id: Entity, data: Box) { - let name = data.type_name().into(); + let name = data.get_represented_type_info().unwrap().type_path().into(); let change_id = ComponentChangeId { id, name }; if self.pushed_component_from_network.contains(&change_id) { self.pushed_component_from_network.remove(&change_id); return; } - self.changed_components + self.changed_components_to_send .push_back(ComponentChange { change_id, data }); } - pub(crate) fn skip_network_handle_change(&mut self, id: HandleId) -> bool { + pub(crate) fn skip_network_handle_change(&mut self, id: AssId) -> bool { if self.pushed_handles_from_network.contains(&id) { self.pushed_handles_from_network.remove(&id); return true; @@ -72,17 +81,11 @@ impl SyncTrackerRes { let registry = world.resource::().clone(); let registry = registry.read(); let component_data = bin_to_compo(data, ®istry); - let registration = registry.get_with_name(name.as_str()).unwrap(); + let registration = registry.get_with_type_path(name.as_str()).unwrap(); let reflect_component = registration.data::().unwrap(); let previous_value = reflect_component.reflect(world.entity(e_id)); - if SyncTrackerRes::needs_to_change(previous_value, &*component_data) { - debug!( - "Changed component from network: {}v{} - {}", - e_id.index(), - e_id.generation(), - &name - ); - world + if equals(previous_value, &*component_data) { + world .resource_mut::() .pushed_component_from_network .insert(ComponentChangeId { id: e_id, name }); @@ -101,7 +104,7 @@ impl SyncTrackerRes { } pub(crate) fn apply_material_change_from_network( - id: HandleId, + id: AssId, material: &[u8], world: &mut World, ) { @@ -114,42 +117,28 @@ impl SyncTrackerRes { let component_data = bin_to_compo(material, ®istry); let mut materials = world.resource_mut::>(); let mat = *component_data.downcast::().unwrap(); - let handle = materials.set(id, mat); - // need to keep a reference somewhere else the material will be destroyed right away - world - .resource_mut::() - .material_handles - .insert(id, handle); + materials.insert(id, mat); } - pub(crate) fn apply_mesh_change_from_network(id: HandleId, mesh: &[u8], world: &mut World) { + pub(crate) fn apply_mesh_change_from_network(id: AssId, mesh: &[u8], world: &mut World) { world .resource_mut::() .pushed_handles_from_network .insert(id); let mut meshes = world.resource_mut::>(); let mesh = bin_to_mesh(mesh); - let handle = meshes.set(id, mesh); - // need to keep a reference somewhere else the material will be destroyed right away - world - .resource_mut::() - .mesh_handles - .insert(id, handle); - } - - fn needs_to_change(previous_value: Option<&dyn Reflect>, component_data: &dyn Reflect) -> bool { - if previous_value.is_none() { - return true; - } - !previous_value - .unwrap() - .reflect_partial_eq(component_data) - .unwrap_or(true) + meshes.insert(id, mesh); } +} - pub(crate) fn sync_materials_enabled(&self) -> bool { - self.sync_materials +fn equals(previous_value: Option<&dyn Reflect>, component_data: &dyn Reflect) -> bool { + if previous_value.is_none() { + return true; } + !previous_value + .unwrap() + .reflect_partial_eq(component_data) + .unwrap_or(true) } #[allow(clippy::type_complexity)] @@ -173,7 +162,9 @@ fn sync_detect_client( } impl SyncComponent for App { - fn sync_component( + fn sync_component< + T: Component + TypePath + DynamicTypePath + Reflect + FromReflect + GetTypeRegistration, + >( &mut self, ) -> &mut Self { self.register_type::(); @@ -181,8 +172,10 @@ impl SyncComponent for App { let c_id = self.world.init_component::(); let c_exclude_id = self.world.init_component::>(); let mut track = self.world.resource_mut::(); - track.sync_components.insert(c_id); - track.exclude_components.insert(c_id, c_exclude_id); + track.registered_componets_for_sync.insert(c_id); + track + .sync_exclude_cid_of_component_cid + .insert(c_id, c_exclude_id); self.add_systems(Update, sync_detect_server::); self.add_systems(Update, sync_detect_client::); @@ -213,6 +206,7 @@ fn setup_cascade_registrations>>(); app.register_type::(); app.register_type::(); + app.register_type::(); } if TypeId::of::() == TypeId::of::() { @@ -222,7 +216,7 @@ fn setup_cascade_registrations) -> bool { - tracker.sync_materials -} - -pub(crate) fn sync_mesh_enabled(tracker: Res) -> bool { - tracker.sync_meshes -} diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 0000000..04173ef --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,66 @@ +use bevy::prelude::*; + +use crate::proto::Message; + +#[derive(Debug)] +pub(crate) enum Who { + Server, + Client, +} + +pub(crate) fn log_message_received(from: Who, message: &Message) { + match message { + Message::EntitySpawn { id } => debug!( + "{:?} received EntitySpawn {{ id: {}v{} }}", + from, + id.index(), + id.generation() + ), + Message::EntityParented { + server_entity_id: eid, + server_parent_id: pid, + } => debug!( + "{:?} received EntityParented {{ eid: {}v{}, pid: {}v{} }}", + from, + eid.index(), + eid.generation(), + pid.index(), + pid.generation() + ), + Message::EntitySpawnBack { + server_entity_id: sid, + client_entity_id: cid, + } => debug!( + "{:?} received EntitySpawnBack {{sid: {}v{}, cid: {}v{}", + from, + sid.index(), + sid.generation(), + cid.index(), + cid.generation() + ), + Message::EntityDelete { id } => debug!( + "{:?} received EntityDelete {{ id: {}v{} }}", + from, + id.index(), + id.generation() + ), + Message::ComponentUpdated { id, name, data: _ } => { + debug!( + "{:?} received ComponentUpdated {{ id: {}v{}, name: {} }}", + from, + id.index(), + id.generation(), + name + ) + } + Message::StandardMaterialUpdated { id, material: _ } => { + debug!( + "{:?} received StandardMaterialUpdated {{ uuid: {} }}", + from, id + ) + } + Message::MeshUpdated { id, mesh: _ } => { + debug!("{:?} received MeshUpdated {{ uuid: {} }}", from, id) + } + } +} diff --git a/src/networking.rs b/src/networking.rs index 25b9575..ef5dcb0 100644 --- a/src/networking.rs +++ b/src/networking.rs @@ -36,16 +36,17 @@ fn create_server(ip: IpAddr, port: u16) -> NetcodeServerTransport { let socket = UdpSocket::bind((ip, port)).unwrap(); let server_addr = socket.local_addr().unwrap(); const MAX_CLIENTS: usize = 64; + let current_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap(); let server_config = ServerConfig { + current_time, max_clients: MAX_CLIENTS, protocol_id: PROTOCOL_ID, - public_addr: server_addr, + public_addresses: vec![server_addr], authentication: ServerAuthentication::Unsecure, }; - let current_time = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap(); - NetcodeServerTransport::new(current_time, server_config, socket).unwrap() + NetcodeServerTransport::new(server_config, socket).unwrap() } pub(crate) fn create_client(ip: IpAddr, port: u16) -> NetcodeClientTransport { diff --git a/src/proto.rs b/src/proto.rs index 9499edd..ca58789 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,9 +1,10 @@ -use bevy::{asset::HandleId, prelude::Entity}; +use bevy::{prelude::Entity, utils::Uuid}; use serde::{Deserialize, Serialize}; -type EntityId = Entity; +pub type EntityId = Entity; +pub type AssId = Uuid; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] #[repr(u8)] pub(crate) enum Message { EntitySpawn { @@ -26,11 +27,11 @@ pub(crate) enum Message { data: Vec, } = 5, StandardMaterialUpdated { - id: HandleId, + id: AssId, material: Vec, } = 6, MeshUpdated { - id: HandleId, + id: AssId, mesh: Vec, } = 7, } diff --git a/src/proto_serde.rs b/src/proto_serde.rs index a531eac..9d8fc12 100644 --- a/src/proto_serde.rs +++ b/src/proto_serde.rs @@ -1,94 +1,46 @@ -use std::any::type_name; - use bevy::reflect::{ serde::{ReflectSerializer, UntypedReflectDeserializer}, - DynamicStruct, Reflect, ReflectFromReflect, TypeRegistryInternal, + DynamicStruct, Reflect, ReflectFromReflect, TypeRegistry, }; use bincode::{DefaultOptions, Options}; -use serde::{ - de::{self, DeserializeSeed, Visitor}, - ser::{SerializeStruct, Serializer}, - Deserializer, Serialize, -}; +use serde::de::DeserializeSeed; -pub(crate) fn compo_to_bin(compo: Box, registry: &TypeRegistryInternal) -> Vec { - let serializer = ComponentData { - data: compo.clone_value(), - registry, - }; - bincode::serialize(&serializer).unwrap() +pub(crate) fn compo_to_bin(compo: &dyn Reflect, registry: &TypeRegistry) -> Vec { + let serializer = ReflectSerializer::new(compo, registry); + DefaultOptions::new() + .with_fixint_encoding() + .allow_trailing_bytes() + .serialize(&serializer) + .unwrap() } -pub(crate) fn bin_to_compo(data: &[u8], registry: &TypeRegistryInternal) -> Box { +pub(crate) fn bin_to_compo(data: &[u8], registry: &TypeRegistry) -> Box { + let reflect_deserializer = UntypedReflectDeserializer::new(registry); let binoptions = DefaultOptions::new() .with_fixint_encoding() .allow_trailing_bytes(); let mut bin_deser = bincode::Deserializer::from_slice(data, binoptions); - let deserializer = ComponentDataDeserializer { registry }; - let data = deserializer.deserialize(&mut bin_deser).unwrap(); - if !data.data.is::() { - return data.data; + let data = reflect_deserializer.deserialize(&mut bin_deser).unwrap(); + if !data.is::() { + return data; } - let data = data.data.downcast::().unwrap(); - let registration = registry.get_with_name(data.type_name()).unwrap(); + let data = data.downcast::().unwrap(); + let type_path = data.get_represented_type_info().unwrap().type_path(); + let registration = registry.get_with_type_path(type_path).unwrap(); let rfr = registry .get_type_data::(registration.type_id()) .unwrap(); rfr.from_reflect(&*data).unwrap() } -struct ComponentData<'a> { - data: Box, - registry: &'a TypeRegistryInternal, -} - -impl<'a> Serialize for ComponentData<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct(type_name::(), 1)?; - state.serialize_field( - "data", - &ReflectSerializer::new(self.data.as_reflect(), self.registry), - )?; - state.end() - } -} - -struct ComponentDataDeserializer<'a> { - registry: &'a TypeRegistryInternal, -} - -impl<'a: 'de, 'de: 'a> DeserializeSeed<'de> for ComponentDataDeserializer<'a> { - type Value = ComponentData<'a>; - - fn deserialize>(self, deserializer: D) -> Result { - let registry = self.registry; - let data = deserializer.deserialize_struct(type_name::(), &["data"], self)?; - Ok(ComponentData { data, registry }) - } -} - -impl<'a: 'de, 'de> Visitor<'de> for ComponentDataDeserializer<'a> { - type Value = Box; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str(type_name::()) - } - - fn visit_seq>(self, mut seq: A) -> Result { - seq.next_element_seed(UntypedReflectDeserializer::new(self.registry))? - .ok_or_else(|| de::Error::invalid_type(de::Unexpected::NewtypeVariant, &self)) - } -} - #[cfg(test)] mod test { + use super::*; use bevy::{ + pbr::OpaqueRendererMethod, prelude::*, - reflect::{GetTypeRegistration, Reflect, ReflectFromReflect, TypeRegistryInternal}, + reflect::{GetTypeRegistration, Reflect, ReflectFromReflect, TypeRegistry}, }; use serde::{Deserialize, Serialize}; @@ -104,10 +56,10 @@ mod test { where T: Reflect + GetTypeRegistration + PartialEq + std::fmt::Debug, { - let mut registry = TypeRegistryInternal::default(); + let mut registry = TypeRegistry::default(); registry.register::(); - let data = compo_to_bin(compo_orig.clone_value(), ®istry); + let data = compo_to_bin(compo_orig.as_reflect(), ®istry); let compo_result = bin_to_compo(&data, ®istry); let compo_result = compo_result.downcast::().unwrap(); @@ -127,7 +79,7 @@ mod test { fn compo_data_serde_bevy_native_component() { let compo_orig = Transform::default(); - let mut registry = TypeRegistryInternal::default(); + let mut registry = TypeRegistry::default(); registry.register::(); registry.register::(); registry.register::(); @@ -135,11 +87,68 @@ mod test { registry.register_type_data::(); registry.register_type_data::(); - let data = compo_to_bin(compo_orig.clone_value(), ®istry); + let data = compo_to_bin(compo_orig.as_reflect(), ®istry); let compo_result = bin_to_compo(&data, ®istry); let compo_result = compo_result.downcast::().unwrap(); assert_eq!(*compo_result, compo_orig); } + + #[test] + fn material_serde() { + let material_orig = StandardMaterial { + base_color: Color::RED, + ..StandardMaterial::default() + }; + + let mut registry = TypeRegistry::default(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::>(); + registry.register::>>(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register_type_data::(); + + let data = compo_to_bin(material_orig.as_reflect(), ®istry); + + let result = bin_to_compo(&data, ®istry); + let result = result.downcast::().unwrap(); + + assert_eq!(result.base_color, material_orig.base_color); + } + + #[test] + fn reflect_material_no_dependencies() { + let compo = StandardMaterial { + base_color: Color::RED, + ..StandardMaterial::default() + }; + + let mut registry = TypeRegistry::default(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::>(); + registry.register::>>(); + registry.register::(); + registry.register::(); + registry.register::(); + + // compo_to_bin inlined + let serializer = ReflectSerializer::new(compo.as_reflect(), ®istry); + let result = DefaultOptions::new() + .with_fixint_encoding() + .allow_trailing_bytes() + .serialize(&serializer) + .unwrap(); + + let result = bin_to_compo(&result, ®istry); + let result = result.downcast::().unwrap(); + assert_eq!(compo.base_color, result.base_color); + } + } diff --git a/src/server/initial_sync.rs b/src/server/initial_sync.rs index 50a5cc3..53e1977 100644 --- a/src/server/initial_sync.rs +++ b/src/server/initial_sync.rs @@ -1,35 +1,54 @@ +use std::error::Error; + use bevy::{prelude::*, utils::HashSet}; -use bevy_renet::renet::{DefaultChannel, RenetServer}; +use bevy_renet::renet::{ClientId, DefaultChannel, RenetServer}; use crate::{ lib_priv::SyncTrackerRes, mesh_serde::mesh_to_bin, proto::Message, proto_serde::compo_to_bin, SyncDown, }; -pub(crate) fn send_initial_sync(client_id: u64, world: &mut World) { - info!("Sending initial sync to client id: {}", client_id); +pub(crate) fn send_initial_sync(client_id: ClientId, world: &mut World) { + info!("Sending initial sync to client id {}", client_id); // exclusive access to world while looping through all objects, this can be blocking/freezing for the server - let mut initial_sync = build_initial_sync(world); + let mut initial_sync = match build_initial_sync(world) { + Ok(initial_sync) => initial_sync, + Err(err) => { + warn!( + "Failed initial sync to client id {} because {}", + client_id, err + ); + return; + } + }; let mut server = world.resource_mut::(); debug!("Initial sync size: {}", initial_sync.len()); for msg in initial_sync.drain(..) { - let msg_bin = bincode::serialize(&msg).unwrap(); + let Ok(msg_bin) = bincode::serialize(&msg) else { + warn!("Could not deserialize {:?}", msg); + continue; + }; server.send_message(client_id, DefaultChannel::ReliableOrdered, msg_bin); } } -pub(crate) fn build_initial_sync(world: &World) -> Vec { - let mut entity_ids_sent: HashSet = HashSet::new(); +fn build_initial_sync(world: &World) -> Result, Box> { let mut result: Vec = Vec::new(); + check_entity_components(world, &mut result)?; + check_parents(world, &mut result)?; + check_materials(world, &mut result)?; + check_meshes(world, &mut result)?; + Ok(result) +} + +fn check_entity_components(world: &World, result: &mut Vec) -> Result<(), Box> { + let mut entity_ids_sent: HashSet = HashSet::new(); let track = world.resource::(); let registry = world.resource::(); let registry = registry.read(); let sync_down_id = world .component_id::() - .expect("SyncDown is not registered"); - let parent_component_id = world - .component_id::() - .expect("Parent is not registered"); + .ok_or("SyncDown is not registered")?; for arch in world .archetypes() .iter() @@ -46,31 +65,31 @@ pub(crate) fn build_initial_sync(world: &World) -> Vec { for c_id in arch .components() - .filter(|&c_id| track.sync_components.contains(&c_id)) + .filter(|&c_id| track.registered_componets_for_sync.contains(&c_id)) { let c_exclude_id = track - .exclude_components + .sync_exclude_cid_of_component_cid .get(&c_id) - .expect("Sync component not setup correctly, missing SyncExclude"); + .ok_or("Sync component not setup correctly, missing SyncExclude")?; if arch.contains(*c_exclude_id) { continue; } let c_info = world .components() .get_info(c_id) - .expect("component not found"); + .ok_or("component not found")?; let type_name = c_info.name(); let registration = registry - .get(c_info.type_id().expect("not registered")) - .expect("not registered"); + .get(c_info.type_id().ok_or("not registered")?) + .ok_or("not registered")?; let reflect_component = registration .data::() - .expect("missing #[reflect(Component)]"); + .ok_or("missing #[reflect(Component)]")?; for arch_entity in arch.entities() { let entity = world.entity(arch_entity.entity()); let e_id = entity.id(); - let component = reflect_component.reflect(entity).expect("not registered"); - let compo_bin = compo_to_bin(component.clone_value(), ®istry); + let component = reflect_component.reflect(entity).ok_or("not registered")?; + let compo_bin = compo_to_bin(component.as_reflect(), ®istry); result.push(Message::ComponentUpdated { id: e_id, name: type_name.into(), @@ -80,7 +99,16 @@ pub(crate) fn build_initial_sync(world: &World) -> Vec { } } - // Iterate again after all entities have been sent to find parenting to avoid missed parent ids + Ok(()) +} + +fn check_parents(world: &World, result: &mut Vec) -> Result<(), Box> { + let sync_down_id = world + .component_id::() + .ok_or("SyncDown is not registered")?; + let parent_component_id = world + .component_id::() + .ok_or("Parent is not registered")?; for arch in world .archetypes() .iter() @@ -93,7 +121,9 @@ pub(crate) fn build_initial_sync(world: &World) -> Vec { for arch_entity in arch.entities() { let entity = world.entity(arch_entity.entity()); let e_id = entity.id(); - let Some(parent) = entity.get::() else {continue}; + let Some(parent) = entity.get::() else { + continue; + }; result.push(Message::EntityParented { server_entity_id: e_id, server_parent_id: parent.get(), @@ -101,26 +131,41 @@ pub(crate) fn build_initial_sync(world: &World) -> Vec { } } } + Ok(()) +} - if track.sync_materials_enabled() { +fn check_materials(world: &World, result: &mut Vec) -> Result<(), Box> { + let track = world.resource::(); + let registry = world.resource::(); + let registry = registry.read(); + if track.sync_materials { let materials = world.resource::>(); for (id, material) in materials.iter() { + let AssetId::Uuid { uuid: id } = id else { + continue; + }; result.push(Message::StandardMaterialUpdated { id, - material: compo_to_bin(material.clone_value(), ®istry), + material: compo_to_bin(material.as_reflect(), ®istry), }); } } + Ok(()) +} - if track.sync_materials_enabled() { +fn check_meshes(world: &World, result: &mut Vec) -> Result<(), Box> { + let track = world.resource::(); + if track.sync_meshes { let meshes = world.resource::>(); for (id, mesh) in meshes.iter() { + let AssetId::Uuid { uuid: id } = id else { + continue; + }; result.push(Message::MeshUpdated { id, mesh: mesh_to_bin(mesh), }); } } - - result + Ok(()) } diff --git a/src/server/mod.rs b/src/server/mod.rs index aadd1fe..db55cff 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -69,12 +69,12 @@ impl Plugin for ServerSyncPlugin { } fn client_connected(mut cmd: Commands, mut server_events: EventReader) { - for event in server_events.iter() { + for event in server_events.read() { match event { ServerEvent::ClientConnected { client_id } => { + let client_id = *client_id; info!("Client connected with client id: {}", client_id); - let c_id = *client_id; - cmd.add(move |world: &mut World| send_initial_sync(c_id, world)); + cmd.add(move |world: &mut World| send_initial_sync(client_id, world)); } ServerEvent::ClientDisconnected { client_id, reason } => { info!( diff --git a/src/server/receiver.rs b/src/server/receiver.rs index 5d6663c..6282534 100644 --- a/src/server/receiver.rs +++ b/src/server/receiver.rs @@ -1,3 +1,7 @@ +use bevy_renet::renet::ClientId; + +use crate::logging::{log_message_received, Who}; + use super::*; pub(crate) fn poll_for_messages( @@ -25,18 +29,14 @@ fn receive_as_server( } fn server_received_a_message( - client_id: u64, + client_id: ClientId, msg: Message, track: &mut ResMut, cmd: &mut Commands, ) { + log_message_received(Who::Server, &msg); match msg { Message::EntitySpawn { id } => { - debug!( - "Server received message of type EntitySpawn for entity {}v{}", - id.index(), - id.generation() - ); let e_id = cmd .spawn(SyncClientGeneratedEntity { client_id, @@ -51,7 +51,9 @@ fn server_received_a_message( server_parent_id: p_id, } => { cmd.add(move |world: &mut World| { - let Some(mut entity) = world.get_entity_mut(e_id) else {return}; + let Some(mut entity) = world.get_entity_mut(e_id) else { + return; + }; let opt_parent = entity.get::(); if opt_parent.is_none() || opt_parent.unwrap().get() != p_id { entity.set_parent(p_id); @@ -68,11 +70,6 @@ fn server_received_a_message( }); } Message::EntityDelete { id } => { - debug!( - "Server received message of type EntityDelete for entity {}v{}", - id.index(), - id.generation() - ); if let Some(mut e) = cmd.get_entity(id) { e.despawn(); } @@ -83,9 +80,10 @@ fn server_received_a_message( client_entity_id: _, } => {} Message::ComponentUpdated { id, name, data } => { - let Some(&e_id) = track.server_to_client_entities.get(&id) else {return}; - let mut entity = cmd.entity(e_id); - entity.add(move |_: Entity, world: &mut World| { + let Some(&e_id) = track.server_to_client_entities.get(&id) else { + return; + }; + cmd.add(move |world: &mut World| { let changed = SyncTrackerRes::apply_component_change_from_network( e_id, name.clone(), @@ -123,7 +121,11 @@ fn server_received_a_message( } } -fn repeat_except_for_client(msg_client_id: u64, server: &mut RenetServer, msg: &Message) { +fn repeat_except_for_client( + msg_client_id: bevy_renet::renet::ClientId, + server: &mut RenetServer, + msg: &Message, +) { for client_id in server.clients_id().into_iter() { if client_id == msg_client_id { continue; diff --git a/src/server/track.rs b/src/server/track.rs index e752fca..6bd1964 100644 --- a/src/server/track.rs +++ b/src/server/track.rs @@ -24,12 +24,7 @@ pub(crate) fn entity_created_on_server( mut query: Query>, ) { for id in query.iter_mut() { - debug!( - "New entity created on server: {}v{}", - id.index(), - id.generation() - ); - for client_id in server.clients_id().into_iter() { + for client_id in server.clients_id().into_iter() { server.send_message( client_id, DefaultChannel::ReliableOrdered, @@ -66,12 +61,7 @@ pub(crate) fn reply_back_to_client_generated_entity( mut query: Query<(Entity, &SyncClientGeneratedEntity), Added>, ) { for (entity_id, marker_component) in query.iter_mut() { - debug!( - "Replying to client generated entity for: {}v{}", - entity_id.index(), - entity_id.generation() - ); - server.send_message( + server.send_message( marker_component.client_id, DefaultChannel::ReliableOrdered, bincode::serialize(&Message::EntitySpawnBack { @@ -111,11 +101,6 @@ pub(crate) fn entity_removed_from_server( } }); for &id in despawned_entities.iter() { - debug!( - "Entity was removed from server: {}v{}", - id.index(), - id.generation() - ); for cid in server.clients_id().into_iter() { server.send_message( cid, @@ -132,11 +117,7 @@ pub(crate) fn react_on_changed_components( mut track: ResMut, ) { let registry = registry.read(); - while let Some(change) = track.changed_components.pop_front() { - debug!( - "Component was changed on server: {}", - change.data.type_name() - ); + while let Some(change) = track.changed_components_to_send.pop_front() { for cid in server.clients_id().into_iter() { server.send_message( cid, @@ -144,7 +125,7 @@ pub(crate) fn react_on_changed_components( bincode::serialize(&Message::ComponentUpdated { id: change.change_id.id, name: change.change_id.name.clone(), - data: compo_to_bin(change.data.clone_value(), ®istry), + data: compo_to_bin(change.data.as_reflect(), ®istry), }) .unwrap(), ); @@ -160,11 +141,16 @@ pub(crate) fn react_on_changed_materials( mut events: EventReader>, ) { let registry = registry.read(); - for event in &mut events { + for event in &mut events.read() { match event { - AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { - let Some(material) = materials.get(handle) else { return; }; - if track.skip_network_handle_change(handle.id()) { + AssetEvent::Added { id } | AssetEvent::Modified { id } => { + let Some(material) = materials.get(*id) else { + return; + }; + let AssetId::Uuid { uuid: id } = id else { + return; + }; + if track.skip_network_handle_change(*id) { return; } for cid in server.clients_id().into_iter() { @@ -172,14 +158,15 @@ pub(crate) fn react_on_changed_materials( cid, DefaultChannel::ReliableOrdered, bincode::serialize(&Message::StandardMaterialUpdated { - id: handle.id(), - material: compo_to_bin(material.clone_value(), ®istry), + id: *id, + material: compo_to_bin(material.as_reflect(), ®istry), }) .unwrap(), ); } } - AssetEvent::Removed { handle: _ } => {} + AssetEvent::Removed { id: _ } => {} + _ => (), } } } @@ -190,11 +177,16 @@ pub(crate) fn react_on_changed_meshes( assets: Res>, mut events: EventReader>, ) { - for event in &mut events { + for event in &mut events.read() { match event { - AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { - let Some(mesh) = assets.get(handle) else { return; }; - if track.skip_network_handle_change(handle.id()) { + AssetEvent::Added { id } | AssetEvent::Modified { id } => { + let Some(mesh) = assets.get(*id) else { + return; + }; + let AssetId::Uuid { uuid: id } = id else { + return; + }; + if track.skip_network_handle_change(*id) { return; } for cid in server.clients_id().into_iter() { @@ -202,14 +194,15 @@ pub(crate) fn react_on_changed_meshes( cid, DefaultChannel::ReliableOrdered, bincode::serialize(&Message::MeshUpdated { - id: handle.id(), + id: *id, mesh: mesh_to_bin(mesh), }) .unwrap(), ); } } - AssetEvent::Removed { handle: _ } => {} + AssetEvent::Removed { id: _ } => {} + _ => (), } } } diff --git a/tests/assert/mod.rs b/tests/assert/mod.rs index b8d3049..0ea1c23 100644 --- a/tests/assert/mod.rs +++ b/tests/assert/mod.rs @@ -1,8 +1,8 @@ -use bevy::{asset::HandleId, prelude::*}; +use bevy::prelude::*; use bevy_renet::renet::{DefaultChannel, RenetClient, RenetServer}; use bevy_sync::{SyncDown, SyncUp}; -use crate::setup::{MySynched, TestEnv}; +use crate::setup::{sample_mesh, MySynched, TestEnv}; #[allow(dead_code)] pub(crate) fn entities_in_sync(env: &mut TestEnv, _: T, entity_count: u32) { @@ -89,18 +89,26 @@ pub(crate) fn get_first_entity_component(app: &mut App) -> Option< } #[allow(dead_code)] -pub(crate) fn material_has_color(app: &mut App, id: HandleId, color: Color) { +pub(crate) fn material_has_color(app: &mut App, id: AssetId, color: Color) { let materials = app.world.resource_mut::>(); - let handle = materials.get_handle(id); - let material = materials.get(&handle).unwrap(); + let material = materials.get(id).unwrap(); assert_eq!(material.base_color, color); } #[allow(dead_code)] -pub(crate) fn assets_has_mesh(app: &mut App, id: HandleId) { +pub(crate) fn assets_has_sample_mesh(app: &mut App, id: AssetId) { let meshes = app.world.resource_mut::>(); - let handle = meshes.get_handle(id); - let _ = meshes.get(&handle).unwrap(); + let mesh = meshes.get(id).unwrap(); + let sample = sample_mesh(); + assert_eq!( + mesh.attribute(Mesh::ATTRIBUTE_POSITION) + .unwrap() + .get_bytes(), + sample + .attribute(Mesh::ATTRIBUTE_POSITION) + .unwrap() + .get_bytes() + ); } #[allow(dead_code)] diff --git a/tests/asset_sync.rs b/tests/asset_sync.rs index c76181e..da6556b 100644 --- a/tests/asset_sync.rs +++ b/tests/asset_sync.rs @@ -1,14 +1,11 @@ mod assert; mod setup; -use assert::{assets_has_mesh, material_has_color}; -use bevy::{ - prelude::*, - render::{mesh::Indices, render_resource::PrimitiveTopology}, -}; +use assert::{material_has_color, assets_has_sample_mesh}; +use bevy::prelude::*; use bevy_sync::SyncComponent; use serial_test::serial; -use setup::TestRun; +use setup::{spawn_new_material, spawn_new_mesh, TestRun}; #[test] #[serial] @@ -20,17 +17,8 @@ fn sync_material_from_server() { env.server.sync_materials(true); }, |env| { - let s = &mut env.server; - let mut materials = s.world.resource_mut::>(); - let material = materials.add(StandardMaterial { - base_color: Color::RED, - ..Default::default() - }); - - let id = material.id(); - s.world.spawn(material); - - id + let app = &mut env.server; + spawn_new_material(app) }, |env, _, id| { material_has_color(&mut env.clients[0], id, Color::RED); @@ -50,16 +38,7 @@ fn sync_material_from_client() { }, |env| { let app = &mut env.clients[0]; - let mut materials = app.world.resource_mut::>(); - let material = materials.add(StandardMaterial { - base_color: Color::RED, - ..Default::default() - }); - - let id = material.id(); - app.world.spawn(material); - - id + spawn_new_material(app) }, |env, _, id| { material_has_color(&mut env.clients[0], id, Color::RED); @@ -79,16 +58,7 @@ fn sync_material_from_client_to_client_across_server() { }, |env| { let app = &mut env.clients[0]; - let mut materials = app.world.resource_mut::>(); - let material = materials.add(StandardMaterial { - base_color: Color::RED, - ..Default::default() - }); - - let id = material.id(); - app.world.spawn(material); - - id + spawn_new_material(app) }, |env, _, id| { material_has_color(&mut env.clients[0], id, Color::RED); @@ -108,16 +78,10 @@ fn test_mesh_transferred_from_server() { }, |env| { let app = &mut env.server; - let mut meshes = app.world.resource_mut::>(); - let mesh = meshes.add(sample_mesh()); - - let id = mesh.id(); - app.world.spawn(mesh); - - id + spawn_new_mesh(app) }, |env, _, id| { - assets_has_mesh(&mut env.clients[0], id); + assets_has_sample_mesh(&mut env.clients[0], id); }, ); } @@ -134,30 +98,10 @@ fn test_mesh_transferred_from_client() { }, |env| { let app = &mut env.clients[0]; - let mut meshes = app.world.resource_mut::>(); - let mesh = meshes.add(sample_mesh()); - - let id = mesh.id(); - app.world.spawn(mesh); - - id + spawn_new_mesh(app) }, |env, _, id| { - assets_has_mesh(&mut env.server, id); + assets_has_sample_mesh(&mut env.server, id); }, ); } - -fn sample_mesh() -> Mesh { - let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); - mesh.insert_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![[0., 0., 0.], [1., 2., 1.], [2., 0., 0.]], - ); - mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0., 1., 0.]; 3]); - mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0., 0.]; 3]); - mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, vec![[0., 1., 0., 0.]; 3]); - mesh.set_indices(Some(Indices::U32(vec![0, 2, 1]))); - - mesh -} diff --git a/tests/component_sync.rs b/tests/component_sync.rs index 8400083..ff7583f 100644 --- a/tests/component_sync.rs +++ b/tests/component_sync.rs @@ -233,7 +233,7 @@ fn test_auto_spawn_for_computed_visibility() { }, |env, _, _| { let _ = get_first_entity_component::(&mut env.clients[0]).unwrap(); - let _ = get_first_entity_component::(&mut env.clients[0]).unwrap(); + let _ = get_first_entity_component::(&mut env.clients[0]).unwrap(); }, ); } diff --git a/tests/initial_sync.rs b/tests/initial_sync.rs index 6682783..8d1a79a 100644 --- a/tests/initial_sync.rs +++ b/tests/initial_sync.rs @@ -1,15 +1,11 @@ mod assert; mod setup; -use assert::{assets_has_mesh, material_has_color}; -use bevy::{ - asset::HandleId, - prelude::*, - render::{mesh::Indices, render_resource::PrimitiveTopology}, -}; +use assert::{assets_has_sample_mesh, material_has_color}; +use bevy::prelude::*; use bevy_sync::{SyncComponent, SyncExclude, SyncMark}; use serial_test::serial; -use setup::{MySynched, TestEnv, TestRun}; +use setup::{spawn_new_material, spawn_new_mesh, MySynched, TestEnv, TestRun}; use crate::{assert::count_entities_with_component, setup::MySynched2}; @@ -28,24 +24,14 @@ fn test_initial_world_sync_sent_from_server() { let mut e = env.server.world.entity_mut(e_id); e.insert(MySynched { value: 7 }); - let mut materials = env.server.world.resource_mut::>(); - let material = materials.add(StandardMaterial { - base_color: Color::RED, - ..Default::default() - }); - let id = material.id(); - env.server.world.spawn(material); - let mut meshes = env.server.world.resource_mut::>(); - let mesh = meshes.add(sample_mesh()); - - let m_id = mesh.id(); - env.server.world.spawn(mesh); + let id = spawn_new_material(&mut env.server); + let m_id = spawn_new_mesh(&mut env.server); (1, id, m_id) }, TestRun::no_setup, - |env, (entity_count, id, m_id): (u32, HandleId, HandleId), _| { + |env, (entity_count, id, m_id): (u32, AssetId, AssetId), _| { assert::initial_sync_for_client_happened( &mut env.server, &mut env.clients[0], @@ -55,7 +41,7 @@ fn test_initial_world_sync_sent_from_server() { assert::no_messages_left_for_client(&mut env.clients[0]); material_has_color(&mut env.clients[0], id, Color::RED); - assets_has_mesh(&mut env.clients[0], m_id); + assets_has_sample_mesh(&mut env.clients[0], m_id); }, ); } @@ -74,28 +60,20 @@ fn test_init_sync_multiple_clients() { let mut e = env.server.world.entity_mut(e_id); e.insert(MySynched { value: 7 }); - let mut materials = env.server.world.resource_mut::>(); - let material = materials.add(StandardMaterial { - base_color: Color::RED, - ..Default::default() - }); - let id = material.id(); - env.server.world.spawn(material); - - let mut meshes = env.server.world.resource_mut::>(); - let mesh = meshes.add(sample_mesh()); - let m_id = mesh.id(); - env.server.world.spawn(mesh); + let id = spawn_new_material(&mut env.server); + let m_id = spawn_new_mesh(&mut env.server); (1, id, m_id) }, TestRun::no_setup, - |env: &mut TestEnv, (entity_count, id, m_id): (u32, HandleId, HandleId), _| { + |env: &mut TestEnv, + (entity_count, id, m_id): (u32, AssetId, AssetId), + _| { for capp in &mut env.clients { assert::initial_sync_for_client_happened(&mut env.server, capp, entity_count); material_has_color(capp, id, Color::RED); - assets_has_mesh(capp, m_id); + assets_has_sample_mesh(capp, m_id); } assert::no_messages_left_for_server(&mut env.server); @@ -132,20 +110,6 @@ fn test_initial_world_sync_not_transfer_excluded_components() { ); } -fn sample_mesh() -> Mesh { - let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); - mesh.insert_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![[0., 0., 0.], [1., 2., 1.], [2., 0., 0.]], - ); - mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0., 1., 0.]; 3]); - mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0., 0.]; 3]); - mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, vec![[0., 1., 0., 0.]; 3]); - mesh.set_indices(Some(Indices::U32(vec![0, 2, 1]))); - - mesh -} - #[test] #[serial] fn test_initial_with_parenting() { diff --git a/tests/parent_sync.rs b/tests/parent_sync.rs index f428c54..e2f4766 100644 --- a/tests/parent_sync.rs +++ b/tests/parent_sync.rs @@ -107,9 +107,3 @@ fn test_entity_parent_is_transferred_from_client() { }, ); } - -#[test] -#[serial] -fn test_mesh_transferred_from_server_to_client() { - TestRun::default().run(1, |_| {}, |_| {}, |_, _, _| {}); -} diff --git a/tests/setup/mod.rs b/tests/setup/mod.rs index 6325739..f3979fe 100644 --- a/tests/setup/mod.rs +++ b/tests/setup/mod.rs @@ -7,8 +7,10 @@ use std::{ use bevy::{ pbr::PbrPlugin, prelude::*, - reflect::{FromReflect, GetTypeRegistration, Reflect}, + reflect::{DynamicTypePath, FromReflect, GetTypeRegistration, Reflect}, + render::{mesh::Indices, render_resource::PrimitiveTopology}, transform::TransformBundle, + utils::Uuid, MinimalPlugins, }; use bevy_renet::renet::RenetClient; @@ -67,7 +69,9 @@ impl TestEnv { } #[allow(dead_code)] - pub(crate) fn setup_registration( + pub(crate) fn setup_registration< + T: Component + TypePath + DynamicTypePath + Reflect + FromReflect + GetTypeRegistration, + >( &mut self, ) { self.server.sync_component::(); @@ -95,16 +99,16 @@ impl TestRun { #[allow(dead_code)] pub(crate) fn no_setup(_: &mut TestEnv) {} - pub(crate) fn run( + pub(crate) fn run( &self, client_count: u32, - mut pre_setup: F0, - mut setup: F1, + mut pre_connect: F0, + mut post_connect: F1, mut assertion: F2, ) where - F0: FnMut(&mut TestEnv) -> T1, - F1: FnMut(&mut TestEnv) -> T2, - F2: FnMut(&mut TestEnv, T1, T2), + F0: FnMut(&mut TestEnv) -> T0, + F1: FnMut(&mut TestEnv) -> T1, + F2: FnMut(&mut TestEnv, T0, T1), { let mut test_run = TestEnv { server: create_server().unwrap(), @@ -114,11 +118,11 @@ impl TestRun { test_run.clients.push(create_client().unwrap()); } - let x = pre_setup(&mut test_run); + let x = pre_connect(&mut test_run); connect_envs(self, &mut test_run.server, &mut test_run.clients).unwrap(); - let y = setup(&mut test_run); + let y = post_connect(&mut test_run); let mut count = 0; while count < self.updates_per_run { @@ -149,8 +153,8 @@ fn create_client() -> Result> { fn add_plugins(app: &mut App) { app.add_plugins(MinimalPlugins); app.add_plugins(AssetPlugin::default()); - app.add_asset::().add_debug_asset::(); - app.add_asset::().add_debug_asset::(); + app.init_asset::(); + app.init_asset::(); app.add_plugins(PbrPlugin::default()); app.add_plugins(SyncPlugin); @@ -194,3 +198,47 @@ fn wait_until_connected( Err(TestError("Client did not connect.".into()).into()) } + +#[allow(dead_code)] +pub(crate) fn spawn_new_material(app: &mut App) -> AssetId { + let mut materials = app.world.resource_mut::>(); + let id = Uuid::new_v4(); + let material = StandardMaterial { + base_color: Color::RED, + ..Default::default() + }; + let handle = Handle::::Weak(id.into()); + materials.insert(id, material); + + app.world.spawn(handle); + + id.into() +} + +#[allow(dead_code)] +pub(crate) fn spawn_new_mesh(app: &mut App) -> AssetId { + let mut meshes = app.world.resource_mut::>(); + let id = Uuid::new_v4(); + let mesh = sample_mesh(); + let handle = Handle::::Weak(id.into()); + meshes.insert(id, mesh); + + app.world.spawn(handle); + + id.into() +} + +#[allow(dead_code)] +pub(crate) fn sample_mesh() -> Mesh { + let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); + mesh.insert_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![[0., 0., 0.], [1., 2., 1.], [2., 0., 0.]], + ); + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0., 1., 0.]; 3]); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0., 0.]; 3]); + mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, vec![[0., 1., 0., 0.]; 3]); + mesh.set_indices(Some(Indices::U32(vec![0, 2, 1]))); + + mesh +}