From 089d9707753d8991a3abff924dbc6197170915ea Mon Sep 17 00:00:00 2001 From: Andrew Brower Date: Fri, 27 Jun 2025 04:56:27 -0400 Subject: [PATCH] feat: basic Encode derive --- Cargo.lock | 1 + crawlspace-macro/Cargo.toml | 1 + crawlspace-macro/src/lib.rs | 91 ++++++++++++++++++- .../src/protocol/packets/login/config.rs | 33 +------ .../src/protocol/packets/play/game_event.rs | 12 +-- .../src/protocol/packets/play/keepalive.rs | 10 +- .../src/protocol/packets/play/teleport.rs | 24 +---- crawlspace/src/protocol/packets/play/tick.rs | 22 +---- crawlspace/src/protocol/packets/play/world.rs | 75 +++------------ 9 files changed, 118 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6fe42fd..55e1341 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,7 @@ dependencies = [ name = "crawlspace-macro" version = "0.1.0" dependencies = [ + "proc-macro2", "quote", "syn 2.0.104", ] diff --git a/crawlspace-macro/Cargo.toml b/crawlspace-macro/Cargo.toml index b7a3316..d18bcc3 100644 --- a/crawlspace-macro/Cargo.toml +++ b/crawlspace-macro/Cargo.toml @@ -7,5 +7,6 @@ edition = "2021" proc-macro = true [dependencies] +proc-macro2 = "1.0.95" quote = "1.0.40" syn = "2.0.104" diff --git a/crawlspace-macro/src/lib.rs b/crawlspace-macro/src/lib.rs index c50ebfd..0fcdf5f 100644 --- a/crawlspace-macro/src/lib.rs +++ b/crawlspace-macro/src/lib.rs @@ -1,6 +1,6 @@ use proc_macro::{Span, TokenStream}; -use quote::quote; -use syn::{parse_macro_input, DeriveInput, Ident, Lit}; +use quote::{quote, TokenStreamExt}; +use syn::{parse_macro_input, DeriveInput, Fields, Ident, Index, Lit}; #[proc_macro_derive(Packet, attributes(packet))] pub fn derive_packet(input: TokenStream) -> TokenStream { @@ -95,3 +95,90 @@ pub fn derive_packet(input: TokenStream) -> TokenStream { } .into() } + +/// Automatically implements "straight-across" encoding for the given struct, i.e. fields are +/// serialized in order as is. Supports #[varint] and #[varlong] attributes on integer types to +/// serialize as those formats instead. +#[proc_macro_derive(Encode, attributes(varint, varlong))] +pub fn derive_encode(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let syn::Data::Struct(data) = input.data else { + panic!("Can only derive Encode on a struct"); + }; + + let name = input.ident; + let where_clause = input.generics.where_clause.clone(); + let generics = input.generics; + + let mut fields_encoded = proc_macro2::TokenStream::new(); + + match data.fields { + Fields::Named(fields) => { + for field in fields.named { + let field_name = field.ident.unwrap(); + + if field + .attrs + .iter() + .any(|attr| attr.meta.path().is_ident("varint")) + { + fields_encoded.extend(quote! { + VarInt(self.#field_name as i32).encode(&mut w)?; + }); + } else if field + .attrs + .iter() + .any(|attr| attr.meta.path().is_ident("varlong")) + { + fields_encoded.extend(quote! { + VarLong(self.#field_name as i64).encode(&mut w)?; + }); + } else { + fields_encoded.extend(quote! { + self.#field_name.encode(&mut w)?; + }); + } + } + } + Fields::Unnamed(fields) => { + for (i, field) in fields.unnamed.iter().enumerate() { + let i = Index::from(i); + + if field + .attrs + .iter() + .any(|attr| attr.meta.path().is_ident("varint")) + { + fields_encoded.extend(quote! { + VarInt(self.#i as i32).encode(&mut w)?; + }); + } else if field + .attrs + .iter() + .any(|attr| attr.meta.path().is_ident("varlong")) + { + fields_encoded.extend(quote! { + VarLong(self.#i as i64).encode(&mut w)?; + }); + } else { + fields_encoded.extend(quote! { + self.#i.encode(&mut w)?; + }); + } + } + } + Fields::Unit => (), + } + + quote! { + impl #generics Encode for #name #generics #where_clause { + fn encode(&self, mut w: impl std::io::Write) -> color_eyre::Result<()> { + #fields_encoded + + Ok(()) + } + } + } + .into() +} diff --git a/crawlspace/src/protocol/packets/login/config.rs b/crawlspace/src/protocol/packets/login/config.rs index 84e741d..3cb71ee 100644 --- a/crawlspace/src/protocol/packets/login/config.rs +++ b/crawlspace/src/protocol/packets/login/config.rs @@ -18,14 +18,14 @@ */ use color_eyre::eyre::{ensure, Result}; -use crawlspace_macro::Packet; +use crawlspace_macro::{Encode, Packet}; use crate::protocol::{ datatypes::{Bounded, VarInt}, Decode, DecodeSized, Encode, Packet, PacketDirection, PacketState, }; -#[derive(Debug, Packet)] +#[derive(Debug, Packet, Encode)] #[packet( id = "minecraft:select_known_packs", clientbound, @@ -36,32 +36,13 @@ pub struct KnownPacksC<'a> { known_packs: Vec>, } -#[derive(Debug)] +#[derive(Debug, Encode)] pub struct KnownPack<'a> { namespace: Bounded<&'a str>, id: Bounded<&'a str>, version: Bounded<&'a str>, } -impl Encode for KnownPacksC<'_> { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - self.known_pack_count.encode(&mut w)?; - self.known_packs.encode(&mut w)?; - - Ok(()) - } -} - -impl Encode for KnownPack<'_> { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - self.namespace.encode(&mut w)?; - self.id.encode(&mut w)?; - self.version.encode(&mut w)?; - - Ok(()) - } -} - impl<'a> KnownPacksC<'a> { pub fn of_version(version: &'a str) -> Self { Self { @@ -115,7 +96,7 @@ impl<'a> Decode<'a> for KnownPack<'a> { } } -#[derive(Debug, Packet)] +#[derive(Debug, Packet, Encode)] #[packet( id = "minecraft:finish_configuration", clientbound, @@ -123,12 +104,6 @@ impl<'a> Decode<'a> for KnownPack<'a> { )] pub struct FinishConfigurationC; -impl Encode for FinishConfigurationC { - fn encode(&self, _w: impl std::io::Write) -> Result<()> { - Ok(()) - } -} - #[derive(Debug, Packet)] #[packet( id = "minecraft:finish_configuration", diff --git a/crawlspace/src/protocol/packets/play/game_event.rs b/crawlspace/src/protocol/packets/play/game_event.rs index fd7cfc5..9de7617 100644 --- a/crawlspace/src/protocol/packets/play/game_event.rs +++ b/crawlspace/src/protocol/packets/play/game_event.rs @@ -17,27 +17,19 @@ * . */ -use crawlspace_macro::Packet; +use crawlspace_macro::{Encode, Packet}; use crate::protocol::{Encode, Packet, PacketDirection, PacketState}; use super::Gamemode; -#[derive(Debug, Packet)] +#[derive(Debug, Packet, Encode)] #[packet(id = "minecraft:game_event", clientbound, state = "PacketState::Play")] pub struct GameEventC { event: u8, value: f32, } -impl Encode for GameEventC { - fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { - self.event.encode(&mut w)?; - self.value.encode(&mut w)?; - Ok(()) - } -} - #[derive(Debug)] #[repr(u8)] #[expect(unused)] diff --git a/crawlspace/src/protocol/packets/play/keepalive.rs b/crawlspace/src/protocol/packets/play/keepalive.rs index f8266d2..c8df52e 100644 --- a/crawlspace/src/protocol/packets/play/keepalive.rs +++ b/crawlspace/src/protocol/packets/play/keepalive.rs @@ -17,20 +17,14 @@ * . */ -use crawlspace_macro::Packet; +use crawlspace_macro::{Encode, Packet}; use crate::protocol::{Decode, Encode, Packet, PacketDirection, PacketState}; -#[derive(Debug, Packet)] +#[derive(Debug, Packet, Encode)] #[packet(id = "minecraft:keep_alive", clientbound, state = "PacketState::Play")] pub struct KeepAliveC(pub i64); -impl Encode for KeepAliveC { - fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { - self.0.encode(&mut w) - } -} - #[derive(Debug, Packet)] #[packet(id = "minecraft:keep_alive", serverbound, state = "PacketState::Play")] #[expect(unused)] diff --git a/crawlspace/src/protocol/packets/play/teleport.rs b/crawlspace/src/protocol/packets/play/teleport.rs index eea06ea..cd4fd70 100644 --- a/crawlspace/src/protocol/packets/play/teleport.rs +++ b/crawlspace/src/protocol/packets/play/teleport.rs @@ -19,19 +19,21 @@ use std::sync::atomic::{AtomicI32, Ordering}; -use crawlspace_macro::Packet; +use crawlspace_macro::{Encode, Packet}; use crate::protocol::{datatypes::VarInt, Decode, Encode, Packet, PacketDirection, PacketState}; static TP_ID: AtomicI32 = AtomicI32::new(0); -#[derive(Debug, Packet)] +#[derive(Debug, Packet, Encode)] #[packet( id = "minecraft:player_position", clientbound, state = "PacketState::Play" )] pub struct SynchronisePositionC { + #[varint] + pub id: i32, x: f64, y: f64, z: f64, @@ -41,7 +43,6 @@ pub struct SynchronisePositionC { yaw: f32, pitch: f32, flags: i32, - pub id: i32, } #[allow(unused)] @@ -130,23 +131,6 @@ impl SynchronisePositionC { } } -impl Encode for SynchronisePositionC { - fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { - VarInt(self.id).encode(&mut w)?; - self.x.encode(&mut w)?; - self.y.encode(&mut w)?; - self.z.encode(&mut w)?; - self.velocity_x.encode(&mut w)?; - self.velocity_y.encode(&mut w)?; - self.velocity_z.encode(&mut w)?; - self.yaw.encode(&mut w)?; - self.pitch.encode(&mut w)?; - self.flags.encode(&mut w)?; - - Ok(()) - } -} - #[derive(Debug, Packet)] #[packet( id = "minecraft:accept_teleportation", diff --git a/crawlspace/src/protocol/packets/play/tick.rs b/crawlspace/src/protocol/packets/play/tick.rs index 73f8364..5735604 100644 --- a/crawlspace/src/protocol/packets/play/tick.rs +++ b/crawlspace/src/protocol/packets/play/tick.rs @@ -17,11 +17,11 @@ * . */ -use crawlspace_macro::Packet; +use crawlspace_macro::{Encode, Packet}; use crate::protocol::{datatypes::VarInt, Encode, Packet, PacketDirection, PacketState}; -#[derive(Debug, Packet)] +#[derive(Debug, Packet, Encode)] #[packet( id = "minecraft:ticking_state", clientbound, @@ -32,24 +32,10 @@ pub struct SetTickingStateC { pub is_frozen: bool, } -impl Encode for SetTickingStateC { - fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { - self.tick_rate.encode(&mut w)?; - self.is_frozen.encode(&mut w)?; - Ok(()) - } -} - -#[derive(Debug, Packet)] +#[derive(Debug, Packet, Encode)] #[packet( id = "minecraft:ticking_step", clientbound, state = "PacketState::Play" )] -pub struct StepTicksC(pub i32); - -impl Encode for StepTicksC { - fn encode(&self, w: impl std::io::Write) -> color_eyre::eyre::Result<()> { - VarInt(self.0).encode(w) - } -} +pub struct StepTicksC(#[varint] pub i32); diff --git a/crawlspace/src/protocol/packets/play/world.rs b/crawlspace/src/protocol/packets/play/world.rs index 2e5d856..cdbf7ad 100644 --- a/crawlspace/src/protocol/packets/play/world.rs +++ b/crawlspace/src/protocol/packets/play/world.rs @@ -21,7 +21,7 @@ use std::collections::HashMap; use bit_vec::BitVec; use bytes::BufMut; -use crawlspace_macro::Packet; +use crawlspace_macro::{Encode, Packet}; use fastnbt::SerOpts; use crate::protocol::{PacketDirection, PacketState}; @@ -37,7 +37,7 @@ use crate::{ CrawlState, }; -#[derive(Debug, Packet)] +#[derive(Debug, Packet, Encode)] #[packet( id = "minecraft:set_chunk_cache_center", clientbound, @@ -48,14 +48,6 @@ pub struct SetCenterChunkC { pub y: VarInt, } -impl Encode for SetCenterChunkC { - fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { - self.x.encode(&mut w)?; - self.y.encode(&mut w)?; - Ok(()) - } -} - #[derive(Debug, Packet)] #[packet( id = "minecraft:level_chunk_with_light", @@ -77,7 +69,7 @@ pub struct ChunkDataUpdateLightC<'a> { block_light_arrays: Vec<&'a [u8]>, } -#[derive(Debug)] +#[derive(Debug, Encode)] struct BlockEntity { packed_xz: u8, y: i16, @@ -85,17 +77,6 @@ struct BlockEntity { data: Vec, } -impl Encode for BlockEntity { - fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { - self.packed_xz.encode(&mut w)?; - self.y.encode(&mut w)?; - self.kind.encode(&mut w)?; - self.data.encode(&mut w)?; - - Ok(()) - } -} - impl From for BlockEntity { fn from(value: world::BlockEntity) -> Self { let data = fastnbt::to_bytes_with_opts(&value.raw_data, fastnbt::SerOpts::network_nbt()) @@ -161,23 +142,13 @@ impl From for BlockEntity { #[derive(Debug)] struct HeightMaps(HashMap); -#[derive(Debug)] +#[derive(Debug, Encode)] struct ChunkSection { block_count: i16, block_states: PalettedContainer, biomes: PalettedContainer, } -impl Encode for ChunkSection { - fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { - self.block_count.encode(&mut w)?; - self.block_states.encode(&mut w)?; - self.biomes.encode(&mut w)?; - - Ok(()) - } -} - #[derive(Debug)] struct PalettedContainer { bits_per_entry: u8, @@ -445,7 +416,7 @@ impl ChunkDataUpdateLightC<'_> { } } -#[derive(Debug, Packet)] +#[derive(Debug, Packet, Encode)] #[packet( id = "minecraft:initialize_border", clientbound, @@ -456,27 +427,17 @@ pub struct InitializeWorldBorderC { pub z: f64, pub old_diameter: f64, pub new_diameter: f64, + #[varlong] pub speed: i64, + #[varint] pub teleport_boundary: i32, + #[varint] pub warning_blocks: i32, + #[varint] pub warning_time_sec: i32, } -impl Encode for InitializeWorldBorderC { - fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { - self.x.encode(&mut w)?; - self.z.encode(&mut w)?; - self.old_diameter.encode(&mut w)?; - self.new_diameter.encode(&mut w)?; - VarLong(self.speed).encode(&mut w)?; - VarInt(self.teleport_boundary).encode(&mut w)?; - VarInt(self.warning_blocks).encode(&mut w)?; - VarInt(self.warning_time_sec).encode(&mut w)?; - Ok(()) - } -} - -#[derive(Debug, Packet)] +#[derive(Debug, Packet, Encode)] #[packet( id = "minecraft:set_border_center", clientbound, @@ -487,24 +448,10 @@ pub struct SetBorderCenterC { pub z: f64, } -impl Encode for SetBorderCenterC { - fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { - self.x.encode(&mut w)?; - self.z.encode(&mut w)?; - Ok(()) - } -} - -#[derive(Debug, Packet)] +#[derive(Debug, Packet, Encode)] #[packet( id = "minecraft:set_border_size", clientbound, state = "PacketState::Play" )] pub struct SetBorderSizeC(pub f64); - -impl Encode for SetBorderSizeC { - fn encode(&self, w: impl std::io::Write) -> color_eyre::eyre::Result<()> { - self.0.encode(w) - } -} -- 2.51.2