From 4ee724be7b15e66a0759e8f1b34805e6012b41f8 Mon Sep 17 00:00:00 2001 From: RanolP Date: Sat, 4 Apr 2020 11:01:31 +0900 Subject: [PATCH 01/14] chore: Separate typealias --- src/core/mod.rs | 3 +++ src/core/typealias.rs | 2 ++ src/main.rs | 1 + src/websocket.rs | 2 +- 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/core/mod.rs create mode 100644 src/core/typealias.rs diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..3b37e5d --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,3 @@ +mod typealias; + +pub use typealias::*; diff --git a/src/core/typealias.rs b/src/core/typealias.rs new file mode 100644 index 0000000..f6b679c --- /dev/null +++ b/src/core/typealias.rs @@ -0,0 +1,2 @@ +pub type UserId = usize; +pub type RoomId = usize; diff --git a/src/main.rs b/src/main.rs index 9e52717..a967191 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use middleware::errhandlers::ErrorHandlerResponse; use crate::gql::{Context, Mutation, Query, Schema}; mod config; +mod core; mod database; mod gql; mod log; diff --git a/src/websocket.rs b/src/websocket.rs index 1ca4866..41f99b3 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -2,6 +2,7 @@ use actix::*; use actix_web::{web, Error, HttpRequest, HttpResponse}; use actix_web_actors::ws; +use crate::core::UserId; use crate::protocol::{PacketClient, PacketServer}; use log::{info, warn}; use std::time::{Duration, Instant}; @@ -9,7 +10,6 @@ use std::time::{Duration, Instant}; const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); -type UserId = usize; type Message = Vec; struct WsSession { From e59b7ece335f42c895e2fbbd232f79b21b550ec2 Mon Sep 17 00:00:00 2001 From: RanolP Date: Sat, 4 Apr 2020 14:53:59 +0900 Subject: [PATCH 02/14] feat: Add some structs --- src/core/mod.rs | 6 ++++++ src/core/room.rs | 1 + src/core/server.rs | 7 +++++++ src/core/session.rs | 11 +++++++++++ 4 files changed, 25 insertions(+) create mode 100644 src/core/room.rs create mode 100644 src/core/server.rs create mode 100644 src/core/session.rs diff --git a/src/core/mod.rs b/src/core/mod.rs index 3b37e5d..64502bd 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,3 +1,9 @@ +mod room; +mod server; +mod session; mod typealias; +pub use room::*; +pub use server::*; +pub use session::*; pub use typealias::*; diff --git a/src/core/room.rs b/src/core/room.rs new file mode 100644 index 0000000..8be9529 --- /dev/null +++ b/src/core/room.rs @@ -0,0 +1 @@ +pub struct Room {} diff --git a/src/core/server.rs b/src/core/server.rs new file mode 100644 index 0000000..87feebc --- /dev/null +++ b/src/core/server.rs @@ -0,0 +1,7 @@ +use crate::core::{Room, RoomId, Session, UserId}; +use std::collections::HashMap; + +pub struct Server { + sessions: HashMap, + rooms: HashMap, +} diff --git a/src/core/session.rs b/src/core/session.rs new file mode 100644 index 0000000..a448254 --- /dev/null +++ b/src/core/session.rs @@ -0,0 +1,11 @@ +use crate::core::{RoomId, UserId}; +use crate::protocol::PacketServer; +use actix::Recipient; + +#[derive(Clone)] +pub struct Session { + id: UserId, + pipe: Recipient, + room: Option, + name: String, +} From 8c21f7b9eecdef014460f213e1200dded403349c Mon Sep 17 00:00:00 2001 From: RanolP Date: Sat, 4 Apr 2020 20:27:10 +0900 Subject: [PATCH 03/14] feat: Add server struct on websocket session --- src/core/room.rs | 1 + src/core/server.rs | 6 ++++++ src/main.rs | 4 ++++ src/websocket.rs | 8 ++++---- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/core/room.rs b/src/core/room.rs index 8be9529..0c77121 100644 --- a/src/core/room.rs +++ b/src/core/room.rs @@ -1 +1,2 @@ +#[derive(Clone)] pub struct Room {} diff --git a/src/core/server.rs b/src/core/server.rs index 87feebc..bbc5e3f 100644 --- a/src/core/server.rs +++ b/src/core/server.rs @@ -1,7 +1,13 @@ use crate::core::{Room, RoomId, Session, UserId}; +use actix::prelude::*; use std::collections::HashMap; +#[derive(Clone, Default)] pub struct Server { sessions: HashMap, rooms: HashMap, } + +impl Actor for Server { + type Context = Context; +} diff --git a/src/main.rs b/src/main.rs index a967191..4932222 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use actix_web::{dev, http, middleware, web, App, Error, HttpResponse, HttpServer use juniper::http::{playground::playground_source, GraphQLRequest}; use middleware::errhandlers::ErrorHandlerResponse; +use crate::core::Server; use crate::gql::{Context, Mutation, Query, Schema}; mod config; @@ -65,10 +66,13 @@ async fn main() -> io::Result<()> { let context = Arc::new(Context::new(pool)); let schema = Arc::new(Schema::new(Query, Mutation)); + let server = Server::default(); + HttpServer::new(move || { App::new() .data(schema.clone()) .data(context.clone()) + .data(server.clone()) .wrap(middleware::Logger::default()) .wrap(ErrorHandlers::new().handler(http::StatusCode::NOT_FOUND, render_404)) .service(web::resource("/graphql").route(web::post().to(graphql))) diff --git a/src/websocket.rs b/src/websocket.rs index 41f99b3..c8f89fc 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -2,7 +2,7 @@ use actix::*; use actix_web::{web, Error, HttpRequest, HttpResponse}; use actix_web_actors::ws; -use crate::core::UserId; +use crate::core::{Server, UserId}; use crate::protocol::{PacketClient, PacketServer}; use log::{info, warn}; use std::time::{Duration, Instant}; @@ -15,7 +15,7 @@ type Message = Vec; struct WsSession { id: UserId, last_heartbeat: Instant, - // host: Addr, + server: Addr, } impl Actor for WsSession { @@ -134,13 +134,13 @@ impl WsSession { pub async fn ws( req: HttpRequest, stream: web::Payload, - // server: web::Data>, + server: web::Data>, ) -> Result { ws::start( WsSession { id: 0, last_heartbeat: Instant::now(), - // host: server.get_ref().clone(), + server: server.as_ref().clone(), }, &req, stream, From b18176bf77029e0cf03a3eaed0997414e305517e Mon Sep 17 00:00:00 2001 From: RanolP Date: Sat, 4 Apr 2020 21:20:47 +0900 Subject: [PATCH 04/14] fix(packet-macro): Add missing attribute copy for enum item --- packet-macro/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packet-macro/src/lib.rs b/packet-macro/src/lib.rs index 37b8c52..b973e09 100644 --- a/packet-macro/src/lib.rs +++ b/packet-macro/src/lib.rs @@ -110,15 +110,25 @@ pub fn packet(args: TokenStream, item: TokenStream) -> TokenStream { let server_name = format_ident!("{}Server", name); let client_name = format_ident!("{}Client", name); let generics = item.generics; + let attrs: Vec<_> = item + .attrs + .iter() + .filter(|it| match it.path.get_ident() { + Some(v) if v.to_string() == "packet" => false, + _ => true, + }) + .collect(); (quote! { #[derive(serde::Serialize)] #[serde(tag = "kind")] + #(#attrs)* #vis enum #server_name #generics { #(#variant_generated_server,)* } #[derive(serde::Deserialize)] #[serde(tag = "kind")] + #(#attrs)* #vis enum #client_name #generics { #(#variant_generated_client,)* } From 73f7315b05322136fd7d3ca59760554536c0b0fb Mon Sep 17 00:00:00 2001 From: RanolP Date: Sat, 4 Apr 2020 21:25:06 +0900 Subject: [PATCH 05/14] feat: Implement deserialization of packet --- src/main.rs | 3 ++- src/protocol/packet.rs | 23 +++++++++++++++++++++-- src/protocol/structure/common/goto.rs | 6 +++--- src/protocol/structure/common/mod.rs | 1 + src/websocket.rs | 14 ++++++-------- 5 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4932222..da9be9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ extern crate diesel; use std::io; use std::sync::Arc; +use actix::prelude::*; use actix_rt; use actix_web::middleware::errhandlers::ErrorHandlers; use actix_web::{dev, http, middleware, web, App, Error, HttpResponse, HttpServer, Responder}; @@ -66,7 +67,7 @@ async fn main() -> io::Result<()> { let context = Arc::new(Context::new(pool)); let schema = Arc::new(Schema::new(Query, Mutation)); - let server = Server::default(); + let server = Server::default().start(); HttpServer::new(move || { App::new() diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index 92b9bf7..ac20e42 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -1,6 +1,8 @@ use crate::protocol::structure::*; use actix::Message; use serde::*; +use std::fmt; + #[derive(Serialize, Deserialize)] #[serde(untagged)] pub enum PacketResult { @@ -11,7 +13,24 @@ pub enum PacketResult { }, } -#[derive(Serialize)] +impl fmt::Debug for PacketResult +where + OkBody: Serialize + fmt::Debug, + ErrorKind: Serialize + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PacketResult::Ok(body) => f.debug_tuple("PacketResult::Ok").field(body).finish(), + PacketResult::Err { kind, description } => f + .debug_struct("PacketResult::Err") + .field("kind", kind) + .field("description", description) + .finish(), + } + } +} + +#[derive(Debug, Serialize)] #[serde(untagged)] pub enum PacketServer { Common(CommonPacketServer), @@ -21,7 +40,7 @@ impl Message for PacketServer { type Result = (); } -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] #[serde(untagged)] pub enum PacketClient { Common(CommonPacketClient), diff --git a/src/protocol/structure/common/goto.rs b/src/protocol/structure/common/goto.rs index dc50b1c..29cc401 100644 --- a/src/protocol/structure/common/goto.rs +++ b/src/protocol/structure/common/goto.rs @@ -1,20 +1,20 @@ use crate::protocol::PacketResult; use serde::*; -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct GotoPacketRequest { // game_category: Option, // room: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum GotoPacketResponseError { IsRoomFull, Unknown, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct GotoPacketResponseOk {} pub type GotoPacketResponse = PacketResult; diff --git a/src/protocol/structure/common/mod.rs b/src/protocol/structure/common/mod.rs index b4a9080..4f528ad 100644 --- a/src/protocol/structure/common/mod.rs +++ b/src/protocol/structure/common/mod.rs @@ -3,6 +3,7 @@ use packet_macro::packet; mod goto; #[packet(namespace = "common")] +#[derive(Debug)] pub enum CommonPacket { #[packet(id = "goto")] Goto { diff --git a/src/websocket.rs b/src/websocket.rs index c8f89fc..2d553a7 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -4,14 +4,12 @@ use actix_web_actors::ws; use crate::core::{Server, UserId}; use crate::protocol::{PacketClient, PacketServer}; -use log::{info, warn}; +use log::{info, trace, warn}; use std::time::{Duration, Instant}; const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); -type Message = Vec; - struct WsSession { id: UserId, last_heartbeat: Instant, @@ -81,7 +79,10 @@ impl StreamHandler> for WsSession { ctx.stop(); } ws::Message::Text(text) => { - if let Ok(message) = serde_json::from_str::(&text) { + trace!("{}", &text); + if let Ok(message) = serde_json::from_str::(&text) { + info!("{:?}", message); + // self.server.do_send(message); /* match message { Message::Chat { text, to } => { @@ -107,10 +108,7 @@ impl StreamHandler> for WsSession { */ } } - ws::Message::Nop => (), - ws::Message::Continuation(_) => { - // wtf is that - } + ws::Message::Nop | ws::Message::Continuation(_) => {} } } } From 7390e66a984ea5553fe48593b4f8fe181268219b Mon Sep 17 00:00:00 2001 From: RanolP Date: Sun, 5 Apr 2020 16:52:24 +0900 Subject: [PATCH 06/14] feat: Add handlers --- packet-macro/README.md | 10 +++- packet-macro/src/lib.rs | 74 ++++++++++++++++++++------- src/protocol/mod.rs | 5 +- src/protocol/packet.rs | 45 +++++----------- src/protocol/result.rs | 55 ++++++++++++++++++++ src/protocol/structure/common/goto.rs | 20 ++++++-- src/protocol/structure/common/mod.rs | 6 ++- src/websocket.rs | 30 ++--------- 8 files changed, 163 insertions(+), 82 deletions(-) create mode 100644 src/protocol/result.rs diff --git a/packet-macro/README.md b/packet-macro/README.md index e49794a..5c17092 100644 --- a/packet-macro/README.md +++ b/packet-macro/README.md @@ -7,7 +7,7 @@ 이렇게 적으면 ```rust -#[packet(namespace = "common")] +#[packet(namespace = "common", handler_target = "Server")] pub enum CommonPacket { #[packet(id = "goto")] Goto { @@ -45,4 +45,12 @@ pub enum CommonPacketClient { body: GotoRequestBody }, } +impl Handler for Server +{ + fn handle(&mut self, message: CommonPacketClient, ctx: &mut Self::Context) { + match message { + Goto { body } => self.handle(body, ctx), + } + } +} ``` diff --git a/packet-macro/src/lib.rs b/packet-macro/src/lib.rs index b973e09..d943526 100644 --- a/packet-macro/src/lib.rs +++ b/packet-macro/src/lib.rs @@ -1,26 +1,42 @@ use darling::{Error, FromMeta, FromVariant}; use proc_macro::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_macro_input, AttributeArgs, Fields, ItemEnum}; +use syn::{ + parse_macro_input, parse_str, spanned::Spanned, Attribute, AttributeArgs, Fields, ItemEnum, + Type, +}; #[derive(Debug, FromMeta)] struct PacketItem { namespace: String, + handler_target: String, } + +// uses_type_params!(PacketItem, handler_target); + #[derive(Debug, FromVariant)] #[darling(attributes(packet))] struct PacketVariant { id: String, + attrs: Vec, } #[proc_macro_attribute] pub fn packet(args: TokenStream, item: TokenStream) -> TokenStream { let attr_args = parse_macro_input!(args as AttributeArgs); + let attr_span = attr_args.first().and_then(|first| { + first.span().join( + attr_args + .last() + .expect("If first exists, last exists") + .span(), + ) + }); let attr_args = match PacketItem::from_list(&attr_args) { Ok(v) => v, Err(e) => { - return TokenStream::from(e.write_errors()); + return e.write_errors().into(); } }; @@ -28,6 +44,7 @@ pub fn packet(args: TokenStream, item: TokenStream) -> TokenStream { let mut variant_generated_client = Vec::new(); let mut variant_generated_server = Vec::new(); + let mut variant_generated_names = Vec::new(); for variant in &item.variants { let variant_attrs = match PacketVariant::from_variant(&variant) { @@ -36,14 +53,7 @@ pub fn packet(args: TokenStream, item: TokenStream) -> TokenStream { return e.write_errors().into(); } }; - let attrs: Vec<_> = variant - .attrs - .iter() - .filter(|it| match it.path.get_ident() { - Some(v) if v.to_string() == "packet" => false, - _ => true, - }) - .collect(); + let attrs: Vec<_> = variant_attrs.attrs; let name = &variant.ident; let rename = format!("{}:{}", attr_args.namespace, variant_attrs.id); match &variant.fields { @@ -87,6 +97,7 @@ pub fn packet(args: TokenStream, item: TokenStream) -> TokenStream { body: #req_type, } }); + variant_generated_names.push(name.clone()); } Fields::Unnamed(unnamed) => variant_generated_server.push(quote! { #(#attrs)* @@ -110,17 +121,22 @@ pub fn packet(args: TokenStream, item: TokenStream) -> TokenStream { let server_name = format_ident!("{}Server", name); let client_name = format_ident!("{}Client", name); let generics = item.generics; - let attrs: Vec<_> = item - .attrs - .iter() - .filter(|it| match it.path.get_ident() { - Some(v) if v.to_string() == "packet" => false, - _ => true, - }) - .collect(); + let attrs = item.attrs; + let handler_target: Type = if let Ok(handler_target) = parse_str(&attr_args.handler_target) { + handler_target + } else { + let mut error = Error::custom(format_args!( + "Expected type in string literal, but {} received", + attr_args.handler_target + )); + if let Some(attr_span) = attr_span { + error = error.with_span(&attr_span); + } + return error.write_errors().into(); + }; (quote! { - #[derive(serde::Serialize)] + #[derive(serde::Serialize, actix::MessageResponse)] #[serde(tag = "kind")] #(#attrs)* #vis enum #server_name #generics { @@ -132,6 +148,26 @@ pub fn packet(args: TokenStream, item: TokenStream) -> TokenStream { #vis enum #client_name #generics { #(#variant_generated_client,)* } + impl Message for #client_name { + type Result = #server_name; + } + + impl actix::Handler<#client_name> for #handler_target { + type Result = #server_name; + fn handle(&mut self, message: #client_name, ctx: &mut Self::Context) -> Self::Result { + match message { + #( + #client_name::#variant_generated_names { body } => { + let result = self.handle(body, ctx); + #server_name::#variant_generated_names { + ok: result.is_ok(), + body: result, + } + } + ),* + } + } + } }) .into() } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 1d6b881..2b91ea0 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,5 +1,8 @@ mod handler; mod packet; +mod result; mod structure; -pub use packet::{PacketClient, PacketResult, PacketServer}; +pub use packet::{PacketClient, PacketServer}; +pub use result::PacketResult; +pub use structure::*; diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index ac20e42..010cb42 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -1,36 +1,9 @@ +use crate::core::Server; use crate::protocol::structure::*; -use actix::Message; +use actix::prelude::*; use serde::*; -use std::fmt; -#[derive(Serialize, Deserialize)] -#[serde(untagged)] -pub enum PacketResult { - Ok(OkBody), - Err { - kind: ErrorKind, - description: Option, - }, -} - -impl fmt::Debug for PacketResult -where - OkBody: Serialize + fmt::Debug, - ErrorKind: Serialize + fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PacketResult::Ok(body) => f.debug_tuple("PacketResult::Ok").field(body).finish(), - PacketResult::Err { kind, description } => f - .debug_struct("PacketResult::Err") - .field("kind", kind) - .field("description", description) - .finish(), - } - } -} - -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, MessageResponse)] #[serde(untagged)] pub enum PacketServer { Common(CommonPacketServer), @@ -47,5 +20,15 @@ pub enum PacketClient { } impl Message for PacketClient { - type Result = (); + type Result = PacketServer; +} + +impl Handler for Server { + type Result = PacketServer; + + fn handle(&mut self, msg: PacketClient, ctx: &mut Self::Context) -> Self::Result { + match msg { + PacketClient::Common(common) => PacketServer::Common(self.handle(common, ctx)), + } + } } diff --git a/src/protocol/result.rs b/src/protocol/result.rs new file mode 100644 index 0000000..262246c --- /dev/null +++ b/src/protocol/result.rs @@ -0,0 +1,55 @@ +use actix::dev::{MessageResponse, ResponseChannel}; +use actix::prelude::*; +use serde::*; +use std::fmt; + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +pub enum PacketResult { + Ok(OkBody), + Err { + kind: ErrorKind, + description: Option, + }, +} + +impl PacketResult +where + OkBody: Serialize, + ErrorKind: Serialize, +{ + pub fn is_ok(&self) -> bool { + matches!(self, &PacketResult::Ok(_)) + } +} + +impl fmt::Debug for PacketResult +where + OkBody: Serialize + fmt::Debug, + ErrorKind: Serialize + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PacketResult::Ok(body) => f.debug_tuple("PacketResult::Ok").field(body).finish(), + PacketResult::Err { kind, description } => f + .debug_struct("PacketResult::Err") + .field("kind", kind) + .field("description", description) + .finish(), + } + } +} + +impl MessageResponse for PacketResult +where + A: Actor, + M: Message>, + OkBody: Serialize + fmt::Debug + 'static, + ErrorKind: Serialize + fmt::Debug + 'static, +{ + fn handle>(self, _: &mut A::Context, tx: Option) { + if let Some(tx) = tx { + tx.send(self); + } + } +} diff --git a/src/protocol/structure/common/goto.rs b/src/protocol/structure/common/goto.rs index 29cc401..dfce980 100644 --- a/src/protocol/structure/common/goto.rs +++ b/src/protocol/structure/common/goto.rs @@ -1,20 +1,34 @@ +use crate::core::Server; use crate::protocol::PacketResult; +use actix::prelude::*; +use log::info; use serde::*; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Message)] +#[rtype(GotoPacketResponse)] pub struct GotoPacketRequest { // game_category: Option, // room: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, MessageResponse)] #[serde(rename_all = "kebab-case")] pub enum GotoPacketResponseError { IsRoomFull, Unknown, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, MessageResponse)] pub struct GotoPacketResponseOk {} pub type GotoPacketResponse = PacketResult; + +impl Handler for Server { + type Result = GotoPacketResponse; + + fn handle(&mut self, message: GotoPacketRequest, context: &mut Self::Context) -> Self::Result { + info!("Test {:?}", message); + + PacketResult::Ok(GotoPacketResponseOk {}) + } +} diff --git a/src/protocol/structure/common/mod.rs b/src/protocol/structure/common/mod.rs index 4f528ad..675e81f 100644 --- a/src/protocol/structure/common/mod.rs +++ b/src/protocol/structure/common/mod.rs @@ -1,8 +1,12 @@ +use crate::core::Server; +use actix::prelude::*; use packet_macro::packet; mod goto; -#[packet(namespace = "common")] +pub use goto::*; + +#[packet(namespace = "common", handler_target = "Server")] #[derive(Debug)] pub enum CommonPacket { #[packet(id = "goto")] diff --git a/src/websocket.rs b/src/websocket.rs index 2d553a7..0640dd9 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -73,7 +73,9 @@ impl StreamHandler> for WsSession { ws::Message::Pong(_) => { self.last_heartbeat = Instant::now(); } - ws::Message::Binary(_) => warn!("Unexpected Binary Data"), + ws::Message::Binary(_) => { + trace!("Unexpected binary data accepted from {}", self.id); + } ws::Message::Close(_) => { ctx.stop(); @@ -81,31 +83,7 @@ impl StreamHandler> for WsSession { ws::Message::Text(text) => { trace!("{}", &text); if let Ok(message) = serde_json::from_str::(&text) { - info!("{:?}", message); - // self.server.do_send(message); - /* - match message { - Message::Chat { text, to } => { - self.host.do_send(game::Chat { - id: self.id, - text, - to, - }); - } - Message::CreateRoom { room } => { - self.host.do_send(game::CreateRoom { id: self.id, room }); - } - Message::GetRoomDetail { room } => { - self.host.do_send(game::GetRoomDetail { id: self.id, room }); - } - Message::JoinRoom { room } => { - self.host.do_send(game::JoinRoom { id: self.id, room }); - } - Message::QuitRoom => { - self.host.do_send(game::QuitRoom { id: self.id }); - } - } - */ + self.server.do_send(message); } } ws::Message::Nop | ws::Message::Continuation(_) => {} From a0bcd366935f928709a04c5d72d71554044ca5dc Mon Sep 17 00:00:00 2001 From: RanolP Date: Sun, 5 Apr 2020 17:00:05 +0900 Subject: [PATCH 07/14] chore: Remove structure and handler directory --- src/protocol/{structure => }/common/goto.rs | 0 src/protocol/{structure => }/common/mod.rs | 0 src/protocol/mod.rs | 7 ++++--- src/protocol/packet.rs | 2 +- src/protocol/structure/mod.rs | 4 ---- src/protocol/structure/wordchain/mod.rs | 0 src/protocol/{handler => wordchain}/mod.rs | 0 7 files changed, 5 insertions(+), 8 deletions(-) rename src/protocol/{structure => }/common/goto.rs (100%) rename src/protocol/{structure => }/common/mod.rs (100%) delete mode 100644 src/protocol/structure/mod.rs delete mode 100644 src/protocol/structure/wordchain/mod.rs rename src/protocol/{handler => wordchain}/mod.rs (100%) diff --git a/src/protocol/structure/common/goto.rs b/src/protocol/common/goto.rs similarity index 100% rename from src/protocol/structure/common/goto.rs rename to src/protocol/common/goto.rs diff --git a/src/protocol/structure/common/mod.rs b/src/protocol/common/mod.rs similarity index 100% rename from src/protocol/structure/common/mod.rs rename to src/protocol/common/mod.rs diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 2b91ea0..3eacaea 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,8 +1,9 @@ -mod handler; +mod common; mod packet; mod result; -mod structure; +mod wordchain; +pub use common::*; pub use packet::{PacketClient, PacketServer}; pub use result::PacketResult; -pub use structure::*; +pub use wordchain::*; diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index 010cb42..6285c46 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -1,5 +1,5 @@ use crate::core::Server; -use crate::protocol::structure::*; +use crate::protocol::*; use actix::prelude::*; use serde::*; diff --git a/src/protocol/structure/mod.rs b/src/protocol/structure/mod.rs deleted file mode 100644 index 8251832..0000000 --- a/src/protocol/structure/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod common; -mod wordchain; - -pub use common::*; diff --git a/src/protocol/structure/wordchain/mod.rs b/src/protocol/structure/wordchain/mod.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/protocol/handler/mod.rs b/src/protocol/wordchain/mod.rs similarity index 100% rename from src/protocol/handler/mod.rs rename to src/protocol/wordchain/mod.rs From ed8f1e7d13442b378c4caf10f079c33ae1c7d6ef Mon Sep 17 00:00:00 2001 From: RanolP Date: Sun, 5 Apr 2020 17:21:36 +0900 Subject: [PATCH 08/14] fix: Remove clone from actors --- src/core/room.rs | 1 - src/core/server.rs | 2 +- src/core/session.rs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/room.rs b/src/core/room.rs index 0c77121..8be9529 100644 --- a/src/core/room.rs +++ b/src/core/room.rs @@ -1,2 +1 @@ -#[derive(Clone)] pub struct Room {} diff --git a/src/core/server.rs b/src/core/server.rs index bbc5e3f..c46de4d 100644 --- a/src/core/server.rs +++ b/src/core/server.rs @@ -2,7 +2,7 @@ use crate::core::{Room, RoomId, Session, UserId}; use actix::prelude::*; use std::collections::HashMap; -#[derive(Clone, Default)] +#[derive(Default)] pub struct Server { sessions: HashMap, rooms: HashMap, diff --git a/src/core/session.rs b/src/core/session.rs index a448254..523719d 100644 --- a/src/core/session.rs +++ b/src/core/session.rs @@ -2,7 +2,6 @@ use crate::core::{RoomId, UserId}; use crate::protocol::PacketServer; use actix::Recipient; -#[derive(Clone)] pub struct Session { id: UserId, pipe: Recipient, From e2c8c978379cce299077df42169ec254333a633c Mon Sep 17 00:00:00 2001 From: RanolP Date: Sun, 5 Apr 2020 17:35:14 +0900 Subject: [PATCH 09/14] fix: Move content of typealias.rs into mod.rs on core --- src/core/mod.rs | 5 +++-- src/core/typealias.rs | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 src/core/typealias.rs diff --git a/src/core/mod.rs b/src/core/mod.rs index 64502bd..53a295a 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,9 +1,10 @@ mod room; mod server; mod session; -mod typealias; pub use room::*; pub use server::*; pub use session::*; -pub use typealias::*; + +pub type UserId = usize; +pub type RoomId = usize; diff --git a/src/core/typealias.rs b/src/core/typealias.rs deleted file mode 100644 index f6b679c..0000000 --- a/src/core/typealias.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub type UserId = usize; -pub type RoomId = usize; From 5cb8375ea87a7d7f279ac99552d65e98be61630c Mon Sep 17 00:00:00 2001 From: RanolP Date: Sun, 5 Apr 2020 17:35:39 +0900 Subject: [PATCH 10/14] chore: Use better verb on trace message --- src/websocket.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/websocket.rs b/src/websocket.rs index 0640dd9..d92ff7d 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -74,7 +74,7 @@ impl StreamHandler> for WsSession { self.last_heartbeat = Instant::now(); } ws::Message::Binary(_) => { - trace!("Unexpected binary data accepted from {}", self.id); + trace!("Unexpected binary data received from {}", self.id); } ws::Message::Close(_) => { From af00621b2ada7081d31b55bbd4ecb4b33fe7339f Mon Sep 17 00:00:00 2001 From: RanolP Date: Sun, 5 Apr 2020 17:59:58 +0900 Subject: [PATCH 11/14] chore: Add missing actix module name --- packet-macro/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packet-macro/src/lib.rs b/packet-macro/src/lib.rs index d943526..5e23255 100644 --- a/packet-macro/src/lib.rs +++ b/packet-macro/src/lib.rs @@ -148,7 +148,7 @@ pub fn packet(args: TokenStream, item: TokenStream) -> TokenStream { #vis enum #client_name #generics { #(#variant_generated_client,)* } - impl Message for #client_name { + impl actix::Message for #client_name { type Result = #server_name; } From 611fe11db97ceceeefc838ce805456a2fbf7359e Mon Sep 17 00:00:00 2001 From: RanolP Date: Sun, 5 Apr 2020 18:05:19 +0900 Subject: [PATCH 12/14] refactor: Reconsideration about struct Server --- src/core/{server.rs => lobby.rs} | 7 +++---- src/core/mod.rs | 6 ++++-- src/core/room_manager.rs | 12 ++++++++++++ src/main.rs | 8 +++++--- src/protocol/common/goto.rs | 6 +++--- src/protocol/common/mod.rs | 5 ++--- src/protocol/packet.rs | 6 +++--- src/websocket.rs | 15 +++++++++------ 8 files changed, 41 insertions(+), 24 deletions(-) rename src/core/{server.rs => lobby.rs} (54%) create mode 100644 src/core/room_manager.rs diff --git a/src/core/server.rs b/src/core/lobby.rs similarity index 54% rename from src/core/server.rs rename to src/core/lobby.rs index c46de4d..dc953ae 100644 --- a/src/core/server.rs +++ b/src/core/lobby.rs @@ -1,13 +1,12 @@ -use crate::core::{Room, RoomId, Session, UserId}; +use crate::core::{Session, UserId}; use actix::prelude::*; use std::collections::HashMap; #[derive(Default)] -pub struct Server { +pub struct Lobby { sessions: HashMap, - rooms: HashMap, } -impl Actor for Server { +impl Actor for Lobby { type Context = Context; } diff --git a/src/core/mod.rs b/src/core/mod.rs index 53a295a..f4d06aa 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,9 +1,11 @@ +mod lobby; mod room; -mod server; +mod room_manager; mod session; +pub use lobby::*; pub use room::*; -pub use server::*; +pub use room_manager::*; pub use session::*; pub type UserId = usize; diff --git a/src/core/room_manager.rs b/src/core/room_manager.rs new file mode 100644 index 0000000..5370ef5 --- /dev/null +++ b/src/core/room_manager.rs @@ -0,0 +1,12 @@ +use crate::core::{Room, RoomId}; +use actix::prelude::*; +use std::collections::HashMap; + +#[derive(Default)] +pub struct RoomManager { + rooms: HashMap, +} + +impl Actor for RoomManager { + type Context = Context; +} diff --git a/src/main.rs b/src/main.rs index da9be9e..5a97884 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use actix_web::{dev, http, middleware, web, App, Error, HttpResponse, HttpServer use juniper::http::{playground::playground_source, GraphQLRequest}; use middleware::errhandlers::ErrorHandlerResponse; -use crate::core::Server; +use crate::core::{Lobby, RoomManager}; use crate::gql::{Context, Mutation, Query, Schema}; mod config; @@ -67,13 +67,15 @@ async fn main() -> io::Result<()> { let context = Arc::new(Context::new(pool)); let schema = Arc::new(Schema::new(Query, Mutation)); - let server = Server::default().start(); + let room_manager_addr = RoomManager::default().start(); + let lobby_addr = Lobby::default().start(); HttpServer::new(move || { App::new() .data(schema.clone()) .data(context.clone()) - .data(server.clone()) + .data(room_manager_addr.clone()) + .data(lobby_addr.clone()) .wrap(middleware::Logger::default()) .wrap(ErrorHandlers::new().handler(http::StatusCode::NOT_FOUND, render_404)) .service(web::resource("/graphql").route(web::post().to(graphql))) diff --git a/src/protocol/common/goto.rs b/src/protocol/common/goto.rs index dfce980..3b2c116 100644 --- a/src/protocol/common/goto.rs +++ b/src/protocol/common/goto.rs @@ -1,6 +1,6 @@ -use crate::core::Server; use crate::protocol::PacketResult; -use actix::prelude::*; +use crate::websocket::WsSession; +use actix::{Handler, Message, MessageResponse}; use log::info; use serde::*; @@ -23,7 +23,7 @@ pub struct GotoPacketResponseOk {} pub type GotoPacketResponse = PacketResult; -impl Handler for Server { +impl Handler for WsSession { type Result = GotoPacketResponse; fn handle(&mut self, message: GotoPacketRequest, context: &mut Self::Context) -> Self::Result { diff --git a/src/protocol/common/mod.rs b/src/protocol/common/mod.rs index 675e81f..161032d 100644 --- a/src/protocol/common/mod.rs +++ b/src/protocol/common/mod.rs @@ -1,12 +1,11 @@ -use crate::core::Server; -use actix::prelude::*; +use crate::websocket::WsSession; use packet_macro::packet; mod goto; pub use goto::*; -#[packet(namespace = "common", handler_target = "Server")] +#[packet(namespace = "common", handler_target = "WsSession")] #[derive(Debug)] pub enum CommonPacket { #[packet(id = "goto")] diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index 6285c46..e6a4d24 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -1,6 +1,6 @@ -use crate::core::Server; use crate::protocol::*; -use actix::prelude::*; +use crate::websocket::WsSession; +use actix::{Handler, Message, MessageResponse}; use serde::*; #[derive(Debug, Serialize, MessageResponse)] @@ -23,7 +23,7 @@ impl Message for PacketClient { type Result = PacketServer; } -impl Handler for Server { +impl Handler for WsSession { type Result = PacketServer; fn handle(&mut self, msg: PacketClient, ctx: &mut Self::Context) -> Self::Result { diff --git a/src/websocket.rs b/src/websocket.rs index d92ff7d..e088712 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -2,7 +2,7 @@ use actix::*; use actix_web::{web, Error, HttpRequest, HttpResponse}; use actix_web_actors::ws; -use crate::core::{Server, UserId}; +use crate::core::{Lobby, RoomManager, UserId}; use crate::protocol::{PacketClient, PacketServer}; use log::{info, trace, warn}; use std::time::{Duration, Instant}; @@ -10,10 +10,11 @@ use std::time::{Duration, Instant}; const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); -struct WsSession { +pub struct WsSession { id: UserId, last_heartbeat: Instant, - server: Addr, + lobby: Addr, + room_manager: Addr, } impl Actor for WsSession { @@ -83,7 +84,7 @@ impl StreamHandler> for WsSession { ws::Message::Text(text) => { trace!("{}", &text); if let Ok(message) = serde_json::from_str::(&text) { - self.server.do_send(message); + Handler::handle(self, message, ctx); } } ws::Message::Nop | ws::Message::Continuation(_) => {} @@ -110,13 +111,15 @@ impl WsSession { pub async fn ws( req: HttpRequest, stream: web::Payload, - server: web::Data>, + room_manager: web::Data>, + lobby: web::Data>, ) -> Result { ws::start( WsSession { id: 0, last_heartbeat: Instant::now(), - server: server.as_ref().clone(), + lobby: lobby.as_ref().clone(), + room_manager: room_manager.as_ref().clone(), }, &req, stream, From 2d7ded469a16ca8473c2e8b587284a1f1b26c431 Mon Sep 17 00:00:00 2001 From: RanolP Date: Sun, 5 Apr 2020 18:27:33 +0900 Subject: [PATCH 13/14] refactor: Use WsSession instead of Session --- src/core/lobby.rs | 5 +++-- src/core/mod.rs | 2 -- src/core/session.rs | 10 ---------- src/websocket.rs | 7 ++++++- 4 files changed, 9 insertions(+), 15 deletions(-) delete mode 100644 src/core/session.rs diff --git a/src/core/lobby.rs b/src/core/lobby.rs index dc953ae..3cd3367 100644 --- a/src/core/lobby.rs +++ b/src/core/lobby.rs @@ -1,10 +1,11 @@ -use crate::core::{Session, UserId}; +use crate::core::UserId; +use crate::websocket::WsSession; use actix::prelude::*; use std::collections::HashMap; #[derive(Default)] pub struct Lobby { - sessions: HashMap, + sessions: HashMap, } impl Actor for Lobby { diff --git a/src/core/mod.rs b/src/core/mod.rs index f4d06aa..f153485 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,12 +1,10 @@ mod lobby; mod room; mod room_manager; -mod session; pub use lobby::*; pub use room::*; pub use room_manager::*; -pub use session::*; pub type UserId = usize; pub type RoomId = usize; diff --git a/src/core/session.rs b/src/core/session.rs deleted file mode 100644 index 523719d..0000000 --- a/src/core/session.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::core::{RoomId, UserId}; -use crate::protocol::PacketServer; -use actix::Recipient; - -pub struct Session { - id: UserId, - pipe: Recipient, - room: Option, - name: String, -} diff --git a/src/websocket.rs b/src/websocket.rs index e088712..1262e1e 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -2,7 +2,7 @@ use actix::*; use actix_web::{web, Error, HttpRequest, HttpResponse}; use actix_web_actors::ws; -use crate::core::{Lobby, RoomManager, UserId}; +use crate::core::{Lobby, RoomId, RoomManager, UserId}; use crate::protocol::{PacketClient, PacketServer}; use log::{info, trace, warn}; use std::time::{Duration, Instant}; @@ -15,6 +15,8 @@ pub struct WsSession { last_heartbeat: Instant, lobby: Addr, room_manager: Addr, + room: Option, + name: String, } impl Actor for WsSession { @@ -115,11 +117,14 @@ pub async fn ws( lobby: web::Data>, ) -> Result { ws::start( + // TODO: WsSession을 초기화할 때 적절한 id 및 name 값으로 초기화해야 함. WsSession { id: 0, last_heartbeat: Instant::now(), lobby: lobby.as_ref().clone(), room_manager: room_manager.as_ref().clone(), + room: None, + name: "".to_owned(), }, &req, stream, From 8b5a7e27608365c6c0f8f0074cda97281f80a07f Mon Sep 17 00:00:00 2001 From: kiwiyou Date: Mon, 6 Apr 2020 01:34:50 +0900 Subject: [PATCH 14/14] feature: Implement basic functions --- src/core/lobby.rs | 49 +++++++++++++++++++++- src/core/room.rs | 90 +++++++++++++++++++++++++++++++++++++++- src/core/room_manager.rs | 82 +++++++++++++++++++++++++++++++++++- 3 files changed, 217 insertions(+), 4 deletions(-) diff --git a/src/core/lobby.rs b/src/core/lobby.rs index 3cd3367..288fdc8 100644 --- a/src/core/lobby.rs +++ b/src/core/lobby.rs @@ -3,11 +3,58 @@ use crate::websocket::WsSession; use actix::prelude::*; use std::collections::HashMap; +const MAX_SESSIONS: usize = 8; + #[derive(Default)] pub struct Lobby { - sessions: HashMap, + sessions: HashMap>, } impl Actor for Lobby { type Context = Context; } + +pub struct Connect { + pub user_id: UserId, + pub user_addr: Addr, +} + +pub enum ConnectError { + Full, + AlreadyConnected, +} + +impl Message for Connect { + type Result = Result<(), ConnectError>; +} + +impl Handler for Lobby { + type Result = ::Result; + + fn handle(&mut self, msg: Connect, _ctx: &mut Self::Context) -> Self::Result { + if self.sessions.contains_key(&msg.user_id) { + Err(ConnectError::AlreadyConnected) + } else if self.sessions.len() >= MAX_SESSIONS { + Err(ConnectError::Full) + } else { + self.sessions.insert(msg.user_id, msg.user_addr); + Ok(()) + } + } +} + +pub struct Disconnect { + pub user_id: UserId, +} + +impl Message for Disconnect { + type Result = (); +} + +impl Handler for Lobby { + type Result = ::Result; + + fn handle(&mut self, msg: Disconnect, _ctx: &mut Self::Context) -> Self::Result { + self.sessions.remove(&msg.user_id); + } +} diff --git a/src/core/room.rs b/src/core/room.rs index 8be9529..70c4758 100644 --- a/src/core/room.rs +++ b/src/core/room.rs @@ -1 +1,89 @@ -pub struct Room {} +use crate::core::{DestroyRoom, RoomId, RoomManager, UserId}; +use crate::websocket::WsSession; +use actix::prelude::*; +use std::collections::HashMap; + +pub struct Room { + manager: Addr, + id: RoomId, + capacity: usize, + player_order: Vec, + player_addr: HashMap>, +} + +impl Actor for Room { + type Context = Context; +} + +impl Room { + pub fn new( + manager: Addr, + id: RoomId, + capacity: usize, + owner_id: UserId, + owner_addr: Addr, + ) -> Self { + Self { + manager, + id, + capacity, + player_order: vec![owner_id], + player_addr: { + let mut map = HashMap::new(); + map.insert(owner_id, owner_addr); + map + }, + } + } +} + +pub struct JoinRoom { + pub user_id: UserId, + pub user_addr: Addr, +} + +pub enum JoinRoomError { + Full, + AlreadyJoined, +} + +impl Message for JoinRoom { + type Result = Result<(), JoinRoomError>; +} + +impl Handler for Room { + type Result = ::Result; + + fn handle(&mut self, msg: JoinRoom, _ctx: &mut Self::Context) -> Self::Result { + if self.player_order.len() >= self.capacity { + Err(JoinRoomError::Full) + } else if self.player_addr.contains_key(&msg.user_id) { + Err(JoinRoomError::AlreadyJoined) + } else { + self.player_order.push(msg.user_id); + self.player_addr.insert(msg.user_id, msg.user_addr); + Ok(()) + } + } +} + +pub struct QuitRoom { + pub user_id: UserId, +} + +impl Message for QuitRoom { + type Result = (); +} + +impl Handler for Room { + type Result = ::Result; + + fn handle(&mut self, msg: QuitRoom, _ctx: &mut Self::Context) -> Self::Result { + self.player_order.retain(|id| id != &msg.user_id); + self.player_addr.remove(&msg.user_id); + + if self.player_order.is_empty() { + self.manager.do_send(DestroyRoom { room_id: self.id }); + } + } +} diff --git a/src/core/room_manager.rs b/src/core/room_manager.rs index 5370ef5..7620488 100644 --- a/src/core/room_manager.rs +++ b/src/core/room_manager.rs @@ -1,12 +1,90 @@ -use crate::core::{Room, RoomId}; +use crate::core::{Room, RoomId, UserId}; +use crate::websocket::WsSession; use actix::prelude::*; use std::collections::HashMap; #[derive(Default)] pub struct RoomManager { - rooms: HashMap, + rooms: HashMap>, + id_seed: usize, } impl Actor for RoomManager { type Context = Context; } + +pub struct GetRoomList {} + +impl Message for GetRoomList { + type Result = Result, ()>; +} + +impl Handler for RoomManager { + type Result = ::Result; + + fn handle(&mut self, _msg: GetRoomList, _ctx: &mut Self::Context) -> Self::Result { + Ok(self.rooms.keys().copied().collect()) + } +} + +pub struct GetRoom { + pub id: RoomId, +} + +impl Message for GetRoom { + type Result = Option>; +} + +impl Handler for RoomManager { + type Result = ::Result; + + fn handle(&mut self, msg: GetRoom, _ctx: &mut Self::Context) -> Self::Result { + self.rooms.get(&msg.id).cloned() + } +} + +pub struct CreateRoom { + pub owner_id: UserId, + pub owner_addr: Addr, +} + +impl Message for CreateRoom { + type Result = Result, ()>; +} + +impl Handler for RoomManager { + type Result = ::Result; + + fn handle( + &mut self, + msg: CreateRoom, + ctx: &mut Self::Context, + ) -> ::Result { + if self.rooms.len() >= 1 { + Err(()) + } else { + let room_id = self.id_seed; + self.id_seed += 1; + let new_room = + Room::new(ctx.address(), room_id, 8, msg.owner_id, msg.owner_addr).start(); + self.rooms.insert(room_id, new_room.clone()); + Ok(new_room) + } + } +} + +pub struct DestroyRoom { + pub room_id: RoomId, +} + +impl Message for DestroyRoom { + type Result = (); +} + +impl Handler for RoomManager { + type Result = ::Result; + + fn handle(&mut self, msg: DestroyRoom, ctx: &mut Self::Context) -> Self::Result { + self.rooms.remove(&msg.room_id); + } +}