Skip to content

Commit

Permalink
Merge pull request #158 from ferrumc-rs/feature/get-registry-entry
Browse files Browse the repository at this point in the history
Compile time (packet ids, registries) stuff
  • Loading branch information
Sweattypalms authored Jan 5, 2025
2 parents 1df4866 + 4387f28 commit 289e743
Show file tree
Hide file tree
Showing 54 changed files with 18,952 additions and 141 deletions.
699 changes: 699 additions & 0 deletions assets/data/packets.json

Large diffs are not rendered by default.

17,880 changes: 17,880 additions & 0 deletions assets/data/registries.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions src/lib/derive_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ proc-macro = true
colored = { workspace = true }
quote = { workspace = true }
syn = { workspace = true, features = ["full"] }
thiserror = { workspace = true }
proc-macro2 = { workspace = true }
proc-macro-crate = { workspace = true }
proc-macro-crate = { workspace = true }
serde_json = { workspace = true }
regex = { workspace = true }
21 changes: 21 additions & 0 deletions src/lib/derive_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod helpers;
mod nbt;
mod net;
mod profiling;
mod static_loading;

#[proc_macro_attribute]
pub fn profile(attr: TokenStream, item: TokenStream) -> TokenStream {
Expand Down Expand Up @@ -44,6 +45,11 @@ pub fn net_decode(input: TokenStream) -> TokenStream {
}

// #=================== PACKETS ===================#
/// You can get the packet_id from:
/// https://protocol.ferrumc.com,
/// In incoming packets (serverbound),
/// You should use the 'resource' value referenced in the packet,
/// e.g. "finish_configuration", which would result in the packet_id being automatically fetched.
#[proc_macro_attribute]
pub fn packet(args: TokenStream, input: TokenStream) -> TokenStream {
net::packets::attribute(args, input)
Expand All @@ -53,4 +59,19 @@ pub fn packet(args: TokenStream, input: TokenStream) -> TokenStream {
pub fn bake_packet_registry(input: TokenStream) -> TokenStream {
net::packets::bake_registry(input)
}

/// Get a packet entry from the packets.json file.
/// returns protocol_id (as 0x??) of the specified packet.
/// e.g. get_packet_entry!("play", "clientbound", "add_entity") -> 0x01
#[proc_macro]
pub fn get_packet_entry(input: TokenStream) -> TokenStream {
static_loading::packets::get(input)
}
// #=================== PACKETS ===================#

/// Get a registry entry from the registries.json file.
/// returns protocol_id (as u64) of the specified entry.
#[proc_macro]
pub fn get_registry_entry(input: TokenStream) -> TokenStream {
static_loading::registry::get(input)
}
32 changes: 8 additions & 24 deletions src/lib/derive_macros/src/net/encode.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,9 @@
use crate::helpers::{get_derive_attributes, StructInfo};
use crate::net::packets::get_packet_details_from_attributes;
use crate::static_loading::packets::PacketBoundiness;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Attribute, DeriveInput, Fields, LitInt};

// Helper function to extract packet ID from attributes
fn extract_packet_id(packet_attr: Vec<Attribute>) -> Option<u8> {
let mut packet_id = None;
packet_attr.iter().for_each(|attr| {
attr.parse_nested_meta(|meta| {
let Some(ident) = meta.path.get_ident() else {
return Ok(());
};

if ident == "packet_id" {
let value = meta.value().expect("value failed");
let value = value.parse::<LitInt>().expect("parse failed");
packet_id = Some(value.base10_parse::<u8>().expect("base10_parse failed"));
}
Ok(())
})
.unwrap();
});
packet_id
}
use syn::{parse_macro_input, DeriveInput, Fields};

// Generate packet ID encoding snippets
fn generate_packet_id_snippets(
Expand Down Expand Up @@ -155,8 +136,11 @@ pub(crate) fn derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

let packet_attr = get_derive_attributes(&input, "packet");
let (packet_id_snippet, async_packet_id_snippet) =
generate_packet_id_snippets(extract_packet_id(packet_attr));
let (packet_id_snippet, async_packet_id_snippet) = generate_packet_id_snippets(
get_packet_details_from_attributes(packet_attr.as_slice(), PacketBoundiness::Clientbound)
.unzip()
.1,
);

let (sync_impl, async_impl) = match &input.data {
syn::Data::Struct(data) => {
Expand Down
181 changes: 113 additions & 68 deletions src/lib/derive_macros/src/net/packets/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,51 @@
use crate::static_loading::packets::{get_packet_id, PacketBoundiness};
use colored::Colorize;
use proc_macro::TokenStream;
use quote::quote;
use quote::{quote, ToTokens};
use regex::Regex;
use std::env;
use std::ops::Add;
use syn::{parse_macro_input, LitInt, LitStr};
use syn::{parse_macro_input, Attribute};

/// Returns: (state, packet_id)
fn parse_packet_attribute(attr: &Attribute) -> Option<(String, String)> {
let attr_str = attr.to_token_stream().to_string();

// This regex matches both formats:
// #[packet(packet_id = "something", state = "play")]
let re = Regex::new(r#"packet_id\s*=\s*"([^"]+)"(?:\s*,\s*)?state\s*=\s*"([^"]+)""#).unwrap();

if let Some(caps) = re.captures(&attr_str) {
let packet_id = caps.get(1).map(|m| m.as_str().to_string())?;
let state = caps.get(2).map(|m| m.as_str().to_string())?;
Some((state, packet_id))
} else {
None
}
}

/// Returns: (state, packet_id)
pub(crate) fn get_packet_details_from_attributes(
attrs: &[Attribute],
bound_to: PacketBoundiness,
) -> Option<(String, u8)> {
let mut val = Option::<(String, String)>::None;

for attr in attrs {
if !attr.path().is_ident("packet") {
continue;
}

val = parse_packet_attribute(attr);
}

let (state, packet_id) = val?;

let packet_id =
parse_packet_id(state.as_str(), packet_id, bound_to).expect("parse_packet_id failed");

Some((state, packet_id))
}

/// Essentially, this just reads all the files in the directory and generates a match arm for each packet.
/// (packet_id, state) => { ... }
Expand Down Expand Up @@ -40,11 +82,21 @@ pub fn bake_registry(input: TokenStream) -> TokenStream {

let start = std::time::Instant::now();

for entry in std::fs::read_dir(dir_path).expect("read_dir call failed") {
let entries = std::fs::read_dir(dir_path).expect("read_dir call failed");

for entry in entries {
let entry = entry.expect("entry failed");
let path = entry.path();
let file_name = path.file_name().expect("file_name failed").to_os_string();

println!(
" {} {}",
"[FERRUMC_MACROS]".bold().blue(),
format!("Parsing file: {}", file_name.to_string_lossy())
.white()
.bold()
);

if !path.is_file() {
continue;
}
Expand All @@ -57,70 +109,47 @@ pub fn bake_registry(input: TokenStream) -> TokenStream {
continue;
};

// format: #[packet(packet_id = 0x00, state = "handshake")]
// If the struct does not have the #[packet(...)] attribute, then skip it.
if !item_struct
.attrs
.iter()
.any(|attr| attr.path().is_ident("packet"))
{
continue;
}

let mut packet_id: Option<u8> = None;
let mut state: Option<String> = None;

for attr in item_struct.attrs {
// #[packet(...)] part.
if !attr.path().is_ident("packet") {
continue;
}

attr.parse_nested_meta(|meta| {
let Some(ident) = meta.path.get_ident() else {
return Ok(());
};

match ident.to_string().as_str() {
"packet_id" => {
let value = meta.value().expect("value failed");
let value = value.parse::<LitInt>().expect("parse failed");
let n: u8 = value.base10_parse().expect("base10_parse failed");
packet_id = Some(n);
}
"state" => {
let value = meta.value().expect("value failed");
let value = value.parse::<LitStr>().expect("parse failed");
let n = value.value();
state = Some(n);
}
&_ => {
return Ok(());
}
}

Ok(())
})
.unwrap();

let packet_id = packet_id.expect("packet_id not found");

let state = state.clone().expect("state not found");
let struct_name = &item_struct.ident;

println!(
" {} {} (ID: {}, State: {}, Struct Name: {})",
"[FERRUMC_MACROS]".bold().blue(),
"Found Packet".white().bold(),
format!("0x{:02X}", packet_id).cyan(),
state.green(),
struct_name.to_string().yellow()
);

let path = format!(
// "crate::net::packets::incoming::{}",
"{}::{}",
base_path,
file_name.to_string_lossy().replace(".rs", "")
);
let struct_path = format!("{}::{}", path, struct_name);

let struct_path =
syn::parse_str::<syn::Path>(&struct_path).expect("parse_str failed");

match_arms.push(quote! {
// format: #[packet(packet_id = 0x00, state = "handshake")]
let (state, packet_id) = get_packet_details_from_attributes(
&item_struct.attrs,
PacketBoundiness::Serverbound,
)
.expect(
"parse_packet_attribute failed\
\nPlease provide the packet_id and state fields in the #[packet(...)] attribute.\
\nExample: #[packet(packet_id = 0x00, state = \"handshake\")]",
);

let struct_name = &item_struct.ident;

println!(
" {} {} (ID: {}, State: {}, Struct Name: {})",
"[FERRUMC_MACROS]".bold().blue(),
"Found Packet".white().bold(),
format!("0x{:02X}", packet_id).cyan(),
state.green(),
struct_name.to_string().yellow()
);

let path = format!(
"{}::{}",
base_path,
file_name.to_string_lossy().replace(".rs", "")
);
let struct_path = format!("{}::{}", path, struct_name);

let struct_path = syn::parse_str::<syn::Path>(&struct_path).expect("parse_str failed");

match_arms.push(quote! {
(#packet_id, #state) => {
// let packet= #struct_path::net_decode(cursor).await?;
let packet = <#struct_path as ferrumc_net_codec::decode::NetDecode>::decode(cursor, &ferrumc_net_codec::decode::NetDecodeOpts::None)?;
Expand All @@ -129,7 +158,6 @@ pub fn bake_registry(input: TokenStream) -> TokenStream {
// tracing::debug!("Received packet: {:?}", packet);
},
});
}
}
}

Expand Down Expand Up @@ -168,6 +196,23 @@ pub fn bake_registry(input: TokenStream) -> TokenStream {
TokenStream::from(output)
}

fn parse_packet_id(state: &str, value: String, bound_to: PacketBoundiness) -> syn::Result<u8> {
//! Sorry to anyone reading this code. The get_packet_id method PANICS if there is any type of error.
//! these macros are treated like trash gah damn. they need better care 😔
// If the user provided a direct integer (like 0x01, or any number) value.
if value.starts_with("0x") {
let value = value.strip_prefix("0x").expect("strip_prefix failed");
let n = u8::from_str_radix(value, 16).expect("from_str_radix failed");
return Ok(n);
}

// If the user provided referencing packet id, then just get that.
let n = get_packet_id(state, bound_to, value.as_str());

Ok(n)
}

/// `#[packet]` attribute is used to declare an incoming/outgoing packet.
///
/// <b>packet_id</b> => The packet id of the packet. In hexadecimal.
Expand Down Expand Up @@ -208,7 +253,7 @@ pub fn attribute(args: TokenStream, input: TokenStream) -> TokenStream {

if !&["packet_id", "state"]
.iter()
.any(|x| args.to_string().contains(x))
.all(|x| args.to_string().contains(x))
{
return TokenStream::from(quote! {
compile_error!(#E);
Expand Down
2 changes: 2 additions & 0 deletions src/lib/derive_macros/src/static_loading/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub(crate) mod packets;
pub(crate) mod registry;
Loading

0 comments on commit 289e743

Please sign in to comment.