Skip to main content

airi_codegen/
blocks.rs

1use std::{collections::HashMap, fs::File, iter};
2
3use heck::ToPascalCase;
4use indexmap::IndexMap;
5use itertools::Itertools;
6use proc_macro2::TokenStream;
7use quote::{format_ident, quote};
8
9pub fn build() -> TokenStream {
10    // panic!("{:?}", std::env::current_dir());
11    let file = File::open("./airi-codegen/assets/block_properties.json").unwrap();
12    let block_properties: IndexMap<String, IndexMap<String, Vec<String>>> =
13        serde_json::from_reader(file).unwrap();
14
15    let mut block_variants = Vec::new();
16    let mut property_structs = Vec::new();
17    let mut state_enums = Vec::new();
18    let mut struct_impls = Vec::new();
19
20    let _block_state_id = 0;
21
22    // property base name -> Vec<(states, assigned_enum_name)>
23    let mut seen_properties: HashMap<String, Vec<(Vec<String>, String)>> = HashMap::new();
24
25    for (block, properties) in &block_properties {
26        let block_id = block.strip_prefix("block.minecraft").unwrap();
27
28        let variant_name = format_ident!("{}", block_id.to_pascal_case());
29        let struct_name = &variant_name;
30
31        block_variants.push(quote! { #variant_name(#struct_name) });
32
33        let mut struct_fields = Vec::new();
34
35        for (property, states) in properties {
36            let entries = seen_properties.entry(property.clone()).or_default();
37            let assigned_name = if let Some((_, existing_name)) = entries
38                .iter()
39                .find(|(existing_states, _)| existing_states == states)
40            {
41                // same name + same states seen before -> reuse that enum
42                existing_name.clone()
43            } else {
44                // same name but different states -> mint a new numbered variant
45                let n = entries.len();
46                let candidate = if n == 0 {
47                    property.clone()
48                } else {
49                    format!("{}{}", property, variant_name)
50                };
51                entries.push((states.clone(), candidate.clone()));
52
53                let state_enum_name = format_ident!("{}", candidate.to_pascal_case());
54                let state_variants = states.iter().map(|s| {
55                    let prefix = if s.chars().next().unwrap().is_ascii_digit() {
56                        "Num"
57                    } else {
58                        ""
59                    };
60                    format_ident!("{}{}", prefix, s.to_pascal_case())
61                });
62                state_enums.push(quote! {enum #state_enum_name {#(#state_variants),*}});
63
64                candidate
65            };
66
67            let state_enum_name = format_ident!("{}", assigned_name.to_pascal_case());
68
69            let property2 = if property == "type" { "ty" } else { property };
70            let property_ident = format_ident!("{}", property2);
71            struct_fields.push(quote! { #property_ident: #state_enum_name });
72        }
73
74        property_structs.push(quote! { struct #struct_name { #(#struct_fields),*} });
75
76        // used for block ids
77        for _product in properties
78            .iter()
79            .map(|(property, states)| iter::repeat(property).zip(states))
80            .multi_cartesian_product()
81        {
82            let struct_impl = quote! {
83
84                // impl
85            };
86
87            struct_impls.push(struct_impl)
88        }
89    }
90
91    quote! {
92        pub enum Block {
93            #(#block_variants),*
94        }
95
96        #(#property_structs)*
97
98        #(#state_enums)*
99
100        #(#struct_impls)*
101    }
102}
103
104pub trait BlockType {
105    fn block_state_id(&self) -> u16;
106}