diff --git a/Cargo.lock b/Cargo.lock index 9ca3f9b..4a9258f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,8 +403,10 @@ dependencies = [ "aes-gcm", "base64 0.21.7", "bevy", + "bincode", "igd", "local-ip-address", + "serde", ] [[package]] @@ -1159,6 +1161,15 @@ dependencies = [ "winit", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.69.4" diff --git a/crates/bevnet/Cargo.toml b/crates/bevnet/Cargo.toml index e3dc782..8a783f9 100644 --- a/crates/bevnet/Cargo.toml +++ b/crates/bevnet/Cargo.toml @@ -17,3 +17,5 @@ aes-gcm = "0.10.3" base64 = "0.21.7" igd = "0.12.1" bevy = "0.12.1" +serde = "1.0.196" +bincode = "1.3.3" diff --git a/crates/bevnet/src/lib.rs b/crates/bevnet/src/lib.rs index c3f05a6..814cf0d 100644 --- a/crates/bevnet/src/lib.rs +++ b/crates/bevnet/src/lib.rs @@ -50,11 +50,15 @@ use std::sync::Mutex; use aes_gcm::aead::{Aead, AeadCore, KeyInit, OsRng}; use aes_gcm::{Aes128Gcm, Key, Nonce}; use base64::prelude::*; +use bevy::ecs::schedule::SystemConfigs; use bevy::prelude::*; use igd::{Gateway, PortMappingProtocol}; use local_ip_address::local_ip; +use serde::de::DeserializeOwned; +use serde::Serialize; /// A non-blocking tcp connection. +#[derive(Component, Resource)] pub struct Connection { /// The underlying [TcpStream] used for the connection. stream: TcpStream, @@ -231,7 +235,7 @@ impl Connection { } // Returning success. - Ok(self.send_buffers.is_empty()) + Ok(()) } /// Receives a byte block from the connection. @@ -375,6 +379,7 @@ impl Connection { } /// A non-blocking tcp listener. +#[derive(Resource)] pub struct Listener(TcpListener, Gateway, SocketAddrV4, Key); impl Listener { @@ -441,5 +446,242 @@ impl Drop for Listener { #[derive(Resource, Default)] struct LastEventId(u16); -/// An extension trait for the bevy [App] to allow registering network events. -pub trait NetworkAppExt {} +/// An utility function that sends an [Event] to a [Connection]. +fn send_event( + connection: &Connection, + event_id: u16, + event: &T, +) { + // Serializing the event. + let message = match bincode::serialize(event) { + Ok(message) => message, + Err(e) => { + error!("failed to serialize event: {e}"); + return; + } + }; + + // Sending the event. + if let Err(e) = connection.send(&message, event_id) { + info!("failed to send event: {e}"); + } +} + +/// An [Event] received from the server on the client. +#[derive(Event)] +pub struct ServerEvent(pub T); + +impl ServerEvent { + /// Returns a system that receives the events from the server. + pub fn receive(event_id: u16) -> SystemConfigs { + (move |connection: Res, mut events: EventWriter| { + // Get all received messages. + let messages = match connection.receive(event_id) { + Ok(messages) => messages, + Err(e) => { + error!("failed to receive event: {e}"); + return; + } + }; + + // Send the events in the world. + for message in messages { + // Deserializing the message. + let event: T = match bincode::deserialize(&message) { + Ok(event) => event, + Err(e) => { + error!("failed to deserialize event: {e}"); + continue; + } + }; + + // Sending the event. + events.send(Self(event)); + } + }) + .run_if(resource_exists::()) + } +} + +/// An [Event] received from a specific client on the server. +#[derive(Event)] +pub struct ClientEvent(pub Entity, pub T); + +impl ClientEvent { + /// Returns a system that receives the events from the clients on the + /// server. + pub fn receive(event_id: u16) -> impl Fn(Query<(Entity, &Connection)>, EventWriter) { + move |connections: Query<(Entity, &Connection)>, mut events: EventWriter| { + for (entity, connection) in connections.iter() { + // Get all received messages. + let messages = match connection.receive(event_id) { + Ok(messages) => messages, + Err(e) => { + error!("failed to receive event: {e}"); + continue; + } + }; + + // Send the events in the world. + for message in messages { + // Deserializing the message. + let event: T = match bincode::deserialize(&message) { + Ok(event) => event, + Err(e) => { + error!("failed to deserialize event: {e}"); + continue; + } + }; + + // Sending the event. + events.send(Self(entity, event)); + } + } + } + } +} + +/// An [Event] used to send a network event to all the connected clients on the +/// server. +#[derive(Event)] +pub struct SendAll(pub T); + +impl SendAll { + /// Returns a system that sends the events to all the connected clients on + /// the server. + pub fn send(event_id: u16) -> impl Fn(EventReader, Query<&Connection>) { + move |mut events: EventReader, connections: Query<&Connection>| { + for event in events.read() { + for connection in connections.iter() { + send_event(connection, event_id, &event.0); + } + } + } + } +} + +/// An [Event] used to send a network event to a specific client on the server. +#[derive(Event)] +pub struct SendTo(pub Entity, pub T); + +impl SendTo { + /// Returns a system that sends the events to a specific client on the + /// server. + pub fn send(event_id: u16) -> impl Fn(EventReader, Query<&Connection>) { + move |mut events: EventReader, connections: Query<&Connection>| { + for event in events.read() { + match connections.get(event.0) { + Ok(connection) => send_event(connection, event_id, &event.1), + Err(e) => error!("tried to send event to non-existent connection: {e}"), + } + } + } + } +} + +/// An [Event] used to send an [Event] from a client to the server. +#[derive(Event)] +pub struct SendToServer(pub T); + +impl SendToServer { + /// Returns a system that sends the events from a client to the server. + pub fn send(event_id: u16) -> SystemConfigs { + (move |mut events: EventReader, connection: Option>| { + for event in events.read() { + if let Some(connection) = connection.as_ref() { + send_event(connection, event_id, &event.0); + } else { + error!("tried to send event to non-existent connection"); + } + } + }) + .run_if(resource_exists::()) + } +} + +/// A plugin that manages network [Connection]s. +pub struct NetworkPlugin; + +impl NetworkPlugin { + /// A system that update the client [Connection]s on the server. + fn update_client_connections( + mut commands: Commands, + mut connections: Query<(Entity, &mut Connection)>, + ) { + for (entity, mut connection) in &mut connections { + if let Err(e) = connection.update() { + info!("closing client connection: {e}"); + commands.entity(entity).remove::(); + } + } + } + + /// A system that update the server [Connection] on the client. + fn update_server_connection(mut commands: Commands, mut connection: ResMut) { + if let Err(e) = connection.update() { + info!("closing server connection: {e}"); + commands.remove_resource::(); + } + } +} + +impl Plugin for NetworkPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Last, + ( + Self::update_client_connections, + Self::update_server_connection.run_if(resource_exists::()), + ), + ); + } +} + +/// An extension trait for a bevy [App] that adds network related features. +pub trait NetworkAppExt { + /// Setup the application to manage network events of type `T`. + /// + /// This will automatically define multiple events: + /// - [ServerEvent]: an [Event] received from the server on the client. + /// - [ClientEvent]: an [Event] received from a specific client on the + /// server. + /// - [SendAll]: used to send a network event to all the connected clients + /// on the server. + /// - [SendTo]: used to send a network event to a specific client on the + /// server. + /// - [SendToServer]: used to send an [Event] from a client to the server. + /// + /// # Examples + /// + /// ``` + /// use bevnet::NetworkAppExt; + /// use bevy::prelude::*; + /// use serde::{Deserialize, Serialize}; + /// + /// #[derive(Event, Deserialize, Serialize)] + /// struct MyEvent; + /// + /// let mut app = App::new(); + /// app.add_network_event::(); + /// ``` + fn add_network_event(&mut self) -> &mut Self; +} + +impl NetworkAppExt for App { + fn add_network_event(&mut self) -> &mut Self { + let mut event_id = self.world.get_resource_or_insert_with(LastEventId::default); + event_id.0 += 1; + let event_id = event_id.0; + + self.add_event::>() + .add_systems(PostUpdate, ServerEvent::::receive(event_id)) + .add_event::>() + .add_systems(PostUpdate, ClientEvent::::receive(event_id)) + .add_event::>() + .add_systems(PreUpdate, SendAll::::send(event_id)) + .add_event::>() + .add_systems(PreUpdate, SendTo::::send(event_id)) + .add_event::>() + .add_systems(PreUpdate, SendToServer::::send(event_id)) + } +}