bevnet 0.1.0

A library for networking in Bevy.
Documentation
use bevy::prelude::*;
pub use packet::Packet;
use std::{
    io,
    net::{SocketAddr, ToSocketAddrs},
    sync::Arc,
};
use tcp::{Connection, Listener};

mod packet;
mod tcp;

/// A connection to a server.
#[derive(Resource)]
pub struct ServerConnection(Connection);

impl ServerConnection {
    /// Creates a [ServerConnection] to the given address.
    pub fn connect<A: ToSocketAddrs>(addr: A) -> io::Result<Self> {
        Ok(Self(Connection::connect(addr)?))
    }

    /// Sends a [Packet] to the server.
    pub fn send<P: Packet>(&self, packet: P) {
        self.0.send(packet);
    }

    /// Gets the address of the server.
    pub fn address(&self) -> SocketAddr {
        self.0.address()
    }
}

/// Used to listen for incoming [ClientConnection]s.
#[derive(Resource)]
pub struct ClientListener(Listener);

impl ClientListener {
    /// Creates a [ClientListener] binded to the given address.
    pub fn bind<A: ToSocketAddrs>(address: A) -> io::Result<Self> {
        Ok(Self(Listener::bind(address)?))
    }

    /// Returns the address the [ClientListener] is bound to.
    pub fn address(&self) -> SocketAddr {
        self.0.address()
    }
}

/// A connection to a client.
#[derive(Component)]
pub struct ClientConnection(Arc<Connection>);

impl ClientConnection {
    /// Sends a [Packet] to the client.
    pub fn send<P: Packet>(&self, packet: P) {
        self.0.send(packet);
    }

    /// Gets the address of the client.
    pub fn address(&self) -> SocketAddr {
        self.0.address()
    }
}

/// A [Plugin] for client networking.
pub struct ClientNetworkPlugin;

impl ClientNetworkPlugin {
    /// Removes the [ServerConnection] resource when it's disconnected.
    fn remove_disconnected(mut commands: Commands, connection: Res<ServerConnection>) {
        if !connection.0.connected() {
            commands.remove_resource::<ServerConnection>();
        }
    }

    /// Clears the packet cache of the [ServerConnection].
    fn clear_cache(connection: Res<ServerConnection>) {
        connection.0.clear();
    }
}

impl Plugin for ClientNetworkPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems((
            Self::remove_disconnected.run_if(resource_exists::<ServerConnection>()),
            Self::clear_cache
                .run_if(resource_exists::<ServerConnection>())
                .after(Self::remove_disconnected),
        ));
    }
}

/// A [Plugin] for server networking.
pub struct ServerNetworkPlugin;

impl ServerNetworkPlugin {
    /// Removes the [ClientConnection] components when it's disconnected.
    fn remove_disconnected(
        mut commands: Commands,
        connections: Query<(Entity, &ClientConnection)>,
    ) {
        for (entity, connection) in connections.iter() {
            if !connection.0.connected() {
                commands.entity(entity).remove::<ClientConnection>();
            }
        }
    }

    /// Clears the packet cache of the [ClientConnection]s.
    fn clear_cache(connections: Query<&ClientConnection>) {
        for connection in connections.iter() {
            connection.0.clear();
        }
    }

    /// Removes the [ClientListener] resource when it stop listening.
    fn remove_not_listening(mut commands: Commands, listener: Res<ClientListener>) {
        if !listener.0.listening() {
            commands.remove_resource::<ClientListener>();
        }
    }

    /// Accepts incoming connections.
    fn accept_connections(mut commands: Commands, listener: Res<ClientListener>) {
        while let Some(connection) = listener.0.accept() {
            commands.spawn(ClientConnection(Arc::new(connection)));
        }
    }
}

impl Plugin for ServerNetworkPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems((
            Self::remove_disconnected,
            Self::clear_cache.after(Self::remove_disconnected),
            Self::remove_not_listening.run_if(resource_exists::<ClientListener>()),
            Self::accept_connections
                .run_if(resource_exists::<ClientListener>())
                .after(Self::remove_not_listening),
        ));
    }
}

/// Receives [Packet]s and sends them as [PacketEvent]s.
fn receive_server_packets<P: Packet>(
    mut writer: EventWriter<PacketEvent<P>>,
    connection: Query<(Entity, &ClientConnection)>,
) {
    for (entity, connection) in connection.iter() {
        for packet in connection.0.recv() {
            writer.send(PacketEvent {
                connection: ClientConnection(Arc::clone(&connection.0)),
                entity,
                packet,
            });
        }
    }
}

/// An extention trait to easily register a [Packet] to the server.
pub trait AppServerNetwork {
    /// Registers a [Packet] for the server.
    fn register_server_packet<P: Packet>(&mut self) -> &mut Self;
}

impl AppServerNetwork for App {
    fn register_server_packet<P: Packet>(&mut self) -> &mut Self {
        self.add_event::<PacketEvent<P>>();
        self.add_system(
            receive_server_packets::<P>
                .after(ServerNetworkPlugin::remove_disconnected)
                .before(ServerNetworkPlugin::clear_cache),
        );
        self
    }
}

/// An event for received [Packet]s on the server.
pub struct PacketEvent<P: Packet> {
    /// The [ClientConnection] from which the [Packet] was received.
    pub connection: ClientConnection,

    /// The [Entity] of the [ClientConnection].
    pub entity: Entity,

    /// The [Packet]
    pub packet: P,
}

/// Receives [Packet]s and sends them as [Event]s.
fn receive_client_packets<P: Packet>(
    mut writer: EventWriter<P>,
    connection: Res<ServerConnection>,
) {
    for packet in connection.0.recv() {
        writer.send(packet);
    }
}

/// An extention trait to easily register a [Packet] to the client.
pub trait AppClientNetwork {
    /// Registers a [Packet] for the client.
    fn register_client_packet<P: Packet>(&mut self) -> &mut Self;
}

impl AppClientNetwork for App {
    fn register_client_packet<P: Packet>(&mut self) -> &mut Self {
        self.add_event::<P>();
        self.add_system(
            receive_client_packets::<P>
                .run_if(resource_exists::<ServerConnection>())
                .after(ClientNetworkPlugin::remove_disconnected)
                .before(ClientNetworkPlugin::clear_cache),
        );
        self
    }
}