forked from lavina/lavina
implemen sending messages to a chan
This commit is contained in:
parent
89f85b4fee
commit
d661f68fb6
|
@ -15,34 +15,51 @@ use std::{
|
||||||
use prometheus::{IntGauge, Registry as MetricsRegistry};
|
use prometheus::{IntGauge, Registry as MetricsRegistry};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::mpsc::{channel, Receiver, Sender},
|
sync::mpsc::{channel, Receiver, Sender},
|
||||||
|
sync::oneshot::{channel as oneshot, Sender as OneshotSender},
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::room::{RoomId, RoomRegistry},
|
core::room::{RoomId, RoomRegistry},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
util::table::AnonTable,
|
util::table::{AnonTable, Key as AnonKey},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Opaque player identifier.
|
/// Opaque player identifier.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct PlayerId(pub ByteVec);
|
pub struct PlayerId(pub ByteVec);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ConnectionId(pub AnonKey);
|
||||||
|
|
||||||
/// Handle to a player actor.
|
/// Handle to a player actor.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PlayerHandle {
|
pub struct PlayerHandle {
|
||||||
tx: Sender<PlayerCommand>,
|
tx: Sender<PlayerCommand>,
|
||||||
}
|
}
|
||||||
impl PlayerHandle {
|
impl PlayerHandle {
|
||||||
pub async fn subscribe(&mut self) -> Receiver<Updates> {
|
pub async fn subscribe(&mut self) -> (ConnectionId, Receiver<Updates>) {
|
||||||
let (sender, rx) = channel(32);
|
let (sender, rx) = channel(32);
|
||||||
self.tx.send(PlayerCommand::AddSocket { sender }).await;
|
let (promise, deferred) = oneshot();
|
||||||
rx
|
self.tx
|
||||||
|
.send(PlayerCommand::AddSocket { sender, promise })
|
||||||
|
.await;
|
||||||
|
let connection_id = deferred.await.unwrap();
|
||||||
|
(connection_id, rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_message(&mut self, room_id: RoomId, body: String) {
|
pub async fn send_message(
|
||||||
|
&mut self,
|
||||||
|
room_id: RoomId,
|
||||||
|
connection_id: ConnectionId,
|
||||||
|
body: String,
|
||||||
|
) {
|
||||||
self.tx
|
self.tx
|
||||||
.send(PlayerCommand::SendMessage { room_id, body })
|
.send(PlayerCommand::SendMessage {
|
||||||
|
room_id,
|
||||||
|
connection_id,
|
||||||
|
body,
|
||||||
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,11 +67,18 @@ impl PlayerHandle {
|
||||||
self.tx.send(PlayerCommand::JoinRoom { room_id }).await;
|
self.tx.send(PlayerCommand::JoinRoom { room_id }).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn receive_message(&mut self, room_id: RoomId, author: PlayerId, body: String) {
|
pub async fn receive_message(
|
||||||
|
&mut self,
|
||||||
|
room_id: RoomId,
|
||||||
|
author: PlayerId,
|
||||||
|
connection_id: ConnectionId,
|
||||||
|
body: String,
|
||||||
|
) {
|
||||||
self.tx
|
self.tx
|
||||||
.send(PlayerCommand::IncomingMessage {
|
.send(PlayerCommand::IncomingMessage {
|
||||||
room_id,
|
room_id,
|
||||||
author,
|
author,
|
||||||
|
connection_id,
|
||||||
body,
|
body,
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
@ -63,22 +87,32 @@ impl PlayerHandle {
|
||||||
|
|
||||||
/// Player update event type which is sent to a connection handler.
|
/// Player update event type which is sent to a connection handler.
|
||||||
pub enum Updates {
|
pub enum Updates {
|
||||||
RoomJoined { room_id: RoomId },
|
RoomJoined {
|
||||||
NewMessage { room_id: RoomId, body: String },
|
room_id: RoomId,
|
||||||
|
},
|
||||||
|
NewMessage {
|
||||||
|
author_id: PlayerId,
|
||||||
|
connection_id: ConnectionId,
|
||||||
|
room_id: RoomId,
|
||||||
|
body: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
enum PlayerCommand {
|
enum PlayerCommand {
|
||||||
AddSocket {
|
AddSocket {
|
||||||
sender: Sender<Updates>,
|
sender: Sender<Updates>,
|
||||||
|
promise: OneshotSender<ConnectionId>,
|
||||||
},
|
},
|
||||||
JoinRoom {
|
JoinRoom {
|
||||||
room_id: RoomId,
|
room_id: RoomId,
|
||||||
},
|
},
|
||||||
SendMessage {
|
SendMessage {
|
||||||
room_id: RoomId,
|
room_id: RoomId,
|
||||||
|
connection_id: ConnectionId,
|
||||||
body: String,
|
body: String,
|
||||||
},
|
},
|
||||||
IncomingMessage {
|
IncomingMessage {
|
||||||
room_id: RoomId,
|
room_id: RoomId,
|
||||||
|
connection_id: ConnectionId,
|
||||||
author: PlayerId,
|
author: PlayerId,
|
||||||
body: String,
|
body: String,
|
||||||
},
|
},
|
||||||
|
@ -108,12 +142,16 @@ impl PlayerRegistry {
|
||||||
sockets: AnonTable::new(),
|
sockets: AnonTable::new(),
|
||||||
};
|
};
|
||||||
let mut inner = self.0.write().unwrap();
|
let mut inner = self.0.write().unwrap();
|
||||||
|
if let Some((handle, _)) = inner.players.get(&id) {
|
||||||
|
handle.clone()
|
||||||
|
} else {
|
||||||
let (handle, fiber) = player.launch(id.clone(), inner.room_registry.clone());
|
let (handle, fiber) = player.launch(id.clone(), inner.room_registry.clone());
|
||||||
inner.players.insert(id, (handle.clone(), fiber));
|
inner.players.insert(id, (handle.clone(), fiber));
|
||||||
inner.metric_active_players.inc();
|
inner.metric_active_players.inc();
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The player registry state representation.
|
/// The player registry state representation.
|
||||||
struct PlayerRegistryInner {
|
struct PlayerRegistryInner {
|
||||||
|
@ -138,18 +176,24 @@ impl Player {
|
||||||
let fiber = tokio::task::spawn(async move {
|
let fiber = tokio::task::spawn(async move {
|
||||||
while let Some(cmd) = rx.recv().await {
|
while let Some(cmd) = rx.recv().await {
|
||||||
match cmd {
|
match cmd {
|
||||||
PlayerCommand::AddSocket { sender } => {
|
PlayerCommand::AddSocket { sender, promise } => {
|
||||||
self.sockets.insert(sender);
|
let connection_id = self.sockets.insert(sender);
|
||||||
|
promise.send(ConnectionId(connection_id));
|
||||||
}
|
}
|
||||||
PlayerCommand::JoinRoom { room_id } => {
|
PlayerCommand::JoinRoom { room_id } => {
|
||||||
let mut room = rooms.get_or_create_room(room_id);
|
let mut room = rooms.get_or_create_room(room_id);
|
||||||
room.subscribe(player_id.clone(), handle.clone()).await;
|
room.subscribe(player_id.clone(), handle.clone()).await;
|
||||||
}
|
}
|
||||||
PlayerCommand::SendMessage { room_id, body } => {
|
PlayerCommand::SendMessage {
|
||||||
|
room_id,
|
||||||
|
connection_id,
|
||||||
|
body,
|
||||||
|
} => {
|
||||||
let room = rooms.get_room(room_id);
|
let room = rooms.get_room(room_id);
|
||||||
match room {
|
match room {
|
||||||
Some(mut room) => {
|
Some(mut room) => {
|
||||||
room.send_message(player_id.clone(), body).await;
|
room.send_message(player_id.clone(), connection_id, body)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
tracing::info!("no room found");
|
tracing::info!("no room found");
|
||||||
|
@ -159,12 +203,16 @@ impl Player {
|
||||||
PlayerCommand::IncomingMessage {
|
PlayerCommand::IncomingMessage {
|
||||||
room_id,
|
room_id,
|
||||||
author,
|
author,
|
||||||
|
connection_id,
|
||||||
body,
|
body,
|
||||||
} => {
|
} => {
|
||||||
tracing::info!("Handling incoming message");
|
tracing::info!("Handling incoming message");
|
||||||
for socket in &self.sockets {
|
for socket in &self.sockets {
|
||||||
|
log::info!("Send message to socket");
|
||||||
socket
|
socket
|
||||||
.send(Updates::NewMessage {
|
.send(Updates::NewMessage {
|
||||||
|
author_id: author.clone(),
|
||||||
|
connection_id: connection_id.clone(),
|
||||||
room_id: room_id.clone(),
|
room_id: room_id.clone(),
|
||||||
body: body.clone(),
|
body: body.clone(),
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,6 +13,8 @@ use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::player::ConnectionId;
|
||||||
|
|
||||||
/// Opaque room id
|
/// Opaque room id
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct RoomId(pub ByteVec);
|
pub struct RoomId(pub ByteVec);
|
||||||
|
@ -37,11 +39,15 @@ impl RoomRegistry {
|
||||||
subscriptions: HashMap::new(),
|
subscriptions: HashMap::new(),
|
||||||
};
|
};
|
||||||
let mut inner = self.0.write().unwrap();
|
let mut inner = self.0.write().unwrap();
|
||||||
|
if let Some((room_handle, _)) = inner.rooms.get(&room_id) {
|
||||||
|
room_handle.clone()
|
||||||
|
} else {
|
||||||
let (room_handle, fiber) = room.launch(room_id.clone());
|
let (room_handle, fiber) = room.launch(room_id.clone());
|
||||||
inner.rooms.insert(room_id, (room_handle.clone(), fiber));
|
inner.rooms.insert(room_id, (room_handle.clone(), fiber));
|
||||||
inner.metric_active_rooms.inc();
|
inner.metric_active_rooms.inc();
|
||||||
room_handle
|
room_handle
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_room(&self, room_id: RoomId) -> Option<RoomHandle> {
|
pub fn get_room(&self, room_id: RoomId) -> Option<RoomHandle> {
|
||||||
let inner = self.0.read().unwrap();
|
let inner = self.0.read().unwrap();
|
||||||
|
@ -73,9 +79,18 @@ impl RoomHandle {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_message(&mut self, player_id: PlayerId, body: String) {
|
pub async fn send_message(
|
||||||
|
&mut self,
|
||||||
|
player_id: PlayerId,
|
||||||
|
connection_id: ConnectionId,
|
||||||
|
body: String,
|
||||||
|
) {
|
||||||
self.tx
|
self.tx
|
||||||
.send(RoomCommand::SendMessage { player_id, body })
|
.send(RoomCommand::SendMessage {
|
||||||
|
player_id,
|
||||||
|
connection_id,
|
||||||
|
body,
|
||||||
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +102,7 @@ enum RoomCommand {
|
||||||
},
|
},
|
||||||
SendMessage {
|
SendMessage {
|
||||||
player_id: PlayerId,
|
player_id: PlayerId,
|
||||||
|
connection_id: ConnectionId,
|
||||||
body: String,
|
body: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -105,10 +121,20 @@ impl Room {
|
||||||
tracing::info!("Adding a subscriber to room");
|
tracing::info!("Adding a subscriber to room");
|
||||||
self.subscriptions.insert(player_id, player);
|
self.subscriptions.insert(player_id, player);
|
||||||
}
|
}
|
||||||
RoomCommand::SendMessage { player_id, body } => {
|
RoomCommand::SendMessage {
|
||||||
|
player_id,
|
||||||
|
connection_id,
|
||||||
|
body,
|
||||||
|
} => {
|
||||||
tracing::info!("Adding a message to room");
|
tracing::info!("Adding a message to room");
|
||||||
for (_, sub) in &mut self.subscriptions {
|
for (_, sub) in &mut self.subscriptions {
|
||||||
sub.receive_message(room_id.clone(), player_id.clone(), body.clone())
|
log::info!("Sending a message from room to player");
|
||||||
|
sub.receive_message(
|
||||||
|
room_id.clone(),
|
||||||
|
player_id.clone(),
|
||||||
|
connection_id.clone(),
|
||||||
|
body.clone(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ async fn handle_registered_socket<'a>(
|
||||||
let mut user_handle = players
|
let mut user_handle = players
|
||||||
.get_or_create_player(PlayerId(user.nickname.clone()))
|
.get_or_create_player(PlayerId(user.nickname.clone()))
|
||||||
.await;
|
.await;
|
||||||
let mut connnection = user_handle.subscribe().await;
|
let (my_connection_id, mut connnection) = user_handle.subscribe().await;
|
||||||
|
|
||||||
ServerMessage {
|
ServerMessage {
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
|
@ -217,8 +217,49 @@ async fn handle_registered_socket<'a>(
|
||||||
},
|
},
|
||||||
ClientMessage::Join(chan) => {
|
ClientMessage::Join(chan) => {
|
||||||
match chan {
|
match chan {
|
||||||
Chan::Global(room) => {
|
Chan::Global(ref room) => {
|
||||||
user_handle.join_room(RoomId(room.clone())).await;
|
user_handle.join_room(RoomId(room.clone())).await;
|
||||||
|
ServerMessage {
|
||||||
|
tags: vec![],
|
||||||
|
sender: Some(user.nickname.clone()),
|
||||||
|
body: ServerMessageBody::Join(chan.clone()),
|
||||||
|
}
|
||||||
|
.write_async(&mut writer)
|
||||||
|
.await?;
|
||||||
|
ServerMessage {
|
||||||
|
tags: vec![],
|
||||||
|
sender: Some(config.server_name.as_bytes().to_vec()),
|
||||||
|
body: ServerMessageBody::N332Topic {
|
||||||
|
client: user.nickname.clone(),
|
||||||
|
chat: chan.clone(),
|
||||||
|
topic: b"chan topic lol".to_vec(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.write_async(&mut writer)
|
||||||
|
.await?;
|
||||||
|
ServerMessage {
|
||||||
|
tags: vec![],
|
||||||
|
sender: Some(config.server_name.as_bytes().to_vec()),
|
||||||
|
body: ServerMessageBody::N353NamesReply {
|
||||||
|
client: user.nickname.clone(),
|
||||||
|
chan: chan.clone(),
|
||||||
|
members: user.nickname.clone(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.write_async(&mut writer)
|
||||||
|
.await?;
|
||||||
|
ServerMessage {
|
||||||
|
tags: vec![],
|
||||||
|
sender: Some(config.server_name.as_bytes().to_vec()),
|
||||||
|
body: ServerMessageBody::N366NamesReplyEnd {
|
||||||
|
client: user.nickname.clone(),
|
||||||
|
chan: chan.clone(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.write_async(&mut writer)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
writer.flush().await?;
|
||||||
},
|
},
|
||||||
Chan::Local(_) => {},
|
Chan::Local(_) => {},
|
||||||
};
|
};
|
||||||
|
@ -227,7 +268,7 @@ async fn handle_registered_socket<'a>(
|
||||||
match recipient {
|
match recipient {
|
||||||
Recipient::Chan(Chan::Global(room)) => {
|
Recipient::Chan(Chan::Global(room)) => {
|
||||||
match String::from_utf8(body) {
|
match String::from_utf8(body) {
|
||||||
Ok(body) => user_handle.send_message(RoomId(room.clone()), body.clone()).await,
|
Ok(body) => user_handle.send_message(RoomId(room.clone()), my_connection_id.clone(), body.clone()).await,
|
||||||
Err(err) => log::warn!("failed to parse incoming message: {err}"),
|
Err(err) => log::warn!("failed to parse incoming message: {err}"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -246,13 +287,15 @@ async fn handle_registered_socket<'a>(
|
||||||
update = connnection.recv() => {
|
update = connnection.recv() => {
|
||||||
match update.unwrap() {
|
match update.unwrap() {
|
||||||
Updates::RoomJoined { room_id } => {},
|
Updates::RoomJoined { room_id } => {},
|
||||||
Updates::NewMessage { room_id, body } => {
|
Updates::NewMessage { author_id, connection_id, room_id, body } => {
|
||||||
|
if my_connection_id != connection_id {
|
||||||
ServerMessage {
|
ServerMessage {
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
sender: None,
|
sender: Some(author_id.0.clone()),
|
||||||
body: ServerMessageBody::PrivateMessage { target: Recipient::Chan(Chan::Global(room_id.0)), body: body.as_bytes().to_vec() }
|
body: ServerMessageBody::PrivateMessage { target: Recipient::Chan(Chan::Global(room_id.0)), body: body.as_bytes().to_vec() }
|
||||||
}.write_async(&mut writer).await?;
|
}.write_async(&mut writer).await?;
|
||||||
writer.flush().await?
|
writer.flush().await?
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@ pub enum ServerMessageBody {
|
||||||
target: Recipient,
|
target: Recipient,
|
||||||
body: ByteVec,
|
body: ByteVec,
|
||||||
},
|
},
|
||||||
|
Join(Chan),
|
||||||
N001Welcome {
|
N001Welcome {
|
||||||
client: ByteVec,
|
client: ByteVec,
|
||||||
text: ByteVec,
|
text: ByteVec,
|
||||||
|
@ -81,6 +82,20 @@ pub enum ServerMessageBody {
|
||||||
client: ByteVec,
|
client: ByteVec,
|
||||||
params: ByteVec, // TODO make this a datatype
|
params: ByteVec, // TODO make this a datatype
|
||||||
},
|
},
|
||||||
|
N332Topic {
|
||||||
|
client: ByteVec,
|
||||||
|
chat: Chan,
|
||||||
|
topic: ByteVec,
|
||||||
|
},
|
||||||
|
N353NamesReply {
|
||||||
|
client: ByteVec,
|
||||||
|
chan: Chan,
|
||||||
|
members: ByteVec, // TODO make this a non-empty list with prefixes
|
||||||
|
},
|
||||||
|
N366NamesReplyEnd {
|
||||||
|
client: ByteVec,
|
||||||
|
chan: Chan,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerMessageBody {
|
impl ServerMessageBody {
|
||||||
|
@ -112,6 +127,10 @@ impl ServerMessageBody {
|
||||||
writer.write_all(b" :").await?;
|
writer.write_all(b" :").await?;
|
||||||
writer.write_all(&body).await?;
|
writer.write_all(&body).await?;
|
||||||
}
|
}
|
||||||
|
ServerMessageBody::Join(chan) => {
|
||||||
|
writer.write_all(b"JOIN ").await?;
|
||||||
|
chan.write_async(writer).await?;
|
||||||
|
}
|
||||||
ServerMessageBody::N001Welcome { client, text } => {
|
ServerMessageBody::N001Welcome { client, text } => {
|
||||||
writer.write_all(b"001 ").await?;
|
writer.write_all(b"001 ").await?;
|
||||||
writer.write_all(&client).await?;
|
writer.write_all(&client).await?;
|
||||||
|
@ -153,6 +172,37 @@ impl ServerMessageBody {
|
||||||
writer.write_all(¶ms).await?;
|
writer.write_all(¶ms).await?;
|
||||||
writer.write_all(b" :are supported by this server").await?;
|
writer.write_all(b" :are supported by this server").await?;
|
||||||
}
|
}
|
||||||
|
ServerMessageBody::N332Topic {
|
||||||
|
client,
|
||||||
|
chat,
|
||||||
|
topic,
|
||||||
|
} => {
|
||||||
|
writer.write_all(b"332 ").await?;
|
||||||
|
writer.write_all(&client).await?;
|
||||||
|
writer.write_all(b" ").await?;
|
||||||
|
chat.write_async(writer).await?;
|
||||||
|
writer.write_all(b" :").await?;
|
||||||
|
writer.write_all(&topic).await?;
|
||||||
|
}
|
||||||
|
ServerMessageBody::N353NamesReply {
|
||||||
|
client,
|
||||||
|
chan,
|
||||||
|
members,
|
||||||
|
} => {
|
||||||
|
writer.write_all(b"353 ").await?;
|
||||||
|
writer.write_all(&client).await?;
|
||||||
|
writer.write_all(b" = ").await?;
|
||||||
|
chan.write_async(writer).await?;
|
||||||
|
writer.write_all(b" :").await?;
|
||||||
|
writer.write_all(&members).await?;
|
||||||
|
}
|
||||||
|
ServerMessageBody::N366NamesReplyEnd { client, chan } => {
|
||||||
|
writer.write_all(b"366 ").await?;
|
||||||
|
writer.write_all(&client).await?;
|
||||||
|
writer.write_all(b" ").await?;
|
||||||
|
chan.write_async(writer).await?;
|
||||||
|
writer.write_all(b" :End of /NAMES list").await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
|
||||||
pub struct Key(u32);
|
pub struct Key(u32);
|
||||||
|
|
||||||
/// Hash map with auto-generated surrogate key.
|
/// Hash map with auto-generated surrogate key.
|
||||||
|
|
Loading…
Reference in New Issue