aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2025-08-20 17:40:55 -1100
committermat <git@matdoes.dev>2025-08-20 17:41:06 -1100
commit01e0541b2d8d3acc30999e34992479b3d79fbdbd (patch)
tree89f4297fc011255d66a566a91721e90e5658c724
parent3327f1e243253a7b51e38b79012c17767f47c99e (diff)
downloadazalea-drasl-01e0541b2d8d3acc30999e34992479b3d79fbdbd.tar.xz
generate better code in azalea-block
-rw-r--r--azalea-block/azalea-block-macros/src/lib.rs229
-rw-r--r--azalea-block/src/generated.rs7
-rw-r--r--azalea-block/src/lib.rs23
3 files changed, 191 insertions, 68 deletions
diff --git a/azalea-block/azalea-block-macros/src/lib.rs b/azalea-block/azalea-block-macros/src/lib.rs
index 4174ed41..0dfe0a24 100644
--- a/azalea-block/azalea-block-macros/src/lib.rs
+++ b/azalea-block/azalea-block-macros/src/lib.rs
@@ -278,9 +278,16 @@ impl Parse for MakeBlockStates {
struct PropertyVariantData {
pub block_state_ids: Vec<BlockStateIntegerRepr>,
pub ident: Ident,
+ pub variant_index: usize,
pub is_enum: bool,
}
+#[derive(Clone, Debug)]
+struct PropertyMeta {
+ pub name: String,
+ pub index: usize,
+}
+
#[proc_macro]
pub fn make_block_states(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as MakeBlockStates);
@@ -331,7 +338,10 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
#i_lit => #property_struct_name::#variant,
});
- property_variant_types.push(variant.to_string());
+ property_variant_types.push(PropertyMeta {
+ name: variant.to_string(),
+ index: i,
+ });
}
property_enums.extend(quote! {
@@ -340,8 +350,8 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
#property_enum_variants
}
- impl From<crate::block_state::BlockStateIntegerRepr> for #property_struct_name {
- fn from(value: crate::block_state::BlockStateIntegerRepr) -> Self {
+ impl From<BlockStateIntegerRepr> for #property_struct_name {
+ fn from(value: BlockStateIntegerRepr) -> Self {
match value {
#property_from_number_variants
_ => panic!("Invalid property value: {}", value),
@@ -353,17 +363,27 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
PropertyType::Boolean { struct_name } => {
property_value_name = Ident::new("bool", proc_macro2::Span::call_site());
property_struct_name = struct_name.clone();
- property_variant_types = vec!["true".to_string(), "false".to_string()];
+ property_variant_types = vec![
+ PropertyMeta {
+ name: "true".into(),
+ index: 0,
+ },
+ PropertyMeta {
+ name: "false".into(),
+ index: 1,
+ },
+ ];
property_enums.extend(quote! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct #property_struct_name(pub bool);
- impl From<crate::block_state::BlockStateIntegerRepr> for #property_struct_name {
- fn from(value: crate::block_state::BlockStateIntegerRepr) -> Self {
+ impl From<BlockStateIntegerRepr> for #property_struct_name {
+ /// In Minecraft, `0 = true` and `1 = false`.
+ fn from(value: BlockStateIntegerRepr) -> Self {
match value {
- 0 => Self(false),
- 1 => Self(true),
+ 0 => Self(true),
+ 1 => Self(false),
_ => panic!("Invalid property value: {}", value),
}
}
@@ -482,8 +502,6 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
proc_macro2::Span::call_site(),
);
- let mut from_block_to_state_match_inner = quote! {};
-
let first_state_id = state_id;
let mut default_state_id = None;
@@ -506,8 +524,8 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let property = &properties_with_name[i];
let property_name_ident = &property.name_ident;
let property_value_name_ident = &property.property_type;
- let variant =
- Ident::new(&combination[i].to_string(), proc_macro2::Span::call_site());
+ let variant = &combination[i];
+ let variant_ident = Ident::new(&variant.name, proc_macro2::Span::call_site());
// this terrible code just gets the property default as a string
let property_default_as_string =
@@ -517,14 +535,14 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
panic!()
}
};
- if property_default_as_string != combination[i] {
+ if property_default_as_string != variant.name {
is_default = false;
}
let property_variant = if property.is_enum {
- quote! {properties::#property_value_name_ident::#variant}
+ quote! {properties::#property_value_name_ident::#variant_ident}
} else {
- quote! {#variant}
+ quote! {#variant_ident}
};
from_block_to_state_combination_match_inner.extend(quote! {
@@ -535,25 +553,21 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let property_variants = properties_to_state_ids
.entry(property_value_name_ident.to_string())
.or_default();
- let property_variant_data =
- property_variants.iter_mut().find(|v| v.ident == variant);
+ let property_variant_data = property_variants
+ .iter_mut()
+ .find(|v| v.ident == variant_ident);
if let Some(property_variant_data) = property_variant_data {
property_variant_data.block_state_ids.push(state_id);
} else {
property_variants.push(PropertyVariantData {
block_state_ids: vec![state_id],
- ident: variant,
+ ident: variant_ident,
+ variant_index: variant.index,
is_enum: property.is_enum,
});
}
}
- from_block_to_state_match_inner.extend(quote! {
- #block_struct_name {
- #from_block_to_state_combination_match_inner
- } => BlockState::new_const(#state_id),
- });
-
if is_default {
default_state_id = Some(state_id);
}
@@ -612,15 +626,48 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
division *= property_variants_count;
}
+ let mut as_block_state_inner = quote! { #first_state_id };
+ let mut factor: BlockStateIntegerRepr = 1;
+ for i in (0..properties_with_name.len()).rev() {
+ let PropertyWithNameAndDefault {
+ name_ident: property_name_ident,
+ property_value_type,
+ ..
+ } = &properties_with_name[i];
+
+ let property_variants = &block_properties_vec[i];
+ let property_variants_count = property_variants.len() as crate::BlockStateIntegerRepr;
+ if &property_value_type.to_string() == "bool" {
+ // this is not a mistake, it starts with true for some reason, so invert it to
+ // make `true be 0`
+ as_block_state_inner.extend(
+ quote! { + (!self.#property_name_ident as BlockStateIntegerRepr) * #factor},
+ );
+ } else {
+ as_block_state_inner.extend(
+ quote! { + (self.#property_name_ident as BlockStateIntegerRepr) * #factor},
+ );
+ };
+
+ factor *= property_variants_count;
+ }
+
let last_state_id = state_id - 1;
- from_state_to_block_match.extend(quote! {
- #first_state_id..=#last_state_id => {
- let b = b - #first_state_id;
- Box::new(#block_struct_name {
- #from_state_to_block_inner
- })
- },
+ from_state_to_block_match.extend(if first_state_id == last_state_id {
+ quote! {
+ #first_state_id => {
+ Box::new(#block_struct_name { #from_state_to_block_inner })
+ },
+ }
+ } else {
+ quote! {
+ #first_state_id..=#last_state_id => {
+ let b = b - #first_state_id;
+ Box::new(#block_struct_name { #from_state_to_block_inner })
+ },
+ }
});
+
from_registry_block_to_block_match.extend(quote! {
azalea_registry::Block::#block_name_pascal_case => Box::new(#block_struct_name::default()),
});
@@ -644,22 +691,18 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let block_behavior = &block.behavior;
let block_id = block.name.to_string();
- let from_block_to_state_match = if block.properties_and_defaults.is_empty() {
- quote! { BlockState::new_const(#first_state_id) }
- } else {
- quote! {
- match self {
- #from_block_to_state_match_inner
- }
- }
- };
-
- let block_struct = quote! {
- #[derive(Debug, Copy, Clone)]
- pub struct #block_struct_name {
- #block_struct_fields
- }
+ let as_block_state = quote! { BlockState::new_const(#as_block_state_inner) };
+ let mut block_struct = quote! {
+ #[derive(Debug, Copy, Clone, PartialEq)]
+ pub struct #block_struct_name
+ };
+ if block_struct_fields.is_empty() {
+ block_struct.extend(quote! {;});
+ } else {
+ block_struct.extend(quote! { { #block_struct_fields } });
+ }
+ block_struct.extend(quote! {
impl BlockTrait for #block_struct_name {
fn behavior(&self) -> BlockBehavior {
#block_behavior
@@ -668,7 +711,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
#block_id
}
fn as_block_state(&self) -> BlockState {
- #from_block_to_state_match
+ #as_block_state
}
fn as_registry_block(&self) -> azalea_registry::Block {
azalea_registry::Block::#block_name_pascal_case
@@ -688,7 +731,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
}
}
}
- };
+ });
block_structs.extend(block_struct);
}
@@ -697,7 +740,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let mut generated = quote! {
impl BlockState {
/// The highest possible block state ID.
- pub const MAX_STATE: crate::block_state::BlockStateIntegerRepr = #last_state_id;
+ pub const MAX_STATE: BlockStateIntegerRepr = #last_state_id;
/// Get a property from this block state. Will be `None` if the block can't have the property.
///
@@ -724,28 +767,83 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
// ```
let mut property_impls = quote! {};
for (property_struct_name, property_values) in properties_to_state_ids {
- let mut enum_inner_generated = quote! {};
-
- let mut is_enum_ = false;
+ let mut is_enum = false;
+ let mut some_block_states_count = 0;
for PropertyVariantData {
block_state_ids,
- ident,
- is_enum,
- } in property_values
+ is_enum: is_enum_,
+ ..
+ } in &property_values
{
- enum_inner_generated.extend(if is_enum {
- quote! {
- #(#block_state_ids)|* => Some(Self::#ident),
+ some_block_states_count += block_state_ids.len();
+ is_enum = *is_enum_;
+ }
+
+ let mut try_from_block_state;
+ // do a simpler lookup if there's few block states
+ if some_block_states_count > 2048 {
+ // create a lookup table - 0 indicates None
+ let table_size = last_state_id as usize + 1;
+ let mut table = vec![0; table_size];
+ for PropertyVariantData {
+ block_state_ids,
+ variant_index,
+ ..
+ } in property_values
+ {
+ for block_state_id in block_state_ids {
+ // add 1 since we're offsetting for zero
+ table[block_state_id as usize] = variant_index + 1;
}
+ }
+
+ let mut table_inner = quote! {};
+ for entry in table {
+ // this makes it not put the "usize" after the number like 0usize
+ let literal_int = syn::Lit::Int(syn::LitInt::new(
+ &entry.to_string(),
+ proc_macro2::Span::call_site(),
+ ));
+ table_inner.extend(quote! { #literal_int, });
+ }
+
+ try_from_block_state = quote! {
+ static TABLE: &[BlockStateIntegerRepr; #table_size] = &[#table_inner];
+ let res = TABLE[block_state.id() as usize];
+ if res == 0 { return None };
+ };
+ if is_enum {
+ try_from_block_state.extend(quote! { Some(Self::from(res - 1)) });
} else {
- quote! {
- #(#block_state_ids)|* => Some(#ident),
+ try_from_block_state.extend(quote! { Some(res != 2) });
+ }
+ } else {
+ let mut enum_inner_generated = quote! {};
+ for PropertyVariantData {
+ block_state_ids,
+ ident,
+ ..
+ } in property_values
+ {
+ enum_inner_generated.extend(if is_enum {
+ quote! {
+ #(#block_state_ids)|* => Some(Self::#ident),
+ }
+ } else {
+ quote! {
+ #(#block_state_ids)|* => Some(#ident),
+ }
+ });
+ }
+
+ try_from_block_state = quote! {
+ match block_state.id() {
+ #enum_inner_generated
+ _ => None
}
- });
- is_enum_ = is_enum;
+ };
}
- let is_enum = is_enum_;
let property_struct_name =
Ident::new(&property_struct_name, proc_macro2::Span::call_site());
@@ -761,10 +859,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
type Value = #value;
fn try_from_block_state(block_state: BlockState) -> Option<Self::Value> {
- match block_state.id() {
- #enum_inner_generated
- _ => None
- }
+ #try_from_block_state
}
}
};
diff --git a/azalea-block/src/generated.rs b/azalea-block/src/generated.rs
index c3ef7510..f7ec4e6c 100644
--- a/azalea-block/src/generated.rs
+++ b/azalea-block/src/generated.rs
@@ -1,8 +1,13 @@
+//! This code is @generated by codegen/genblocks.py.
+
use std::fmt::Debug;
use azalea_block_macros::make_block_states;
-use crate::{BlockBehavior, BlockState, BlockStates, BlockTrait, Property};
+use crate::{
+ BlockBehavior, BlockState, BlockStates, BlockTrait, Property,
+ block_state::BlockStateIntegerRepr,
+};
make_block_states! {
Properties => {
diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs
index 4f929cd3..ead63bef 100644
--- a/azalea-block/src/lib.rs
+++ b/azalea-block/src/lib.rs
@@ -38,3 +38,26 @@ pub trait Property {
fn try_from_block_state(state: BlockState) -> Option<Self::Value>;
}
+
+#[cfg(test)]
+mod tests {
+ use crate::BlockTrait;
+
+ #[test]
+ pub fn roundtrip_block_state() {
+ let block = crate::blocks::OakTrapdoor {
+ facing: crate::properties::FacingCardinal::East,
+ half: crate::properties::TopBottom::Bottom,
+ open: true,
+ powered: false,
+ waterlogged: false,
+ };
+ let block_state = block.as_block_state();
+ let block_from_state = Box::<dyn BlockTrait>::from(block_state);
+ let block_from_state = block_from_state
+ .downcast_ref::<crate::blocks::OakTrapdoor>()
+ .unwrap()
+ .clone();
+ assert_eq!(block, block_from_state);
+ }
+}