From b8f1d7bc41fff502dacf69932746f474be9de923 Mon Sep 17 00:00:00 2001 From: dank_meme01 <42031238+dankmeme01@users.noreply.github.com> Date: Sun, 3 Dec 2023 16:56:58 +0100 Subject: [PATCH] derive macros for enums + more server work --- README.md | 2 +- server/Cargo.toml | 5 +- server/central/Cargo.toml | 2 +- server/game-derives/src/lib.rs | 172 +++++++++++++++--- server/game/Cargo.toml | 3 +- server/game/src/data/bytebufferext.rs | 111 ++++++----- .../src/data/packets/client/connection.rs | 14 +- server/game/src/data/packets/client/game.rs | 23 ++- server/game/src/data/packets/mod.rs | 2 +- .../src/data/packets/server/connection.rs | 14 +- server/game/src/data/packets/server/game.rs | 21 +-- server/game/src/data/types/audio_frame.rs | 14 +- server/game/src/data/types/cocos.rs | 6 +- server/game/src/data/types/common.rs | 76 ++++---- server/game/src/data/types/fast_string.rs | 39 ++-- server/game/src/data/types/gd.rs | 33 +++- server/game/src/server_thread/error.rs | 48 ++--- server/game/src/server_thread/handlers/mod.rs | 4 +- server/game/src/server_thread/mod.rs | 30 ++- server/game/src/util/channel.rs | 104 +++++++++++ server/game/src/util/mod.rs | 4 +- server/shared/Cargo.toml | 2 +- 22 files changed, 488 insertions(+), 241 deletions(-) create mode 100644 server/game/src/util/channel.rs diff --git a/README.md b/README.md index 11359554..8696c384 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ See the [server readme](./server/readme.md) for more information about the serve ## Credit -ca7x3, Firee, Croozington, Coloride, Cvolton, mat, alk, maki, xTymon - thank you for being awesome, whether it's because you helped me, suggested ideas, helped with testing, or if I just found you awesome in general :D +ca7x3, Firee, Croozington, Coloride, Cvolton, mat, alk, maki, xTymon - thank you for being awesome, whether it's because you helped me directly, suggested ideas, helped with testing, or if I just found you awesome in general :D camila314 - thank you for [UIBuilder](https://github.com/camila314/uibuilder) diff --git a/server/Cargo.toml b/server/Cargo.toml index 110a4d9c..eb053df4 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,11 +1,10 @@ [workspace] members = ["central", "game", "game-derives", "shared"] resolver = "2" - -[profile.release] # my observation thus far with LTO: # compile times -> ~100% increase # executable size -> ~30% decrease # performance -> too lazy to benchmark but probably a very minor improvement # so.. good enough to keep! -lto = "fat" +# TODO Bring back in release +# lto = "fat" diff --git a/server/central/Cargo.toml b/server/central/Cargo.toml index e1f82bf0..deb295c5 100644 --- a/server/central/Cargo.toml +++ b/server/central/Cargo.toml @@ -18,7 +18,7 @@ hmac = "0.12.1" rand = "0.8.5" reqwest = "0.11.22" roa = { version = "0.6.1", features = ["router"] } -serde = { version = "1.0.192", features = ["serde_derive"] } +serde = { version = "1.0.193", features = ["serde_derive"] } serde_json = "1.0.108" sha2 = "0.10.8" tokio = { version = "1.34.0", features = ["full"] } diff --git a/server/game-derives/src/lib.rs b/server/game-derives/src/lib.rs index 083c3280..d5de94ac 100644 --- a/server/game-derives/src/lib.rs +++ b/server/game-derives/src/lib.rs @@ -1,20 +1,15 @@ -/* -* proc macros for Encodable, Decodable, EncodableWithKnownSize, and Packet traits. -* Using the first three is as simple as `#[derive(Encodable, ...)]`, Packet is a bit more complex: -* #[derive(Packet)] -* #[packet(id = 10000, encrypted = false)] -* pub struct MyPacket {} -*/ - #![allow(clippy::missing_panics_doc)] use darling::FromDeriveInput; use proc_macro::{self, TokenStream}; -use quote::quote; -use syn::{parse_macro_input, Data, DeriveInput}; - -/* Encodable, EncodableWithKnownSize, and Decodable derive macros */ - +use quote::{quote, ToTokens}; +use syn::{parse_macro_input, punctuated::Punctuated, Data, DeriveInput, Meta, Token}; + +/// Implements `Encodable` for the given type. For `Encodable` to be successfully derived, +/// for structs, all of the members of the struct must also implement `Encodable`. +/// +/// For enums, the enum must have no associated data fields (only variants), and may have a +/// `#[repr(u*)]` or `#[repr(i*)]` attribute to indicate the encoded type. By default it will be `i32` if omitted. #[proc_macro_derive(Encodable)] pub fn derive_encodable(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -46,17 +41,37 @@ pub fn derive_encodable(input: TokenStream) -> TokenStream { } } } - _ => { - return TokenStream::from(quote! { - compile_error!("Encodable can only be derived for structs"); - }) + Data::Enum(_) => { + let repr_type = get_enum_repr_type(&input); + + quote! { + impl Encodable for #struct_name { + fn encode(&self, buf: &mut bytebuffer::ByteBuffer) { + buf.write_value(&(*self as #repr_type)) + } + + fn encode_fast(&self, buf: &mut crate::data::FastByteBuffer) { + buf.write_value(&(*self as #repr_type)) + } + } + } + } + Data::Union(_) => { + return quote! { + compile_error!("Encodable cannot be derived for unions"); + } + .into() } }; gen.into() } -#[proc_macro_derive(EncodableWithKnownSize)] +/// Implements `KnownSize` for the given type. For `KnownSize` to be successfully derived, +/// for structs, all of the members of the struct must also implement `KnownSize`. +/// +/// For enums, all the same limitations apply as in `Encodable`. +#[proc_macro_derive(KnownSize)] pub fn derive_known_size(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -74,15 +89,23 @@ pub fn derive_known_size(input: TokenStream) -> TokenStream { size_of_types!(#(#field_types),*) } } - _ => { - return TokenStream::from(quote! { - compile_error!("EncodableWithKnownSize can only be derived for structs"); - }); + Data::Enum(_) => { + let repr_type = get_enum_repr_type(&input); + + quote! { + std::mem::size_of::<#repr_type>() + } + } + Data::Union(_) => { + return quote! { + compile_error!("KnownSize cannot be drived for unions"); + } + .into(); } }; let gen = quote! { - impl EncodableWithKnownSize for #struct_name { + impl KnownSize for #struct_name { const ENCODED_SIZE: usize = #encoded_size; } }; @@ -91,6 +114,10 @@ pub fn derive_known_size(input: TokenStream) -> TokenStream { gen.into() } +/// Implements `Decodable` for the given type. For `Decodable` to be successfully derived, +/// for structs, all of the members of the struct must also implement `Decodable`. +/// +/// For enums, all the same limitations apply as in `Encodable` plus the enum must have explicitly specified values for all variants. #[proc_macro_derive(Decodable)] pub fn derive_decodable(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -114,7 +141,7 @@ pub fn derive_decodable(input: TokenStream) -> TokenStream { quote! { impl Decodable for #struct_name { - fn decode(buf: &mut bytebuffer::ByteBuffer) -> anyhow::Result { + fn decode(buf: &mut bytebuffer::ByteBuffer) -> crate::data::DecodeResult { #(#decode_fields)* Ok(Self { #( @@ -123,7 +150,7 @@ pub fn derive_decodable(input: TokenStream) -> TokenStream { }) } - fn decode_from_reader(buf: &mut bytebuffer::ByteReader) -> anyhow::Result { + fn decode_from_reader(buf: &mut bytebuffer::ByteReader) -> crate::data::DecodeResult { #(#decode_fields)* Ok(Self { #( @@ -134,17 +161,94 @@ pub fn derive_decodable(input: TokenStream) -> TokenStream { } } } - _ => { - return TokenStream::from(quote! { - compile_error!("Decodable can only be derived for structs"); - }) + Data::Enum(data) => { + let repr_type = get_enum_repr_type(&input); + + let mut err_flag: bool = false; + let decode_variants: Vec<_> = data + .variants + .iter() + .map(|variant| { + let ident = &variant.ident; + let variant_repr = match &variant.discriminant { + Some((_, expr)) => { + quote! { #expr } + } + None => { + err_flag = true; + quote! { 0 } + } + }; + quote! { + #variant_repr => Ok(#struct_name::#ident), + } + }) + .collect(); + + if err_flag { + return quote! { + compile_error!("Decodable currently cannot be derived for enums without explicitly specified discriminants"); + } + .into(); + } + + quote! { + impl Decodable for #struct_name { + fn decode(buf: &mut bytebuffer::ByteBuffer) -> crate::data::DecodeResult { + let value: #repr_type = buf.read_value()?; + match value { + #(#decode_variants)* + _ => Err(crate::data::DecodeError::InvalidEnumValue) + } + } + + fn decode_from_reader(buf: &mut bytebuffer::ByteReader) -> crate::data::DecodeResult { + let value: #repr_type = buf.read_value()?; + match value { + #(#decode_variants)* + _ => Err(crate::data::DecodeError::InvalidEnumValue) + } + } + } + } + } + Data::Union(_) => { + return quote! { + compile_error!("Decodable cannot be drived for unions"); + } + .into(); } }; gen.into() } -/* #[packet()] implementation */ +fn get_enum_repr_type(input: &DeriveInput) -> proc_macro2::TokenStream { + let mut repr_type: Option = None; + for attr in &input.attrs { + if attr.path().is_ident("repr") { + let nested = attr.parse_args_with(Punctuated::::parse_terminated).unwrap(); + for meta in nested { + match meta { + Meta::Path(path) => { + if let Some(ident) = path.get_ident() { + repr_type = Some(ident.to_token_stream()); + } + } + _ => { + return TokenStream::from(quote! { + compile_error!("unrecognized repr attribute"); + }) + .into(); + } + } + } + } + } + + // assume i32 by default + repr_type.unwrap_or(quote! { i32 }) +} #[derive(FromDeriveInput)] #[darling(attributes(packet))] @@ -153,6 +257,14 @@ struct PacketAttributes { encrypted: bool, } +/// Implements `Packet`, `PacketMetadata` and the function `header() -> PacketHeader` for the given struct. +/// You must also pass additional attributes with `#[packet]`, specifically packet ID and whether the packet should be encrypted. +/// Example: +/// ```rust +/// #[derive(Packet, Encodable, Decodable)] +/// #[packet(id = 10000, encrypted = false)] +/// pub struct MyPacket { /* ... */ } +/// ``` #[proc_macro_derive(Packet, attributes(packet))] pub fn packet(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); diff --git a/server/game/Cargo.toml b/server/game/Cargo.toml index c5465057..04358e87 100644 --- a/server/game/Cargo.toml +++ b/server/game/Cargo.toml @@ -14,10 +14,9 @@ anyhow = "1.0.75" array-init = "2.1.0" bytebuffer = "2.2.0" crypto_box = { version = "0.9.1", features = ["std", "chacha20"] } -num_enum = "0.7.1" parking_lot = "0.12.1" reqwest = "0.11.22" rustc-hash = "1.1.0" -serde = { version = "1.0.192", features = ["serde_derive"] } +serde = { version = "1.0.193", features = ["serde_derive"] } serde_json = "1.0.108" tokio = { version = "1.34.0", features = ["full"] } diff --git a/server/game/src/data/bytebufferext.rs b/server/game/src/data/bytebufferext.rs index f8772acd..06e63d6b 100644 --- a/server/game/src/data/bytebufferext.rs +++ b/server/game/src/data/bytebufferext.rs @@ -1,25 +1,53 @@ +use std::fmt::Display; + use crate::data::{ packets::{PacketHeader, PacketMetadata}, types::cocos, }; -use anyhow::{anyhow, Result}; use bytebuffer::{ByteBuffer, ByteReader}; +#[derive(Debug)] +pub enum DecodeError { + NotEnoughData, + NotEnoughCapacityString, + InvalidEnumValue, + InvalidStringValue, +} + +impl Display for DecodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::NotEnoughData => f.write_str("could not read enough bytes from the ByteBuffer"), + Self::NotEnoughCapacityString => f.write_str("not enough capacity to fit the given string into a FastString"), + Self::InvalidEnumValue => f.write_str("invalid enum value was passed"), + Self::InvalidStringValue => f.write_str("invalid string was passed, likely not properly UTF-8 encoded"), + } + } +} + +impl From for DecodeError { + fn from(_: std::io::Error) -> Self { + DecodeError::NotEnoughData + } +} + +pub type DecodeResult = core::result::Result; + pub trait Encodable { fn encode(&self, buf: &mut ByteBuffer); fn encode_fast(&self, buf: &mut FastByteBuffer); } pub trait Decodable { - fn decode(buf: &mut ByteBuffer) -> Result + fn decode(buf: &mut ByteBuffer) -> DecodeResult where Self: Sized; - fn decode_from_reader(buf: &mut ByteReader) -> Result + fn decode_from_reader(buf: &mut ByteReader) -> DecodeResult where Self: Sized; } -pub trait EncodableWithKnownSize: Encodable { +pub trait KnownSize { /// For dynamically sized types, this must be the maximum permitted size in the encoded form. /// If `FastByteBuffer::write` tries to write more bytes than this value, it may panic. const ENCODED_SIZE: usize; @@ -48,12 +76,12 @@ pub const MAX_PROFILES_REQUESTED: usize = 128; /// ``` macro_rules! decode_impl { ($typ:ty, $buf:ident, $decode:expr) => { - impl crate::data::bytebufferext::Decodable for $typ { - fn decode($buf: &mut bytebuffer::ByteBuffer) -> anyhow::Result { + impl crate::data::Decodable for $typ { + fn decode($buf: &mut bytebuffer::ByteBuffer) -> crate::data::DecodeResult { $decode } - fn decode_from_reader($buf: &mut bytebuffer::ByteReader) -> anyhow::Result { + fn decode_from_reader($buf: &mut bytebuffer::ByteReader) -> crate::data::DecodeResult { $decode } } @@ -74,19 +102,19 @@ macro_rules! decode_impl { /// ``` macro_rules! encode_impl { ($typ:ty, $buf:ident, $self:ident, $encode:expr) => { - impl crate::data::bytebufferext::Encodable for $typ { + impl crate::data::Encodable for $typ { fn encode(&$self, $buf: &mut bytebuffer::ByteBuffer) { $encode } - fn encode_fast(&$self, $buf: &mut crate::data::bytebufferext::FastByteBuffer) { + fn encode_fast(&$self, $buf: &mut crate::data::FastByteBuffer) { $encode } } }; } -/// Simple and compact way of implementing `EncodableWithKnownSize`. +/// Simple and compact way of implementing `KnownSize`. /// /// Example usage: /// ```rust @@ -98,7 +126,7 @@ macro_rules! encode_impl { /// ``` macro_rules! size_calc_impl { ($typ:ty, $calc:expr) => { - impl crate::data::bytebufferext::EncodableWithKnownSize for $typ { + impl crate::data::bytebufferext::KnownSize for $typ { const ENCODED_SIZE: usize = $calc; } }; @@ -116,7 +144,7 @@ macro_rules! size_of_primitives { }}; } -/// Simple way of getting total (maximum) encoded size of given types that implement `Encodable` and `EncodableWithKnownSize` +/// Simple way of getting total (maximum) encoded size of given types that implement `Encodable` and `KnownSize` /// /// Example usage: /// ```rust @@ -158,7 +186,6 @@ pub trait ByteBufferExtWrite { /// write a `Vec`, prefixed with 4 bytes indicating the amount of values fn write_value_vec(&mut self, val: &[T]); - fn write_enum, B: Encodable>(&mut self, val: E); fn write_packet_header(&mut self); fn write_color3(&mut self, val: cocos::Color3B); @@ -168,30 +195,29 @@ pub trait ByteBufferExtWrite { pub trait ByteBufferExtRead { /// alias to `read_value` - fn read(&mut self) -> Result { + fn read(&mut self) -> DecodeResult { self.read_value() } - fn read_bool(&mut self) -> Result; + fn read_bool(&mut self) -> DecodeResult; /// read a byte vector, prefixed with 4 bytes indicating length - fn read_byte_array(&mut self) -> Result>; + fn read_byte_array(&mut self) -> DecodeResult>; /// read the remaining data into a Vec - fn read_remaining_bytes(&mut self) -> Result>; + fn read_remaining_bytes(&mut self) -> DecodeResult>; - fn read_value(&mut self) -> Result; - fn read_optional_value(&mut self) -> Result>; + fn read_value(&mut self) -> DecodeResult; + fn read_optional_value(&mut self) -> DecodeResult>; /// read an array `[T; N]`, size must be known at compile time - fn read_value_array(&mut self) -> Result<[T; N]>; + fn read_value_array(&mut self) -> DecodeResult<[T; N]>; /// read a `Vec` - fn read_value_vec(&mut self) -> Result>; + fn read_value_vec(&mut self) -> DecodeResult>; - fn read_enum, B: Decodable>(&mut self) -> Result; - fn read_packet_header(&mut self) -> Result; + fn read_packet_header(&mut self) -> DecodeResult; - fn read_color3(&mut self) -> Result; - fn read_color4(&mut self) -> Result; - fn read_point(&mut self) -> Result; + fn read_color3(&mut self) -> DecodeResult; + fn read_color4(&mut self) -> DecodeResult; + fn read_point(&mut self) -> DecodeResult; } /// Buffer for encoding that does zero heap allocation but also has limited functionality. @@ -344,10 +370,6 @@ macro_rules! impl_extwrite { } } - fn write_enum, B: Encodable>(&mut self, val: E) { - self.write_value(&val.into()); - } - fn write_packet_header(&mut self) { self.write_value(&PacketHeader::from_packet::()); } @@ -368,16 +390,16 @@ macro_rules! impl_extwrite { macro_rules! impl_extread { ($decode_fn:ident) => { - fn read_bool(&mut self) -> Result { + fn read_bool(&mut self) -> DecodeResult { Ok(self.read_u8()? != 0u8) } - fn read_byte_array(&mut self) -> Result> { + fn read_byte_array(&mut self) -> DecodeResult> { let length = self.read_u32()? as usize; Ok(self.read_bytes(length)?) } - fn read_remaining_bytes(&mut self) -> Result> { + fn read_remaining_bytes(&mut self) -> DecodeResult> { let remainder = self.len() - self.get_rpos(); let mut data = Vec::with_capacity(remainder); @@ -392,22 +414,22 @@ macro_rules! impl_extread { Ok(data) } - fn read_value(&mut self) -> Result { + fn read_value(&mut self) -> DecodeResult { T::$decode_fn(self) } - fn read_optional_value(&mut self) -> Result> { + fn read_optional_value(&mut self) -> DecodeResult> { Ok(match self.read_bool()? { false => None, true => Some(self.read_value::()?), }) } - fn read_value_array(&mut self) -> Result<[T; N]> { + fn read_value_array(&mut self) -> DecodeResult<[T; N]> { array_init::try_array_init(|_| self.read_value::()) } - fn read_value_vec(&mut self) -> Result> { + fn read_value_vec(&mut self) -> DecodeResult> { let mut out = Vec::new(); let length = self.read_u32()? as usize; for _ in 0..length { @@ -417,26 +439,19 @@ macro_rules! impl_extread { Ok(out) } - fn read_enum, B: Decodable>(&mut self) -> Result { - let val = self.read_value::()?; - let val: Result = val.try_into(); - - val.map_err(|_| anyhow!("failed to decode enum")) - } - - fn read_packet_header(&mut self) -> Result { + fn read_packet_header(&mut self) -> DecodeResult { self.read_value() } - fn read_color3(&mut self) -> Result { + fn read_color3(&mut self) -> DecodeResult { self.read_value() } - fn read_color4(&mut self) -> Result { + fn read_color4(&mut self) -> DecodeResult { self.read_value() } - fn read_point(&mut self) -> Result { + fn read_point(&mut self) -> DecodeResult { self.read_value() } }; diff --git a/server/game/src/data/packets/client/connection.rs b/server/game/src/data/packets/client/connection.rs index d9a75272..06005ff2 100644 --- a/server/game/src/data/packets/client/connection.rs +++ b/server/game/src/data/packets/client/connection.rs @@ -1,24 +1,24 @@ use crate::data::*; -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 10000, encrypted = false)] pub struct PingPacket { pub id: u32, } -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 10001, encrypted = false)] pub struct CryptoHandshakeStartPacket { pub protocol: u16, pub key: PublicKey, } -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 10002, encrypted = false)] -pub struct KeepalivePacket {} +pub struct KeepalivePacket; pub const MAX_TOKEN_SIZE: usize = 164; -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 10003, encrypted = true)] pub struct LoginPacket { pub account_id: i32, @@ -26,6 +26,6 @@ pub struct LoginPacket { pub token: FastString, } -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 10004, encrypted = false)] -pub struct DisconnectPacket {} +pub struct DisconnectPacket; diff --git a/server/game/src/data/packets/client/game.rs b/server/game/src/data/packets/client/game.rs index 0eedcb51..24b483dd 100644 --- a/server/game/src/data/packets/client/game.rs +++ b/server/game/src/data/packets/client/game.rs @@ -1,51 +1,50 @@ use crate::data::*; -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 11000, encrypted = false)] pub struct SyncIconsPacket { pub icons: PlayerIconData, } -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 11001, encrypted = false)] pub struct RequestProfilesPacket { pub ids: [i32; MAX_PROFILES_REQUESTED], } -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 11002, encrypted = false)] pub struct LevelJoinPacket { pub level_id: i32, } -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 11003, encrypted = false)] -pub struct LevelLeavePacket {} +pub struct LevelLeavePacket; -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 11004, encrypted = false)] pub struct PlayerDataPacket { pub data: PlayerData, } -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 11005, encrypted = false)] -pub struct RequestPlayerListPacket {} +pub struct RequestPlayerListPacket; -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 11006, encrypted = false)] pub struct SyncPlayerMetadataPacket { pub data: PlayerMetadata, } -#[derive(Packet, Encodable, Decodable)] +#[derive(Packet, Decodable)] #[packet(id = 11010, encrypted = true)] pub struct VoicePacket { pub data: FastEncodedAudioFrame, } -/* ChatMessagePacket - 11011 */ -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Decodable)] #[packet(id = 11011, encrypted = true)] pub struct ChatMessagePacket { pub message: FastString, diff --git a/server/game/src/data/packets/mod.rs b/server/game/src/data/packets/mod.rs index ff3f99a6..cbefde62 100644 --- a/server/game/src/data/packets/mod.rs +++ b/server/game/src/data/packets/mod.rs @@ -8,7 +8,7 @@ use crate::data::bytebufferext::*; type PacketId = u16; -pub trait Packet: Encodable + Decodable + Send + Sync + PacketMetadata { +pub trait Packet: Send + Sync + PacketMetadata { fn get_packet_id(&self) -> PacketId; fn get_encrypted(&self) -> bool; } diff --git a/server/game/src/data/packets/server/connection.rs b/server/game/src/data/packets/server/connection.rs index 11775d98..971711f3 100644 --- a/server/game/src/data/packets/server/connection.rs +++ b/server/game/src/data/packets/server/connection.rs @@ -1,44 +1,44 @@ use crate::data::*; -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Encodable, KnownSize)] #[packet(id = 20000, encrypted = false)] pub struct PingResponsePacket { pub id: u32, pub player_count: u32, } -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Encodable, KnownSize)] #[packet(id = 20001, encrypted = false)] pub struct CryptoHandshakeResponsePacket { pub key: PublicKey, } -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Encodable, KnownSize)] #[packet(id = 20002, encrypted = false)] pub struct KeepaliveResponsePacket { pub player_count: u32, } -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Encodable, KnownSize)] #[packet(id = 20003, encrypted = false)] pub struct ServerDisconnectPacket { pub message: FastString, } -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Encodable, KnownSize)] #[packet(id = 20004, encrypted = false)] pub struct LoggedInPacket { pub tps: u32, } -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Encodable, KnownSize)] #[packet(id = 20005, encrypted = false)] pub struct LoginFailedPacket { pub message: FastString, } // used to communicate a simple message to the user -#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Packet, Encodable, KnownSize)] #[packet(id = 20006, encrypted = false)] pub struct ServerNoticePacket { pub message: FastString, diff --git a/server/game/src/data/packets/server/game.rs b/server/game/src/data/packets/server/game.rs index db76cf92..c8bd8582 100644 --- a/server/game/src/data/packets/server/game.rs +++ b/server/game/src/data/packets/server/game.rs @@ -1,6 +1,6 @@ use crate::data::*; -#[derive(Packet, Encodable, Decodable)] +#[derive(Packet, Encodable)] #[packet(id = 21000, encrypted = false)] pub struct PlayerProfilesPacket { pub message: Vec, @@ -10,30 +10,29 @@ pub struct PlayerProfilesPacket { * PlayerListPacket - 21002 * PlayerMetadataPacket - 21003 * -* For optimization reasons, these 3 cannot be dynamically dispatched, and are not defined here. -* They are encoded inline in the packet handlers in server_thread/handlers/game.rs. +* For optimization reasons, these 3 are encoded inline in the packet handlers in server_thread/handlers/game.rs. */ -#[derive(Packet, Encodable, Decodable)] +#[derive(Packet, Encodable)] #[packet(id = 21001, encrypted = false)] -pub struct LevelDataPacket {} +pub struct LevelDataPacket; -#[derive(Packet, Encodable, Decodable)] +#[derive(Packet, Encodable)] #[packet(id = 21002, encrypted = false)] -pub struct PlayerListPacket {} +pub struct PlayerListPacket; -#[derive(Packet, Encodable, Decodable)] +#[derive(Packet, Encodable)] #[packet(id = 21003, encrypted = false)] -pub struct PlayerMetadataPacket {} +pub struct PlayerMetadataPacket; -#[derive(Packet, Encodable, Decodable)] +#[derive(Packet, Encodable)] #[packet(id = 21010, encrypted = true)] pub struct VoiceBroadcastPacket { pub player_id: i32, pub data: FastEncodedAudioFrame, } -#[derive(Clone, Packet, Encodable, EncodableWithKnownSize, Decodable)] +#[derive(Clone, Packet, Encodable, KnownSize)] #[packet(id = 21011, encrypted = true)] pub struct ChatMessageBroadcastPacket { pub player_id: i32, diff --git a/server/game/src/data/types/audio_frame.rs b/server/game/src/data/types/audio_frame.rs index 65dbced0..c4e50d48 100644 --- a/server/game/src/data/types/audio_frame.rs +++ b/server/game/src/data/types/audio_frame.rs @@ -10,17 +10,7 @@ pub struct EncodedAudioFrame { } /// `FastEncodedAudioFrame` requires just one heap allocation as opposed to 10. -#[derive(Clone)] +#[derive(Clone, Encodable, Decodable)] pub struct FastEncodedAudioFrame { - pub data: Vec, + pub data: RemainderBytes, } - -encode_impl!(FastEncodedAudioFrame, buf, self, { - buf.write_bytes(&self.data); -}); - -decode_impl!(FastEncodedAudioFrame, buf, { - Ok(Self { - data: buf.read_remaining_bytes()?, - }) -}); diff --git a/server/game/src/data/types/cocos.rs b/server/game/src/data/types/cocos.rs index f3197986..416e1146 100644 --- a/server/game/src/data/types/cocos.rs +++ b/server/game/src/data/types/cocos.rs @@ -26,7 +26,7 @@ impl From for ColorParseError { } } -#[derive(Copy, Clone, Default, Encodable, Decodable, EncodableWithKnownSize)] +#[derive(Copy, Clone, Default, Encodable, Decodable, KnownSize)] pub struct Color3B { pub r: u8, pub g: u8, @@ -53,7 +53,7 @@ impl FromStr for Color3B { } } -#[derive(Copy, Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] +#[derive(Copy, Clone, Default, Encodable, KnownSize, Decodable)] pub struct Color4B { pub r: u8, pub g: u8, @@ -86,7 +86,7 @@ impl FromStr for Color4B { } } -#[derive(Copy, Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] +#[derive(Copy, Clone, Default, Encodable, KnownSize, Decodable)] pub struct Point { pub x: f32, pub y: f32, diff --git a/server/game/src/data/types/common.rs b/server/game/src/data/types/common.rs index 10768506..66e680c3 100644 --- a/server/game/src/data/types/common.rs +++ b/server/game/src/data/types/common.rs @@ -1,5 +1,4 @@ -use anyhow::Result; -use std::io::Read; +use std::{io::Read, ops::Deref}; use bytebuffer::{ByteBuffer, ByteReader}; pub use crypto_box::{PublicKey, KEY_SIZE}; @@ -52,9 +51,9 @@ where } } -impl EncodableWithKnownSize for Option +impl KnownSize for Option where - T: EncodableWithKnownSize, + T: KnownSize, { const ENCODED_SIZE: usize = size_of_types!(bool, T); } @@ -63,14 +62,14 @@ impl Decodable for Option where T: Decodable, { - fn decode(buf: &mut ByteBuffer) -> Result + fn decode(buf: &mut ByteBuffer) -> DecodeResult where Self: Sized, { buf.read_optional_value() } - fn decode_from_reader(buf: &mut ByteReader) -> Result + fn decode_from_reader(buf: &mut ByteReader) -> DecodeResult where Self: Sized, { @@ -93,9 +92,9 @@ where } } -impl EncodableWithKnownSize for [T; N] +impl KnownSize for [T; N] where - T: EncodableWithKnownSize, + T: KnownSize, { const ENCODED_SIZE: usize = size_of_types!(T) * N; } @@ -104,14 +103,14 @@ impl Decodable for [T; N] where T: Decodable, { - fn decode(buf: &mut ByteBuffer) -> Result + fn decode(buf: &mut ByteBuffer) -> DecodeResult where Self: Sized, { buf.read_value_array() } - fn decode_from_reader(buf: &mut ByteReader) -> Result + fn decode_from_reader(buf: &mut ByteReader) -> DecodeResult where Self: Sized, { @@ -138,14 +137,14 @@ impl Decodable for Vec where T: Decodable, { - fn decode(buf: &mut ByteBuffer) -> Result + fn decode(buf: &mut ByteBuffer) -> DecodeResult where Self: Sized, { buf.read_value_vec() } - fn decode_from_reader(buf: &mut ByteReader) -> Result + fn decode_from_reader(buf: &mut ByteReader) -> DecodeResult where Self: Sized, { @@ -155,35 +154,40 @@ where /* crypto_box::PublicKey */ -impl Encodable for PublicKey { - fn encode(&self, buf: &mut bytebuffer::ByteBuffer) { - buf.write_bytes(self.as_bytes()); - } +encode_impl!(PublicKey, buf, self, { + buf.write_bytes(self.as_bytes()); +}); - fn encode_fast(&self, buf: &mut FastByteBuffer) { - buf.write_bytes(self.as_bytes()); - } -} +size_calc_impl!(PublicKey, KEY_SIZE); + +decode_impl!(PublicKey, buf, { + let mut key = [0u8; KEY_SIZE]; + buf.read_exact(&mut key)?; + + Ok(Self::from_bytes(key)) +}); + +/* RemainderBytes - wrapper around Vec that decodes with `buf.read_remaining_bytes()` and encodes with `buf.write_bytes()` */ -impl EncodableWithKnownSize for PublicKey { - const ENCODED_SIZE: usize = KEY_SIZE; +#[derive(Clone)] +#[repr(transparent)] +pub struct RemainderBytes { + data: Vec, } -impl Decodable for PublicKey { - fn decode(buf: &mut bytebuffer::ByteBuffer) -> anyhow::Result - where - Self: Sized, - { - Self::decode_from_reader(&mut ByteReader::from_bytes(buf.as_bytes())) - } +encode_impl!(RemainderBytes, buf, self, { + buf.write_bytes(&self.data); +}); - fn decode_from_reader(buf: &mut ByteReader) -> anyhow::Result - where - Self: Sized, - { - let mut key = [0u8; KEY_SIZE]; - buf.read_exact(&mut key)?; +decode_impl!(RemainderBytes, buf, { + Ok(Self { + data: buf.read_remaining_bytes()?, + }) +}); - Ok(Self::from_bytes(key)) +impl Deref for RemainderBytes { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.data } } diff --git a/server/game/src/data/types/fast_string.rs b/server/game/src/data/types/fast_string.rs index 92685424..56325dd6 100644 --- a/server/game/src/data/types/fast_string.rs +++ b/server/game/src/data/types/fast_string.rs @@ -1,8 +1,9 @@ -use anyhow::{bail, Result}; use bytebuffer::{ByteBuffer, ByteReader}; -use std::fmt; +use std::{fmt, str::Utf8Error}; use crate::data::bytebufferext::*; +#[allow(unused_imports)] +use globed_shared::logger::*; /// `FastString` is a string class that doesn't use heap allocation like `String` but also owns the string (unlike `&str`). /// When encoding or decoding into a byte buffer of any kind, the encoded form is identical to a normal `String`, @@ -82,11 +83,11 @@ impl FastString { } } - pub fn to_string(&self) -> Result { + pub fn to_string(&self) -> Result { Ok(self.to_str()?.to_owned()) } - pub fn to_str(&self) -> Result<&str> { + pub fn to_str(&self) -> Result<&str, Utf8Error> { std::str::from_utf8(&self.buffer[..self.len]).map_err(Into::into) } } @@ -103,25 +104,28 @@ impl Encodable for FastString { } } -impl EncodableWithKnownSize for FastString { +impl KnownSize for FastString { const ENCODED_SIZE: usize = size_of_types!(u32) + N; } impl Decodable for FastString { - fn decode(buf: &mut ByteBuffer) -> Result + fn decode(buf: &mut ByteBuffer) -> DecodeResult where Self: Sized, { Self::decode_from_reader(&mut ByteReader::from_bytes(buf.as_bytes())) } - fn decode_from_reader(buf: &mut ByteReader) -> Result + fn decode_from_reader(buf: &mut ByteReader) -> DecodeResult where Self: Sized, { let len = buf.read_u32()? as usize; if len > N { - bail!("string is too long ({len} chars) to fit into a FastString with capacity {N}"); + #[cfg(debug_assertions)] + warn!("string is too long ({len} chars) to fit into a FastString with capacity {N}"); + + return Err(DecodeError::NotEnoughCapacityString); } let mut buffer = [0u8; N]; @@ -138,27 +142,30 @@ impl fmt::Display for FastString { } impl TryInto for FastString { - type Error = anyhow::Error; - fn try_into(self) -> anyhow::Result { - self.to_string() + type Error = DecodeError; + fn try_into(self) -> DecodeResult { + self.to_string().map_err(|_| DecodeError::InvalidStringValue) } } impl TryFrom for FastString { - type Error = anyhow::Error; - fn try_from(value: String) -> anyhow::Result { + type Error = DecodeError; + fn try_from(value: String) -> DecodeResult { TryFrom::<&str>::try_from(&value) } } impl TryFrom<&str> for FastString { - type Error = anyhow::Error; - fn try_from(value: &str) -> anyhow::Result { + type Error = DecodeError; + fn try_from(value: &str) -> DecodeResult { if value.len() > N { - bail!( + #[cfg(debug_assertions)] + warn!( "Attempting to convert a string slice with size {} into a FastString with capacity {N}", value.len() ); + + return Err(DecodeError::NotEnoughCapacityString); } Ok(Self::from_str(value)) diff --git a/server/game/src/data/types/gd.rs b/server/game/src/data/types/gd.rs index 1db2be2a..d861be2e 100644 --- a/server/game/src/data/types/gd.rs +++ b/server/game/src/data/types/gd.rs @@ -4,7 +4,7 @@ use crate::data::*; use super::{Color3B, ColorParseError}; -#[derive(Clone, Encodable, EncodableWithKnownSize, Decodable)] +#[derive(Clone, Encodable, KnownSize, Decodable)] pub struct PlayerIconData { pub cube: i16, pub ship: i16, @@ -48,7 +48,7 @@ impl PlayerIconData { /* SpecialUserData */ -#[derive(Clone, Encodable, EncodableWithKnownSize, Decodable)] +#[derive(Clone, Encodable, KnownSize, Decodable)] pub struct SpecialUserData { pub name_color: Color3B, } @@ -72,7 +72,7 @@ impl TryFrom for SpecialUserData { /* PlayerAccountData */ -#[derive(Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] +#[derive(Clone, Default, Encodable, KnownSize, Decodable)] pub struct PlayerAccountData { pub account_id: i32, pub name: FastString, @@ -95,7 +95,7 @@ impl PlayerAccountData { /* PlayerPreviewAccountData - like PlayerAccountData but more limited, for the total player list */ -#[derive(Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] +#[derive(Clone, Default, Encodable, KnownSize, Decodable)] pub struct PlayerPreviewAccountData { pub account_id: i32, pub name: FastString, @@ -105,14 +105,31 @@ pub struct PlayerPreviewAccountData { pub level_id: i32, } +/* IconType */ + +#[derive(Debug, Copy, Clone, Encodable, Decodable, KnownSize)] +#[repr(u8)] +pub enum IconType { + Unknown = 0, + Cube = 1, + Ship = 2, + Ball = 3, + Ufo = 4, + Wave = 5, + Robot = 6, + Spider = 7, + Swing = 8, + Jetpack = 9, +} + /* PlayerData (data in a level) */ -#[derive(Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] +#[derive(Clone, Default, Encodable, KnownSize, Decodable)] pub struct PlayerData {} /* AssociatedPlayerData */ -#[derive(Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] +#[derive(Clone, Default, Encodable, KnownSize, Decodable)] pub struct AssociatedPlayerData { pub account_id: i32, pub data: PlayerData, @@ -120,7 +137,7 @@ pub struct AssociatedPlayerData { /* PlayerMetadata (things like your percentage in a level, attempt count) */ -#[derive(Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] +#[derive(Clone, Default, Encodable, KnownSize, Decodable)] pub struct PlayerMetadata { percentage: u16, attempts: i32, @@ -128,7 +145,7 @@ pub struct PlayerMetadata { /* AssociatedPlayerMetadata */ -#[derive(Clone, Default, Encodable, EncodableWithKnownSize)] +#[derive(Clone, Default, Encodable, KnownSize)] pub struct AssociatedPlayerMetadata { pub account_id: i32, pub data: PlayerMetadata, diff --git a/server/game/src/server_thread/error.rs b/server/game/src/server_thread/error.rs index 21a6c2a6..f5d41641 100644 --- a/server/game/src/server_thread/error.rs +++ b/server/game/src/server_thread/error.rs @@ -1,27 +1,27 @@ use std::{fmt::Display, time::SystemTimeError}; -use crate::data::types::ColorParseError; +use crate::data::{bytebufferext::DecodeError, types::ColorParseError}; pub enum PacketHandlingError { - Other(String), // unknown generic error - WrongCryptoBoxState, // cryptobox was either Some or None when should've been the other one - EncryptionError, // failed to encrypt data - DecryptionError, // failed to decrypt data - IOError(std::io::Error), // generic IO error - MalformedMessage, // packet is missing a header - MalformedLoginAttempt, // LoginPacket with cleartext credentials - MalformedCiphertext, // missing nonce/mac in the encrypted ciphertext - MalformedPacketStructure, // failed to decode the packet - NoHandler(u16), // no handler found for this packet ID - WebRequestError(reqwest::Error), // error making a web request to the central server - UnexpectedPlayerData, // client sent PlayerDataPacket or SyncPlayerMetadataPacket outside of a level - SystemTimeError(SystemTimeError), // clock went backwards..? - SocketSendFailed(std::io::Error), // failed to send data on a socket due to an IO error - SocketWouldBlock, // failed to send data on a socket because operation would block - UnexpectedCentralResponse, // malformed response from the central server - ColorParseFailed(ColorParseError), // failed to parse a hex color into a Color3B struct - Ratelimited, // too many packets per second - DangerousAllocation(usize), // attempted to allocate a huge chunk of memory with alloca + Other(String), // unknown generic error + WrongCryptoBoxState, // cryptobox was either Some or None when should've been the other one + EncryptionError, // failed to encrypt data + DecryptionError, // failed to decrypt data + IOError(std::io::Error), // generic IO error + MalformedMessage, // packet is missing a header + MalformedLoginAttempt, // LoginPacket with cleartext credentials + MalformedCiphertext, // missing nonce/mac in the encrypted ciphertext + MalformedPacketStructure(DecodeError), // failed to decode the packet + NoHandler(u16), // no handler found for this packet ID + WebRequestError(reqwest::Error), // error making a web request to the central server + UnexpectedPlayerData, // client sent PlayerDataPacket or SyncPlayerMetadataPacket outside of a level + SystemTimeError(SystemTimeError), // clock went backwards..? + SocketSendFailed(std::io::Error), // failed to send data on a socket due to an IO error + SocketWouldBlock, // failed to send data on a socket because operation would block + UnexpectedCentralResponse, // malformed response from the central server + ColorParseFailed(ColorParseError), // failed to parse a hex color into a Color3B struct + Ratelimited, // too many packets per second + DangerousAllocation(usize), // attempted to allocate a huge chunk of memory with alloca } pub type Result = core::result::Result; @@ -56,6 +56,12 @@ impl From for PacketHandlingError { } } +impl From for PacketHandlingError { + fn from(value: DecodeError) -> Self { + PacketHandlingError::MalformedPacketStructure(value) + } +} + impl Display for PacketHandlingError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -67,7 +73,7 @@ impl Display for PacketHandlingError { Self::MalformedCiphertext => f.write_str("malformed ciphertext in an encrypted packet"), Self::MalformedMessage => f.write_str("malformed message structure"), Self::MalformedLoginAttempt => f.write_str("malformed login attempt"), - Self::MalformedPacketStructure => f.write_str("malformed packet structure, could not decode it"), + Self::MalformedPacketStructure(err) => f.write_fmt(format_args!("could not decode a packet: {err}")), Self::NoHandler(id) => f.write_fmt(format_args!("no packet handler for packet ID {id}")), Self::WebRequestError(msg) => f.write_fmt(format_args!("web request error: {msg}")), Self::UnexpectedPlayerData => { diff --git a/server/game/src/server_thread/handlers/mod.rs b/server/game/src/server_thread/handlers/mod.rs index 3d629bc2..c5457e60 100644 --- a/server/game/src/server_thread/handlers/mod.rs +++ b/server/game/src/server_thread/handlers/mod.rs @@ -7,7 +7,7 @@ pub use game::MAX_VOICE_PACKET_SIZE; macro_rules! gs_handler { ($self:ident,$name:ident,$pktty:ty,$pkt:ident,$code:expr) => { pub(crate) async fn $name(&$self, buf: &mut bytebuffer::ByteReader<'_>) -> crate::server_thread::Result<()> { - let $pkt = <$pktty>::decode_from_reader(buf).map_err(|_| crate::server_thread::error::PacketHandlingError::MalformedPacketStructure)?; + let $pkt = <$pktty>::decode_from_reader(buf)?; #[cfg(debug_assertions)] globed_shared::logger::debug!( "[{} @ {}] Handling packet {}", @@ -24,7 +24,7 @@ macro_rules! gs_handler { macro_rules! gs_handler_sync { ($self:ident,$name:ident,$pktty:ty,$pkt:ident,$code:expr) => { pub(crate) fn $name(&$self, buf: &mut bytebuffer::ByteReader<'_>) -> crate::server_thread::Result<()> { - let $pkt = <$pktty>::decode_from_reader(buf).map_err(|_| crate::server_thread::error::PacketHandlingError::MalformedPacketStructure)?; + let $pkt = <$pktty>::decode_from_reader(buf)?; #[cfg(debug_assertions)] globed_shared::logger::debug!( "[{} @ {}] Handling packet {}", diff --git a/server/game/src/server_thread/mod.rs b/server/game/src/server_thread/mod.rs index e442c52a..8259f53a 100644 --- a/server/game/src/server_thread/mod.rs +++ b/server/game/src/server_thread/mod.rs @@ -15,13 +15,12 @@ use crypto_box::{ ChaChaBox, }; use globed_shared::logger::*; -use tokio::sync::{mpsc, Mutex}; use crate::{ data::*, server::GameServer, server_thread::handlers::*, - util::{rate_limiter::UnsafeRateLimiter, SimpleRateLimiter}, + util::{SimpleRateLimiter, UnsafeChannel, UnsafeRateLimiter}, }; mod error; @@ -49,8 +48,7 @@ pub enum ServerThreadMessage { pub struct GameServerThread { game_server: &'static GameServer, - rx: Mutex>, - tx: mpsc::Sender, + channel: UnsafeChannel, awaiting_termination: AtomicBool, pub authenticated: AtomicBool, crypto_box: OnceLock, @@ -68,13 +66,11 @@ impl GameServerThread { /* public api for the main server */ pub fn new(peer: SocketAddrV4, game_server: &'static GameServer) -> Self { - let (tx, rx) = mpsc::channel::(CHANNEL_BUFFER_SIZE); let rl_request_limit = (game_server.central_conf.lock().tps + 5) as usize; let rate_limiter = SimpleRateLimiter::new(rl_request_limit, Duration::from_millis(950)); let rate_limiter = UnsafeRateLimiter::new(rate_limiter); Self { - tx, - rx: Mutex::new(rx), + channel: UnsafeChannel::new(CHANNEL_BUFFER_SIZE), peer, crypto_box: OnceLock::new(), account_id: AtomicI32::new(0), @@ -89,19 +85,17 @@ impl GameServerThread { } pub async fn run(&self) { - let mut rx = self.rx.lock().await; - loop { if self.awaiting_termination.load(Ordering::Relaxed) { break; } - match tokio::time::timeout(Duration::from_secs(60), rx.recv()).await { - Ok(Some(message)) => match self.handle_message(message).await { + match tokio::time::timeout(Duration::from_secs(60), self.channel.recv()).await { + Ok(Ok(message)) => match self.handle_message(message).await { Ok(()) => {} Err(err) => self.print_error(&err), }, - Ok(None) | Err(_) => break, // sender closed | timeout + Ok(Err(_)) | Err(_) => break, // sender closed | timeout }; } } @@ -133,7 +127,7 @@ impl GameServerThread { PacketHandlingError::MalformedMessage | PacketHandlingError::MalformedCiphertext | PacketHandlingError::MalformedLoginAttempt - | PacketHandlingError::MalformedPacketStructure + | PacketHandlingError::MalformedPacketStructure(_) | PacketHandlingError::NoHandler(_) | PacketHandlingError::SocketWouldBlock | PacketHandlingError::Ratelimited @@ -144,7 +138,7 @@ impl GameServerThread { /// send a new message to this thread. pub fn push_new_message(&self, data: ServerThreadMessage) -> anyhow::Result<()> { - self.tx.try_send(data)?; + self.channel.try_send(data)?; Ok(()) } @@ -163,7 +157,7 @@ impl GameServerThread { /// send a packet normally. with zero extra optimizations. like a sane person typically would. #[allow(dead_code)] - async fn send_packet(&self, packet: &P) -> Result<()> { + async fn send_packet(&self, packet: &P) -> Result<()> { #[cfg(debug_assertions)] debug!( "[{} @ {}] Sending packet {} (normal)", @@ -178,7 +172,7 @@ impl GameServerThread { /// fast packet sending with best-case zero heap allocation. /// if the packet size isn't known at compile time, calculate it and use `send_packet_fast_rough` - async fn send_packet_fast(&self, packet: &P) -> Result<()> { + async fn send_packet_fast(&self, packet: &P) -> Result<()> { // in theory, the size is known at compile time, however for some reason, // alloca manages to be significantly faster than a `[MaybeUninit; N]`. // i have no idea why or how, but yeah. @@ -187,7 +181,7 @@ impl GameServerThread { /// like `send_packet_fast` but without the size being known at compile time. /// you have to provide a rough estimate of the packet size, if the packet doesn't fit, the function panics. - async fn send_packet_fast_rough(&self, packet: &P, packet_size: usize) -> Result<()> { + async fn send_packet_fast_rough(&self, packet: &P, packet_size: usize) -> Result<()> { #[cfg(debug_assertions)] debug!( "[{} @ {}] Sending packet {} ({})", @@ -282,7 +276,7 @@ impl GameServerThread { /// serialize (and potentially encrypt) a packet, returning a `ByteBuffer` with the output data #[allow(dead_code)] - fn serialize_packet(&self, packet: &P) -> Result { + fn serialize_packet(&self, packet: &P) -> Result { let mut buf = ByteBuffer::new(); buf.write_packet_header::

