diff --git a/examples/ping_pong.rs b/examples/ping_pong.rs deleted file mode 100644 index 4a666b7..0000000 --- a/examples/ping_pong.rs +++ /dev/null @@ -1,67 +0,0 @@ -use bevnet::{ - client::{ClientAppExt, ClientPlugin, ServerConnection}, - server::{ClientListener, ServerAppExt, ServerPlugin}, -}; -use bevy::prelude::*; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] -struct Ping; - -#[derive(Serialize, Deserialize)] -struct Pong; - -fn start_server(mut commands: Commands, keys: Res>) { - if keys.just_pressed(KeyCode::B) { - println!("Starting server..."); - match ClientListener::bind("127.0.0.1:8000") { - Ok(listener) => { - commands.insert_resource(listener); - println!("Server started"); - } - Err(e) => println!("Failed to start server: {}", e), - } - } -} - -fn connect(mut commands: Commands, keys: Res>) { - if keys.just_pressed(KeyCode::C) { - println!("Connecting to server..."); - match ServerConnection::connect("127.0.0.1:8000") { - Ok(connection) => { - commands.insert_resource(connection); - println!("Connected to server"); - } - Err(e) => println!("Failed to connect: {}", e), - } - } -} - -fn send_ping(connection: Option>, keys: Res>) { - if keys.just_pressed(KeyCode::S) { - println!("Sending ping..."); - if let Some(connection) = connection { - connection.send(Ping); - println!("Ping sent"); - } - } -} - -fn main() { - App::new() - .add_plugins(DefaultPlugins) - .add_plugin(ServerPlugin) - .add_system(start_server) - .add_server_packet_handler::(|entity, connection, _, _| { - println!("Received ping from {:?}", entity); - connection.send(Pong); - println!("Sent pong to {:?}", entity); - }) - .add_plugin(ClientPlugin) - .add_system(connect) - .add_client_packet_handler::(|_, _| { - println!("Received pong"); - }) - .add_system(send_ping) - .run(); -} diff --git a/src/client.rs b/src/client/mod.rs similarity index 91% rename from src/client.rs rename to src/client/mod.rs index d368e0f..7a3214a 100644 --- a/src/client.rs +++ b/src/client/mod.rs @@ -1,8 +1,8 @@ +use crate::{tcp::Connection, Packet}; +use bevy::prelude::*; use std::{collections::HashMap, io, net::ToSocketAddrs, sync::Arc}; -use bevy::prelude::*; - -use crate::{tcp::Connection, Packet}; +pub mod sync; /// A function that handle a received [Packet] on the client. pub type ClientPacketHandler = Box, &mut World) + Send + Sync>; @@ -22,8 +22,8 @@ impl ServerConnection { } /// Sends a packet through this connection. - pub fn send(&self, packet: P) { - let mut data = bincode::serialize(&packet).expect("Failed to serialize packet"); + pub fn send(&self, packet: &P) { + let mut data = bincode::serialize(packet).expect("Failed to serialize packet"); data.extend(P::packet_id().to_be_bytes()); self.0.send(data); } @@ -83,14 +83,14 @@ impl Plugin for ClientPlugin { /// An extension to add packet handlers. pub trait ClientAppExt { /// Add a new packet handler. - fn add_client_packet_handler(&mut self, handler: H) -> &mut Self + fn add_packet_handler(&mut self, handler: H) -> &mut Self where P: Packet, H: Fn(Vec, &mut World) + Send + Sync + 'static; } impl ClientAppExt for App { - fn add_client_packet_handler(&mut self, handler: H) -> &mut Self + fn add_packet_handler(&mut self, handler: H) -> &mut Self where P: Packet, H: Fn(Vec, &mut World) + Send + Sync + 'static, diff --git a/src/client/sync.rs b/src/client/sync.rs new file mode 100644 index 0000000..fa1b195 --- /dev/null +++ b/src/client/sync.rs @@ -0,0 +1,129 @@ +use std::{any::type_name, marker::PhantomData}; + +use super::{ClientAppExt, ServerConnection}; +use crate::Packet; +use bevy::prelude::*; +use serde::{de::DeserializeOwned, Serialize}; + +/// An event that comes from the server. +#[derive(Deref)] +pub struct FromServer { + /// The event. + pub event: E, +} + +/// Mark an [Entity] as synced by the server. +#[derive(Component)] +pub struct ServerEntity(Entity); + +/// A plugin for the syncronization system. +pub struct ClientSyncPlugin; + +impl ClientSyncPlugin { + /// Removes [ServerEntity] when disconnected. + fn remove_synced(mut commands: Commands, entities: Query>) { + for entity in entities.iter() { + commands.entity(entity).despawn(); + } + } +} + +impl Plugin for ClientSyncPlugin { + fn build(&self, app: &mut App) { + app.add_packet_handler::(|data, world| { + match bincode::deserialize::(&data) { + Ok(entity) => { + if let Some((local_entity, _)) = world + .query::<(Entity, &ServerEntity)>() + .iter(world) + .find(|(_, server_entity)| server_entity.0 == entity) + { + println!("Despawning {:?}", local_entity); + world.despawn(local_entity); + } else { + println!("Spawning {:?}", entity); + world.spawn(ServerEntity(entity)); + } + } + Err(_) => println!("Failed to deserialize packet: {}", type_name::()), + } + }); + app.add_system(Self::remove_synced.run_if(resource_removed::())); + } +} + +/// An extention to add syncronization to the client. +pub trait ClientSyncExt { + /// Register syncronization for an [Event] that comes from the server. + fn server_event_sync(&mut self) -> &mut Self; + + /// Register syncronization for an [Event] that can be sent by the client. + fn client_event_sync(&mut self) -> &mut Self; + + /// Register a [Component] to be synced. + fn sync_component(&mut self) -> &mut Self; +} + +impl ClientSyncExt for App { + fn server_event_sync(&mut self) -> &mut Self { + self.add_event::>() + .add_packet_handler::(|data, world| match bincode::deserialize::(&data) { + Ok(event) => world.send_event(FromServer { event }), + Err(_) => println!("Failed to deserialize packet: {}", type_name::()), + }) + } + + fn client_event_sync(&mut self) -> &mut Self { + self.add_event::().add_system( + |mut events: EventReader, connection: Option>| { + if let Some(connection) = connection { + for event in events.iter() { + connection.send(event); + } + } + }, + ) + } + + fn sync_component(&mut self) -> &mut Self { + self.add_packet_handler::<(Entity, C), _>(|data, world| { + match bincode::deserialize::<(Entity, C)>(&data) { + Ok((entity, component)) => { + if let Some((local_entity, _)) = world + .query::<(Entity, &ServerEntity)>() + .iter(world) + .find(|(_, server_entity)| server_entity.0 == entity) + { + let mut local_entity = world.entity_mut(local_entity); + match local_entity.get_mut::() { + Some(mut local_component) => { + println!("CA CHANGE: {}", type_name::()); + *local_component = component; + } + None => { + local_entity.insert(component); + } + } + } else { + println!("Received component for unknown entity: {:?}", entity); + } + } + Err(_) => println!("Failed to deserialize packet: {}", type_name::()), + } + }) + .add_packet_handler::<(Entity, PhantomData), _>( + |data, world| match bincode::deserialize::<(Entity, PhantomData)>(&data) { + Ok((entity, _)) => { + if let Some((local_entity, _)) = world + .query::<(Entity, &ServerEntity)>() + .iter(world) + .find(|(_, server_entity)| server_entity.0 == entity) + { + world.entity_mut(local_entity).remove::(); + } + } + Err(_) => println!("Failed to deserialize packet: {}", type_name::()), + }, + ) + } +} diff --git a/src/server.rs b/src/server/mod.rs similarity index 93% rename from src/server.rs rename to src/server/mod.rs index 2bced84..9a31ad3 100644 --- a/src/server.rs +++ b/src/server/mod.rs @@ -5,6 +5,8 @@ use crate::{ use bevy::prelude::*; use std::{collections::HashMap, io, net::ToSocketAddrs, sync::Arc}; +pub mod sync; + /// A function that handle a received [Packet] on the server. pub type ServerPacketHandler = Box, &mut World) + Send + Sync>; @@ -30,8 +32,8 @@ pub struct ClientConnection(Arc); impl ClientConnection { /// Sends a packet through this connection. - pub fn send(&self, packet: P) { - let mut data = bincode::serialize(&packet).expect("Failed to serialize packet"); + pub fn send(&self, packet: &P) { + let mut data = bincode::serialize(packet).expect("Failed to serialize packet"); data.extend(P::packet_id().to_be_bytes()); self.0.send(data); } @@ -107,14 +109,14 @@ impl Plugin for ServerPlugin { /// An extension to add packet handlers. pub trait ServerAppExt { /// Add a new packet handler. - fn add_server_packet_handler(&mut self, handler: H) -> &mut Self + fn add_packet_handler(&mut self, handler: H) -> &mut Self where P: Packet, H: Fn(Entity, ClientConnection, Vec, &mut World) + Send + Sync + 'static; } impl ServerAppExt for App { - fn add_server_packet_handler(&mut self, handler: H) -> &mut Self + fn add_packet_handler(&mut self, handler: H) -> &mut Self where P: Packet, H: Fn(Entity, ClientConnection, Vec, &mut World) + Send + Sync + 'static, diff --git a/src/server/sync.rs b/src/server/sync.rs new file mode 100644 index 0000000..ad2e7b7 --- /dev/null +++ b/src/server/sync.rs @@ -0,0 +1,154 @@ +use super::ServerAppExt; +use crate::{server::ClientConnection, Packet}; +use bevy::prelude::*; +use serde::{de::DeserializeOwned, Serialize}; +use std::{any::type_name, marker::PhantomData, ops::Deref}; + +/// An event that comes from a client. +pub struct FromClient { + /// The entity of the [ClientConnection] that sent the event. + pub entity: Entity, + + /// The [ClientConnection] that sent the event. + pub connection: ClientConnection, + + /// The event. + pub event: E, +} + +impl Deref for FromClient { + type Target = E; + + fn deref(&self) -> &Self::Target { + &self.event + } +} + +/// Mark an [Entity] to be synced. +#[derive(Component)] +pub struct Synced; + +/// A plugin for the syncronization system. +pub struct ServerSyncPlugin; + +impl ServerSyncPlugin { + /// Send to clients the [Synced] entity that has been added to the server. + fn send_added( + added_entities: Query>, + connections: Query<&ClientConnection>, + ) { + for entity in added_entities.iter() { + for connection in connections.iter() { + connection.send(&entity); + } + } + } + + /// Send [Synced] entities to new clients. + fn send_synced( + synced_entities: Query>, + new_connections: Query<&ClientConnection, Added>, + ) { + for entity in synced_entities.iter() { + for connection in new_connections.iter() { + connection.send(&entity); + } + } + } + + /// Send to clients the [Synced] entity that has been removed. + fn send_removed( + mut removed_entities: RemovedComponents, + connections: Query<&ClientConnection>, + ) { + for entity in removed_entities.iter() { + for connection in connections.iter() { + connection.send(&entity); + } + } + } +} + +impl Plugin for ServerSyncPlugin { + fn build(&self, app: &mut App) { + app.add_system(Self::send_added); + app.add_system(Self::send_synced); + app.add_system(Self::send_removed); + } +} + +/// An extention to add syncronization to the server. +pub trait ServerSyncExt { + /// Register syncronization for an [Event] that can be sent by the server. + fn server_event_sync(&mut self) -> &mut Self; + + /// Register syncronization for an [Event] that comes from the client. + fn client_event_sync(&mut self) -> &mut Self; + + /// Register a [Component] to be synced. + fn sync_component(&mut self) -> &mut Self; +} + +impl ServerSyncExt for App { + fn server_event_sync(&mut self) -> &mut Self { + self.add_event::().add_system( + |mut events: EventReader, connections: Query<&ClientConnection>| { + for event in events.iter() { + for connection in connections.iter() { + connection.send(event); + } + } + }, + ) + } + + fn client_event_sync(&mut self) -> &mut Self { + self.add_event::>() + .add_packet_handler::( + |entity, connection, data, world| match bincode::deserialize::(&data) { + Ok(event) => world.send_event(FromClient { + entity, + connection, + event, + }), + Err(_) => println!("Failed to deserialize packet: {}", type_name::()), + }, + ) + } + + fn sync_component(&mut self) -> &mut Self { + let update_components = + |changed_components: Query<(Entity, &C), (Changed, With)>, + connections: Query<&ClientConnection>| { + for (entity, component) in changed_components.iter() { + for connection in connections.iter() { + connection.send(&(entity, component.clone())); + } + } + }; + let send_components = + |components: Query<(Entity, &C), With>, + new_connections: Query<&ClientConnection, Added>| { + for (entity, component) in components.iter() { + for connection in new_connections.iter() { + connection.send(&(entity, component.clone())); + } + } + }; + let remove_components = + |mut removed_components: RemovedComponents, + synced_entities: Query>, + connections: Query<&ClientConnection>| { + for entity in removed_components.iter() { + if synced_entities.contains(entity) { + for connection in connections.iter() { + connection.send(&(entity, PhantomData::)); + } + } + } + }; + self.add_system(update_components.after(ServerSyncPlugin::send_added)) + .add_system(send_components.after(ServerSyncPlugin::send_synced)) + .add_system(remove_components.after(update_components)) + } +}