valence/valence_derive/src/lib.rs
Ryan Johnson 420f2d1b7c
Move protocol code to valence_protocol + redesigns (#153)
Closes #83 

This PR aims to move all of Valence's networking code to the new
`valence_protocol` crate. Anything not specific to valence is going in
the new crate. It also redesigns the way packets are defined and makes a
huge number of small additions and improvements. It should be much
easier to see where code is supposed to go from now on.

`valence_protocol` is a new library which enables interactions with
Minecraft's protocol. It is completely decoupled from valence and can be
used to build new clients, servers, tools, etc.

There are two additions that will help with #5 especially:
- It is now easy to define new packets or modifications of existing
packets. Not all packets need to be bidirectional.
- The `CachedEncode` type has been created. This is used to safely cache
redundant calls to `Encode::encode`.
2022-11-13 06:10:42 -08:00

152 lines
4.5 KiB
Rust

//! This crate provides derive macros for [`Encode`], [`Decode`], and
//! [`Packet`].
//!
//! See `valence_protocol`'s documentation for more information.
use proc_macro::TokenStream as StdTokenStream;
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
parse2, parse_quote, Attribute, DeriveInput, Error, GenericParam, Generics, Lifetime,
LifetimeDef, Lit, LitInt, Meta, Result, Variant,
};
mod decode;
mod encode;
#[proc_macro_derive(Encode, attributes(packet_id, tag))]
pub fn derive_encode(item: StdTokenStream) -> StdTokenStream {
match encode::derive_encode(item.into()) {
Ok(tokens) => tokens.into(),
Err(e) => e.into_compile_error().into(),
}
}
#[proc_macro_derive(Decode, attributes(packet_id, tag))]
pub fn derive_decode(item: StdTokenStream) -> StdTokenStream {
match decode::derive_decode(item.into()) {
Ok(tokens) => tokens.into(),
Err(e) => e.into_compile_error().into(),
}
}
#[proc_macro_derive(Packet)]
pub fn derive_packet(item: StdTokenStream) -> StdTokenStream {
match derive_packet_inner(item.into()) {
Ok(tokens) => tokens.into(),
Err(e) => e.into_compile_error().into(),
}
}
fn derive_packet_inner(item: TokenStream) -> Result<TokenStream> {
let input = parse2::<DeriveInput>(item)?;
if find_packet_id_attr(&input.attrs)?.is_none() {
return Err(Error::new(
Span::call_site(),
"cannot derive `Packet` without `#[packet_id = ...]` attribute. Consider implementing \
the trait manually",
));
}
let name = input.ident;
let string_name = name.to_string();
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
Ok(quote! {
impl #impl_generics ::valence_protocol::Packet for #name #ty_generics
#where_clause
{
fn packet_name(&self) -> &'static str {
#string_name
}
}
})
}
fn find_packet_id_attr(attrs: &[Attribute]) -> Result<Option<LitInt>> {
for attr in attrs {
if let Meta::NameValue(nv) = attr.parse_meta()? {
if nv.path.is_ident("packet_id") {
let span = nv.lit.span();
return match nv.lit {
Lit::Int(i) => Ok(Some(i)),
_ => Err(Error::new(span, "packet ID must be an integer literal")),
};
}
}
}
Ok(None)
}
fn pair_variants_with_discriminants(
variants: impl IntoIterator<Item = Variant>,
) -> Result<Vec<(i32, Variant)>> {
let mut discriminant = 0;
variants
.into_iter()
.map(|v| {
if let Some(i) = find_tag_attr(&v.attrs)? {
discriminant = i;
}
let pair = (discriminant, v);
discriminant += 1;
Ok(pair)
})
.collect::<Result<_>>()
}
fn find_tag_attr(attrs: &[Attribute]) -> Result<Option<i32>> {
for attr in attrs {
if let Meta::NameValue(nv) = attr.parse_meta()? {
if nv.path.is_ident("tag") {
let span = nv.lit.span();
return match nv.lit {
Lit::Int(lit) => Ok(Some(lit.base10_parse::<i32>()?)),
_ => Err(Error::new(
span,
"discriminant value must be an integer literal",
)),
};
}
}
}
Ok(None)
}
/// Adding our lifetime to the generics before calling `.split_for_impl()` would
/// also add it to the resulting ty_generics, which we don't want. So I'm doing
/// this hack.
fn decode_split_for_impl(
mut generics: Generics,
lifetime: Lifetime,
) -> (TokenStream, TokenStream, TokenStream) {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let mut impl_generics = impl_generics.to_token_stream();
let ty_generics = ty_generics.to_token_stream();
let where_clause = where_clause.to_token_stream();
if generics.lifetimes().next().is_none() {
generics
.params
.push(GenericParam::Lifetime(LifetimeDef::new(lifetime)));
impl_generics = generics.split_for_impl().0.to_token_stream();
}
(impl_generics, ty_generics, where_clause)
}
fn add_trait_bounds(generics: &mut Generics, trait_: TokenStream) {
for param in &mut generics.params {
if let GenericParam::Type(type_param) = param {
type_param.bounds.push(parse_quote!(#trait_))
}
}
}