(); diff --git a/server/game/src/util/channel.rs b/server/game/src/util/channel.rs new file mode 100644 index 00000000..3d902176 --- /dev/null +++ b/server/game/src/util/channel.rs @@ -0,0 +1,104 @@ +use std::cell::SyncUnsafeCell; +use tokio::sync::mpsc; + +/// Simple wrapper around `tokio::mpsc` channels except receiver does not need to be mutable. +/// Obviously not safe to call `recv` from multiple threads. +pub struct UnsafeChannel { + pub tx: mpsc::Sender, + pub rx: SyncUnsafeCell>, +} + +pub struct SenderDropped; + +impl UnsafeChannel { + pub fn new(size: usize) -> Self { + Self::from_tx_rx(mpsc::channel(size)) + } + + fn from_tx_rx((tx, rx): (mpsc::Sender, mpsc::Receiver)) -> Self { + Self { + tx, + rx: SyncUnsafeCell::new(rx), + } + } + + pub fn try_send(&self, msg: T) -> Result<(), mpsc::error::TrySendError> { + self.tx.try_send(msg) + } + + pub async fn send(&self, msg: T) -> Result<(), mpsc::error::SendError> { + self.tx.send(msg).await + } + + pub async fn recv(&self) -> Result { + let chan = unsafe { self.rx.get().as_mut().unwrap() }; + chan.recv().await.ok_or(SenderDropped) + } +} +/* +* Supposedly, those two should be faster than tokio::mpsc but I noticed no real difference in my benchmarking. +* Feel free to install crates `loole` and `kanal` and try those channels yourself if you want. + */ + +// /// Channel significantly faster than `tokio::mpsc` (and by relation, `UnsafeChannel`) +// pub struct FastChannel { +// pub tx: kanal::AsyncSender, +// pub rx: kanal::AsyncReceiver, +// } + +// impl FastChannel { +// pub fn new_bounded(size: usize) -> Self { +// Self::from_tx_rx(kanal::bounded_async(size)) +// } + +// pub fn new_unbounded() -> Self { +// Self::from_tx_rx(kanal::unbounded_async()) +// } + +// fn from_tx_rx((tx, rx): (kanal::AsyncSender, kanal::AsyncReceiver)) -> Self { +// Self { tx, rx } +// } + +// pub fn try_send(&self, msg: T) -> Result { +// self.tx.try_send(msg) +// } + +// pub async fn send(&self, msg: T) -> Result<(), kanal::SendError> { +// self.tx.send(msg).await +// } + +// pub async fn recv(&self) -> Result { +// self.rx.recv().await +// } +// } + +// pub struct LooleChannel { +// pub tx: loole::Sender, +// pub rx: loole::Receiver, +// } + +// impl LooleChannel { +// pub fn new_bounded(size: usize) -> Self { +// Self::from_tx_rx(loole::bounded(size)) +// } + +// pub fn new_unbounded() -> Self { +// Self::from_tx_rx(loole::unbounded()) +// } + +// pub fn try_send(&self, msg: T) -> Result<(), loole::TrySendError> { +// self.tx.try_send(msg) +// } + +// pub async fn send(&self, msg: T) -> Result<(), loole::SendError> { +// self.tx.send_async(msg).await +// } + +// pub async fn recv(&self) -> Result { +// self.rx.recv_async().await +// } + +// fn from_tx_rx((tx, rx): (loole::Sender, loole::Receiver)) -> Self { +// Self { tx, rx } +// } +// } diff --git a/server/game/src/util/mod.rs b/server/game/src/util/mod.rs index 36f9842b..911da44c 100644 --- a/server/game/src/util/mod.rs +++ b/server/game/src/util/mod.rs @@ -1,3 +1,5 @@ +pub mod channel; pub mod rate_limiter; -pub use rate_limiter::SimpleRateLimiter; +pub use channel::{SenderDropped, UnsafeChannel}; +pub use rate_limiter::{SimpleRateLimiter, UnsafeRateLimiter}; diff --git a/server/shared/Cargo.toml b/server/shared/Cargo.toml index 1ee3f0a7..4f0c4511 100644 --- a/server/shared/Cargo.toml +++ b/server/shared/Cargo.toml @@ -8,6 +8,6 @@ edition = "2021" [dependencies] colored = "2.0.4" log = { version = "0.4.20" } -serde = { version = "1.0.192", features = ["serde_derive"] } +serde = { version = "1.0.193", features = ["serde_derive"] } serde_json = "1.0.108" time = { version = "0.3.30", features = ["formatting"] }