wrym 0.1.0

Cross-platform, lightweight networking
Documentation
use std::{
    collections::{HashMap, VecDeque},
    time::{Duration, Instant},
};

use wrym_transport::{ReliableTransport, Transport};

use crate::Opcode;

pub struct ServerConfig {
    pub client_timeout: Duration,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            client_timeout: Duration::from_secs(60),
        }
    }
}

pub struct ClientData {
    last_activity: Instant,
}

pub enum ServerEvent {
    ClientConnected(String),
    ClientDisconnected(String),
    MessageReceived(String, Vec<u8>),
}

pub struct Server<T: Transport> {
    transport: T,
    config: ServerConfig,
    clients: HashMap<String, ClientData>,
    events: VecDeque<ServerEvent>,
}

impl<T: Transport> Server<T> {
    pub fn new(transport: T, config: ServerConfig) -> Self {
        Self {
            transport,
            config,
            clients: HashMap::new(),
            events: VecDeque::new(),
        }
    }

    fn add_client(&mut self, addr: &str) {
        if self
            .clients
            .insert(
                addr.to_string(),
                ClientData {
                    last_activity: Instant::now(),
                },
            )
            .is_none()
        {
            self.transport
                .send_to(&addr, &[Opcode::ClientConnected as u8]);
            self.events
                .push_back(ServerEvent::ClientConnected(addr.to_string()));
        }
    }

    fn drop_client(&mut self, addr: &str) {
        if self.clients.remove(addr).is_some() {
            self.transport
                .send_to(&addr, &[Opcode::ClientDisconnected as u8]);
            self.events
                .push_back(ServerEvent::ClientDisconnected(addr.to_string()));
        }
    }

    fn drop_inactive_clients(&mut self, timeout: Duration) {
        let to_disconnect: Vec<String> = self
            .clients
            .iter()
            .filter_map(|(addr, data)| {
                if Instant::now().duration_since(data.last_activity) > timeout {
                    Some(addr.to_owned())
                } else {
                    None
                }
            })
            .collect();

        for addr in to_disconnect {
            self.drop_client(&addr);
        }
    }

    pub fn poll(&mut self) {
        self.drop_inactive_clients(self.config.client_timeout);

        if let Some((addr, mut bytes)) = self.transport.recv() {
            if bytes.is_empty() {
                return;
            }

            if let Some(data) = self.clients.get_mut(&addr) {
                data.last_activity = Instant::now();
            }

            match bytes.remove(0).into() {
                Opcode::ClientConnected => self.add_client(&addr),
                Opcode::ClientDisconnected => self.drop_client(&addr),
                Opcode::Message => {
                    self.events
                        .push_back(ServerEvent::MessageReceived(addr, bytes));
                }
            }
        }
    }

    pub fn recv_event(&mut self) -> Option<ServerEvent> {
        self.events.pop_front()
    }

    pub fn send_to(&self, addr: &str, bytes: &[u8]) {
        self.transport
            .send_to(addr, &Opcode::Message.with_bytes(bytes));
    }

    pub fn broadcast(&self, bytes: &[u8]) {
        let msg = Opcode::Message.with_bytes(bytes);

        for addr in self.clients.keys() {
            self.transport.send_to(addr, &msg);
        }
    }
}

impl<T: Transport + ReliableTransport> Server<T> {
    pub fn send_reliable_to(&self, addr: &str, bytes: &[u8], ordered: bool) {
        self.transport
            .send_reliable_to(addr, &Opcode::Message.with_bytes(bytes), ordered);
    }

    pub fn broadcast_reliable(&self, bytes: &[u8], ordered: bool) {
        let msg = Opcode::Message.with_bytes(bytes);

        for addr in self.clients.keys() {
            self.transport.send_reliable_to(addr, &msg, ordered)
        }
    }
}