2025-02-23 17:07:09 +00:00
|
|
|
use crate::prelude::*;
|
|
|
|
|
2025-02-24 01:43:08 +00:00
|
|
|
type Id = u64;
|
|
|
|
|
2025-02-23 18:06:07 +00:00
|
|
|
pub(crate) struct Args {
|
|
|
|
pub(crate) port: u16,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Args {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self { port: 9000 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-23 17:07:09 +00:00
|
|
|
pub(crate) struct App {
|
2025-02-24 02:29:05 +00:00
|
|
|
client_streams: BTreeMap<Id, Framed<TcpStream, LengthDelimitedCodec>>,
|
2025-02-23 17:07:09 +00:00
|
|
|
listener: TcpListener,
|
2025-02-24 03:11:08 +00:00
|
|
|
server: Server,
|
2025-02-23 17:07:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl App {
|
2025-02-23 18:06:07 +00:00
|
|
|
pub(crate) async fn new(args: Args) -> Result<Self> {
|
|
|
|
let listener = TcpListener::bind(("0.0.0.0", args.port)).await?;
|
2025-02-23 17:07:09 +00:00
|
|
|
|
|
|
|
Ok(Self {
|
2025-02-24 02:29:05 +00:00
|
|
|
client_streams: Default::default(),
|
2025-02-23 17:07:09 +00:00
|
|
|
listener,
|
2025-02-24 03:11:08 +00:00
|
|
|
server: Default::default(),
|
2025-02-23 17:07:09 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn poll_run(&mut self, cx: &mut Context<'_>) -> Poll<Result<()>> {
|
2025-02-24 03:11:08 +00:00
|
|
|
match self.step(cx) {
|
|
|
|
Ok(()) => Poll::Pending,
|
|
|
|
Err(err) => Poll::Ready(Err(err)),
|
|
|
|
}
|
|
|
|
}
|
2025-02-23 17:07:09 +00:00
|
|
|
|
2025-02-24 03:11:08 +00:00
|
|
|
fn step(&mut self, cx: &mut Context<'_>) -> Result<()> {
|
|
|
|
let mut clients_to_remove = vec![];
|
|
|
|
|
|
|
|
// Pump the network streams
|
|
|
|
for (id, stream) in &mut self.client_streams {
|
2025-02-24 02:29:05 +00:00
|
|
|
let mut stream = pin!(stream);
|
2025-02-23 17:07:09 +00:00
|
|
|
|
2025-02-24 03:11:08 +00:00
|
|
|
// Try to send from the client's outbox
|
|
|
|
if let Poll::Ready(result) =
|
|
|
|
<_ as futures_sink::Sink<Bytes>>::poll_ready(stream.as_mut(), cx)
|
|
|
|
{
|
|
|
|
result.context("Can't check network write half for readiness")?;
|
|
|
|
let client = self
|
|
|
|
.server
|
|
|
|
.clients
|
|
|
|
.get_mut(id)
|
|
|
|
.context("Logic error: Stream has no associated client")?;
|
|
|
|
if let Some(frame) = client.poll_send() {
|
|
|
|
stream.as_mut().start_send(frame).context("start_send")?;
|
|
|
|
match <_ as futures_sink::Sink<Bytes>>::poll_flush(stream.as_mut(), cx) {
|
|
|
|
Poll::Pending => {}
|
|
|
|
Poll::Ready(result) => result.context("poll_flush")?,
|
2025-02-23 17:07:09 +00:00
|
|
|
}
|
2025-02-24 03:11:08 +00:00
|
|
|
tracing::debug!("Started send");
|
2025-02-23 17:07:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-24 03:11:08 +00:00
|
|
|
// Try to read into the client's inbox
|
2025-02-24 02:29:05 +00:00
|
|
|
match stream.as_mut().poll_next(cx) {
|
2025-02-23 17:07:09 +00:00
|
|
|
Poll::Pending => {}
|
2025-02-24 03:11:08 +00:00
|
|
|
Poll::Ready(None) => clients_to_remove.push(*id),
|
|
|
|
Poll::Ready(Some(result)) => {
|
|
|
|
let frame = result.context("stream.poll_next")?;
|
2025-02-24 02:29:05 +00:00
|
|
|
cx.waker().wake_by_ref();
|
2025-02-24 03:11:08 +00:00
|
|
|
let client = self
|
|
|
|
.server
|
|
|
|
.clients
|
|
|
|
.get_mut(id)
|
|
|
|
.context("Logic error: Stream has no associated client")?;
|
|
|
|
client
|
|
|
|
.handle_frame(frame.into())
|
|
|
|
.context("client.handle_frame")?
|
2025-02-23 17:07:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-24 03:11:08 +00:00
|
|
|
// Close out disconnected clients
|
|
|
|
for id in clients_to_remove {
|
2025-02-24 01:43:08 +00:00
|
|
|
tracing::info!(?id, "Closing client");
|
2025-02-24 03:11:08 +00:00
|
|
|
self.client_streams.remove(&id);
|
|
|
|
self.server.clients.remove(&id);
|
2025-02-24 01:43:08 +00:00
|
|
|
}
|
2025-02-23 17:07:09 +00:00
|
|
|
|
2025-02-24 03:11:08 +00:00
|
|
|
// Broadcast chat lines across all clients
|
|
|
|
|
|
|
|
let mut new_messages = vec![];
|
|
|
|
for client in &mut self.server.clients.values_mut() {
|
|
|
|
if let Some(ChatLine { id, line }) = client.poll_inbox() {
|
|
|
|
new_messages.push(ToClient::ChatLine { id, line });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for client in &mut self.server.clients.values_mut() {
|
2025-02-23 17:07:09 +00:00
|
|
|
for msg in &new_messages {
|
2025-02-24 02:29:05 +00:00
|
|
|
client.handle_outgoing(msg)?;
|
2025-02-23 17:07:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.listener.poll_accept(cx) {
|
|
|
|
Poll::Pending => {}
|
2025-02-24 03:11:08 +00:00
|
|
|
Poll::Ready(result) => {
|
|
|
|
let (stream, _addr) = result.context("listener.poll_accept")?;
|
2025-02-23 17:07:09 +00:00
|
|
|
cx.waker().wake_by_ref();
|
2025-02-24 02:29:05 +00:00
|
|
|
let stream = Framed::new(stream, LengthDelimitedCodec::new());
|
2025-02-24 03:11:08 +00:00
|
|
|
let id = self.server.handle_new_client();
|
2025-02-24 02:29:05 +00:00
|
|
|
self.client_streams.insert(id, stream);
|
2025-02-23 17:07:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-24 03:11:08 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
struct Server {
|
|
|
|
clients: BTreeMap<Id, Client>,
|
|
|
|
next_client_id: Id,
|
|
|
|
}
|
2025-02-23 17:07:09 +00:00
|
|
|
|
2025-02-24 03:11:08 +00:00
|
|
|
impl Server {
|
|
|
|
fn handle_new_client(&mut self) -> Id {
|
|
|
|
let id = self.next_client_id;
|
|
|
|
self.next_client_id += 1;
|
|
|
|
let client = Client {
|
|
|
|
id,
|
|
|
|
inbox: Default::default(),
|
|
|
|
outbox: Default::default(),
|
|
|
|
sequence: 0,
|
|
|
|
};
|
|
|
|
tracing::info!(
|
|
|
|
id = client.id,
|
|
|
|
total = self.clients.len(),
|
|
|
|
"Accepted client"
|
|
|
|
);
|
|
|
|
self.clients.insert(id, client);
|
|
|
|
id
|
2025-02-23 17:07:09 +00:00
|
|
|
}
|
|
|
|
}
|
2025-02-23 18:06:07 +00:00
|
|
|
|
2025-02-24 02:29:05 +00:00
|
|
|
struct ChatLine {
|
|
|
|
id: Id,
|
|
|
|
line: String,
|
|
|
|
}
|
|
|
|
|
2025-02-23 18:06:07 +00:00
|
|
|
struct Client {
|
|
|
|
id: u64,
|
2025-02-24 02:29:05 +00:00
|
|
|
inbox: VecDeque<ChatLine>,
|
|
|
|
outbox: VecDeque<Bytes>,
|
2025-02-23 18:06:07 +00:00
|
|
|
sequence: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Client {
|
2025-02-24 02:29:05 +00:00
|
|
|
fn handle_frame(&mut self, frame: Bytes) -> Result<()> {
|
|
|
|
match rmp_serde::from_slice(&frame)? {
|
|
|
|
ToServer::ChatLine { line, sequence } => {
|
|
|
|
if sequence != self.sequence {
|
|
|
|
tracing::error!(
|
|
|
|
expected = self.sequence,
|
|
|
|
actual = sequence,
|
|
|
|
"Sequence mismatch"
|
|
|
|
);
|
|
|
|
bail!("Sequence mismatch");
|
|
|
|
}
|
|
|
|
self.sequence += 1;
|
|
|
|
self.inbox.push_back(ChatLine { id: self.id, line });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_outgoing(&mut self, msg: &ToClient) -> Result<()> {
|
|
|
|
if !self.outbox.is_empty() {
|
|
|
|
bail!("Outbox full");
|
|
|
|
}
|
|
|
|
let bytes = rmp_serde::to_vec(msg)?.into();
|
|
|
|
self.outbox.push_back(bytes);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn poll_inbox(&mut self) -> Option<ChatLine> {
|
|
|
|
self.inbox.pop_front()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn poll_send(&mut self) -> Option<Bytes> {
|
|
|
|
self.outbox.pop_front()
|
2025-02-23 18:06:07 +00:00
|
|
|
}
|
|
|
|
}
|