From d89d539f3b1ebc23822022bce16fdd8712dcb928 Mon Sep 17 00:00:00 2001 From: Michael Bradley Date: Sun, 6 Jul 2025 17:47:19 -0400 Subject: [PATCH 1/5] Make Seed a component Conceptually it works better as a resource, but that's an extra layer of complexity for the upcoming automatic distribution work. --- src/game/net.rs | 22 +++++----------------- src/game/plugin.rs | 4 ++-- src/game/seed.rs | 2 +- src/game/setup.rs | 6 +++--- src/game/ui.rs | 10 ++++------ 5 files changed, 15 insertions(+), 29 deletions(-) diff --git a/src/game/net.rs b/src/game/net.rs index 524b376..2ffa40b 100644 --- a/src/game/net.rs +++ b/src/game/net.rs @@ -2,17 +2,9 @@ use bevy::prelude::*; use crate::net::prelude::*; -use super::seed::Seed; - -pub fn handle_new_peer( - seed: Option>, - new_peers: Query<&Peer, Added>, - mut outbound: EventWriter, -) { - if let Some(seed) = seed { - for peer in new_peers { - outbound.write(OutboundPacket(Packet::new((*seed).into(), peer.uuid))); - } +pub fn handle_new_peer(new_peers: Query<&Peer, Added>) { + for peer in new_peers { + info!("Peer {} was added", peer.uuid); } } @@ -27,12 +19,8 @@ pub fn handle_deleted_peer(mut old_peers: RemovedComponents, peers: Query< Ok(()) } -pub fn handle_incoming_packets(mut packets: EventReader, mut commands: Commands) { +pub fn handle_incoming_packets(mut packets: EventReader) { for packet in packets.read() { - if let Ok(seed) = packet.0.message.clone().try_into() { - commands.insert_resource::(seed); - } else { - info!("Packet not seed: {:?}", packet.0.message); - } + info!("Packet received: {:?}", packet.0.message); } } diff --git a/src/game/plugin.rs b/src/game/plugin.rs index 6943b7b..0e920fd 100644 --- a/src/game/plugin.rs +++ b/src/game/plugin.rs @@ -96,10 +96,10 @@ impl Plugin for GamePlugin { info!("Will retrieve seed from peer => {peer}"); } DataSource::Seed(seed) => { - app.insert_resource(seed); + app.world_mut().spawn(seed); } DataSource::None => { - app.insert_resource(Seed::random()); + app.world_mut().spawn(Seed::random()); } }; } diff --git a/src/game/seed.rs b/src/game/seed.rs index 12d6a07..a003612 100644 --- a/src/game/seed.rs +++ b/src/game/seed.rs @@ -7,7 +7,7 @@ use bevy::prelude::*; use rand::random; /// Value with which to initialize the PRNG -#[derive(Resource, Debug, Clone, Copy)] +#[derive(Clone, Component, Copy, Debug)] pub struct Seed(u64); impl Seed { diff --git a/src/game/setup.rs b/src/game/setup.rs index eb4585c..464b9db 100644 --- a/src/game/setup.rs +++ b/src/game/setup.rs @@ -23,7 +23,7 @@ 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(seed: Option>, mut next_state: ResMut>) { +pub fn check_for_seed(seed: Option>, mut next_state: ResMut>) { if seed.is_some() { next_state.set(AppState::InGame); } @@ -34,8 +34,8 @@ pub fn check_for_seed(seed: Option>, mut next_state: ResMut) { - let mut rng = WyRand::from_seed((*seed).into()); +pub fn setup_from_seed(mut commands: Commands, seed: Single<&Seed>) { + let mut rng = WyRand::from_seed((**seed).into()); commands.insert_resource(PlayableArea( rng.random_range(DIMENSION_SIZES), rng.random_range(DIMENSION_SIZES), diff --git a/src/game/ui.rs b/src/game/ui.rs index 4abd6d1..16dfb5e 100644 --- a/src/game/ui.rs +++ b/src/game/ui.rs @@ -21,13 +21,11 @@ pub fn setup_seed_ui(mut commands: Commands) { .with_child((TextSpan::new(""), SeedUI)); } -pub fn update_seed_ui(seed: Option>, text: Query<&mut TextSpan, With>) { +pub fn update_seed_ui(seed: Option>, text: Query<&mut TextSpan, With>) { if let Some(value) = seed { - if value.is_changed() { - for mut span in text { - let number: u64 = (*value).into(); - **span = format!("{}", number); - } + for mut span in text { + let number: u64 = (**value).into(); + **span = format!("{}", number); } } } From ddf2883e4fb4b538f0c6c140d53c81f219298728 Mon Sep 17 00:00:00 2001 From: Michael Bradley Date: Sun, 6 Jul 2025 17:47:47 -0400 Subject: [PATCH 2/5] Increase how much you can zoom out --- src/game/runtime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/runtime.rs b/src/game/runtime.rs index ab393ad..fa828f0 100644 --- a/src/game/runtime.rs +++ b/src/game/runtime.rs @@ -78,6 +78,6 @@ pub fn zoom_camera( MouseScrollUnit::Line => 0.1, MouseScrollUnit::Pixel => 0.001, }; - projection.scale = (projection.scale - scroll.delta.y * scroll_type_multiplier).clamp(0.1, 2.5); + projection.scale = (projection.scale - scroll.delta.y * scroll_type_multiplier).clamp(0.1, 4.0); Ok(()) } From d76afe92f032c72fbd8629202909966693f532ad Mon Sep 17 00:00:00 2001 From: Michael Bradley Date: Sun, 6 Jul 2025 17:58:18 -0400 Subject: [PATCH 3/5] Use From when Into can't auto-infer type --- src/game/seed.rs | 3 +-- src/game/ui.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/game/seed.rs b/src/game/seed.rs index a003612..085c10c 100644 --- a/src/game/seed.rs +++ b/src/game/seed.rs @@ -62,7 +62,6 @@ impl TryFrom> for Seed { type Error = TryFromSliceError; fn try_from(value: Vec) -> Result { - let bytes: [u8; 8] = value.as_slice().try_into()?; - Ok(bytes.into()) + Ok(TryInto::<[u8; 8]>::try_into(value.as_slice())?.into()) } } diff --git a/src/game/ui.rs b/src/game/ui.rs index 16dfb5e..3ee6c50 100644 --- a/src/game/ui.rs +++ b/src/game/ui.rs @@ -24,8 +24,7 @@ pub fn setup_seed_ui(mut commands: Commands) { pub fn update_seed_ui(seed: Option>, text: Query<&mut TextSpan, With>) { if let Some(value) = seed { for mut span in text { - let number: u64 = (**value).into(); - **span = format!("{}", number); + **span = format!("{}", u64::from(**value)); } } } From 4db82f328b50ec742f9beca9bcf395c70a7e7195 Mon Sep 17 00:00:00 2001 From: Michael Bradley Date: Sun, 6 Jul 2025 18:29:58 -0400 Subject: [PATCH 4/5] Create generic distribution system Still incredibly basic and only handles initial distribution and spawning with no relationships or anything. --- src/game/plugin.rs | 2 ++ src/net/distribution.rs | 49 +++++++++++++++++++++++++++++++++++++++++ src/net/mod.rs | 6 ++++- src/net/plugin.rs | 9 ++------ src/net/state.rs | 8 +++++++ 5 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 src/net/distribution.rs create mode 100644 src/net/state.rs diff --git a/src/game/plugin.rs b/src/game/plugin.rs index 0e920fd..d879c20 100644 --- a/src/game/plugin.rs +++ b/src/game/plugin.rs @@ -91,6 +91,8 @@ impl Plugin for GamePlugin { ), ); + Seed::register(app); + match self.source { DataSource::Address(peer) => { info!("Will retrieve seed from peer => {peer}"); diff --git a/src/net/distribution.rs b/src/net/distribution.rs new file mode 100644 index 0000000..e5e512d --- /dev/null +++ b/src/net/distribution.rs @@ -0,0 +1,49 @@ +use bevy::prelude::*; + +use super::{ + packet::{InboundPacket, OutboundPacket, Packet}, + peer::Peer, + state::NetworkState, +}; + +fn spawner> + Component>( + mut inbound: EventReader, + mut commands: Commands, +) { + for packet in inbound.read() { + if let Ok(entity) = T::try_from(packet.0.message.clone()) { + commands.spawn(entity); + } + } +} + +fn sender> + Component + Clone>( + peers: Query<&Peer, Added>, + entities: Query<&T>, + mut outbound: EventWriter, +) { + for peer in peers { + for entity in entities { + outbound.write(OutboundPacket(Packet::new( + (*entity).clone().into(), + peer.uuid, + ))); + } + } +} + +pub trait Networked: Into> + TryFrom> + Component + Clone { + fn register(app: &mut App); +} + +impl Networked for T +where + T: Into> + TryFrom> + Component + Clone, +{ + fn register(app: &mut App) { + app.add_systems( + FixedUpdate, + (sender::, spawner::).run_if(in_state(NetworkState::MultiPlayer)), + ); + } +} diff --git a/src/net/mod.rs b/src/net/mod.rs index 678172f..e4475c4 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,14 +1,18 @@ +mod distribution; mod io; mod packet; mod peer; mod plugin; mod queues; mod socket; +mod state; mod thread; #[allow(unused_imports)] pub mod prelude { + pub use super::distribution::Networked; pub use super::packet::{InboundPacket, OutboundPacket, Packet}; pub use super::peer::{Peer, PeerReceiveTiming, PeerSendTiming}; - pub use super::plugin::{NetIOPlugin, NetworkState}; + pub use super::plugin::NetIOPlugin; + pub use super::state::NetworkState; } diff --git a/src/net/plugin.rs b/src/net/plugin.rs index bbe807f..9cf00b1 100644 --- a/src/net/plugin.rs +++ b/src/net/plugin.rs @@ -4,20 +4,15 @@ use bevy::prelude::*; use uuid::Uuid; use super::{ + distribution::Networked, io::{Config, handle_network_input, handle_network_output, heartbeat, timeout}, packet::{InboundPacket, OutboundPacket}, peer::{Peer, PeerChangeEvent, PeerMap, handle_new_peer, handle_peer_change}, queues::{NetworkReceive, NetworkSend}, socket::bind_socket, + state::NetworkState, }; -#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)] -pub enum NetworkState { - #[default] - SinglePlayer, - MultiPlayer, -} - pub struct NetIOPlugin { listen: u16, peer: Option, diff --git a/src/net/state.rs b/src/net/state.rs new file mode 100644 index 0000000..2321bbe --- /dev/null +++ b/src/net/state.rs @@ -0,0 +1,8 @@ +use bevy::prelude::*; + +#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)] +pub enum NetworkState { + #[default] + SinglePlayer, + MultiPlayer, +} From 1a5a628000530638e5e1cbd8fa73bc91f6e6f643 Mon Sep 17 00:00:00 2001 From: Michael Bradley Date: Wed, 9 Jul 2025 19:37:46 -0400 Subject: [PATCH 5/5] Partial: Dedicated Peer distribution system This is an uncompleted commit to move the work over to my other machine. Should compile though. --- src/game/net.rs | 4 +- src/game/ui.rs | 4 +- src/net/distribution.rs | 2 +- src/net/io.rs | 45 ++++++------ src/net/peer.rs | 148 +++++++++++++++++++++++++++++++++++----- src/net/plugin.rs | 11 +-- 6 files changed, 161 insertions(+), 53 deletions(-) diff --git a/src/game/net.rs b/src/game/net.rs index 2ffa40b..8e43e3f 100644 --- a/src/game/net.rs +++ b/src/game/net.rs @@ -4,14 +4,14 @@ use crate::net::prelude::*; pub fn handle_new_peer(new_peers: Query<&Peer, Added>) { for peer in new_peers { - info!("Peer {} was added", peer.uuid); + info!("Peer {} was added", peer.id); } } pub fn handle_deleted_peer(mut old_peers: RemovedComponents, peers: Query<&Peer>) -> Result { for entity in old_peers.read() { if let Ok(peer) = peers.get(entity) { - info!("Peer {} was removed", peer.uuid); + info!("Peer {} was removed", peer.id); } else { info!("Peer {} was removed", entity); } diff --git a/src/game/ui.rs b/src/game/ui.rs index 3ee6c50..391a39f 100644 --- a/src/game/ui.rs +++ b/src/game/ui.rs @@ -90,8 +90,8 @@ pub fn update_peer_ui_timings( if let Ok((peer, recv, send)) = peers.get(id.0) { **row = format!( "{} {:.2} {:.2}", - peer.uuid, - (time.elapsed() - recv.time().unwrap_or(default())).as_secs_f64(), + peer.id, + (time.elapsed() - recv.time()).as_secs_f64(), (time.elapsed() - send.time()).as_secs_f64() ) } diff --git a/src/net/distribution.rs b/src/net/distribution.rs index e5e512d..161ea1a 100644 --- a/src/net/distribution.rs +++ b/src/net/distribution.rs @@ -26,7 +26,7 @@ fn sender> + Component + Clone>( for entity in entities { outbound.write(OutboundPacket(Packet::new( (*entity).clone().into(), - peer.uuid, + peer.id, ))); } } diff --git a/src/net/io.rs b/src/net/io.rs index 7dcceca..25774b6 100644 --- a/src/net/io.rs +++ b/src/net/io.rs @@ -3,16 +3,18 @@ use std::time::Duration; use bevy::prelude::*; use uuid::Uuid; +use crate::net::peer::PotentialPeer; + use super::{ packet::{InboundPacket, OutboundPacket, Packet}, - peer::{Peer, PeerChangeEvent, PeerMap, PeerReceiveTiming, PeerSendTiming}, + peer::{Peer, PeerChangeEvent, PeerData, PeerMap, PeerReceiveTiming, PeerSendTiming}, queues::{NetworkReceive, NetworkSend}, }; pub fn handle_network_input( from_socket: Res, peer_map: Res, - mut peers: Query<(&Peer, &mut PeerReceiveTiming)>, + mut peers: Query<(&PeerData, &mut PeerReceiveTiming)>, mut to_app: EventWriter, time: Res