use std::cell::RefCell;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::rc::Rc;
use std::time::Duration;
use runite::channel::{mpsc, watch};
use runite::io::{AsyncWriteExt, BufReader};
use runite::net::{TcpListener, TcpStream};
use runite::time::{sleep, timeout};
use runite::{select, spawn};
struct Peer {
name: String,
outbox: mpsc::Sender<String>,
}
type Room = Rc<RefCell<HashMap<usize, Peer>>>;
fn broadcast(room: &Room, line: &str) {
println!("{line}");
for peer in room.borrow().values() {
let _ = peer.outbox.try_send(format!("{line}\n"));
}
}
async fn serve(room: Room, id: usize, stream: TcpStream) {
let (read_half, mut write_half) = stream.into_split();
let name = format!("guest-{id}");
let (outbox, mut inbox) = mpsc::channel::<String>(32);
room.borrow_mut().insert(
id,
Peer {
name: name.clone(),
outbox,
},
);
let writer = spawn(async move {
while let Some(message) = inbox.recv().await {
if write_half.write_all(message.as_bytes()).await.is_err() {
break;
}
}
let _ = write_half.shutdown().await;
});
broadcast(&room, &format!("* {name} joined"));
let mut lines = BufReader::new(read_half);
let mut line = String::new();
loop {
line.clear();
match lines.read_line(&mut line).await {
Ok(0) | Err(_) => break, Ok(_) => match line.trim() {
"" => {}
"/quit" => break,
"/who" => {
let names: Vec<String> =
room.borrow().values().map(|p| p.name.clone()).collect();
if let Some(peer) = room.borrow().get(&id) {
let _ = peer
.outbox
.try_send(format!("* here: {}\n", names.join(", ")));
}
}
text => broadcast(&room, &format!("{name}: {text}")),
},
}
}
room.borrow_mut().remove(&id);
broadcast(&room, &format!("* {name} left"));
let _ = writer.await;
}
async fn run_server(
room: Room,
listener: TcpListener,
mut shutdown: watch::Receiver<bool>,
) -> std::io::Result<()> {
let mut next_id = 1usize;
loop {
let accept = listener.accept();
let changed = shutdown.changed();
let accepted = select! {
accepted = accept => Some(accepted),
_ = changed => None,
};
match accepted {
Some(accepted) => {
let (stream, _peer_addr) = accepted?;
let id = next_id;
next_id += 1;
spawn(serve(Rc::clone(&room), id, stream));
}
None => break,
}
}
broadcast(&room, "* server: shutting down, goodbye");
let drained = timeout(Duration::from_secs(2), async {
while !room.borrow().is_empty() {
sleep(Duration::from_millis(25)).await;
}
})
.await;
match drained {
Ok(()) => println!("* all clients drained"),
Err(_) => println!("* drain deadline reached with clients still connected"),
}
Ok(())
}
async fn demo_client(addr: SocketAddr, script: &[&str]) -> std::io::Result<()> {
let mut stream = TcpStream::connect(addr).await?;
for line in script {
sleep(Duration::from_millis(60)).await;
stream.write_all(format!("{line}\n").as_bytes()).await?;
}
sleep(Duration::from_millis(60)).await;
stream.shutdown(std::net::Shutdown::Both).await
}
#[runite::main]
async fn main() -> std::io::Result<()> {
let room: Room = Rc::new(RefCell::new(HashMap::new()));
let listener = TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
let (shutdown_tx, shutdown_rx) = watch::channel(false);
if std::env::args().any(|arg| arg == "--demo") {
spawn(async move {
let alice = spawn(demo_client(addr, &["hello room", "/who", "anyone here?"]));
sleep(Duration::from_millis(30)).await;
let bob = spawn(demo_client(addr, &["hi!", "gotta run", "/quit"]));
let _ = alice.await;
let _ = bob.await;
sleep(Duration::from_millis(100)).await;
let _ = shutdown_tx.send(true);
});
} else {
println!(
"chat server listening — connect with: nc 127.0.0.1 {}",
addr.port()
);
println!("Ctrl-C to shut down gracefully");
spawn(async move {
if runite::signal::ctrl_c().await.is_ok() {
let _ = shutdown_tx.send(true);
}
});
}
run_server(room, listener, shutdown_rx).await
}