Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile time (packet ids, registries) stuff #158

Merged
merged 8 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

4 changes: 3 additions & 1 deletion src/lib/derive_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ 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 }
Sweattypalms marked this conversation as resolved.
Show resolved Hide resolved
regex = "1.11.1"
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 @@ -54,3 +60,18 @@ pub fn bake_packet_registry(input: TokenStream) -> TokenStream {
net::packets::bake_registry(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)
}

/// 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)
}
Sweattypalms marked this conversation as resolved.
Show resolved Hide resolved
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
Loading