use std::collections::HashMap;
use std::time::{Duration, Instant};
use tokio::sync::mpsc;
use super::room::PeerId;
pub struct Peer<S> {
pub id: PeerId,
pub state: S,
pub last_seen: Instant,
tx: mpsc::UnboundedSender<String>,
rate: HashMap<&'static str, Instant>,
}
impl<S> Peer<S> {
pub(super) fn new(id: PeerId, state: S, tx: mpsc::UnboundedSender<String>) -> Self {
Self {
id,
state,
last_seen: Instant::now(),
tx,
rate: HashMap::new(),
}
}
pub fn send(&self, msg: impl Into<String>) {
let _ = self.tx.send(msg.into());
}
pub fn touch(&mut self) {
self.last_seen = Instant::now();
}
pub fn allow_rate(&mut self, kind: &'static str, min_interval: Duration) -> bool {
let now = Instant::now();
match self.rate.get(kind) {
Some(last) if now.duration_since(*last) < min_interval => false,
_ => {
self.rate.insert(kind, now);
true
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn peer_with_rx(id: &str) -> (Peer<()>, mpsc::UnboundedReceiver<String>) {
let (tx, rx) = mpsc::unbounded_channel();
(Peer::new(id.to_string(), (), tx), rx)
}
#[test]
fn allow_rate_gates_first_call_only_within_window() {
let (mut p, _rx) = peer_with_rx("a");
assert!(p.allow_rate("pose", Duration::from_secs(60)));
assert!(!p.allow_rate("pose", Duration::from_secs(60)));
assert!(p.allow_rate("place", Duration::from_secs(60)));
}
#[test]
fn send_does_not_panic_after_receiver_dropped() {
let (p, rx) = peer_with_rx("a");
drop(rx);
p.send("hello"); }
}