aboutsummaryrefslogtreecommitdiff
path: root/azalea-protocol/azalea-protocol-macros/src
diff options
context:
space:
mode:
authormat <github@matdoes.dev>2022-09-05 10:36:48 -0500
committermat <github@matdoes.dev>2022-09-05 10:36:48 -0500
commite9f88ce546acb57fe190740d0a74ae0ce6c8d671 (patch)
treed1a5afdac92089bc1d6ba3e2642c3ecc28e23ae1 /azalea-protocol/azalea-protocol-macros/src
parent9ca95194696f8e9ef3ca84420f5d3b5082ff70ca (diff)
downloadazalea-drasl-e9f88ce546acb57fe190740d0a74ae0ce6c8d671.tar.xz
Publish everything* to crates.io
Except azalea-client since it's not ready yet
Diffstat (limited to 'azalea-protocol/azalea-protocol-macros/src')
-rw-r--r--azalea-protocol/azalea-protocol-macros/src/lib.rs352
1 files changed, 352 insertions, 0 deletions
diff --git a/azalea-protocol/azalea-protocol-macros/src/lib.rs b/azalea-protocol/azalea-protocol-macros/src/lib.rs
new file mode 100644
index 00000000..4f47c9a9
--- /dev/null
+++ b/azalea-protocol/azalea-protocol-macros/src/lib.rs
@@ -0,0 +1,352 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{
+ self, braced,
+ parse::{Parse, ParseStream, Result},
+ parse_macro_input, DeriveInput, FieldsNamed, Ident, LitInt, Token,
+};
+
+fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> TokenStream {
+ let DeriveInput { ident, data, .. } = parse_macro_input!(input);
+
+ let fields = match &data {
+ syn::Data::Struct(syn::DataStruct { fields, .. }) => fields,
+ _ => panic!("#[derive(*Packet)] can only be used on structs"),
+ };
+ let FieldsNamed { named: _, .. } = match fields {
+ syn::Fields::Named(f) => f,
+ _ => panic!("#[derive(*Packet)] can only be used on structs with named fields"),
+ };
+
+ let contents = quote! {
+ impl #ident {
+ pub fn get(self) -> #state {
+ #state::#ident(self)
+ }
+
+ pub fn write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
+ azalea_buf::McBufWritable::write_into(self, buf)
+ }
+
+ pub fn read(
+ buf: &mut impl std::io::Read,
+ ) -> Result<#state, azalea_buf::BufReadError> {
+ use azalea_buf::McBufReadable;
+ Ok(Self::read_from(buf)?.get())
+ }
+ }
+ };
+
+ contents.into()
+}
+
+#[proc_macro_derive(ServerboundGamePacket, attributes(var))]
+pub fn derive_serverbound_game_packet(input: TokenStream) -> TokenStream {
+ as_packet_derive(input, quote! {crate::packets::game::ServerboundGamePacket})
+}
+#[proc_macro_derive(ServerboundHandshakePacket, attributes(var))]
+pub fn derive_serverbound_handshake_packet(input: TokenStream) -> TokenStream {
+ as_packet_derive(
+ input,
+ quote! {crate::packets::handshake::ServerboundHandshakePacket},
+ )
+}
+#[proc_macro_derive(ServerboundLoginPacket, attributes(var))]
+pub fn derive_serverbound_login_packet(input: TokenStream) -> TokenStream {
+ as_packet_derive(
+ input,
+ quote! {crate::packets::login::ServerboundLoginPacket},
+ )
+}
+#[proc_macro_derive(ServerboundStatusPacket, attributes(var))]
+pub fn derive_serverbound_status_packet(input: TokenStream) -> TokenStream {
+ as_packet_derive(
+ input,
+ quote! {crate::packets::status::ServerboundStatusPacket},
+ )
+}
+
+#[proc_macro_derive(ClientboundGamePacket, attributes(var))]
+pub fn derive_clientbound_game_packet(input: TokenStream) -> TokenStream {
+ as_packet_derive(input, quote! {crate::packets::game::ClientboundGamePacket})
+}
+#[proc_macro_derive(ClientboundHandshakePacket, attributes(var))]
+pub fn derive_clientbound_handshake_packet(input: TokenStream) -> TokenStream {
+ as_packet_derive(
+ input,
+ quote! {crate::packets::handshake::ClientboundHandshakePacket},
+ )
+}
+#[proc_macro_derive(ClientboundLoginPacket, attributes(var))]
+pub fn derive_clientbound_login_packet(input: TokenStream) -> TokenStream {
+ as_packet_derive(
+ input,
+ quote! {crate::packets::login::ClientboundLoginPacket},
+ )
+}
+#[proc_macro_derive(ClientboundStatusPacket, attributes(var))]
+pub fn derive_clientbound_status_packet(input: TokenStream) -> TokenStream {
+ as_packet_derive(
+ input,
+ quote! {crate::packets::status::ClientboundStatusPacket},
+ )
+}
+
+#[derive(Debug)]
+struct PacketIdPair {
+ id: u32,
+ module: Ident,
+ name: Ident,
+}
+#[derive(Debug)]
+struct PacketIdMap {
+ packets: Vec<PacketIdPair>,
+}
+
+impl Parse for PacketIdMap {
+ fn parse(input: ParseStream) -> Result<Self> {
+ let mut packets = vec![];
+
+ // example:
+ // 0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket,
+
+ // 0x0e
+ while let Ok(packet_id) = input.parse::<LitInt>() {
+ let packet_id = packet_id.base10_parse::<u32>()?;
+ // :
+ input.parse::<Token![:]>()?;
+ // clientbound_change_difficulty_packet
+ let module: Ident = input.parse()?;
+ // ::
+ input.parse::<Token![::]>()?;
+ // ClientboundChangeDifficultyPacket
+ let name: Ident = input.parse()?;
+
+ packets.push(PacketIdPair {
+ id: packet_id,
+ module,
+ name,
+ });
+
+ if input.parse::<Token![,]>().is_err() {
+ break;
+ }
+ }
+
+ Ok(PacketIdMap { packets })
+ }
+}
+
+#[derive(Debug)]
+struct DeclareStatePackets {
+ name: Ident,
+ serverbound: PacketIdMap,
+ clientbound: PacketIdMap,
+}
+
+impl Parse for DeclareStatePackets {
+ fn parse(input: ParseStream) -> Result<Self> {
+ let name = input.parse()?;
+ input.parse::<Token![,]>()?;
+
+ let serverbound_token: Ident = input.parse()?;
+ if serverbound_token != "Serverbound" {
+ return Err(syn::Error::new(
+ serverbound_token.span(),
+ "Expected `Serverbound`",
+ ));
+ }
+ input.parse::<Token![=>]>()?;
+ let content;
+ braced!(content in input);
+ let serverbound = content.parse()?;
+
+ input.parse::<Token![,]>()?;
+
+ let clientbound_token: Ident = input.parse()?;
+ if clientbound_token != "Clientbound" {
+ return Err(syn::Error::new(
+ clientbound_token.span(),
+ "Expected `Clientbound`",
+ ));
+ }
+ input.parse::<Token![=>]>()?;
+ let content;
+ braced!(content in input);
+ let clientbound = content.parse()?;
+
+ Ok(DeclareStatePackets {
+ name,
+ serverbound,
+ clientbound,
+ })
+ }
+}
+#[proc_macro]
+pub fn declare_state_packets(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeclareStatePackets);
+
+ let serverbound_state_name =
+ Ident::new(&format!("Serverbound{}", input.name), input.name.span());
+ let clientbound_state_name =
+ Ident::new(&format!("Clientbound{}", input.name), input.name.span());
+
+ let state_name_litstr = syn::LitStr::new(&input.name.to_string(), input.name.span());
+
+ let has_serverbound_packets = !input.serverbound.packets.is_empty();
+ let has_clientbound_packets = !input.clientbound.packets.is_empty();
+
+ let mut serverbound_enum_contents = quote!();
+ let mut clientbound_enum_contents = quote!();
+ let mut serverbound_id_match_contents = quote!();
+ let mut clientbound_id_match_contents = quote!();
+ let mut serverbound_write_match_contents = quote!();
+ let mut clientbound_write_match_contents = quote!();
+ let mut serverbound_read_match_contents = quote!();
+ let mut clientbound_read_match_contents = quote!();
+
+ for PacketIdPair { id, module, name } in input.serverbound.packets {
+ let name_litstr = syn::LitStr::new(&name.to_string(), name.span());
+ serverbound_enum_contents.extend(quote! {
+ #name(#module::#name),
+ });
+ serverbound_id_match_contents.extend(quote! {
+ #serverbound_state_name::#name(_packet) => #id,
+ });
+ serverbound_write_match_contents.extend(quote! {
+ #serverbound_state_name::#name(packet) => packet.write(buf),
+ });
+ serverbound_read_match_contents.extend(quote! {
+ #id => {
+ let data = #module::#name::read(buf).map_err(|e| crate::read::ReadPacketError::Parse { source: e, packet_id: #id, packet_name: #name_litstr.to_string() })?;
+ let mut leftover = Vec::new();
+ let _ = buf.read_to_end(&mut leftover);
+ if !leftover.is_empty() {
+ return Err(crate::read::ReadPacketError::LeftoverData { packet_name: #name_litstr.to_string(), data: leftover });
+ }
+ data
+ },
+ });
+ }
+ for PacketIdPair { id, module, name } in input.clientbound.packets {
+ let name_litstr = syn::LitStr::new(&name.to_string(), name.span());
+ clientbound_enum_contents.extend(quote! {
+ #name(#module::#name),
+ });
+ clientbound_id_match_contents.extend(quote! {
+ #clientbound_state_name::#name(_packet) => #id,
+ });
+ clientbound_write_match_contents.extend(quote! {
+ #clientbound_state_name::#name(packet) => packet.write(buf),
+ });
+ clientbound_read_match_contents.extend(quote! {
+ #id => {
+ let data = #module::#name::read(buf).map_err(|e| crate::read::ReadPacketError::Parse { source: e, packet_id: #id, packet_name: #name_litstr.to_string() })?;
+ let mut leftover = Vec::new();
+ let _ = buf.read_to_end(&mut leftover);
+ if !leftover.is_empty() {
+ return Err(crate::read::ReadPacketError::LeftoverData { packet_name: #name_litstr.to_string(), data: leftover });
+ }
+ data
+ },
+ });
+ }
+
+ if !has_serverbound_packets {
+ serverbound_id_match_contents.extend(quote! {
+ _ => unreachable!("This enum is empty and can't exist.")
+ });
+ serverbound_write_match_contents.extend(quote! {
+ _ => unreachable!("This enum is empty and can't exist.")
+ });
+ }
+ if !has_clientbound_packets {
+ clientbound_id_match_contents.extend(quote! {
+ _ => unreachable!("This enum is empty and can't exist.")
+ });
+ clientbound_write_match_contents.extend(quote! {
+ _ => unreachable!("This enum is empty and can't exist.")
+ });
+ }
+
+ let mut contents = quote! {
+ #[derive(Clone, Debug)]
+ pub enum #serverbound_state_name
+ where
+ Self: Sized,
+ {
+ #serverbound_enum_contents
+ }
+ #[derive(Clone, Debug)]
+ pub enum #clientbound_state_name
+ where
+ Self: Sized,
+ {
+ #clientbound_enum_contents
+ }
+ };
+
+ contents.extend(quote! {
+ #[allow(unreachable_code)]
+ impl crate::packets::ProtocolPacket for #serverbound_state_name {
+ fn id(&self) -> u32 {
+ match self {
+ #serverbound_id_match_contents
+ }
+ }
+
+ fn write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
+ match self {
+ #serverbound_write_match_contents
+ }
+ }
+
+ /// Read a packet by its id, ConnectionProtocol, and flow
+ fn read(
+ id: u32,
+ buf: &mut impl std::io::Read,
+ ) -> Result<#serverbound_state_name, crate::read::ReadPacketError>
+ where
+ Self: Sized,
+ {
+ Ok(match id {
+ #serverbound_read_match_contents
+ _ => return Err(crate::read::ReadPacketError::UnknownPacketId { state_name: #state_name_litstr.to_string(), id }),
+ })
+ }
+ }
+ });
+
+ contents.extend(quote! {
+ #[allow(unreachable_code)]
+ impl crate::packets::ProtocolPacket for #clientbound_state_name {
+ fn id(&self) -> u32 {
+ match self {
+ #clientbound_id_match_contents
+ }
+ }
+
+ fn write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
+ match self {
+ #clientbound_write_match_contents
+ }
+ }
+
+ /// Read a packet by its id, ConnectionProtocol, and flow
+ fn read(
+ id: u32,
+ buf: &mut impl std::io::Read,
+ ) -> Result<#clientbound_state_name, crate::read::ReadPacketError>
+ where
+ Self: Sized,
+ {
+ Ok(match id {
+ #clientbound_read_match_contents
+ _ => return Err(crate::read::ReadPacketError::UnknownPacketId { state_name: #state_name_litstr.to_string(), id }),
+ })
+ }
+ }
+ });
+
+ contents.into()
+}