diff --git a/.vscode/launch.json b/.vscode/launch.json index 323b2e1..c35f2ae 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -30,7 +30,7 @@ "cargo": { "args": ["build"] }, - "args": ["--port=25566", "--connect=[::1]:25565"], + "args": ["--connect=[::1]:25565"], "cwd": "${workspaceFolder}", "env": { "CARGO_MANIFEST_DIR": "${workspaceFolder}", diff --git a/src/game/seed.rs b/src/game/seed.rs index 2b1ff6b..5fabafa 100644 --- a/src/game/seed.rs +++ b/src/game/seed.rs @@ -1,13 +1,10 @@ -use std::{ - array::TryFromSliceError, - hash::{DefaultHasher, Hash, Hasher}, -}; +use std::hash::{DefaultHasher, Hash, Hasher}; use bevy::prelude::*; use rand::random; /// Value with which to initialize the PRNG -#[derive(Resource, Clone, Copy)] +#[derive(Clone, Resource)] pub struct Seed(u64); impl Seed { @@ -29,40 +26,20 @@ impl From for Seed { } impl From for [u8; 8] { + /// Convert to a u8 array for ingestion by random number generator fn from(value: Seed) -> Self { value.0.to_le_bytes() } } -impl From<[u8; 8]> for Seed { - fn from(value: [u8; 8]) -> Self { - u64::from_le_bytes(value).into() - } -} - -impl From for u64 { - fn from(value: Seed) -> Self { - value.0 - } -} - impl From for Seed { fn from(value: u64) -> Self { Seed(value) } } -impl From for Vec { +impl From for u64 { fn from(value: Seed) -> Self { - value.0.to_le_bytes().to_vec() - } -} - -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()) + value.0 } } diff --git a/src/game/setup.rs b/src/game/setup.rs index 14c2d44..01b987c 100644 --- a/src/game/setup.rs +++ b/src/game/setup.rs @@ -35,7 +35,7 @@ pub struct PlayableArea(f32, f32); /// Initialize deterministic values pub fn setup_from_seed(mut commands: Commands, seed: Res) { - let mut rng = WyRand::from_seed((*seed).into()); + let mut rng = WyRand::from_seed(seed.clone().into()); commands.insert_resource(PlayableArea( rng.random_range(DIMENSION_SIZES), rng.random_range(DIMENSION_SIZES), diff --git a/src/lib.rs b/src/lib.rs index b6783f6..cb4d69c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,7 @@ impl Plugin for AppSettings { ); if let Some(ref seed) = self.source.seed { - app.insert_resource(*seed); + app.insert_resource(seed.clone()); } else if let Some(ref peer) = self.source.connect { info!("Will retrieve seed from peer => {peer}"); } else { diff --git a/src/net.rs b/src/net.rs new file mode 100644 index 0000000..3b1b227 --- /dev/null +++ b/src/net.rs @@ -0,0 +1,126 @@ +use std::{ + net::{Ipv6Addr, SocketAddr, UdpSocket}, + thread::{JoinHandle, spawn}, +}; + +use bevy::{prelude::*, tasks::futures_lite::io}; +use crossbeam_channel::{Receiver, Sender, unbounded}; + +use crate::game::seed::Seed; + +fn configure_socket(socket: &UdpSocket) -> io::Result<()> { + socket.set_read_timeout(None)?; + socket.set_write_timeout(None)?; + Ok(()) +} + +type NetworkMessage = (u64, SocketAddr); + +fn start_network_thread( + network_loop: fn(M, UdpSocket) -> Result, + messages: M, + socket: UdpSocket, +) -> JoinHandle { + spawn(move || { + let result = network_loop(messages, socket); + match result { + Ok(()) => error!("Network thread: Loop returned without error?"), + Err(ref err) => error!("Network thread: {err}"), + }; + result + }) +} + +fn network_send_loop(messages: Receiver, socket: UdpSocket) -> Result { + loop { + let (message, address) = messages.recv()?; + socket.send_to(&message.to_le_bytes(), address)?; + } +} + +fn network_receive_loop(messages: Sender, socket: UdpSocket) -> Result { + loop { + let mut message = [0u8; 8]; + let (len, address) = socket.recv_from(&mut message)?; + info!("Network thread: Received {len} bytes"); + messages.try_send((u64::from_le_bytes(message), address))?; + } +} + +fn setup_socket(port: u16) -> Result<(Sender, Receiver)> { + let socket = UdpSocket::bind((Ipv6Addr::UNSPECIFIED, port))?; + configure_socket(&socket)?; + let (send_inbound, receive_inbound) = unbounded(); + start_network_thread(network_receive_loop, send_inbound, socket.try_clone()?); + let (send_outbound, receive_outbound) = unbounded(); + start_network_thread(network_send_loop, receive_outbound, socket); + Ok((send_outbound, receive_inbound)) +} + +#[derive(Resource)] +pub struct NetworkSend(Sender); + +#[derive(Resource)] +pub struct NetworkReceive(Receiver); + +fn handle_network_io( + receive: Res, + send: Res, + seed: Option>, + mut commands: Commands, +) -> Result { + let Ok((message, address)) = receive.0.try_recv() else { + return Ok(()); + }; + if let Some(value) = seed { + send.0.try_send((value.clone().into(), address))?; + } else { + commands.insert_resource::(message.into()); + } + Ok(()) +} + +#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)] +enum NetworkState { + #[default] + SinglePlayer, + MultiPlayer, +} + +pub struct NetIOPlugin { + listen: u16, + peer: Option, +} + +impl NetIOPlugin { + pub fn new(listen: u16, peer: Option) -> Self { + Self { listen, peer } + } +} + +impl Plugin for NetIOPlugin { + fn build(&self, app: &mut App) { + app.init_state::().add_systems( + FixedUpdate, + handle_network_io.run_if(in_state(NetworkState::MultiPlayer)), + ); + + match setup_socket(self.listen) { + Ok((send, receive)) => { + if let Some(socket) = self.peer { + if let Err(err) = send.try_send((0, socket)) { + warn!("Failed to send to peer: {err}"); + return; + } + } + + app.insert_state(NetworkState::MultiPlayer) + .insert_resource(NetworkSend(send)) + .insert_resource(NetworkReceive(receive)); + } + Err(err) => { + warn!("Failed to set up networking: {err}"); + } + }; + } +} diff --git a/src/net/mod.rs b/src/net/mod.rs deleted file mode 100644 index 286d87d..0000000 --- a/src/net/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod plugin; -mod socket; -mod thread; -mod types; - -pub use plugin::NetIOPlugin; diff --git a/src/net/plugin.rs b/src/net/plugin.rs deleted file mode 100644 index 57aa8fa..0000000 --- a/src/net/plugin.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::net::SocketAddr; - -use bevy::prelude::*; - -use crate::game::seed::Seed; - -use super::{ - socket::bind_socket, - types::{NetworkReceive, NetworkSend}, -}; - -fn handle_network_io( - receive: Res, - send: Res, - seed: Option>, - mut commands: Commands, -) -> Result { - for (message, address) in receive.iter() { - if let Some(ref value) = seed { - send.send((**value).into(), address)?; - } else { - commands.insert_resource::(message.try_into()?); - } - } - - Ok(()) -} - -#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)] -enum NetworkState { - #[default] - SinglePlayer, - MultiPlayer, -} - -pub struct NetIOPlugin { - listen: u16, - peer: Option, -} - -impl NetIOPlugin { - pub fn new(listen: u16, peer: Option) -> Self { - Self { listen, peer } - } -} - -impl Plugin for NetIOPlugin { - fn build(&self, app: &mut App) { - app.init_state::().add_systems( - FixedUpdate, - handle_network_io.run_if(in_state(NetworkState::MultiPlayer)), - ); - - match bind_socket(self.listen) { - Ok((send, receive)) => { - if let Some(socket) = self.peer { - if let Err(err) = send.try_send((Vec::new(), socket)) { - warn!("Failed to send to peer: {err}"); - return; - } - } - - app.insert_state(NetworkState::MultiPlayer) - .insert_resource(NetworkSend::new(send)) - .insert_resource(NetworkReceive::new(receive)); - } - Err(err) => { - warn!("Failed to set up networking: {err}"); - } - }; - } -} diff --git a/src/net/socket.rs b/src/net/socket.rs deleted file mode 100644 index a4deb2b..0000000 --- a/src/net/socket.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::{ - io::Result, - net::{Ipv6Addr, UdpSocket}, -}; - -use crossbeam_channel::unbounded; - -use super::{ - thread::{start_receive_thread, start_send_thread}, - types::{ReceiveQueue, SendQueue}, -}; - -fn configure_socket(socket: &UdpSocket) -> Result<()> { - socket.set_read_timeout(None)?; - socket.set_write_timeout(None)?; - Ok(()) -} - -pub fn bind_socket(port: u16) -> Result<(SendQueue, ReceiveQueue)> { - let socket = UdpSocket::bind((Ipv6Addr::UNSPECIFIED, port))?; - configure_socket(&socket)?; - let (send_inbound, receive_inbound) = unbounded(); - start_receive_thread(send_inbound, socket.try_clone()?); - let (send_outbound, receive_outbound) = unbounded(); - start_send_thread(receive_outbound, socket); - Ok((send_outbound, receive_inbound)) -} diff --git a/src/net/thread.rs b/src/net/thread.rs deleted file mode 100644 index 511746c..0000000 --- a/src/net/thread.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::{ - net::UdpSocket, - thread::{JoinHandle, spawn}, -}; - -use bevy::prelude::*; - -use super::types::{ReceiveQueue, SendQueue}; - -fn network_send_loop(messages: ReceiveQueue, socket: UdpSocket) -> Result { - loop { - let (message, address) = messages.recv()?; - debug!("Sending {} bytes to {}", message.len(), address); - let sent = socket.send_to(message.as_slice(), address)?; - if message.len() != sent { - error!( - "Network thread: Tried to send {} bytes but only sent {}", - message.len(), - sent - ); - } - } -} - -fn network_receive_loop(messages: SendQueue, socket: UdpSocket) -> Result { - loop { - let mut message = [0u8; 1024]; // 1 KiB seems like it would be enough, TBD though - let (len, address) = socket.recv_from(&mut message)?; - debug!("Network thread: Received {len} bytes from {address}"); - messages.try_send((message[..len].into(), address))?; - } -} - -fn start_network_thread( - network_loop: fn(M, UdpSocket) -> Result, - messages: M, - socket: UdpSocket, -) -> JoinHandle { - spawn(move || { - let result = network_loop(messages, socket); - match result { - Ok(()) => error!("Network thread: Loop returned without error?"), - Err(ref err) => error!("Network thread: {err}"), - }; - result - }) -} - -pub fn start_send_thread(messages: ReceiveQueue, socket: UdpSocket) -> JoinHandle { - start_network_thread(network_send_loop, messages, socket) -} - -pub fn start_receive_thread(messages: SendQueue, socket: UdpSocket) -> JoinHandle { - start_network_thread(network_receive_loop, messages, socket) -} diff --git a/src/net/types.rs b/src/net/types.rs deleted file mode 100644 index 44de878..0000000 --- a/src/net/types.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::net::SocketAddr; - -use bevy::prelude::*; -use crossbeam_channel::{Receiver, Sender, TrySendError}; - -pub type NetworkMessage = (Vec, SocketAddr); -pub type SendQueue = Sender; -pub type ReceiveQueue = Receiver; - -#[derive(Resource)] -pub struct NetworkSend(SendQueue); - -impl NetworkSend { - pub fn new(queue: SendQueue) -> Self { - Self(queue) - } - - /// Send the message, erroring if the queue is full or disconnected - pub fn send( - &self, - value: Vec, - address: SocketAddr, - ) -> Result<(), TrySendError> { - self.0.try_send((value, address)) - } -} - -#[derive(Resource, Clone)] -pub struct NetworkReceive(ReceiveQueue); - -impl NetworkReceive { - pub fn new(queue: ReceiveQueue) -> Self { - Self(queue) - } - - /// Non-blocking iterator - pub fn iter(&self) -> Iter { - Iter(self.0.clone()) - } -} - -pub struct Iter(ReceiveQueue); - -impl Iterator for Iter { - type Item = NetworkMessage; - - fn next(&mut self) -> Option { - self.0.try_recv().ok() - } -}