diff --git a/tests/update_replication.rs b/tests/changes.rs similarity index 97% rename from tests/update_replication.rs rename to tests/changes.rs index ba18a6f7..a51e5ac1 100644 --- a/tests/update_replication.rs +++ b/tests/changes.rs @@ -162,7 +162,7 @@ fn with_insertion() { }), )) .replicate::() - .replicate::(); + .replicate::(); } server_app.connect_client(&mut client_app); @@ -179,7 +179,7 @@ fn with_insertion() { let mut server_entity = server_app.world.entity_mut(server_entity); server_entity.get_mut::().unwrap().0 = true; - server_entity.insert(TableComponent); + server_entity.insert(DummyComponent); server_app.update(); server_app.exchange_with_client(&mut client_app); @@ -187,7 +187,7 @@ fn with_insertion() { let component = client_app .world - .query_filtered::<&BoolComponent, With>() + .query_filtered::<&BoolComponent, With>() .single(&client_app.world); assert!(component.0); } @@ -204,8 +204,7 @@ fn with_despawn() { ..Default::default() }), )) - .replicate::() - .replicate::(); + .replicate::(); } server_app.connect_client(&mut client_app); @@ -384,7 +383,7 @@ fn acknowledgment() { } #[derive(Component, Deserialize, Serialize)] -struct TableComponent; +struct DummyComponent; #[derive(Clone, Component, Copy, Deserialize, Serialize)] struct BoolComponent(bool); diff --git a/tests/despawn.rs b/tests/despawn.rs new file mode 100644 index 00000000..18c5d9cc --- /dev/null +++ b/tests/despawn.rs @@ -0,0 +1,122 @@ +use bevy::prelude::*; +use bevy_replicon::{ + client::client_mapper::ServerEntityMap, prelude::*, test_app::ServerTestAppExt, +}; +use serde::{Deserialize, Serialize}; + +#[test] +fn single() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )); + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn(Replication).id(); + let client_entity = client_app.world.spawn(Replication).id(); + + client_app + .world + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.world.despawn(server_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + assert!(client_app.world.entities().is_empty()); + + let entity_map = client_app.world.resource::(); + assert!(entity_map.to_client().is_empty()); + assert!(entity_map.to_server().is_empty()); +} + +#[test] +fn with_heirarchy() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )); + } + + server_app.connect_client(&mut client_app); + + let server_child_entity = server_app.world.spawn(Replication).id(); + let server_entity = server_app + .world + .spawn(Replication) + .push_children(&[server_child_entity]) + .id(); + + let client_child_entity = client_app.world.spawn(Replication).id(); + let client_entity = client_app + .world + .spawn(Replication) + .push_children(&[client_child_entity]) + .id(); + + let mut entity_map = client_app.world.resource_mut::(); + entity_map.insert(server_entity, client_entity); + entity_map.insert(server_child_entity, client_child_entity); + + server_app.world.despawn(server_entity); + server_app.world.despawn(server_child_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + server_app.world.despawn(server_entity); + server_app.world.despawn(server_child_entity); + + assert!(client_app.world.entities().is_empty()); +} + +#[test] +fn after_spawn() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + } + + server_app.connect_client(&mut client_app); + + // Insert and remove `Replication` to trigger spawn and despawn for client at the same time. + server_app + .world + .spawn((Replication, DummyComponent)) + .remove::(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + assert!(client_app.world.entities().is_empty()); +} + +#[derive(Component, Deserialize, Serialize)] +struct DummyComponent; diff --git a/tests/init_replication.rs b/tests/init_replication.rs deleted file mode 100644 index 8afb7e17..00000000 --- a/tests/init_replication.rs +++ /dev/null @@ -1,648 +0,0 @@ -use bevy::{ecs::entity::MapEntities, prelude::*}; -use bevy_replicon::{ - client::client_mapper::ServerEntityMap, prelude::*, test_app::ServerTestAppExt, -}; -use serde::{Deserialize, Serialize}; - -#[test] -fn spawn() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )); - } - - server_app.connect_client(&mut client_app); - - let server_entity = server_app.world.spawn(Replication).id(); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - let client_entity = client_app - .world - .query_filtered::>() - .single(&client_app.world); - - let entity_map = client_app.world.resource::(); - assert_eq!( - entity_map.to_client().get(&server_entity), - Some(&client_entity), - "server entity should be mapped to a replicated entity on client" - ); - assert_eq!( - entity_map.to_server().get(&client_entity), - Some(&server_entity), - "replicated entity on client should be mapped to a server entity" - ); -} - -#[test] -fn spawn_with_component() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )) - .replicate::(); - } - - server_app.connect_client(&mut client_app); - - server_app.world.spawn((Replication, TableComponent)); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - client_app - .world - .query_filtered::<(), (With, With)>() - .single(&client_app.world); -} - -#[test] -fn spawn_with_old_component() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )) - .replicate::(); - } - - server_app.connect_client(&mut client_app); - - // Spawn an entity with replicated component, but without a marker. - let server_entity = server_app.world.spawn(TableComponent).id(); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - server_app.exchange_with_client(&mut client_app); - - assert!(client_app.world.entities().is_empty()); - - // Enable replication for previously spawned entity - server_app - .world - .entity_mut(server_entity) - .insert(Replication); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - client_app - .world - .query_filtered::<(), (With, With)>() - .single(&client_app.world); -} - -#[test] -fn spawn_before_connection() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )) - .replicate::(); - } - - // Spawn an entity before client connected. - server_app.world.spawn((Replication, TableComponent)); - - server_app.connect_client(&mut client_app); - - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - client_app - .world - .query_filtered::<(), (With, With)>() - .single(&client_app.world); -} - -#[test] -fn client_spawn() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )) - .replicate::(); - } - - server_app.connect_client(&mut client_app); - - // Make client and server have different entity IDs. - server_app.world.spawn_empty(); - - let client_entity = client_app.world.spawn_empty().id(); - let server_entity = server_app.world.spawn((Replication, TableComponent)).id(); - - let client = client_app.world.resource::(); - let client_id = client.id().unwrap(); - - let mut entity_map = server_app.world.resource_mut::(); - entity_map.insert( - client_id, - ClientMapping { - server_entity, - client_entity, - }, - ); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - let entity_map = client_app.world.resource::(); - assert_eq!( - entity_map.to_client().get(&server_entity), - Some(&client_entity), - "server entity should be mapped to a replicated entity on client" - ); - assert_eq!( - entity_map.to_server().get(&client_entity), - Some(&server_entity), - "replicated entity on client should be mapped to a server entity" - ); - - let client_entity = client_app.world.entity(client_entity); - assert!( - client_entity.contains::(), - "server should confirm replication of client entity" - ); - assert!( - client_entity.contains::(), - "component from server should be replicated" - ); - - assert_eq!( - client_app.world.entities().len(), - 1, - "new entity shouldn't be spawned on client" - ); -} - -#[test] -fn despawn() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )); - } - - server_app.connect_client(&mut client_app); - - let server_entity = server_app.world.spawn(Replication).id(); - let client_entity = client_app.world.spawn(Replication).id(); - - client_app - .world - .resource_mut::() - .insert(server_entity, client_entity); - - server_app.world.despawn(server_entity); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - assert!(client_app.world.entities().is_empty()); - - let entity_map = client_app.world.resource::(); - assert!(entity_map.to_client().is_empty()); - assert!(entity_map.to_server().is_empty()); -} - -#[test] -fn despawn_with_heirarchy() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )); - } - - server_app.connect_client(&mut client_app); - - let server_child_entity = server_app.world.spawn(Replication).id(); - let server_entity = server_app - .world - .spawn(Replication) - .push_children(&[server_child_entity]) - .id(); - - let client_child_entity = client_app.world.spawn(Replication).id(); - let client_entity = client_app - .world - .spawn(Replication) - .push_children(&[client_child_entity]) - .id(); - - let mut entity_map = client_app.world.resource_mut::(); - entity_map.insert(server_entity, client_entity); - entity_map.insert(server_child_entity, client_child_entity); - - server_app.world.despawn(server_entity); - server_app.world.despawn(server_child_entity); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - server_app.world.despawn(server_entity); - server_app.world.despawn(server_child_entity); - - assert!(client_app.world.entities().is_empty()); -} - -#[test] -fn despawn_after_spawn() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )) - .replicate::(); - } - - server_app.connect_client(&mut client_app); - - // Insert and remove `Replication` to trigger spawn and despawn for client at the same time. - server_app - .world - .spawn((Replication, TableComponent)) - .remove::(); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - assert!(client_app.world.entities().is_empty()); -} - -#[test] -fn spawn_after_despawn() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )) - .replicate::(); - } - - server_app.connect_client(&mut client_app); - - // Remove and insert `Replication` to trigger despawn and spawn for client at the same time. - server_app - .world - .spawn((Replication, TableComponent)) - .remove::() - .insert(Replication); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - client_app - .world - .query_filtered::<(), (With, With)>() - .single(&client_app.world); -} - -#[test] -fn insertion() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )) - .replicate::() - .replicate::() - .replicate_mapped::() - .replicate_group::<(GroupComponentA, GroupComponentB)>(); - } - - server_app.connect_client(&mut client_app); - - // Make client and server have different entity IDs. - server_app.world.spawn_empty(); - - let server_map_entity = server_app.world.spawn_empty().id(); - let client_map_entity = client_app.world.spawn_empty().id(); - - let server_entity = server_app.world.spawn(Replication).id(); - let client_entity = client_app.world.spawn(Replication).id(); - - let mut entity_map = client_app.world.resource_mut::(); - entity_map.insert(server_map_entity, client_map_entity); - entity_map.insert(server_entity, client_entity); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - server_app.exchange_with_client(&mut client_app); - - server_app.world.entity_mut(server_entity).insert(( - Replication, - TableComponent, - SparseSetComponent, - MappedComponent(server_map_entity), - (GroupComponentA, GroupComponentB), - NotReplicatedComponent, - )); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - let client_entity = client_app.world.entity(client_entity); - assert!(client_entity.contains::()); - assert!(client_entity.contains::()); - assert!(client_entity.contains::()); - assert!(client_entity.contains::()); - assert!(!client_entity.contains::()); - assert_eq!( - client_entity.get::().unwrap().0, - client_map_entity - ); -} - -#[test] -fn removal() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )) - .replicate::() - .replicate_group::<(GroupComponentA, GroupComponentB)>(); - } - - server_app.connect_client(&mut client_app); - - let server_entity = server_app - .world - .spawn(( - Replication, - TableComponent, - (GroupComponentA, GroupComponentB), - NotReplicatedComponent, - )) - .id(); - - let client_entity = client_app - .world - .spawn(( - Replication, - TableComponent, - NotReplicatedComponent, - (GroupComponentA, GroupComponentB), - )) - .id(); - - client_app - .world - .resource_mut::() - .insert(server_entity, client_entity); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - server_app.exchange_with_client(&mut client_app); - - server_app - .world - .entity_mut(server_entity) - .remove::<(TableComponent, (GroupComponentA, GroupComponentB))>(); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - let client_entity = client_app.world.entity(client_entity); - assert!(!client_entity.contains::()); - assert!(!client_entity.contains::()); - assert!(!client_entity.contains::()); - assert!(client_entity.contains::()); -} - -#[test] -fn removal_after_insertion() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )) - .replicate::(); - } - - server_app.connect_client(&mut client_app); - - let server_entity = server_app.world.spawn((Replication, TableComponent)).id(); - let client_entity = client_app.world.spawn((Replication, TableComponent)).id(); - - client_app - .world - .resource_mut::() - .insert(server_entity, client_entity); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - server_app.exchange_with_client(&mut client_app); - - // Insert and remove at the same time. - server_app - .world - .entity_mut(server_entity) - .insert(TableComponent) - .remove::(); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - let client_entity = client_app.world.entity(client_entity); - assert!(!client_entity.contains::()); -} - -#[test] -fn insertion_after_removal() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )) - .replicate::(); - } - - server_app.connect_client(&mut client_app); - - let server_entity = server_app.world.spawn((Replication, TableComponent)).id(); - let client_entity = client_app.world.spawn((Replication, TableComponent)).id(); - - client_app - .world - .resource_mut::() - .insert(server_entity, client_entity); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - server_app.exchange_with_client(&mut client_app); - - // Insert and remove at the same time. - server_app - .world - .entity_mut(server_entity) - .remove::() - .insert(TableComponent); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - let client_entity = client_app.world.entity(client_entity); - assert!(client_entity.contains::()); -} - -#[test] -fn removal_with_despawn() { - let mut server_app = App::new(); - let mut client_app = App::new(); - for app in [&mut server_app, &mut client_app] { - app.add_plugins(( - MinimalPlugins, - RepliconPlugins.set(ServerPlugin { - tick_policy: TickPolicy::EveryFrame, - ..Default::default() - }), - )) - .replicate::(); - } - - server_app.connect_client(&mut client_app); - - let server_entity = server_app.world.spawn((Replication, TableComponent)).id(); - let client_entity = client_app.world.spawn((Replication, TableComponent)).id(); - - client_app - .world - .resource_mut::() - .insert(server_entity, client_entity); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - server_app.exchange_with_client(&mut client_app); - - // Un-replicate and remove at the same time. - server_app - .world - .entity_mut(server_entity) - .remove::() - .remove::(); - - server_app.update(); - server_app.exchange_with_client(&mut client_app); - client_app.update(); - - assert!(client_app.world.entities().is_empty()); -} - -#[derive(Component, Deserialize, Serialize)] -struct MappedComponent(Entity); - -impl MapEntities for MappedComponent { - fn map_entities(&mut self, entity_mapper: &mut M) { - self.0 = entity_mapper.map_entity(self.0); - } -} - -#[derive(Component, Deserialize, Serialize)] -struct TableComponent; - -#[derive(Component, Deserialize, Serialize)] -#[component(storage = "SparseSet")] -struct SparseSetComponent; - -#[derive(Component, Deserialize, Serialize)] -struct GroupComponentA; - -#[derive(Component, Deserialize, Serialize)] -struct GroupComponentB; - -#[derive(Component, Deserialize, Serialize)] -struct NotReplicatedComponent; diff --git a/tests/insertion.rs b/tests/insertion.rs new file mode 100644 index 00000000..818f7ba3 --- /dev/null +++ b/tests/insertion.rs @@ -0,0 +1,323 @@ +use bevy::{ecs::entity::MapEntities, prelude::*}; +use bevy_replicon::{ + client::client_mapper::ServerEntityMap, prelude::*, test_app::ServerTestAppExt, +}; +use serde::{Deserialize, Serialize}; + +#[test] +fn table_storage() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn(Replication).id(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .insert((Replication, TableComponent)); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + client_app + .world + .query_filtered::<(), With>() + .single(&client_app.world); +} + +#[test] +fn sparse_set_storage() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn(Replication).id(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .insert((Replication, SparseSetComponent)); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + client_app + .world + .query_filtered::<(), With>() + .single(&client_app.world); +} + +#[test] +fn mapped_existing_entity() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate_mapped::(); + } + + server_app.connect_client(&mut client_app); + + // Make client and server have different entity IDs. + server_app.world.spawn_empty(); + + let server_entity = server_app.world.spawn(Replication).id(); + let server_map_entity = server_app.world.spawn_empty().id(); + let client_map_entity = client_app.world.spawn_empty().id(); + + client_app + .world + .resource_mut::() + .insert(server_map_entity, client_map_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .insert((Replication, MappedComponent(server_map_entity))); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let mapped_component = client_app + .world + .query::<&MappedComponent>() + .single(&client_app.world); + assert_eq!(mapped_component.0, client_map_entity); +} + +#[test] +fn mapped_new_entity() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate_mapped::(); + } + + server_app.connect_client(&mut client_app); + + // Make client and server have different entity IDs. + server_app.world.spawn_empty(); + + let server_entity = server_app.world.spawn(Replication).id(); + let server_map_entity = server_app.world.spawn_empty().id(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .insert((Replication, MappedComponent(server_map_entity))); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let mapped_component = client_app + .world + .query::<&MappedComponent>() + .single(&client_app.world); + assert!(client_app.world.get_entity(mapped_component.0).is_some()); + assert_eq!(client_app.world.entities().len(), 2); +} + +#[test] +fn group() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate_group::<(GroupComponentA, GroupComponentB)>(); + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn(Replication).id(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .insert((Replication, (GroupComponentA, GroupComponentB))); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + client_app + .world + .query_filtered::<(), (With, With)>() + .single(&client_app.world); +} + +#[test] +fn not_replicated() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )); + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn(Replication).id(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .insert((Replication, NotReplicatedComponent)); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let non_replicated_components = client_app + .world + .query_filtered::<(), With>() + .iter(&client_app.world) + .count(); + assert_eq!(non_replicated_components, 0); +} + +#[test] +fn after_removal() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn((Replication, TableComponent)).id(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + // Insert and remove at the same time. + server_app + .world + .entity_mut(server_entity) + .remove::() + .insert(TableComponent); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + client_app + .world + .query_filtered::<(), With>() + .single(&client_app.world); +} + +#[derive(Component, Deserialize, Serialize)] +struct MappedComponent(Entity); + +impl MapEntities for MappedComponent { + fn map_entities(&mut self, entity_mapper: &mut M) { + self.0 = entity_mapper.map_entity(self.0); + } +} + +#[derive(Component, Deserialize, Serialize)] +struct TableComponent; + +#[derive(Component, Deserialize, Serialize)] +#[component(storage = "SparseSet")] +struct SparseSetComponent; + +#[derive(Component, Deserialize, Serialize)] +struct GroupComponentA; + +#[derive(Component, Deserialize, Serialize)] +struct GroupComponentB; + +#[derive(Component, Deserialize, Serialize)] +struct NotReplicatedComponent; diff --git a/tests/removal.rs b/tests/removal.rs new file mode 100644 index 00000000..658e3675 --- /dev/null +++ b/tests/removal.rs @@ -0,0 +1,249 @@ +use bevy::prelude::*; +use bevy_replicon::{ + client::client_mapper::ServerEntityMap, prelude::*, test_app::ServerTestAppExt, +}; +use serde::{Deserialize, Serialize}; + +#[test] +fn single() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn((Replication, DummyComponent)).id(); + let client_entity = client_app.world.spawn((Replication, DummyComponent)).id(); + + client_app + .world + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .remove::(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let client_entity = client_app.world.entity(client_entity); + assert!(!client_entity.contains::()); +} + +#[test] +fn group() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate_group::<(GroupComponentA, GroupComponentB)>(); + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app + .world + .spawn((Replication, (GroupComponentA, GroupComponentB))) + .id(); + + let client_entity = client_app + .world + .spawn((Replication, (GroupComponentA, GroupComponentB))) + .id(); + + client_app + .world + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .remove::<(GroupComponentA, GroupComponentB)>(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let client_entity = client_app.world.entity(client_entity); + assert!(!client_entity.contains::()); + assert!(!client_entity.contains::()); +} + +#[test] +fn not_replicated() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )); + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app + .world + .spawn((Replication, NotReplicatedComponent)) + .id(); + + let client_entity = client_app + .world + .spawn((Replication, NotReplicatedComponent)) + .id(); + + client_app + .world + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + server_app + .world + .entity_mut(server_entity) + .remove::(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let client_entity = client_app.world.entity(client_entity); + assert!(client_entity.contains::()); +} + +#[test] +fn after_insertion() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn((Replication, DummyComponent)).id(); + let client_entity = client_app.world.spawn((Replication, DummyComponent)).id(); + + client_app + .world + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + // Insert and remove at the same time. + server_app + .world + .entity_mut(server_entity) + .insert(DummyComponent) + .remove::(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let client_entity = client_app.world.entity(client_entity); + assert!(!client_entity.contains::()); +} + +#[test] +fn with_despawn() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn((Replication, DummyComponent)).id(); + let client_entity = client_app.world.spawn((Replication, DummyComponent)).id(); + + client_app + .world + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + // Un-replicate and remove at the same time. + server_app + .world + .entity_mut(server_entity) + .remove::() + .remove::(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + assert!(client_app.world.entities().is_empty()); +} + +#[derive(Component, Deserialize, Serialize)] +struct DummyComponent; + +#[derive(Component, Deserialize, Serialize)] +struct GroupComponentA; + +#[derive(Component, Deserialize, Serialize)] +struct GroupComponentB; + +#[derive(Component, Deserialize, Serialize)] +struct NotReplicatedComponent; diff --git a/tests/spawn.rs b/tests/spawn.rs new file mode 100644 index 00000000..c9635cdf --- /dev/null +++ b/tests/spawn.rs @@ -0,0 +1,251 @@ +use bevy::prelude::*; +use bevy_replicon::{ + client::client_mapper::ServerEntityMap, prelude::*, test_app::ServerTestAppExt, +}; +use serde::{Deserialize, Serialize}; + +#[test] +fn empty() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )); + } + + server_app.connect_client(&mut client_app); + + let server_entity = server_app.world.spawn(Replication).id(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let client_entity = client_app + .world + .query_filtered::>() + .single(&client_app.world); + + let entity_map = client_app.world.resource::(); + assert_eq!( + entity_map.to_client().get(&server_entity), + Some(&client_entity), + "server entity should be mapped to a replicated entity on client" + ); + assert_eq!( + entity_map.to_server().get(&client_entity), + Some(&server_entity), + "replicated entity on client should be mapped to a server entity" + ); +} + +#[test] +fn with_component() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + } + + server_app.connect_client(&mut client_app); + + server_app.world.spawn((Replication, DummyComponent)); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + client_app + .world + .query_filtered::<(), (With, With)>() + .single(&client_app.world); +} + +#[test] +fn with_old_component() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + } + + server_app.connect_client(&mut client_app); + + // Spawn an entity with replicated component, but without a marker. + let server_entity = server_app.world.spawn(DummyComponent).id(); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + server_app.exchange_with_client(&mut client_app); + + assert!(client_app.world.entities().is_empty()); + + // Enable replication for previously spawned entity + server_app + .world + .entity_mut(server_entity) + .insert(Replication); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + client_app + .world + .query_filtered::<(), (With, With)>() + .single(&client_app.world); +} + +#[test] +fn before_connection() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + } + + // Spawn an entity before client connected. + server_app.world.spawn((Replication, DummyComponent)); + + server_app.connect_client(&mut client_app); + + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + client_app + .world + .query_filtered::<(), (With, With)>() + .single(&client_app.world); +} + +#[test] +fn pre_spawn() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + } + + server_app.connect_client(&mut client_app); + + // Make client and server have different entity IDs. + server_app.world.spawn_empty(); + + let client_entity = client_app.world.spawn_empty().id(); + let server_entity = server_app.world.spawn((Replication, DummyComponent)).id(); + + let client = client_app.world.resource::(); + let client_id = client.id().unwrap(); + + let mut entity_map = server_app.world.resource_mut::(); + entity_map.insert( + client_id, + ClientMapping { + server_entity, + client_entity, + }, + ); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let entity_map = client_app.world.resource::(); + assert_eq!( + entity_map.to_client().get(&server_entity), + Some(&client_entity), + "server entity should be mapped to a replicated entity on client" + ); + assert_eq!( + entity_map.to_server().get(&client_entity), + Some(&server_entity), + "replicated entity on client should be mapped to a server entity" + ); + + let client_entity = client_app.world.entity(client_entity); + assert!( + client_entity.contains::(), + "server should confirm replication of client entity" + ); + assert!( + client_entity.contains::(), + "component from server should be replicated" + ); + + assert_eq!( + client_app.world.entities().len(), + 1, + "new entity shouldn't be spawned on client" + ); +} + +#[test] +fn after_despawn() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .replicate::(); + } + + server_app.connect_client(&mut client_app); + + // Remove and insert `Replication` to trigger despawn and spawn for client at the same time. + server_app + .world + .spawn((Replication, DummyComponent)) + .remove::() + .insert(Replication); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + client_app + .world + .query_filtered::<(), (With, With)>() + .single(&client_app.world); +} + +#[derive(Component, Deserialize, Serialize)] +struct DummyComponent;