I have a macro that add opcodes
method to struct:
#[macro_export]
macro_rules! with_opcode {
(
$(@login_opcode($($login_opcode:expr),*))?
$(@world_opcode($($world_opcode:expr),*))?
$(#[$outer:meta])*
$vis:vis struct $PacketStruct:ident {
$(
$(#[$field_attr: meta])? $field_vis:vis $field_name:ident: $field_type:ty
),*$(,)?
}
$($PacketStructImpl: item)*
) => {
$(#[$outer])*
$vis struct $PacketStruct {
$($(#[$field_attr])? $field_vis $field_name: $field_type),*
}
$($PacketStructImpl)*
impl $PacketStruct {
$(
fn opcodes() -> Vec<u8> {
let opcodes: Vec<u8> = vec![$($login_opcode as u8),*];
opcodes
}
)?
$(
fn opcodes() -> Vec<u32> {
let opcodes: Vec<u32> = vec![$($world_opcode as u32),*];
opcodes
}
)?
}
};
}
I use opcodes method inside my proc macro to evaluate how much bytes to omit before std::io::Cursor
can read:
use proc_macro::{TokenStream};
use proc_macro2::{Ident};
use syn::ItemStruct;
use syn::{parse_macro_input};
use quote::{quote, format_ident};
mod types;
use types::{Attributes, Imports};
#[proc_macro_derive(Packet, attributes(options, dynamic_field))]
pub fn derive_packet(input: TokenStream) -> TokenStream {
let ItemStruct { ident, fields, attrs, .. } = parse_macro_input!(input);
let Imports {
cursor,
binary_converter,
deflate_decoder,
read,
types,
} = Imports::get();
let mut is_compressed = quote!(false);
if attrs.iter().any(|attr| attr.path.is_ident("options")) {
let attributes = attrs.iter().next().unwrap();
let attr: Attributes = attributes.parse_args().unwrap();
let value = attr.compressed.value;
is_compressed = quote!(#value);
}
let field_names = fields.iter().map(|f| {
f.ident.clone()
}).collect::<Vec<Option<Ident>>>();
let mut dynamic_fields: Vec<Option<Ident>> = vec![];
for field in fields.iter() {
let ident = field.ident.clone().unwrap().to_string();
let field_type = field.ty.clone();
let ident = format_ident!("{}", field.ident.as_ref().unwrap());
if field.attrs.iter().any(|attr| attr.path.is_ident("dynamic_field")) {
dynamic_fields.push(Some(ident));
}
}
let initializers = field_names
.iter()
.map(|field_name| {
if dynamic_fields.contains(field_name) {
quote!{ Self::#field_name(&mut reader, &mut initial) }
} else {
quote!{ #binary_converter::read_from(&mut reader).unwrap() }
}
});
let output = quote! {
impl #ident {
pub fn from_binary(buffer: &Vec<u8>) -> Self {
let mut omit_bytes = Self::opcodes()[0].to_le_bytes().len();
if omit_bytes == 4 {
// 2 bytes packet size + 4 bytes opcode size
omit_bytes += 2;
}
let mut buffer = match #is_compressed {
true => {
let mut internal_buffer: Vec<u8> = Vec::new();
// 4 bytes uncompressed + 2 bytes used by zlib
omit_bytes += 6;
let data = &buffer[omit_bytes..];
let mut decoder = #deflate_decoder::new(data);
#read::read_to_end(&mut decoder, &mut internal_buffer).unwrap();
internal_buffer.to_vec()
},
false => buffer.to_vec(),
};
let mut initial_reader = #cursor::new(&mut buffer);
let mut initial = Self {
#(#field_names: #binary_converter::read_from(&mut initial_reader).unwrap()),*
};
let mut reader = #cursor::new(&mut buffer);
Self {
#(#field_names: #initializers),*
}
}
pub fn to_binary(&mut self) -> Vec<u8> {
let mut body = Vec::new();
#(
#binary_converter::write_into(
&mut self.#field_names,
&mut body
).unwrap();
)*
body
}
pub fn unpack(&mut self) -> #types::PacketOutcome {
let opcode = Self::opcodes()[0] as u32;
(opcode, self.to_binary())
}
}
};
TokenStream::from(output)
}
also I use opcodes
method for output.
Inside my project I have a place where I need to generate packets dynamically (smth like that, but this is primary branch where I have not merged macro feature yet).
So, I just trying to find the way how can I do the same in context of my macro.
P.S. usually I implement Income
and Outcome
structs, and since I can pass single opcode or multiple opcodes:
// like this
with_opcode! {
@world_opcode(1)
}
// or this
with_opcode! {
@world_opcode(1, 2, 3)
}
you can see I use only first. I did this for readability to see which opcodes are used with specific packet. I can do the same using comments but I cannot do code folding in comments. Multiple opcodes I use only for Income
, just for clarify. This not functional for now.
P.P.S: with_opcode!
usage is optional, but any struct where I derive my macro should have opcodes
method. So I tried to manually create it using closure to solve the issue with dynamic opcode capturing.