Skip to content

Commit 289e743

Browse files
authored
Merge pull request #158 from ferrumc-rs/feature/get-registry-entry
Compile time (packet ids, registries) stuff
2 parents 1df4866 + 4387f28 commit 289e743

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+18952
-141
lines changed

assets/data/packets.json

+699
Large diffs are not rendered by default.

assets/data/registries.json

+17,880
Large diffs are not rendered by default.

src/lib/derive_macros/Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ proc-macro = true
1414
colored = { workspace = true }
1515
quote = { workspace = true }
1616
syn = { workspace = true, features = ["full"] }
17-
thiserror = { workspace = true }
1817
proc-macro2 = { workspace = true }
19-
proc-macro-crate = { workspace = true }
18+
proc-macro-crate = { workspace = true }
19+
serde_json = { workspace = true }
20+
regex = { workspace = true }

src/lib/derive_macros/src/lib.rs

+21
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod helpers;
77
mod nbt;
88
mod net;
99
mod profiling;
10+
mod static_loading;
1011

1112
#[proc_macro_attribute]
1213
pub fn profile(attr: TokenStream, item: TokenStream) -> TokenStream {
@@ -44,6 +45,11 @@ pub fn net_decode(input: TokenStream) -> TokenStream {
4445
}
4546

4647
// #=================== PACKETS ===================#
48+
/// You can get the packet_id from:
49+
/// https://protocol.ferrumc.com,
50+
/// In incoming packets (serverbound),
51+
/// You should use the 'resource' value referenced in the packet,
52+
/// e.g. "finish_configuration", which would result in the packet_id being automatically fetched.
4753
#[proc_macro_attribute]
4854
pub fn packet(args: TokenStream, input: TokenStream) -> TokenStream {
4955
net::packets::attribute(args, input)
@@ -53,4 +59,19 @@ pub fn packet(args: TokenStream, input: TokenStream) -> TokenStream {
5359
pub fn bake_packet_registry(input: TokenStream) -> TokenStream {
5460
net::packets::bake_registry(input)
5561
}
62+
63+
/// Get a packet entry from the packets.json file.
64+
/// returns protocol_id (as 0x??) of the specified packet.
65+
/// e.g. get_packet_entry!("play", "clientbound", "add_entity") -> 0x01
66+
#[proc_macro]
67+
pub fn get_packet_entry(input: TokenStream) -> TokenStream {
68+
static_loading::packets::get(input)
69+
}
5670
// #=================== PACKETS ===================#
71+
72+
/// Get a registry entry from the registries.json file.
73+
/// returns protocol_id (as u64) of the specified entry.
74+
#[proc_macro]
75+
pub fn get_registry_entry(input: TokenStream) -> TokenStream {
76+
static_loading::registry::get(input)
77+
}

src/lib/derive_macros/src/net/encode.rs

+8-24
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,9 @@
11
use crate::helpers::{get_derive_attributes, StructInfo};
2+
use crate::net::packets::get_packet_details_from_attributes;
3+
use crate::static_loading::packets::PacketBoundiness;
24
use proc_macro::TokenStream;
35
use quote::quote;
4-
use syn::{parse_macro_input, Attribute, DeriveInput, Fields, LitInt};
5-
6-
// Helper function to extract packet ID from attributes
7-
fn extract_packet_id(packet_attr: Vec<Attribute>) -> Option<u8> {
8-
let mut packet_id = None;
9-
packet_attr.iter().for_each(|attr| {
10-
attr.parse_nested_meta(|meta| {
11-
let Some(ident) = meta.path.get_ident() else {
12-
return Ok(());
13-
};
14-
15-
if ident == "packet_id" {
16-
let value = meta.value().expect("value failed");
17-
let value = value.parse::<LitInt>().expect("parse failed");
18-
packet_id = Some(value.base10_parse::<u8>().expect("base10_parse failed"));
19-
}
20-
Ok(())
21-
})
22-
.unwrap();
23-
});
24-
packet_id
25-
}
6+
use syn::{parse_macro_input, DeriveInput, Fields};
267

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

157138
let packet_attr = get_derive_attributes(&input, "packet");
158-
let (packet_id_snippet, async_packet_id_snippet) =
159-
generate_packet_id_snippets(extract_packet_id(packet_attr));
139+
let (packet_id_snippet, async_packet_id_snippet) = generate_packet_id_snippets(
140+
get_packet_details_from_attributes(packet_attr.as_slice(), PacketBoundiness::Clientbound)
141+
.unzip()
142+
.1,
143+
);
160144

161145
let (sync_impl, async_impl) = match &input.data {
162146
syn::Data::Struct(data) => {

src/lib/derive_macros/src/net/packets/mod.rs

+113-68
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,51 @@
1+
use crate::static_loading::packets::{get_packet_id, PacketBoundiness};
12
use colored::Colorize;
23
use proc_macro::TokenStream;
3-
use quote::quote;
4+
use quote::{quote, ToTokens};
5+
use regex::Regex;
46
use std::env;
57
use std::ops::Add;
6-
use syn::{parse_macro_input, LitInt, LitStr};
8+
use syn::{parse_macro_input, Attribute};
9+
10+
/// Returns: (state, packet_id)
11+
fn parse_packet_attribute(attr: &Attribute) -> Option<(String, String)> {
12+
let attr_str = attr.to_token_stream().to_string();
13+
14+
// This regex matches both formats:
15+
// #[packet(packet_id = "something", state = "play")]
16+
let re = Regex::new(r#"packet_id\s*=\s*"([^"]+)"(?:\s*,\s*)?state\s*=\s*"([^"]+)""#).unwrap();
17+
18+
if let Some(caps) = re.captures(&attr_str) {
19+
let packet_id = caps.get(1).map(|m| m.as_str().to_string())?;
20+
let state = caps.get(2).map(|m| m.as_str().to_string())?;
21+
Some((state, packet_id))
22+
} else {
23+
None
24+
}
25+
}
26+
27+
/// Returns: (state, packet_id)
28+
pub(crate) fn get_packet_details_from_attributes(
29+
attrs: &[Attribute],
30+
bound_to: PacketBoundiness,
31+
) -> Option<(String, u8)> {
32+
let mut val = Option::<(String, String)>::None;
33+
34+
for attr in attrs {
35+
if !attr.path().is_ident("packet") {
36+
continue;
37+
}
38+
39+
val = parse_packet_attribute(attr);
40+
}
41+
42+
let (state, packet_id) = val?;
43+
44+
let packet_id =
45+
parse_packet_id(state.as_str(), packet_id, bound_to).expect("parse_packet_id failed");
46+
47+
Some((state, packet_id))
48+
}
749

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

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

43-
for entry in std::fs::read_dir(dir_path).expect("read_dir call failed") {
85+
let entries = std::fs::read_dir(dir_path).expect("read_dir call failed");
86+
87+
for entry in entries {
4488
let entry = entry.expect("entry failed");
4589
let path = entry.path();
4690
let file_name = path.file_name().expect("file_name failed").to_os_string();
4791

92+
println!(
93+
" {} {}",
94+
"[FERRUMC_MACROS]".bold().blue(),
95+
format!("Parsing file: {}", file_name.to_string_lossy())
96+
.white()
97+
.bold()
98+
);
99+
48100
if !path.is_file() {
49101
continue;
50102
}
@@ -57,70 +109,47 @@ pub fn bake_registry(input: TokenStream) -> TokenStream {
57109
continue;
58110
};
59111

60-
// format: #[packet(packet_id = 0x00, state = "handshake")]
112+
// If the struct does not have the #[packet(...)] attribute, then skip it.
113+
if !item_struct
114+
.attrs
115+
.iter()
116+
.any(|attr| attr.path().is_ident("packet"))
117+
{
118+
continue;
119+
}
61120

62-
let mut packet_id: Option<u8> = None;
63-
let mut state: Option<String> = None;
64-
65-
for attr in item_struct.attrs {
66-
// #[packet(...)] part.
67-
if !attr.path().is_ident("packet") {
68-
continue;
69-
}
70-
71-
attr.parse_nested_meta(|meta| {
72-
let Some(ident) = meta.path.get_ident() else {
73-
return Ok(());
74-
};
75-
76-
match ident.to_string().as_str() {
77-
"packet_id" => {
78-
let value = meta.value().expect("value failed");
79-
let value = value.parse::<LitInt>().expect("parse failed");
80-
let n: u8 = value.base10_parse().expect("base10_parse failed");
81-
packet_id = Some(n);
82-
}
83-
"state" => {
84-
let value = meta.value().expect("value failed");
85-
let value = value.parse::<LitStr>().expect("parse failed");
86-
let n = value.value();
87-
state = Some(n);
88-
}
89-
&_ => {
90-
return Ok(());
91-
}
92-
}
93-
94-
Ok(())
95-
})
96-
.unwrap();
97-
98-
let packet_id = packet_id.expect("packet_id not found");
99-
100-
let state = state.clone().expect("state not found");
101-
let struct_name = &item_struct.ident;
102-
103-
println!(
104-
" {} {} (ID: {}, State: {}, Struct Name: {})",
105-
"[FERRUMC_MACROS]".bold().blue(),
106-
"Found Packet".white().bold(),
107-
format!("0x{:02X}", packet_id).cyan(),
108-
state.green(),
109-
struct_name.to_string().yellow()
110-
);
111-
112-
let path = format!(
113-
// "crate::net::packets::incoming::{}",
114-
"{}::{}",
115-
base_path,
116-
file_name.to_string_lossy().replace(".rs", "")
117-
);
118-
let struct_path = format!("{}::{}", path, struct_name);
119-
120-
let struct_path =
121-
syn::parse_str::<syn::Path>(&struct_path).expect("parse_str failed");
122-
123-
match_arms.push(quote! {
121+
// format: #[packet(packet_id = 0x00, state = "handshake")]
122+
let (state, packet_id) = get_packet_details_from_attributes(
123+
&item_struct.attrs,
124+
PacketBoundiness::Serverbound,
125+
)
126+
.expect(
127+
"parse_packet_attribute failed\
128+
\nPlease provide the packet_id and state fields in the #[packet(...)] attribute.\
129+
\nExample: #[packet(packet_id = 0x00, state = \"handshake\")]",
130+
);
131+
132+
let struct_name = &item_struct.ident;
133+
134+
println!(
135+
" {} {} (ID: {}, State: {}, Struct Name: {})",
136+
"[FERRUMC_MACROS]".bold().blue(),
137+
"Found Packet".white().bold(),
138+
format!("0x{:02X}", packet_id).cyan(),
139+
state.green(),
140+
struct_name.to_string().yellow()
141+
);
142+
143+
let path = format!(
144+
"{}::{}",
145+
base_path,
146+
file_name.to_string_lossy().replace(".rs", "")
147+
);
148+
let struct_path = format!("{}::{}", path, struct_name);
149+
150+
let struct_path = syn::parse_str::<syn::Path>(&struct_path).expect("parse_str failed");
151+
152+
match_arms.push(quote! {
124153
(#packet_id, #state) => {
125154
// let packet= #struct_path::net_decode(cursor).await?;
126155
let packet = <#struct_path as ferrumc_net_codec::decode::NetDecode>::decode(cursor, &ferrumc_net_codec::decode::NetDecodeOpts::None)?;
@@ -129,7 +158,6 @@ pub fn bake_registry(input: TokenStream) -> TokenStream {
129158
// tracing::debug!("Received packet: {:?}", packet);
130159
},
131160
});
132-
}
133161
}
134162
}
135163

@@ -168,6 +196,23 @@ pub fn bake_registry(input: TokenStream) -> TokenStream {
168196
TokenStream::from(output)
169197
}
170198

199+
fn parse_packet_id(state: &str, value: String, bound_to: PacketBoundiness) -> syn::Result<u8> {
200+
//! Sorry to anyone reading this code. The get_packet_id method PANICS if there is any type of error.
201+
//! these macros are treated like trash gah damn. they need better care 😔
202+
203+
// If the user provided a direct integer (like 0x01, or any number) value.
204+
if value.starts_with("0x") {
205+
let value = value.strip_prefix("0x").expect("strip_prefix failed");
206+
let n = u8::from_str_radix(value, 16).expect("from_str_radix failed");
207+
return Ok(n);
208+
}
209+
210+
// If the user provided referencing packet id, then just get that.
211+
let n = get_packet_id(state, bound_to, value.as_str());
212+
213+
Ok(n)
214+
}
215+
171216
/// `#[packet]` attribute is used to declare an incoming/outgoing packet.
172217
///
173218
/// <b>packet_id</b> => The packet id of the packet. In hexadecimal.
@@ -208,7 +253,7 @@ pub fn attribute(args: TokenStream, input: TokenStream) -> TokenStream {
208253

209254
if !&["packet_id", "state"]
210255
.iter()
211-
.any(|x| args.to_string().contains(x))
256+
.all(|x| args.to_string().contains(x))
212257
{
213258
return TokenStream::from(quote! {
214259
compile_error!(#E);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub(crate) mod packets;
2+
pub(crate) mod registry;

0 commit comments

Comments
 (0)