use bevy::prelude::*;
#[cfg(feature = "prediction")]
use bevy::time::TimeUpdateStrategy;
#[cfg(feature = "prediction")]
use bevy_networker_multiplayer::{LinearMotionPredictionPlugin, PredictLinearMotion, Velocity2d};
use bevy_networker_multiplayer::{
netres::{NetResource, ReplicationPacket},
replicated::{EntityIndex, NetworkId, Replicated},
sync,
ReplicatedPlugin,
};
#[cfg(feature = "prediction")]
use std::time::Duration;
#[sync]
#[derive(Component)]
struct Health(u32);
#[sync(prefab(
Sprite::from_color(Color::srgb(0.2, 0.8, 1.0), Vec2::splat(32.0)),
Transform::from_xyz(0.0, 0.0, 0.0)
))]
#[derive(Component)]
struct VisualPosition(Vec2);
#[cfg(feature = "prediction")]
#[sync(prefab(
Sprite::from_color(Color::srgb(0.2, 0.8, 1.0), Vec2::splat(32.0)),
Transform::from_xyz(0.0, 0.0, 0.0)
))]
#[derive(Component, PredictLinearMotion)]
struct PredictedPosition(Vec2);
#[cfg(feature = "prediction")]
#[sync]
#[derive(Component, Velocity2d)]
struct PredictedVelocity(Vec2);
#[sync(resource)]
#[derive(Resource)]
struct MatchState {
score: u32,
}
#[test]
fn replicated_entities_get_ids_and_updates() {
let mut app = App::new();
app.add_plugins(MinimalPlugins);
app.add_plugins(ReplicatedPlugin);
app.world_mut()
.resource_mut::<NetResource>()
.start_server(0);
let entity = app.world_mut().spawn((Replicated, Health(100))).id();
app.update();
let network_id = app
.world()
.resource::<EntityIndex>()
.network_id(entity)
.expect("replicated entity should have a network id");
assert_eq!(network_id.0, 0);
let packets = app.world_mut().resource_mut::<NetResource>().drain_outbox();
assert_eq!(packets.len(), 2);
assert!(matches!(
packets[0],
ReplicationPacket::SpawnEntity {
network_id: 0,
prefab_wire_id: 0
}
));
assert!(matches!(
packets[1],
ReplicationPacket::UpdateComponent {
network_id: 0,
component_wire_id: _,
..
}
));
}
#[test]
fn replicated_resources_queue_and_apply_updates() {
let mut server = App::new();
server.add_plugins(MinimalPlugins);
server.add_plugins(ReplicatedPlugin);
server.world_mut().resource_mut::<NetResource>().start_server(0);
server.world_mut().insert_resource(MatchState { score: 7 });
server.update();
let packets = server.world_mut().resource_mut::<NetResource>().drain_outbox();
assert_eq!(packets.len(), 1);
let bytes = match &packets[0] {
ReplicationPacket::UpdateResource {
resource_wire_id,
bytes,
} => {
assert_eq!(
*resource_wire_id,
<MatchState as sync::SyncResource>::WIRE_ID
);
bytes.clone()
}
other => panic!("unexpected packet: {other:?}"),
};
let mut client = App::new();
client.add_plugins(MinimalPlugins);
client.add_plugins(ReplicatedPlugin);
client
.world_mut()
.resource_mut::<NetResource>()
.inject_packet(ReplicationPacket::UpdateResource {
resource_wire_id: <MatchState as sync::SyncResource>::WIRE_ID,
bytes,
});
sync::apply_incoming_packets(client.world_mut());
let resource = client.world().resource::<MatchState>();
assert_eq!(resource.score, 7);
}
#[test]
fn component_updates_wait_for_spawn_entity() {
let mut client = App::new();
client.add_plugins(MinimalPlugins);
client.add_plugins(ReplicatedPlugin);
let bytes = bincode::serde::encode_to_vec(Health(42), bincode::config::standard())
.expect("health should serialize");
client
.world_mut()
.resource_mut::<NetResource>()
.inject_packet(ReplicationPacket::UpdateComponent {
network_id: 9,
component_wire_id: <Health as sync::SyncComponent>::WIRE_ID,
bytes,
});
sync::apply_incoming_packets(client.world_mut());
assert!(
client
.world()
.resource::<EntityIndex>()
.entity(NetworkId(9))
.is_none()
);
client
.world_mut()
.resource_mut::<NetResource>()
.inject_packet(ReplicationPacket::SpawnEntity {
network_id: 9,
prefab_wire_id: 0,
});
sync::apply_incoming_packets(client.world_mut());
let entity = client
.world()
.resource::<EntityIndex>()
.entity(NetworkId(9))
.expect("spawn packet should create the replicated entity");
assert_eq!(client.world().entity(entity).get::<Health>().unwrap().0, 42);
}
#[test]
fn prefab_spawn_packets_create_client_visuals() {
let mut server = App::new();
server.add_plugins(MinimalPlugins);
server.add_plugins(ReplicatedPlugin);
server
.world_mut()
.resource_mut::<NetResource>()
.start_server(0);
server
.world_mut()
.spawn((Replicated, VisualPosition(Vec2::new(12.0, 34.0))));
server.update();
let packets = server
.world_mut()
.resource_mut::<NetResource>()
.drain_outbox();
assert!(packets.iter().any(|packet| matches!(
packet,
ReplicationPacket::SpawnEntity {
network_id: 0,
prefab_wire_id
} if *prefab_wire_id == <VisualPosition as sync::SyncComponent>::WIRE_ID
)));
let mut client = App::new();
client.add_plugins(MinimalPlugins);
client.add_plugins(ReplicatedPlugin);
for packet in packets {
client
.world_mut()
.resource_mut::<NetResource>()
.inject_packet(packet);
}
sync::apply_incoming_packets(client.world_mut());
let entity = client
.world()
.resource::<EntityIndex>()
.entity(NetworkId(0))
.expect("spawn packet should create the replicated entity");
let transform = client
.world()
.entity(entity)
.get::<Transform>()
.expect("prefab should insert a transform");
assert!(client.world().entity(entity).contains::<Sprite>());
assert_eq!(transform.translation.x, 12.0);
assert_eq!(transform.translation.y, 34.0);
}
#[cfg(feature = "prediction")]
#[test]
fn prediction_updates_prefab_visual_transform() {
let mut client = App::new();
client.add_plugins(MinimalPlugins);
client.add_plugins(ReplicatedPlugin);
client.add_plugins(LinearMotionPredictionPlugin::<
PredictedPosition,
PredictedVelocity,
>::new());
client.insert_resource(TimeUpdateStrategy::ManualDuration(Duration::ZERO));
let position_bytes = bincode::serde::encode_to_vec(
PredictedPosition(Vec2::new(10.0, 20.0)),
bincode::config::standard(),
)
.expect("position should serialize");
let velocity_bytes = bincode::serde::encode_to_vec(
PredictedVelocity(Vec2::new(4.0, 6.0)),
bincode::config::standard(),
)
.expect("velocity should serialize");
for packet in [
ReplicationPacket::SpawnEntity {
network_id: 11,
prefab_wire_id: <PredictedPosition as sync::SyncComponent>::WIRE_ID,
},
ReplicationPacket::UpdateComponent {
network_id: 11,
component_wire_id: <PredictedPosition as sync::SyncComponent>::WIRE_ID,
bytes: position_bytes,
},
ReplicationPacket::UpdateComponent {
network_id: 11,
component_wire_id: <PredictedVelocity as sync::SyncComponent>::WIRE_ID,
bytes: velocity_bytes,
},
] {
client
.world_mut()
.resource_mut::<NetResource>()
.inject_packet(packet);
}
client.update();
client.insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_secs(1)));
client.update();
let entity = client
.world()
.resource::<EntityIndex>()
.entity(NetworkId(11))
.expect("spawn packet should create the replicated entity");
let transform = client
.world()
.entity(entity)
.get::<Transform>()
.expect("prefab should insert a transform");
assert_eq!(transform.translation.x, 11.0);
assert_eq!(transform.translation.y, 21.5);
}