More work.
Added chat service Updated packet service to pass the tcp stream around in a Arc type. Updated character position data to not require multiplying the coords Added more debug logs Added an interceptor for gRPC comms with the chat server Updated build and push script for the chat server changes
This commit is contained in:
22
chat-service/src/chat_channels/guild_chat.rs
Normal file
22
chat-service/src/chat_channels/guild_chat.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use crate::chat_channels::ChatChannel;
|
||||
use crate::chat_service::Clients;
|
||||
use crate::chat_service::chat::ChatMessage;
|
||||
|
||||
/// A dedicated module for guild chat.
|
||||
#[derive(Debug)]
|
||||
pub struct GuildChat;
|
||||
|
||||
impl ChatChannel for GuildChat {
|
||||
fn handle_message(&self, message: ChatMessage, sender_id: &str, clients: &Clients) {
|
||||
// This is a placeholder. In a real implementation, verify
|
||||
// guild membership by consulting your Character or Guild service.
|
||||
let clients_lock = clients.lock().unwrap();
|
||||
for (id, tx) in clients_lock.iter() {
|
||||
// For demonstration, send only to clients whose IDs contain
|
||||
// "guild". Replace this logic with your actual membership check.
|
||||
if id != sender_id && id.contains("guild") {
|
||||
let _ = tx.try_send(message.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
chat-service/src/chat_channels/local_chat.rs
Normal file
22
chat-service/src/chat_channels/local_chat.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use tracing::debug;
|
||||
use crate::chat_channels::ChatChannel;
|
||||
use crate::chat_service::Clients;
|
||||
use crate::chat_service::chat::ChatMessage;
|
||||
|
||||
/// A dedicated module for local chat.
|
||||
#[derive(Debug)]
|
||||
pub struct LocalChat;
|
||||
|
||||
impl ChatChannel for LocalChat {
|
||||
fn handle_message(&self, message: ChatMessage, sender_id: &str, clients: &Clients) {
|
||||
// In a full implementation, you might query for nearby clients.
|
||||
// For demo purposes, we simply broadcast to all clients except the sender.
|
||||
debug!("LocalChat::handle_message: {:?}", message);
|
||||
let clients_lock = clients.lock().unwrap();
|
||||
for (id, tx) in clients_lock.iter() {
|
||||
// if id != sender_id {
|
||||
let _ = tx.try_send(message.clone());
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
12
chat-service/src/chat_channels/mod.rs
Normal file
12
chat-service/src/chat_channels/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
pub mod local_chat;
|
||||
pub mod shout_chat;
|
||||
pub mod guild_chat;
|
||||
|
||||
/// Define a common trait for all dedicated chat channels.
|
||||
use crate::chat_service::Clients;
|
||||
use crate::chat_service::chat::ChatMessage;
|
||||
|
||||
pub trait ChatChannel: Send + Sync {
|
||||
/// Process and route the chat message.
|
||||
fn handle_message(&self, message: ChatMessage, sender_id: &str, clients: &Clients);
|
||||
}
|
||||
20
chat-service/src/chat_channels/shout_chat.rs
Normal file
20
chat-service/src/chat_channels/shout_chat.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use crate::chat_channels::ChatChannel;
|
||||
use crate::chat_service::Clients;
|
||||
use crate::chat_service::chat::ChatMessage;
|
||||
|
||||
/// A dedicated module for shout chat.
|
||||
#[derive(Debug)]
|
||||
pub struct ShoutChat;
|
||||
|
||||
impl ChatChannel for ShoutChat {
|
||||
fn handle_message(&self, message: ChatMessage, sender_id: &str, clients: &Clients) {
|
||||
// For demo purposes, we simply broadcast to all clients except the sender.
|
||||
// TODO: make sure the clients are on the same map
|
||||
let clients_lock = clients.lock().unwrap();
|
||||
for (id, tx) in clients_lock.iter() {
|
||||
if id != sender_id {
|
||||
let _ = tx.try_send(message.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
130
chat-service/src/chat_service.rs
Normal file
130
chat-service/src/chat_service.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::collections::HashMap;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tonic::{Request, Response, Status};
|
||||
use tracing::debug;
|
||||
|
||||
pub mod common {
|
||||
tonic::include_proto!("common");
|
||||
}
|
||||
pub mod chat {
|
||||
tonic::include_proto!("chat");
|
||||
}
|
||||
|
||||
use chat::chat_service_server::{ChatService, ChatServiceServer};
|
||||
use chat::{ChatMessage, MessageType};
|
||||
|
||||
/// Type alias for storing client connections.
|
||||
pub type Clients =
|
||||
Arc<Mutex<HashMap<String, tokio::sync::mpsc::Sender<ChatMessage>>>>;
|
||||
|
||||
use crate::chat_channels::ChatChannel;
|
||||
|
||||
/// Our chat service struct.
|
||||
#[derive(Clone)]
|
||||
pub struct MyChatService {
|
||||
pub clients: Clients,
|
||||
pub local_channel: Arc<dyn ChatChannel>,
|
||||
pub shout_channel: Arc<dyn ChatChannel>,
|
||||
pub guild_channel: Arc<dyn ChatChannel>,
|
||||
}
|
||||
|
||||
impl MyChatService {
|
||||
/// Wrap our service as a gRPC service.
|
||||
pub fn into_service(self) -> ChatServiceServer<Self> {
|
||||
ChatServiceServer::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl ChatService for MyChatService {
|
||||
type ChatStreamStream =
|
||||
Pin<Box<dyn Stream<Item = Result<ChatMessage, Status>> + Send + Sync + 'static>>;
|
||||
|
||||
async fn chat_stream(
|
||||
&self,
|
||||
request: Request<tonic::Streaming<ChatMessage>>,
|
||||
) -> Result<Response<Self::ChatStreamStream>, Status> {
|
||||
debug!("New chat client connected");
|
||||
debug!("request: {:?}", request);
|
||||
|
||||
let mut inbound = request.into_inner();
|
||||
|
||||
// Create a new client ID. In production, use authenticated IDs.
|
||||
let client_id = format!("client-{}", uuid::Uuid::new_v4());
|
||||
|
||||
// Create a channel for sending outbound messages to this client.
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(32);
|
||||
|
||||
{
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
clients.insert(client_id.clone(), tx);
|
||||
}
|
||||
|
||||
// Clone shared resources for the spawned task.
|
||||
let clients_clone = self.clients.clone();
|
||||
let local_channel = self.local_channel.clone();
|
||||
let shout_channel = self.shout_channel.clone();
|
||||
let guild_channel = self.guild_channel.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(result) = inbound.next().await {
|
||||
match result {
|
||||
Ok(message) => {
|
||||
debug!("message: {:?}", message);
|
||||
// Dispatch based on the chat type.
|
||||
match TryFrom::try_from(message.r#type)
|
||||
.unwrap_or(MessageType::Normal)
|
||||
{
|
||||
MessageType::Normal => {
|
||||
local_channel.handle_message(
|
||||
message,
|
||||
&client_id,
|
||||
&clients_clone,
|
||||
);
|
||||
}
|
||||
MessageType::Shout => {
|
||||
shout_channel.handle_message(
|
||||
message,
|
||||
&client_id,
|
||||
&clients_clone,
|
||||
);
|
||||
}
|
||||
MessageType::Clan => {
|
||||
guild_channel.handle_message(
|
||||
message,
|
||||
&client_id,
|
||||
&clients_clone,
|
||||
);
|
||||
}
|
||||
// For other types, we simply broadcast as default.
|
||||
_ => {
|
||||
let clients_lock = clients_clone.lock().unwrap();
|
||||
for (id, tx) in clients_lock.iter() {
|
||||
if id != &client_id {
|
||||
let _ = tx.try_send(message.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error receiving message from {}: {:?}", client_id, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the client when the stream ends.
|
||||
let mut clients = clients_clone.lock().unwrap();
|
||||
clients.remove(&client_id);
|
||||
println!("Client {} disconnected", client_id);
|
||||
});
|
||||
|
||||
// Convert the rx half into a stream for the response.
|
||||
let out_stream = tokio_stream::wrappers::ReceiverStream::new(rx)
|
||||
.map(|msg| Ok(msg));
|
||||
Ok(Response::new(Box::pin(out_stream) as Self::ChatStreamStream))
|
||||
}
|
||||
}
|
||||
51
chat-service/src/main.rs
Normal file
51
chat-service/src/main.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
mod chat_service;
|
||||
mod chat_channels;
|
||||
|
||||
use dotenv::dotenv;
|
||||
use std::env;
|
||||
use utils::service_discovery::{get_kube_service_endpoints_by_dns, get_service_endpoints_by_dns};
|
||||
use utils::{health_check, logging};
|
||||
use chat_service::MyChatService;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tonic::transport::Server;
|
||||
use crate::chat_service::chat::chat_service_server::ChatServiceServer;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
dotenv().ok();
|
||||
let app_name = env!("CARGO_PKG_NAME");
|
||||
logging::setup_logging(app_name, &["chat_service", "health_check"]);
|
||||
|
||||
// Set the gRPC server address
|
||||
let addr = env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0".to_string());
|
||||
let port = env::var("SERVICE_PORT").unwrap_or_else(|_| "50055".to_string());
|
||||
|
||||
let clients = Arc::new(Mutex::new(HashMap::new()));
|
||||
|
||||
let chat_service = MyChatService {
|
||||
clients: clients.clone(),
|
||||
local_channel: Arc::new(chat_channels::local_chat::LocalChat),
|
||||
shout_channel: Arc::new(chat_channels::shout_chat::ShoutChat),
|
||||
guild_channel: Arc::new(chat_channels::guild_chat::GuildChat),
|
||||
};
|
||||
|
||||
// Register service with Consul
|
||||
let (mut health_reporter, health_service) = tonic_health::server::health_reporter();
|
||||
health_reporter
|
||||
.set_serving::<ChatServiceServer<MyChatService>>()
|
||||
.await;
|
||||
let address = SocketAddr::new(addr.parse()?, port.parse()?);
|
||||
tokio::spawn(
|
||||
Server::builder()
|
||||
.add_service(chat_service.into_service())
|
||||
.add_service(health_service)
|
||||
.serve(address),
|
||||
);
|
||||
|
||||
println!("Chat Service listening on {}", address);
|
||||
|
||||
utils::signal_handler::wait_for_signal().await;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user