From d6101ef02154186672f6ad5324314afa0fb6a94f Mon Sep 17 00:00:00 2001 From: Tipragot Date: Tue, 13 Feb 2024 14:01:36 +0100 Subject: [PATCH] Event synchronisation between client --- Cargo.lock | 14 ++++ crates/bevnet/Cargo.toml | 5 ++ crates/bevnet/src/lib.rs | 151 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 8b7bace..3f17b55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -428,7 +428,12 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" name = "bevnet" version = "0.2.0" dependencies = [ + "bevy", + "bincode", + "dashmap", "relay-client", + "serde", + "uuid", ] [[package]] @@ -1183,6 +1188,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 e2e0037..a5850b5 100644 --- a/crates/bevnet/Cargo.toml +++ b/crates/bevnet/Cargo.toml @@ -13,3 +13,8 @@ workspace = true [dependencies] relay-client = { path = "../relay-client" } +serde = "1.0.196" +bincode = "1.3.3" +dashmap = "5.5.3" +bevy = "0.12.1" +uuid = "1.7.0" diff --git a/crates/bevnet/src/lib.rs b/crates/bevnet/src/lib.rs index a25e0a9..62efd0d 100644 --- a/crates/bevnet/src/lib.rs +++ b/crates/bevnet/src/lib.rs @@ -1 +1,152 @@ //! A networking library for Bevy. + +use std::borrow::Cow; +use std::collections::LinkedList; + +use bevy::prelude::*; +use dashmap::DashMap; +use serde::de::DeserializeOwned; +use serde::Serialize; +pub use uuid::Uuid; + +/// A connection to a relay server. +#[derive(Resource)] +pub struct Connection(relay_client::Connection); + +/// A resource that stores the received messages. +#[derive(Resource)] +pub struct ReceivedMessages(DashMap)>>); + +impl Connection { + /// Returns the identifier of the connection. + pub const fn identifier(&self) -> Option { + self.0.identifier() + } +} + +/// A bevy plugin to make multiplayer game using a relay server. +pub struct NetworkPlugin(String); + +impl NetworkPlugin { + /// Create a new [NetworkPlugin] plugin with the given domain for the relay + /// server. + pub fn new<'a>(domain: impl Into>) -> Self { + Self(domain.into().into_owned()) + } +} + +/// Update the relay connection. +fn update_connection(mut connection: ResMut, received_messages: Res) { + let messages = connection.0.update(); + for (sender, mut message) in messages { + if message.len() < 2 { + error!("message too short received"); + continue; + } + let id_start = message.len() - 2; + let event_id = u16::from_be_bytes([message[id_start], message[id_start + 1]]); + message.truncate(id_start); + received_messages + .0 + .entry(event_id) + .or_default() + .push_back((sender, message)); + } +} + +/// A system that clear the received messages. +fn clear_received_messages(received_messages: Res) { + received_messages.0.clear(); +} + +impl Plugin for NetworkPlugin { + fn build(&self, app: &mut App) { + app.insert_resource(Connection( + relay_client::Connection::new(&self.0).expect("could not create connection"), + )) + .insert_resource(ReceivedMessages(DashMap::new())) + .add_systems(PreUpdate, update_connection) + .add_systems(PreUpdate, clear_received_messages.after(update_connection)); + } +} + +/// A resource that store the last event id used to register an [Event]. +/// +/// This is used to give an unique id to each event. +#[derive(Resource, Default)] +struct LastEventId(u16); + +/// An [Event] used to send an [Event] to another client on the relay server. +#[derive(Event)] +pub struct SendTo(pub Uuid, pub T); + +/// An [Event] used to receive an [Event] from another client on the relay +/// server. +#[derive(Event)] +pub struct Receive(pub Uuid, pub T); + +/// A trait that extends a bevy [App] to add multiplayer support. +pub trait NetworkAppExt { + /// Setup the application to manage network events of type `T`. + fn add_network_event(&mut self) -> &mut Self; +} + +impl NetworkAppExt for App { + fn add_network_event(&mut self) -> &mut Self { + // Get a new event id. + let mut event_id = self.world.get_resource_or_insert_with(LastEventId::default); + event_id.0 += 1; + let event_id = event_id.0; + + // Register the event. + self.add_event::>() + .add_event::>() + .add_systems( + PreUpdate, + (move |mut events: EventReader>, connection: Res| { + for event in events.read() { + // Get the size of the serialized event. + let size = match bincode::serialized_size(&event.1) { + Ok(size) => size, + Err(e) => { + error!("failed to serialize event: {}", e); + continue; + } + }; + + // Serialize the event we add 18 here because we will add the event id (2 + // bytes) at the end and after that, the relay client will add the target id + // at the end (16 bytes). + let mut data = Vec::with_capacity(size as usize + 18); + if let Err(e) = bincode::serialize_into(&mut data, &event.1) { + error!("failed to serialize event: {}", e); + continue; + } + + // Add the event id. + data.extend_from_slice(&event_id.to_be_bytes()); + + // Send the event. + connection.0.send(event.0, data); + } + }) + .before(update_connection), + ) + .add_systems( + PreUpdate, + (move |mut writer: EventWriter>, + received_messages: Res| { + if let Some(mut messages) = received_messages.0.get_mut(&event_id) { + while let Some((sender, message)) = messages.pop_front() { + match bincode::deserialize(&message) { + Ok(event) => writer.send(Receive(sender, event)), + Err(e) => error!("failed to deserialize event: {}", e), + } + } + } + }) + .before(clear_received_messages) + .after(update_connection), + ) + } +}