bevy_connect 0.18.6

Connectivity via TCP sessions
Documentation
use crate::ClientId;
use bevy_ecs::prelude::*;

use crate::Message;
use crate::channel::{Channel, SessionConfig};
use crate::events::{SessionConnectedEvent, SessionDisconnectedEvent};
use std::marker::PhantomData;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use tracing::debug;
use tracing::error;

/// Starts a new connection based on the passed [`SessionConfig`].
/// Issuing a connection with an already existing connected resource will just
/// replace the existing connection (disconnecting the old) with another.
/// A disconnect + connect event will occur in that case.
/// They are unique by type T.
pub struct SessionConnectCommand<T: Message> {
    config: SessionConfig,
    _t: PhantomData<T>,
}

impl<T: Message> Command for SessionConnectCommand<T> {
    fn apply(self, world: &mut World) {
        let SessionConfig::Direct {
            addr,
            port,
            host,
            compress: _,
            key: _,
            options: _,
        } = self.config;
        let addr = addr.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
        let addr = SocketAddr::from((addr, port));
        if host {
            debug!("Creating new host to {addr}...");
            let channel = Channel::<T>::new_host(&self.config);
            let Ok(channel) = channel else {
                error!("Could not connect to host {addr}");
                return;
            };
            let uuid = channel.uuid();
            world.insert_resource(channel);
            world.write_message(SessionConnectedEvent::<T> { _t: PhantomData });
            debug!("Host created on {addr} with uuid {uuid}.");
        } else {
            debug!("Connecting new client to {addr}...");
            let channel = Channel::<T>::try_new_client(&self.config);
            let Ok(channel) = channel else {
                error!("Could not connect to host {addr}");
                return;
            };
            let uuid = channel.uuid();
            world.insert_resource(channel);
            world.write_message(SessionConnectedEvent::<T> { _t: PhantomData });
            debug!("Client connected to {addr} with uuid {uuid}.");
        }
    }
}

impl<T: Message> SessionConnectCommand<T> {
    #[must_use]
    pub fn from_config(config: SessionConfig) -> Self {
        Self {
            config,
            _t: PhantomData,
        }
    }
}

/// Disconnects the current typed T connection. If there was no connection,
/// this is a no-op.
pub struct SessionDisconnectCommand<T: Message> {
    _t: PhantomData<T>,
}

impl<T: Message> Default for SessionDisconnectCommand<T> {
    fn default() -> Self {
        Self { _t: PhantomData }
    }
}

impl<T: Message> Command for SessionDisconnectCommand<T> {
    fn apply(self, world: &mut World) {
        debug!("Disconnecting channel...");
        if let Some(c) = world.remove_resource::<Channel<T>>() {
            drop(c);
        }
        world.write_message(SessionDisconnectedEvent::<T> { _t: PhantomData });
        debug!("Channel disconnected.");
    }
}

/// Promotes one of the clients to a host
/// All the other participants will then become clients to this new host.
/// The session is considered unaltered, the uuids will be staying the same.
/// The [`Channel::host_uuid`] will return the new host as the uuid once the
/// process is complete.
pub struct SessionPromoteToHostCommand<T: Message> {
    pub(crate) new_host: ClientId,
    pub(crate) port: Option<u16>,
    _t: PhantomData<T>,
}

impl<T: Message> SessionPromoteToHostCommand<T> {
    #[must_use]
    pub fn new(new_host: ClientId, port: Option<u16>) -> Self {
        Self {
            new_host,
            port,
            _t: PhantomData,
        }
    }
}

impl<T: Message> Command for SessionPromoteToHostCommand<T> {
    fn apply(self, world: &mut World) {
        let mut channel = world.resource_mut::<Channel<T>>();
        channel.promote_new_host(self.new_host, self.port);
    }
}