Compare commits
3 commits
dd57bc30e1
...
9ac45e9249
Author | SHA1 | Date | |
---|---|---|---|
9ac45e9249 | |||
c005a4dbb9 | |||
ba7737671e |
10 changed files with 241 additions and 134 deletions
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -30,7 +30,7 @@
|
|||
"cargo": {
|
||||
"args": ["build"]
|
||||
},
|
||||
"args": ["--connect=[::1]:25565"],
|
||||
"args": ["--port=25566", "--connect=[::1]:25565"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"CARGO_MANIFEST_DIR": "${workspaceFolder}",
|
||||
|
|
|
@ -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<String> for Seed {
|
|||
}
|
||||
|
||||
impl From<Seed> 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<Seed> for u64 {
|
||||
fn from(value: Seed) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Seed {
|
||||
fn from(value: u64) -> Self {
|
||||
Seed(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Seed> for u64 {
|
||||
impl From<Seed> for Vec<u8> {
|
||||
fn from(value: Seed) -> Self {
|
||||
value.0
|
||||
value.0.to_le_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for Seed {
|
||||
type Error = TryFromSliceError;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
let bytes: [u8; 8] = value.as_slice().try_into()?;
|
||||
Ok(bytes.into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ pub struct PlayableArea(f32, f32);
|
|||
|
||||
/// Initialize deterministic values
|
||||
pub fn setup_from_seed(mut commands: Commands, seed: Res<Seed>) {
|
||||
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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
126
src/net.rs
126
src/net.rs
|
@ -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<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()?;
|
||||
socket.send_to(&message.to_le_bytes(), address)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn network_receive_loop(messages: Sender<NetworkMessage>, 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<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>);
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct NetworkReceive(Receiver<NetworkMessage>);
|
||||
|
||||
fn handle_network_io(
|
||||
receive: Res<NetworkReceive>,
|
||||
send: Res<NetworkSend>,
|
||||
seed: Option<Res<Seed>>,
|
||||
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::<Seed>(message.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((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}");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
6
src/net/mod.rs
Normal file
6
src/net/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
mod plugin;
|
||||
mod socket;
|
||||
mod thread;
|
||||
mod types;
|
||||
|
||||
pub use plugin::NetIOPlugin;
|
72
src/net/plugin.rs
Normal file
72
src/net/plugin.rs
Normal 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
27
src/net/socket.rs
Normal 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
55
src/net/thread.rs
Normal 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
50
src/net/types.rs
Normal 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()
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue