diff --git a/.vscode/launch.json b/.vscode/launch.json index c35f2ae..323b2e1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -30,7 +30,7 @@ "cargo": { "args": ["build"] }, - "args": ["--connect=[::1]:25565"], + "args": ["--port=25566", "--connect=[::1]:25565"], "cwd": "${workspaceFolder}", "env": { "CARGO_MANIFEST_DIR": "${workspaceFolder}", diff --git a/src/game/seed.rs b/src/game/seed.rs index 5fabafa..2b1ff6b 100644 --- a/src/game/seed.rs +++ b/src/game/seed.rs @@ -1,10 +1,13 @@ -use std::hash::{DefaultHasher, Hash, Hasher}; +use std::{ + array::TryFromSliceError, + hash::{DefaultHasher, Hash, Hasher}, +}; use bevy::prelude::*; use rand::random; /// Value with which to initialize the PRNG -#[derive(Clone, Resource)] +#[derive(Resource, Clone, Copy)] pub struct Seed(u64); impl Seed { @@ -26,20 +29,40 @@ 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 u64 { +impl From for Vec { fn from(value: Seed) -> Self { - value.0 + 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()) } } diff --git a/src/game/setup.rs b/src/game/setup.rs index 01b987c..14c2d44 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.clone().into()); + 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/lib.rs b/src/lib.rs index cb4d69c..b6783f6 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.clone()); + app.insert_resource(*seed); } 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 deleted file mode 100644 index 3b1b227..0000000 --- a/src/net.rs +++ /dev/null @@ -1,126 +0,0 @@ -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 new file mode 100644 index 0000000..286d87d --- /dev/null +++ b/src/net/mod.rs @@ -0,0 +1,6 @@ +mod plugin; +mod socket; +mod thread; +mod types; + +pub use plugin::NetIOPlugin; diff --git a/src/net/plugin.rs b/src/net/plugin.rs new file mode 100644 index 0000000..57aa8fa --- /dev/null +++ b/src/net/plugin.rs @@ -0,0 +1,72 @@ +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 new file mode 100644 index 0000000..a4deb2b --- /dev/null +++ b/src/net/socket.rs @@ -0,0 +1,27 @@ +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 new file mode 100644 index 0000000..511746c --- /dev/null +++ b/src/net/thread.rs @@ -0,0 +1,55 @@ +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 new file mode 100644 index 0000000..44de878 --- /dev/null +++ b/src/net/types.rs @@ -0,0 +1,50 @@ +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() + } +}