diff --git a/.vscode/settings.json b/.vscode/settings.json index 2d7649e..8ebe79c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,21 +23,20 @@ // ], "rust-analyzer.cargo.targetDir": true, "cSpell.words": [ - "Backquote", - "Cheatbook", - "codegen", - "despawn", - "Despawns", - "Iyes", - "lerp", - "PRNG", - "recip", - "respawns", - "Seedable", - "timestep", - "timesteps", - "winit", - "wyrand", - "zerocopy" -] + "Backquote", + "Cheatbook", + "codegen", + "despawn", + "Despawns", + "Iyes", + "lerp", + "PRNG", + "recip", + "respawns", + "Seedable", + "timestep", + "timesteps", + "winit", + "wyrand" + ] } diff --git a/Cargo.lock b/Cargo.lock index 1b049ba..19fe817 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4599,7 +4599,6 @@ checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom", "js-sys", - "rand", "serde", "wasm-bindgen", ] diff --git a/Cargo.toml b/Cargo.toml index f30754f..b4c9950 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ rand = { version = "0.9.2", default-features = false, features = [ "std", "thread_rng", ] } -uuid = { version = "1.18.1", features = ["v4", "fast-rng"] } +uuid = { version = "1.18.1", features = ["v4"] } wyrand = "0.3.2" [features] diff --git a/src/game/plugin.rs b/src/game/plugin.rs index 13ea24f..586dd48 100644 --- a/src/game/plugin.rs +++ b/src/game/plugin.rs @@ -1,16 +1,13 @@ use std::net::SocketAddr; use avian2d::PhysicsPlugins; -use bevy::{ - input::common_conditions::{input_just_pressed, input_pressed}, - prelude::*, -}; +use bevy::{input::common_conditions::input_pressed, prelude::*}; use crate::net::prelude::*; use super::{ net::{handle_deleted_peer, handle_incoming_packets, handle_new_peer}, - runtime::{move_camera, move_player, quit, reset_seed, zoom_camera}, + runtime::{move_camera, move_player, quit, zoom_camera}, seed::Seed, setup::{ check_for_seed, setup_balls, setup_camera, setup_from_seed, setup_player, setup_walls, @@ -79,7 +76,13 @@ impl Plugin for GamePlugin { OnEnter(AppState::InGame), (setup_from_seed, (setup_player, setup_balls, setup_walls)).chain(), ) - .add_systems(FixedUpdate, handle_incoming_packets) + .add_systems( + FixedUpdate, + ( + check_for_seed.run_if(in_state(AppState::Loading)), + handle_incoming_packets, + ), + ) .add_systems( Update, ( @@ -91,13 +94,11 @@ impl Plugin for GamePlugin { update_peer_ui_timings, update_potential_peer_ui, ), - reset_seed.run_if(input_just_pressed(KeyCode::KeyR)), quit.run_if(input_pressed(KeyCode::KeyQ)), ), ) .add_observer(handle_new_peer) - .add_observer(handle_deleted_peer) - .add_observer(check_for_seed); + .add_observer(handle_deleted_peer); match self.source { DataSource::Address(peer) => { diff --git a/src/game/runtime.rs b/src/game/runtime.rs index 405a7e9..f5273f3 100644 --- a/src/game/runtime.rs +++ b/src/game/runtime.rs @@ -7,10 +7,7 @@ use bevy::{ prelude::*, }; -use super::{ - objects::{Player, Radius}, - seed::Seed, -}; +use super::objects::{Player, Radius}; /// Move the player character based on the keyboard input pub fn move_player( @@ -84,7 +81,3 @@ pub fn zoom_camera( projection.scale = (projection.scale - scroll.delta.y * scroll_type_multiplier).clamp(0.1, 4.0); Ok(()) } - -pub fn reset_seed(mut seed: Single<&mut Seed>) { - **seed = Seed::random() -} diff --git a/src/game/seed.rs b/src/game/seed.rs index 8c470ce..d95a957 100644 --- a/src/game/seed.rs +++ b/src/game/seed.rs @@ -6,11 +6,8 @@ use std::{ use bevy::prelude::*; use rand::random; -use crate::net::prelude::{EntityNetworkID, Networked}; - /// Value with which to initialize the PRNG #[derive(Clone, Component, Copy, Debug)] -#[require(EntityNetworkID)] pub struct Seed(u64); impl Seed { diff --git a/src/game/setup.rs b/src/game/setup.rs index 7ab9251..464b9db 100644 --- a/src/game/setup.rs +++ b/src/game/setup.rs @@ -23,8 +23,10 @@ const BALL_COUNT: u8 = 32; const BALL_SIZES: Range = 10.0..25.0; const DIMENSION_SIZES: Range = 500.0..2000.0; -pub fn check_for_seed(_add: On, mut next_state: ResMut>) { - next_state.set(AppState::InGame); +pub fn check_for_seed(seed: Option>, mut next_state: ResMut>) { + if seed.is_some() { + next_state.set(AppState::InGame); + } } /// The size of the playable area (x, y) diff --git a/src/net/distribution.rs b/src/net/distribution.rs index 6c47c61..70f2061 100644 --- a/src/net/distribution.rs +++ b/src/net/distribution.rs @@ -1,136 +1,52 @@ -use bevy::{ - ecs::{component::Mutable, query::QueryFilter}, - prelude::*, -}; -use uuid::Uuid; +use bevy::prelude::*; use super::{ packet::{InboundPacket, OutboundPacket, Packet}, peer::PeerID, }; -/// Entities wishing to be networked must have this in their bundle -#[derive(Component, Debug)] -pub struct EntityNetworkID(Uuid); - -impl Default for EntityNetworkID { - fn default() -> Self { - Self(Uuid::new_v4()) - } -} - -#[derive(Component, Debug)] +#[derive(Component)] pub struct PeerOwned; -pub trait NetworkEncodable { - fn encode(&self) -> Vec; -} - -impl NetworkEncodable for T -where - T: Clone + Into>, -{ - fn encode(&self) -> Vec { - self.clone().into() - } -} - -pub trait NetworkDecodable: Sized { - type DecodeError; - fn decode(buffer: Vec) -> std::result::Result; -} - -impl NetworkDecodable for T -where - T: TryFrom>, -{ - type DecodeError = T::Error; - fn decode(buffer: Vec) -> std::result::Result { - T::try_from(buffer) - } -} - -/// Components wishing to be networked must implement this type -pub trait Networked: Component + NetworkEncodable + NetworkDecodable { - type LocalFilter: QueryFilter; - type RemoteFilter: QueryFilter; -} - -impl Networked for T -where - T: Component + NetworkEncodable + NetworkDecodable, -{ - type LocalFilter = Without; - type RemoteFilter = With; -} - -fn incoming_network_entity< - T: NetworkDecodable + Component, - F: QueryFilter, ->( +fn spawner> + Component>( mut inbound: MessageReader, - mut components: Query<(&mut T, &EntityNetworkID), F>, mut commands: Commands, ) { - 'packets: for InboundPacket(packet) in inbound.read() { - if let Ok(component) = T::decode(packet.message.clone()) { - for (mut existing_component, id) in &mut components { - if id.0 == packet.entity { - *existing_component = component; - continue 'packets; - } - } - commands.spawn((component, EntityNetworkID(packet.entity), PeerOwned)); + for InboundPacket(packet) in inbound.read() { + if let Ok(component) = T::try_from(packet.message.clone()) { + commands.spawn((component, PeerOwned)); } } } -fn new_peer( +fn new_peer> + Component + Clone>( add: On, - components: Query<(&T, &EntityNetworkID), Without>, peers: Query<&PeerID>, + components: Query<&T, Without>, mut outbound: MessageWriter, ) -> Result { let peer = peers.get(add.entity)?; - for (component, id) in components { - outbound.write(Packet::create(peer.id, id.0, component.encode())); + for component in components { + outbound.write(Packet::create(component.clone().into(), peer.id)); } Ok(()) } -fn new_local_entity( +fn new_entity> + Component + Clone>( add: On, - components: Query<(&T, &EntityNetworkID), Without>, peers: Query<&PeerID>, + components: Query<&T, Without>, mut outbound: MessageWriter, ) { - if let Ok((component, id)) = components.get(add.entity) { + if let Ok(component) = components.get(add.entity) { for peer in peers { - outbound.write(Packet::create(peer.id, id.0, component.encode())); + outbound.write(Packet::create(component.clone().into(), peer.id)); } } } -fn changed_local_entity( - components: Query<(&T, &EntityNetworkID), (F, Changed)>, - peers: Query<&PeerID>, - mut outbound: MessageWriter, -) { - for (component, id) in components { - for peer in peers { - outbound.write(Packet::create(peer.id, id.0, component.encode())); - } - } -} - -pub fn distribution_plugin(app: &mut App) { - app.add_systems( - FixedUpdate, - ( - changed_local_entity::, - incoming_network_entity::, - ), - ) - .add_observer(new_peer::) - .add_observer(new_local_entity::); +pub fn distribution_plugin> + TryFrom> + Component + Clone>(app: &mut App) { + app.add_systems(FixedUpdate, spawner::) + .add_observer(new_peer::) + .add_observer(new_entity::); } diff --git a/src/net/heartbeat.rs b/src/net/heartbeat.rs index 3fc87bb..3e2003e 100644 --- a/src/net/heartbeat.rs +++ b/src/net/heartbeat.rs @@ -1,12 +1,13 @@ use std::time::Duration; use bevy::prelude::*; -use uuid::Uuid; + +use crate::net::{packet::PacketType, peer::PotentialPeers}; use super::{ io::{Config, format_message}, packet::{OutboundPacket, Packet}, - peer::{Peer, PeerChangeMessage, PeerID, PeerReceiveTiming, PeerSendTiming, PotentialPeers}, + peer::{Peer, PeerChangeMessage, PeerID, PeerReceiveTiming, PeerSendTiming}, queues::NetworkSend, }; @@ -23,7 +24,7 @@ pub fn heartbeat( if last.time() + PING_FREQUENCY > time.elapsed() { continue; } - outbound.write(Packet::create(peer.id, Uuid::nil(), Vec::new())); + outbound.write(Packet::create(Vec::new(), peer.id)); } Ok(()) } @@ -47,7 +48,10 @@ pub fn ping_potential_peers( config: Res, ) -> Result { for peer in &peers.addresses { - to_socket.send(format_message(config.id, Uuid::nil(), &Vec::new()), *peer)?; + to_socket.send( + format_message(&Vec::new(), PacketType::Peer, config.id), + *peer, + )?; } Ok(()) } diff --git a/src/net/io.rs b/src/net/io.rs index 6aa12d5..23afa93 100644 --- a/src/net/io.rs +++ b/src/net/io.rs @@ -1,6 +1,8 @@ use bevy::prelude::*; use uuid::Uuid; +use crate::net::packet::PacketType; + use super::{ packet::{InboundPacket, OutboundPacket, Packet}, peer::{PeerChangeMessage, PeerData, PeerMap, PeerReceiveTiming, PeerSendTiming}, @@ -18,8 +20,8 @@ impl Default for Config { } } -pub fn format_message(peer: Uuid, entity: Uuid, data: &Vec) -> Vec { - [peer.as_bytes(), entity.as_bytes(), data.as_slice()].concat() +pub fn format_message(data: &Vec, variant: PacketType, id: Uuid) -> Vec { + [data.as_slice(), &[variant as u8], id.as_bytes()].concat() } pub fn handle_network_input( @@ -33,6 +35,7 @@ pub fn handle_network_input( for (message, address) in from_socket.iter() { match Packet::try_from(message) { Ok(packet) => { + // TODO: Handle packet variant if !packet.message.is_empty() { to_app.write(packet.clone().into()); } @@ -62,7 +65,7 @@ pub fn handle_network_output( for OutboundPacket(packet) in from_app.read() { let peer_id = peer_map.try_get(&packet.peer)?; let (peer, mut last) = peers.get_mut(*peer_id)?; - let message = format_message(config.id, packet.entity, &packet.message); + let message = format_message(&packet.message, packet.variant, config.id); to_socket.send(message, peer.addr.into())?; last.update(&time); } diff --git a/src/net/mod.rs b/src/net/mod.rs index bcbb70f..69f2151 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -10,9 +10,7 @@ mod thread; #[allow(unused_imports)] pub mod prelude { - pub use super::distribution::{ - EntityNetworkID, NetworkDecodable, NetworkEncodable, Networked, distribution_plugin, - }; + pub use super::distribution::distribution_plugin; pub use super::packet::{InboundPacket, OutboundPacket, Packet}; pub use super::peer::{Peer, PeerID, PeerReceiveTiming, PeerSendTiming, PotentialPeers}; pub use super::plugin::NetIOPlugin; diff --git a/src/net/packet.rs b/src/net/packet.rs index 275dad0..f11987f 100644 --- a/src/net/packet.rs +++ b/src/net/packet.rs @@ -5,47 +5,75 @@ use uuid::Uuid; pub enum TryFromBytesError { InsufficientLength, NotUUID, + NotVariant, } -#[derive(Clone, Debug)] -pub struct Packet { - pub peer: Uuid, - pub entity: Uuid, - pub message: Vec, +#[derive(Clone, Copy, Debug)] +pub enum PacketType { + Standard = 0x00, + Peer = 0x01, } -impl Packet { - pub fn create>(peer: Uuid, entity: Uuid, message: Vec) -> T { - Self { - peer, - entity, - message, +impl From for u8 { + fn from(value: PacketType) -> Self { + value as u8 + } +} + +impl TryFrom for PacketType { + type Error = TryFromBytesError; + + fn try_from(value: u8) -> std::result::Result { + match value { + value if value == PacketType::Standard as u8 => Ok(PacketType::Standard), + value if value == PacketType::Peer as u8 => Ok(PacketType::Peer), + _ => Err(TryFromBytesError::NotVariant), } - .into() } } pub const UUID_SIZE: usize = 16; -fn extract_uuid(buffer: &mut Vec) -> std::result::Result { - if buffer.len() < UUID_SIZE { - return Err(TryFromBytesError::InsufficientLength); +#[derive(Clone, Debug)] +pub struct Packet { + pub message: Vec, + pub variant: PacketType, + pub peer: Uuid, +} + +impl Packet { + pub fn new(message: Vec, variant: PacketType, peer: Uuid) -> Self { + Self { + message, + variant, + peer, + } + } + + pub fn create>(message: Vec, peer: Uuid) -> T { + Self { + message, + variant: PacketType::Standard, + peer, + } + .into() } - let og_not_uuid = &mut buffer.split_off(UUID_SIZE); - // Return the rest of the vector through the argument - // TODO: Check if this has a performance penalty - std::mem::swap(og_not_uuid, buffer); - // TODO: The Uuid crate has support for zerocopy (although I'm copying in a ton of other place regardless) - Uuid::from_slice(og_not_uuid.as_slice()).map_err(|_| TryFromBytesError::NotUUID) } impl TryFrom> for Packet { type Error = TryFromBytesError; fn try_from(mut value: Vec) -> std::result::Result { - let peer = extract_uuid(&mut value)?; - let entity = extract_uuid(&mut value)?; - Ok(Packet::create(peer, entity, value)) + if value.len() < UUID_SIZE { + return Err(TryFromBytesError::InsufficientLength); + } + let uuid = Uuid::from_slice(value.split_off(value.len() - UUID_SIZE).as_slice()) + .map_err(|_| TryFromBytesError::NotUUID)?; + let variant = value + .pop() + .ok_or(TryFromBytesError::InsufficientLength)? + .try_into()?; + Ok(Packet::new(value, variant, uuid)) } } diff --git a/src/net/peer.rs b/src/net/peer.rs index 1e7a974..3b7bbfc 100644 --- a/src/net/peer.rs +++ b/src/net/peer.rs @@ -8,7 +8,7 @@ use std::{ use bevy::prelude::*; use uuid::Uuid; -use super::packet::{InboundPacket, OutboundPacket, Packet}; +use super::packet::{InboundPacket, OutboundPacket, Packet, PacketType}; #[derive(Component, Debug, Default)] pub struct PeerSendTiming(Duration); @@ -280,7 +280,7 @@ pub fn handle_new_peer( if change.is_added() { for (_, other, data) in peers { if peer.id != other.id { - outbound.write(Packet::create(peer.id, other.id, data.addr.into())); + outbound.write(Packet::new(data.addr.into(), PacketType::Peer, peer.id).into()); } } }