Pumpkin/pumpkin-macros/src/lib.rs
Alexander Medvedev 5bfa6528da chore: changed block registration
only require block ids, should make bin size smaller and let the compiler better optimize, should also be faster performance wise
2026-01-23 23:32:17 +01:00

530 lines
17 KiB
Rust

use proc_macro::TokenStream;
use proc_macro_error2::{abort, abort_call_site, proc_macro_error};
use pumpkin_data::Block;
use pumpkin_data::tag::{RegistryKey, get_tag_ids};
use quote::quote;
use syn::spanned::Spanned;
use syn::{self, Attribute, DeriveInput, LitStr, Type, parse_quote};
use syn::{Expr, Field, Fields, ItemStruct, Stmt, parse_macro_input};
#[proc_macro_derive(Event)]
pub fn event(item: TokenStream) -> TokenStream {
let ast = parse_macro_input!(item as DeriveInput);
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
quote! {
impl #impl_generics crate::plugin::Payload for #name #ty_generics #where_clause {
fn get_name_static() -> &'static str {
stringify!(#name)
}
fn get_name(&self) -> &'static str {
stringify!(#name)
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
}
.into()
}
#[proc_macro_attribute]
pub fn cancellable(_args: TokenStream, input: TokenStream) -> TokenStream {
let mut item_struct = parse_macro_input!(input as ItemStruct);
let name = &item_struct.ident;
let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl();
match &mut item_struct.fields {
Fields::Named(fields) => {
if fields
.named
.iter()
.any(|f| f.ident.as_ref().map(|i| i == "cancelled").unwrap_or(false))
{
abort!(fields.span(), "Struct already has a `cancelled` field");
}
let field: Field = parse_quote! {
pub cancelled: bool
};
fields.named.push(field);
}
_ => abort!(
item_struct.span(),
"#[cancellable] can only be used on structs with named fields"
),
}
quote! {
#item_struct
impl #impl_generics crate::plugin::Cancellable for #name #ty_generics #where_clause {
fn cancelled(&self) -> bool {
self.cancelled
}
fn set_cancelled(&mut self, cancelled: bool) {
self.cancelled = cancelled;
}
}
}
.into()
}
#[proc_macro_error]
#[proc_macro]
pub fn send_cancellable(input: TokenStream) -> TokenStream {
let block = parse_macro_input!(input as syn::Block);
let mut event_expr = None;
let mut after_block = None;
let mut cancelled_block = None;
for stmt in block.stmts {
match stmt {
Stmt::Expr(expr, _) => {
// Check if it is a labeled block first
let mut is_special_block = false;
if let Expr::Block(ref b) = expr
&& let Some(ref label) = b.label
{
let label_name = label.name.ident.to_string();
if label_name == "after" {
after_block = Some(b.clone()); // Clone strictly necessary here as we split AST
is_special_block = true;
} else if label_name == "cancelled" {
cancelled_block = Some(b.clone());
is_special_block = true;
}
}
// If it wasn't a special block, it must be the event expression
if !is_special_block {
if event_expr.is_some() {
abort!(
expr.span(),
"Multiple event expressions found. Only one event expression allowed."
);
}
event_expr = Some(expr);
}
}
// Abort on other statements (like `let x = ...`) if strictness is desired
_ => abort!(
stmt.span(),
"Only event expressions and labeled blocks allowed in `send_cancellable!`"
),
}
}
let event = match event_expr {
Some(e) => e,
None => abort_call_site!("Event expression must be specified"),
};
// Construct the if/else logic
let logic = match (after_block, cancelled_block) {
(Some(after), Some(cancelled)) => quote! {
if !event.cancelled { #after } else { #cancelled }
},
(Some(after), None) => quote! {
if !event.cancelled { #after }
},
(None, Some(cancelled)) => quote! {
if event.cancelled { #cancelled }
},
(None, None) => quote! {},
};
quote! {
let event = crate::PLUGIN_MANAGER.fire(#event).await;
#logic
}
.into()
}
#[proc_macro_attribute]
pub fn packet(args: TokenStream, item: TokenStream) -> TokenStream {
let packet_id_expr = parse_macro_input!(args as Expr);
let ast = parse_macro_input!(item as DeriveInput);
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
quote! {
#ast
impl #impl_generics crate::packet::Packet for #name #ty_generics #where_clause {
const PACKET_ID: i32 = #packet_id_expr;
}
}
.into()
}
#[proc_macro_attribute]
pub fn pumpkin_block(args: TokenStream, item: TokenStream) -> TokenStream {
let input_item = item.clone();
let arg_lit = parse_macro_input!(args as LitStr);
let arg_value = arg_lit.value();
let block_name = arg_value.strip_prefix("minecraft:").unwrap_or(&arg_value);
let block = match Block::from_name(block_name) {
Some(b) => b,
None => {
return syn::Error::new(arg_lit.span(), "Invalid block name")
.to_compile_error()
.into();
}
};
let block_id = block.id;
let ast = parse_macro_input!(item as DeriveInput);
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let generated = quote! {
impl #impl_generics crate::block::BlockMetadata for #name #ty_generics #where_clause {
fn ids() -> Box<[u16]> {
[#block_id].into()
}
}
};
// Combine original item and new impl
let mut output = input_item;
output.extend(TokenStream::from(generated));
output
}
#[proc_macro_attribute]
pub fn pumpkin_block_from_tag(args: TokenStream, item: TokenStream) -> TokenStream {
let original_item = item.clone();
let arg_lit = parse_macro_input!(args as LitStr);
let ast = parse_macro_input!(item as DeriveInput);
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let full_tag = arg_lit.value();
let values = match get_tag_ids(RegistryKey::Block, &full_tag) {
Some(v) => v,
None => {
return syn::Error::new(
arg_lit.span(),
format!("Failed to get tag IDs: {}", full_tag),
)
.to_compile_error()
.into();
}
};
let expanded = quote! {
impl #impl_generics crate::block::BlockMetadata for #name #ty_generics #where_clause {
fn ids() -> Box<[u16]> {
Box::new([ #(#values),* ])
}
}
};
let mut output = original_item;
output.extend(TokenStream::from(expanded));
output
}
// #[proc_macro_error]
// #[proc_macro_attribute]
// pub fn block_property(input: TokenStream, item: TokenStream) -> TokenStream {
// let ast: syn::DeriveInput = syn::parse(item.clone()).unwrap();
// let name = &ast.ident;
// let (impl_generics, ty_generics, _) = ast.generics.split_for_impl();
// let input_string = input.to_string();
// let input_parts: Vec<&str> = input_string.split("[").collect();
// let property_name = input_parts[0].trim_ascii().trim_matches(&['"', ','][..]);
// let mut property_values: Vec<&str> = Vec::new();
// if input_parts.len() > 1 {
// property_values = input_parts[1]
// .trim_matches(']')
// .split(", ")
// .map(|p| p.trim_ascii().trim_matches(&['"', ','][..]))
// .collect::<Vec<&str>>();
// }
// let item: proc_macro2::TokenStream = item.into();
// let (variants, is_enum): (Vec<proc_macro2::Ident>, bool) = match ast.data {
// syn::Data::Enum(enum_item) => (
// enum_item.variants.into_iter().map(|v| v.ident).collect(),
// true,
// ),
// syn::Data::Struct(s) => {
// let fields = match s.fields {
// Fields::Named(f) => abort!(f.span(), "Block properties can't have named fields"),
// Fields::Unnamed(fields) => fields.unnamed,
// Fields::Unit => abort!(s.fields.span(), "Block properties must have fields"),
// };
// if fields.len() != 1 {
// abort!(
// fields.span(),
// "Block properties `struct`s must have exactly one field"
// );
// }
// let field = fields.first().unwrap();
// let ty = &field.ty;
// let struct_type = match field.ty {
// syn::Type::Path(ref type_path) => {
// type_path.path.segments.first().unwrap().ident.to_string()
// }
// ref other => abort!(
// other.span(),
// "Block properties can only have primitive types"
// ),
// };
// match struct_type.as_str() {
// "bool" => (
// vec![
// proc_macro2::Ident::new("true", proc_macro2::Span::call_site()),
// proc_macro2::Ident::new("false", proc_macro2::Span::call_site()),
// ],
// false,
// ),
// other => abort!(
// ty.span(),
// format!("`{other}` is not supported (why not implement it yourself?)")
// ),
// }
// }
// _ => abort_call_site!("Block properties can only be `enum`s or `struct`s"),
// };
// let values = variants.iter().enumerate().map(|(i, v)| match is_enum {
// true => {
// let mut value = v.to_string().to_snake_case();
// if !property_values.is_empty() && i < property_values.len() {
// value = property_values[i].to_string();
// }
// quote! {
// Self::#v => #value.to_string(),
// }
// }
// false => {
// let value = v.to_string();
// quote! {
// Self(#v) => #value.to_string(),
// }
// }
// });
// let from_values = variants.iter().enumerate().map(|(i, v)| match is_enum {
// true => {
// let mut value = v.to_string().to_snake_case();
// if !property_values.is_empty() && i < property_values.len() {
// value = property_values[i].to_string();
// }
// quote! {
// #value => Self::#v,
// }
// }
// false => {
// let value = v.to_string();
// quote! {
// #value => Self(#v),
// }
// }
// });
// let extra_fns = variants.iter().map(|v| {
// let title = proc_macro2::Ident::new(
// &v.to_string().to_pascal_case(),
// proc_macro2::Span::call_site(),
// );
// quote! {
// pub fn #title() -> Self {
// Self(#v)
// }
// }
// });
// let extra = if is_enum {
// quote! {}
// } else {
// quote! {
// impl #name {
// #(#extra_fns)*
// }
// }
// };
// let code = quote! {
// #item
// impl #impl_generics pumpkin_world::block::properties::BlockPropertyMetadata for #name #ty_generics {
// fn name(&self) -> &'static str {
// #property_name
// }
// fn value(&self) -> String {
// match self {
// #(#values)*
// }
// }
// fn from_value(value: String) -> Self {
// match value.as_str() {
// #(#from_values)*
// _ => panic!("Invalid value for block property"),
// }
// }
// }
// #extra
// };
// code.into()
// }
#[rustfmt::skip]
#[proc_macro_derive(PacketWrite, attributes(serial))]
pub fn derive_serialize(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
let name = &input.ident;
let fields = if let syn::Data::Struct(data) = &input.data {
data.fields.iter().map(|f| {
let ident = f.ident.as_ref().unwrap();
let (is_big_endian, no_prefix) = check_serial_attributes(&f.attrs);
let is_vec = is_vec(&f.ty);
if is_vec && !no_prefix {
// Vec with prefix: write VarUInt length, then data
if is_big_endian {
quote! {
crate::codec::var_uint::VarUInt(self.#ident.len() as u32).write(writer)?;
self.#ident.write_be(writer)?;
}
} else {
quote! {
crate::codec::var_uint::VarUInt(self.#ident.len() as u32).write(writer)?;
self.#ident.write(writer)?;
}
}
} else {
// Non-Vec or Vec with no_prefix: write directly
if is_big_endian {
quote! {
self.#ident.write_be(writer)?;
}
} else {
quote! {
self.#ident.write(writer)?;
}
}
}
})
} else {
unimplemented!()
};
let expanded = quote! {
impl PacketWrite for #name {
fn write<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
#(#fields)*
Ok(())
}
}
};
expanded.into()
}
#[rustfmt::skip]
#[proc_macro_derive(PacketRead, attributes(serial))]
pub fn derive_deserialize(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
let name = &input.ident;
let fields = if let syn::Data::Struct(data) = &input.data {
data.fields.iter().map(|f| {
let ident = f.ident.as_ref().unwrap();
let (is_big_endian, no_prefix) = check_serial_attributes(&f.attrs);
let is_vec = is_vec(&f.ty);
if is_vec && !no_prefix {
// Vec with prefix: read VarUInt length, then data
quote! {
#ident: {
let len = crate::codec::var_uint::VarUInt::read(reader)?.0 as usize;
let mut buf = vec![0u8; len];
reader.read_exact(&mut buf)?;
buf
}
}
} else {
// Non-Vec or Vec with no_prefix: read directly
if is_big_endian {
quote! {
#ident: PacketRead::read_be(reader)?
}
} else {
quote! {
#ident: PacketRead::read(reader)?
}
}
}
})
} else {
unimplemented!()
};
let expanded = quote! {
impl PacketRead for #name {
fn read<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
Ok(Self {
#(#fields),*
})
}
}
};
expanded.into()
}
fn check_serial_attributes(attrs: &[Attribute]) -> (bool, bool) {
let mut is_big_endian = false;
let mut no_prefix = false;
for attr in attrs.iter() {
if attr.path().is_ident("serial") {
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("big_endian") {
is_big_endian = true;
} else if meta.path.is_ident("no_prefix") {
no_prefix = true;
}
Ok(())
});
}
}
(is_big_endian, no_prefix)
}
fn is_vec(ty: &Type) -> bool {
if let Type::Path(type_path) = ty {
type_path
.path
.segments
.iter()
.last()
.map(|segment| segment.ident == "Vec")
.unwrap_or(false)
} else {
false
}
}