From d0b459e82764987e1f820978e2af2d01a4b91084 Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 29 Mar 2025 22:29:37 +0000 Subject: [PATCH] add support for unnamed structs to azalea-buf-macros --- azalea-buf/azalea-buf-macros/src/read.rs | 149 +++++++++++------- azalea-buf/azalea-buf-macros/src/write.rs | 101 +++++++----- .../src/packets/game/s_container_click.rs | 2 +- 3 files changed, 156 insertions(+), 96 deletions(-) diff --git a/azalea-buf/azalea-buf-macros/src/read.rs b/azalea-buf/azalea-buf-macros/src/read.rs index 0500ec7c..5f75ad4d 100644 --- a/azalea-buf/azalea-buf-macros/src/read.rs +++ b/azalea-buf/azalea-buf-macros/src/read.rs @@ -1,62 +1,6 @@ use quote::{ToTokens, quote}; use syn::{Data, Field, FieldsNamed, Ident, punctuated::Punctuated, token::Comma}; -fn read_named_fields( - named: &Punctuated, -) -> (Vec, Vec<&Option>) { - let read_fields = named - .iter() - .map(|f| { - let field_name = &f.ident; - let field_type = &f.ty; - - let is_variable_length = f.attrs.iter().any(|a| a.path().is_ident("var")); - let limit = f - .attrs - .iter() - .find(|a| a.path().is_ident("limit")) - .map(|a| { - a.parse_args::() - .unwrap() - .base10_parse::() - .unwrap() - }); - - if is_variable_length && limit.is_some() { - panic!("Fields cannot have both var and limit attributes"); - } - - // do a different buf.write_* for each field depending on the type - // if it's a string, use buf.write_string - match field_type { - syn::Type::Path(_) | syn::Type::Array(_) => { - if is_variable_length { - quote! { - let #field_name = azalea_buf::AzaleaReadVar::azalea_read_var(buf)?; - } - } else if let Some(limit) = limit { - quote! { - let #field_name = azalea_buf::AzaleaReadLimited::azalea_read_limited(buf, #limit)?; - } - } else { - quote! { - let #field_name = azalea_buf::AzaleaRead::azalea_read(buf)?; - } - } - } - _ => panic!( - "Error reading field {}: {}", - field_name.clone().unwrap(), - field_type.to_token_stream() - ), - } - }) - .collect::>(); - let read_field_names = named.iter().map(|f| &f.ident).collect::>(); - - (read_fields, read_field_names) -} - pub fn create_impl_azalearead(ident: &Ident, data: &Data) -> proc_macro2::TokenStream { match data { syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields { @@ -83,8 +27,18 @@ pub fn create_impl_azalearead(ident: &Ident, data: &Data) -> proc_macro2::TokenS } } } - _ => { - panic!("#[derive(AzBuf)] can only be used on structs with named fields") + syn::Fields::Unnamed(fields) => { + let read_fields = read_unnamed_fields(&fields.unnamed); + + quote! { + impl azalea_buf::AzaleaRead for #ident { + fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> Result { + Ok(Self( + #(#read_fields),* + )) + } + } + } } }, syn::Data::Enum(syn::DataEnum { variants, .. }) => { @@ -202,3 +156,82 @@ pub fn create_impl_azalearead(ident: &Ident, data: &Data) -> proc_macro2::TokenS _ => panic!("#[derive(AzBuf)] can only be used on structs"), } } + +fn read_named_fields( + named: &Punctuated, +) -> (Vec, Vec<&Option>) { + let read_fields = named + .iter() + .map(|f| { + let field_name = &f.ident; + + let reader_call = get_reader_call(f); + quote! { let #field_name = #reader_call; } + }) + .collect::>(); + let read_field_names = named.iter().map(|f| &f.ident).collect::>(); + + (read_fields, read_field_names) +} + +fn read_unnamed_fields(unnamed: &Punctuated) -> Vec { + let read_fields = unnamed + .iter() + .map(|f| { + let reader_call = get_reader_call(f); + quote! { #reader_call } + }) + .collect::>(); + + read_fields +} + +fn get_reader_call(f: &Field) -> proc_macro2::TokenStream { + let is_variable_length = f + .attrs + .iter() + .any(|a: &syn::Attribute| a.path().is_ident("var")); + let limit = f + .attrs + .iter() + .find(|a| a.path().is_ident("limit")) + .map(|a| { + a.parse_args::() + .unwrap() + .base10_parse::() + .unwrap() + }); + + if is_variable_length && limit.is_some() { + panic!("Fields cannot have both var and limit attributes"); + } + + let field_type = &f.ty; + + // do a different buf.write_* for each field depending on the type + // if it's a string, use buf.write_string + let reader_call = match field_type { + syn::Type::Path(_) | syn::Type::Array(_) => { + if is_variable_length { + quote! { + azalea_buf::AzaleaReadVar::azalea_read_var(buf)? + } + } else if let Some(limit) = limit { + quote! { + azalea_buf::AzaleaReadLimited::azalea_read_limited(buf, #limit)? + } + } else { + quote! { + azalea_buf::AzaleaRead::azalea_read(buf)? + } + } + } + _ => panic!( + "Error reading field {:?}: {}", + f.ident.clone(), + field_type.to_token_stream() + ), + }; + + reader_call +} diff --git a/azalea-buf/azalea-buf-macros/src/write.rs b/azalea-buf/azalea-buf-macros/src/write.rs index de436e70..d433d1d7 100644 --- a/azalea-buf/azalea-buf-macros/src/write.rs +++ b/azalea-buf/azalea-buf-macros/src/write.rs @@ -2,41 +2,6 @@ use proc_macro2::Span; use quote::{ToTokens, quote}; use syn::{Data, Field, FieldsNamed, Ident, punctuated::Punctuated, token::Comma}; -fn write_named_fields( - named: &Punctuated, - ident_name: Option<&Ident>, -) -> proc_macro2::TokenStream { - let write_fields = named.iter().map(|f| { - let field_name = &f.ident; - let field_type = &f.ty; - let ident_dot_field = match ident_name { - Some(ident) => quote! { &#ident.#field_name }, - None => quote! { #field_name }, - }; - // do a different buf.write_* for each field depending on the type - // if it's a string, use buf.write_string - match field_type { - syn::Type::Path(_) | syn::Type::Array(_) => { - if f.attrs.iter().any(|attr| attr.path().is_ident("var")) { - quote! { - azalea_buf::AzaleaWriteVar::azalea_write_var(#ident_dot_field, buf)?; - } - } else { - quote! { - azalea_buf::AzaleaWrite::azalea_write(#ident_dot_field, buf)?; - } - } - } - _ => panic!( - "Error writing field {}: {}", - field_name.clone().unwrap(), - field_type.to_token_stream() - ), - } - }); - quote! { #(#write_fields)* } -} - pub fn create_impl_azaleawrite(ident: &Ident, data: &Data) -> proc_macro2::TokenStream { match data { syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields { @@ -62,8 +27,17 @@ pub fn create_impl_azaleawrite(ident: &Ident, data: &Data) -> proc_macro2::Token } } } - _ => { - panic!("#[derive(AzBuf)] can only be used on structs with named fields") + syn::Fields::Unnamed(fields) => { + let write_fields = write_unnamed_fields(&fields.unnamed); + + quote! { + impl azalea_buf::AzaleaWrite for #ident { + fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { + #write_fields + Ok(()) + } + } + } } }, syn::Data::Enum(syn::DataEnum { variants, .. }) => { @@ -200,3 +174,56 @@ pub fn create_impl_azaleawrite(ident: &Ident, data: &Data) -> proc_macro2::Token _ => panic!("#[derive(AzBuf)] can only be used on structs"), } } + +fn write_named_fields( + named: &Punctuated, + ident_name: Option<&Ident>, +) -> proc_macro2::TokenStream { + let write_fields = named.iter().map(|f| { + let field_name = &f.ident; + let ident_dot_field = match ident_name { + Some(ident) => quote! { &#ident.#field_name }, + None => quote! { #field_name }, + }; + + make_write_call(f, ident_dot_field) + }); + quote! { #(#write_fields)* } +} + +fn write_unnamed_fields(named: &Punctuated) -> proc_macro2::TokenStream { + let write_fields = named.iter().enumerate().map(|(i, f)| { + let i_literal = syn::Index::from(i); + let ident_dot_field = quote! { &self.#i_literal }; + + make_write_call(f, ident_dot_field) + }); + quote! { #(#write_fields)* } +} + +fn make_write_call( + f: &Field, + ident_dot_field: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + let field_type = &f.ty; + // do a different buf.write_* for each field depending on the type + // if it's a string, use buf.write_string + match field_type { + syn::Type::Path(_) | syn::Type::Array(_) => { + if f.attrs.iter().any(|attr| attr.path().is_ident("var")) { + quote! { + azalea_buf::AzaleaWriteVar::azalea_write_var(#ident_dot_field, buf)?; + } + } else { + quote! { + azalea_buf::AzaleaWrite::azalea_write(#ident_dot_field, buf)?; + } + } + } + _ => panic!( + "Error writing field {:?}: {}", + f.ident, + field_type.to_token_stream() + ), + } +} diff --git a/azalea-protocol/src/packets/game/s_container_click.rs b/azalea-protocol/src/packets/game/s_container_click.rs index 2f7ef3d8..ed68de6e 100755 --- a/azalea-protocol/src/packets/game/s_container_click.rs +++ b/azalea-protocol/src/packets/game/s_container_click.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use azalea_buf::AzBuf; -use azalea_inventory::{operations::ClickType, ItemStack}; +use azalea_inventory::{ItemStack, operations::ClickType}; use azalea_protocol_macros::ServerboundGamePacket; #[derive(Clone, Debug, AzBuf, ServerboundGamePacket)]