Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Structure actors and its packet handling #1

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
10 changes: 9 additions & 1 deletion packet-macro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
이렇게 적으면

```rust
#[packet(namespace = "common")]
#[packet(namespace = "common", handler_target = "Server")]
pub enum CommonPacket {
#[packet(id = "goto")]
Goto {
Expand Down Expand Up @@ -45,4 +45,12 @@ pub enum CommonPacketClient {
body: GotoRequestBody
},
}
impl Handler<CommonPacketClient> for Server
{
fn handle(&mut self, message: CommonPacketClient, ctx: &mut Self::Context) {
match message {
Goto { body } => self.handle(body, ctx),
}
}
}
```
68 changes: 57 additions & 11 deletions packet-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,50 @@
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<Attribute>,
}

#[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();
}
};

let item = parse_macro_input!(item as ItemEnum);

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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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)*
Expand All @@ -110,18 +121,53 @@ 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 = 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 {
#(#variant_generated_server,)*
}
#[derive(serde::Deserialize)]
#[serde(tag = "kind")]
#(#attrs)*
#vis enum #client_name #generics {
#(#variant_generated_client,)*
}
impl actix::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()
}
60 changes: 60 additions & 0 deletions src/core/lobby.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use crate::core::UserId;
use crate::websocket::WsSession;
use actix::prelude::*;
use std::collections::HashMap;

const MAX_SESSIONS: usize = 8;

#[derive(Default)]
pub struct Lobby {
sessions: HashMap<UserId, Addr<WsSession>>,
}

impl Actor for Lobby {
type Context = Context<Self>;
}

pub struct Connect {
pub user_id: UserId,
pub user_addr: Addr<WsSession>,
}

pub enum ConnectError {
Full,
AlreadyConnected,
}

impl Message for Connect {
type Result = Result<(), ConnectError>;
}

impl Handler<Connect> for Lobby {
type Result = <Connect as Message>::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<Disconnect> for Lobby {
type Result = <Disconnect as Message>::Result;

fn handle(&mut self, msg: Disconnect, _ctx: &mut Self::Context) -> Self::Result {
self.sessions.remove(&msg.user_id);
}
}
10 changes: 10 additions & 0 deletions src/core/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
mod lobby;
mod room;
mod room_manager;

pub use lobby::*;
pub use room::*;
pub use room_manager::*;

pub type UserId = usize;
pub type RoomId = usize;
89 changes: 89 additions & 0 deletions src/core/room.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use crate::core::{DestroyRoom, RoomId, RoomManager, UserId};
use crate::websocket::WsSession;
use actix::prelude::*;
use std::collections::HashMap;

pub struct Room {
manager: Addr<RoomManager>,
id: RoomId,
capacity: usize,
player_order: Vec<UserId>,
player_addr: HashMap<UserId, Addr<WsSession>>,
}

impl Actor for Room {
type Context = Context<Self>;
}

impl Room {
pub fn new(
manager: Addr<RoomManager>,
id: RoomId,
capacity: usize,
owner_id: UserId,
owner_addr: Addr<WsSession>,
) -> 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<WsSession>,
}

pub enum JoinRoomError {
Full,
AlreadyJoined,
}

impl Message for JoinRoom {
type Result = Result<(), JoinRoomError>;
}

impl Handler<JoinRoom> for Room {
type Result = <JoinRoom as Message>::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<QuitRoom> for Room {
type Result = <QuitRoom as Message>::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 });
}
}
}
Loading