Move netcode to module
All checks were successful
CI / Formatting (push) Successful in 1m5s

This commit is contained in:
Michael Bradley 2025-05-25 20:36:49 -04:00
parent c005a4dbb9
commit 9ac45e9249
Signed by: MichaelBradley
SSH key fingerprint: SHA256:o/aaeYtRubILK7OYYjYP12DmU7BsPUhKji1AgaQ+ge4
6 changed files with 210 additions and 161 deletions

View file

@ -1,161 +0,0 @@
use std::{
net::{Ipv6Addr, SocketAddr, UdpSocket},
thread::{JoinHandle, spawn},
};
use bevy::{prelude::*, tasks::futures_lite::io};
use crossbeam_channel::{Receiver, Sender, TrySendError, 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 = (Vec<u8>, SocketAddr);
fn start_network_thread<M: Send + 'static>(
network_loop: fn(M, UdpSocket) -> Result,
messages: M,
socket: UdpSocket,
) -> JoinHandle<Result> {
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<NetworkMessage>, 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: Sender<NetworkMessage>, 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 setup_socket(port: u16) -> Result<(Sender<NetworkMessage>, Receiver<NetworkMessage>)> {
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<NetworkMessage>);
impl NetworkSend {
/// Send the message, erroring if the queue is full or disconnected
pub fn send(
&self,
value: Vec<u8>,
address: SocketAddr,
) -> Result<(), TrySendError<NetworkMessage>> {
self.0.try_send((value, address))
}
}
#[derive(Resource, Clone)]
struct NetworkReceive(Receiver<NetworkMessage>);
impl NetworkReceive {
fn iter(&self) -> Iter {
Iter(self.0.clone())
}
}
struct Iter(Receiver<NetworkMessage>);
impl Iterator for Iter {
type Item = NetworkMessage;
fn next(&mut self) -> Option<Self::Item> {
self.0.try_recv().ok()
}
}
fn handle_network_io(
receive: Res<NetworkReceive>,
send: Res<NetworkSend>,
seed: Option<Res<Seed>>,
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::<Seed>(message.try_into()?);
}
}
Ok(())
}
#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
enum NetworkState {
#[default]
SinglePlayer,
MultiPlayer,
}
pub struct NetIOPlugin {
listen: u16,
peer: Option<SocketAddr>,
}
impl NetIOPlugin {
pub fn new(listen: u16, peer: Option<SocketAddr>) -> Self {
Self { listen, peer }
}
}
impl Plugin for NetIOPlugin {
fn build(&self, app: &mut App) {
app.init_state::<NetworkState>().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((Vec::new(), 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}");
}
};
}
}

6
src/net/mod.rs Normal file
View file

@ -0,0 +1,6 @@
mod plugin;
mod socket;
mod thread;
mod types;
pub use plugin::NetIOPlugin;

72
src/net/plugin.rs Normal file
View file

@ -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<NetworkReceive>,
send: Res<NetworkSend>,
seed: Option<Res<Seed>>,
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::<Seed>(message.try_into()?);
}
}
Ok(())
}
#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
enum NetworkState {
#[default]
SinglePlayer,
MultiPlayer,
}
pub struct NetIOPlugin {
listen: u16,
peer: Option<SocketAddr>,
}
impl NetIOPlugin {
pub fn new(listen: u16, peer: Option<SocketAddr>) -> Self {
Self { listen, peer }
}
}
impl Plugin for NetIOPlugin {
fn build(&self, app: &mut App) {
app.init_state::<NetworkState>().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}");
}
};
}
}

27
src/net/socket.rs Normal file
View file

@ -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))
}

55
src/net/thread.rs Normal file
View file

@ -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<M: Send + 'static>(
network_loop: fn(M, UdpSocket) -> Result,
messages: M,
socket: UdpSocket,
) -> JoinHandle<Result> {
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<Result> {
start_network_thread(network_send_loop, messages, socket)
}
pub fn start_receive_thread(messages: SendQueue, socket: UdpSocket) -> JoinHandle<Result> {
start_network_thread(network_receive_loop, messages, socket)
}

50
src/net/types.rs Normal file
View file

@ -0,0 +1,50 @@
use std::net::SocketAddr;
use bevy::prelude::*;
use crossbeam_channel::{Receiver, Sender, TrySendError};
pub type NetworkMessage = (Vec<u8>, SocketAddr);
pub type SendQueue = Sender<NetworkMessage>;
pub type ReceiveQueue = Receiver<NetworkMessage>;
#[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<u8>,
address: SocketAddr,
) -> Result<(), TrySendError<NetworkMessage>> {
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::Item> {
self.0.try_recv().ok()
}
}