Skip to content

Commit

Permalink
derive macros for enums + more server work
Browse files Browse the repository at this point in the history
  • Loading branch information
dankmeme01 committed Dec 3, 2023
1 parent 7885d4e commit b8f1d7b
Show file tree
Hide file tree
Showing 22 changed files with 488 additions and 241 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
5 changes: 2 additions & 3 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 1 addition & 1 deletion server/central/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
172 changes: 142 additions & 30 deletions server/game-derives/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -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);

Expand All @@ -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;
}
};
Expand All @@ -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);
Expand All @@ -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<Self> {
fn decode(buf: &mut bytebuffer::ByteBuffer) -> crate::data::DecodeResult<Self> {
#(#decode_fields)*
Ok(Self {
#(
Expand All @@ -123,7 +150,7 @@ pub fn derive_decodable(input: TokenStream) -> TokenStream {
})
}

fn decode_from_reader(buf: &mut bytebuffer::ByteReader) -> anyhow::Result<Self> {
fn decode_from_reader(buf: &mut bytebuffer::ByteReader) -> crate::data::DecodeResult<Self> {
#(#decode_fields)*
Ok(Self {
#(
Expand All @@ -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<Self> {
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<Self> {
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<proc_macro2::TokenStream> = None;
for attr in &input.attrs {
if attr.path().is_ident("repr") {
let nested = attr.parse_args_with(Punctuated::<Meta, Token![,]>::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))]
Expand All @@ -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);
Expand Down
3 changes: 1 addition & 2 deletions server/game/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Loading

0 comments on commit b8f1d7b

Please sign in to comment.