Put base system in the same file using features
This commit is contained in:
parent
e109b67cfb
commit
a69973b3fa
|
@ -13,3 +13,9 @@ repository = "https://git.tipragot.fr/tipragot/bevnet"
|
||||||
serde = { version = "1.0.160", features = ["derive"] }
|
serde = { version = "1.0.160", features = ["derive"] }
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
bevy = "0.10.1"
|
bevy = "0.10.1"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["server", "sync"]
|
||||||
|
server = []
|
||||||
|
client = []
|
||||||
|
sync = []
|
||||||
|
|
206
src/lib.rs
206
src/lib.rs
|
@ -1,11 +1,11 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
collections::hash_map::DefaultHasher,
|
collections::hash_map::DefaultHasher,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
};
|
};
|
||||||
|
use std::{collections::HashMap, io, net::ToSocketAddrs, sync::Arc};
|
||||||
|
|
||||||
pub mod client;
|
|
||||||
pub mod server;
|
|
||||||
mod tcp;
|
mod tcp;
|
||||||
|
|
||||||
/// A packet that can be sent over a [Connection].
|
/// A packet that can be sent over a [Connection].
|
||||||
|
@ -24,3 +24,205 @@ pub trait Packet: DeserializeOwned + Serialize + Send + Sync {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: DeserializeOwned + Serialize + Send + Sync> Packet for T {}
|
impl<T: DeserializeOwned + Serialize + Send + Sync> Packet for T {}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
/// A function that handle a received [Packet]s.
|
||||||
|
pub type PacketHandler = Box<dyn Fn(Entity, Connection, Vec<u8>, &mut World) + Send + Sync>;
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
/// A function that handle a received [Packet]s.
|
||||||
|
pub type PacketHandler = Box<dyn Fn(Vec<u8>, &mut World) + Send + Sync>;
|
||||||
|
|
||||||
|
/// A Bevy resource that store the packets handlers.
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct HandlerManager(Arc<HashMap<u64, PacketHandler>>);
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
/// A Bevy resource that listens for incoming [Connection]s.
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct Listener(tcp::Listener);
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl Listener {
|
||||||
|
/// Creates a new listener on the given address.
|
||||||
|
pub fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<Self> {
|
||||||
|
Ok(Self(tcp::Listener::bind(addr)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
/// A connection to a remote client.
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Connection(Arc<tcp::Connection>);
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
/// A connection to a remote server.
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct Connection(tcp::Connection);
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
/// Connects to a remote server.
|
||||||
|
pub fn connect<A: ToSocketAddrs>(addr: A) -> io::Result<Self> {
|
||||||
|
Ok(Self(tcp::Connection::connect(addr)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a packet through this connection.
|
||||||
|
pub fn send<P: Packet>(&self, packet: &P) {
|
||||||
|
let mut data = bincode::serialize(packet).expect("Failed to serialize packet");
|
||||||
|
data.extend(P::packet_id().to_be_bytes());
|
||||||
|
self.0.send(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A plugin that manage the network connections.
|
||||||
|
pub struct NetworkPlugin;
|
||||||
|
|
||||||
|
impl NetworkPlugin {
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
/// Accept new [Connection]s.
|
||||||
|
fn accept_connections(mut commands: Commands, listener: Option<Res<Listener>>) {
|
||||||
|
if let Some(listener) = listener {
|
||||||
|
if let Some(connection) = listener.0.accept() {
|
||||||
|
commands.spawn(Connection(Arc::new(connection)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
/// Handles a received [Packet]s.
|
||||||
|
fn handle_packets(world: &mut World) {
|
||||||
|
// Get all received packets
|
||||||
|
let mut packets = Vec::new();
|
||||||
|
for (entity, connection) in world.query::<(Entity, &Connection)>().iter(world) {
|
||||||
|
while let Some(mut packet) = connection.0.recv() {
|
||||||
|
if packet.len() < 8 {
|
||||||
|
println!("Invalid packet received: {:?}", packet);
|
||||||
|
} else {
|
||||||
|
let id_buffer = packet.split_off(packet.len() - 8);
|
||||||
|
let packet_id = u64::from_be_bytes(id_buffer.try_into().unwrap());
|
||||||
|
packets.push((
|
||||||
|
entity,
|
||||||
|
Connection(Arc::clone(&connection.0)),
|
||||||
|
packet_id,
|
||||||
|
packet,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the packet handlers
|
||||||
|
let handlers = Arc::clone(&world.resource_mut::<HandlerManager>().0);
|
||||||
|
|
||||||
|
// Handle all received packets
|
||||||
|
for (entity, connection, packet_id, packet) in packets {
|
||||||
|
if let Some(handler) = handlers.get(&packet_id) {
|
||||||
|
handler(entity, connection, packet, world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
/// Handles a received [Packet] on the server.
|
||||||
|
pub fn handle_packets(world: &mut World) {
|
||||||
|
// Get all received packets
|
||||||
|
let mut packets = Vec::new();
|
||||||
|
if let Some(connection) = world.get_resource::<Connection>() {
|
||||||
|
while let Some(mut packet) = connection.0.recv() {
|
||||||
|
if packet.len() < 8 {
|
||||||
|
println!("Invalid packet received: {:?}", packet);
|
||||||
|
} else {
|
||||||
|
let id_buffer = packet.split_off(packet.len() - 8);
|
||||||
|
let packet_id = u64::from_be_bytes(id_buffer.try_into().unwrap());
|
||||||
|
packets.push((packet_id, packet));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the packet handlers
|
||||||
|
let handlers = Arc::clone(&world.resource_mut::<HandlerManager>().0);
|
||||||
|
|
||||||
|
// Handle all received packets
|
||||||
|
for (packet_id, packet) in packets {
|
||||||
|
if let Some(handler) = handlers.get(&packet_id) {
|
||||||
|
handler(packet, world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
/// Remove disconnected [Connection]s.
|
||||||
|
fn remove_disconnected(mut commands: Commands, connections: Query<(Entity, &Connection)>) {
|
||||||
|
for (entity, connection) in connections.iter() {
|
||||||
|
if connection.0.closed() {
|
||||||
|
commands.entity(entity).remove::<Connection>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
/// Remove [ServerConnection] if it's disconnected.
|
||||||
|
pub fn remove_disconnected(mut commands: Commands, connection: Option<Res<Connection>>) {
|
||||||
|
if let Some(connection) = connection {
|
||||||
|
if connection.0.closed() {
|
||||||
|
commands.remove_resource::<Connection>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for NetworkPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.insert_resource(HandlerManager(Arc::new(HashMap::new())));
|
||||||
|
app.add_system(NetworkPlugin::handle_packets);
|
||||||
|
app.add_system(NetworkPlugin::remove_disconnected);
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
app.add_system(NetworkPlugin::accept_connections);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An extension to add packet handlers.
|
||||||
|
pub trait NetworkExt {
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
/// Add a new packet handler.
|
||||||
|
fn add_packet_handler<P, H>(&mut self, handler: H) -> &mut Self
|
||||||
|
where
|
||||||
|
P: Packet,
|
||||||
|
H: Fn(Entity, Connection, Vec<u8>, &mut World) + Send + Sync + 'static;
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
/// Add a new packet handler.
|
||||||
|
fn add_packet_handler<P, H>(&mut self, handler: H) -> &mut Self
|
||||||
|
where
|
||||||
|
P: Packet,
|
||||||
|
H: Fn(Vec<u8>, &mut World) + Send + Sync + 'static;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkExt for App {
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
fn add_packet_handler<P, H>(&mut self, handler: H) -> &mut Self
|
||||||
|
where
|
||||||
|
P: Packet,
|
||||||
|
H: Fn(Entity, Connection, Vec<u8>, &mut World) + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
Arc::get_mut(&mut self.world.resource_mut::<HandlerManager>().0)
|
||||||
|
.unwrap()
|
||||||
|
.insert(P::packet_id(), Box::new(handler));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
fn add_packet_handler<P, H>(&mut self, handler: H) -> &mut Self
|
||||||
|
where
|
||||||
|
P: Packet,
|
||||||
|
H: Fn(Vec<u8>, &mut World) + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
Arc::get_mut(&mut self.world.resource_mut::<HandlerManager>().0)
|
||||||
|
.unwrap()
|
||||||
|
.insert(P::packet_id(), Box::new(handler));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
16
src/tcp.rs
16
src/tcp.rs
|
@ -1,6 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, Read, Write},
|
io::{self, Read, Write},
|
||||||
net::{Shutdown, TcpListener, TcpStream, ToSocketAddrs},
|
net::{Shutdown, TcpStream, ToSocketAddrs},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
mpsc::{channel, Receiver, Sender},
|
mpsc::{channel, Receiver, Sender},
|
||||||
|
@ -51,6 +51,7 @@ impl Connection {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
/// Creates a new connection to the given address.
|
/// Creates a new connection to the given address.
|
||||||
pub fn connect<A: ToSocketAddrs>(addr: A) -> io::Result<Self> {
|
pub fn connect<A: ToSocketAddrs>(addr: A) -> io::Result<Self> {
|
||||||
Self::new(TcpStream::connect(addr)?)
|
Self::new(TcpStream::connect(addr)?)
|
||||||
|
@ -82,13 +83,7 @@ impl Connection {
|
||||||
|
|
||||||
/// The sending loop for this connection.
|
/// The sending loop for this connection.
|
||||||
fn sending_loop(mut stream: TcpStream, receiver: Receiver<Vec<u8>>, closed: Arc<AtomicBool>) {
|
fn sending_loop(mut stream: TcpStream, receiver: Receiver<Vec<u8>>, closed: Arc<AtomicBool>) {
|
||||||
loop {
|
while let Ok(packet) = receiver.recv() {
|
||||||
// Get the next packet to send
|
|
||||||
let packet = match receiver.recv() {
|
|
||||||
Ok(packet) => packet,
|
|
||||||
Err(_) => break,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send the length of the packet
|
// Send the length of the packet
|
||||||
let len_buffer = u32::to_be_bytes(packet.len() as u32);
|
let len_buffer = u32::to_be_bytes(packet.len() as u32);
|
||||||
if stream.write_all(&len_buffer).is_err() {
|
if stream.write_all(&len_buffer).is_err() {
|
||||||
|
@ -128,12 +123,17 @@ impl Drop for Connection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
use std::net::TcpListener;
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
/// A [Connection] listener.
|
/// A [Connection] listener.
|
||||||
pub struct Listener {
|
pub struct Listener {
|
||||||
/// The [TcpListener] of the listener.
|
/// The [TcpListener] of the listener.
|
||||||
listener: TcpListener,
|
listener: TcpListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
impl Listener {
|
impl Listener {
|
||||||
/// Creates a new TCP listener on the given address.
|
/// Creates a new TCP listener on the given address.
|
||||||
pub fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<Self> {
|
pub fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<Self> {
|
||||||
|
|
Loading…
Reference in a new issue