Skip to content

Commit

Permalink
add BlockState::property
Browse files Browse the repository at this point in the history
  • Loading branch information
mat-1 committed Dec 10, 2023
1 parent 10ee7e1 commit f15f032
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 32 deletions.
162 changes: 134 additions & 28 deletions azalea-block/azalea-block-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ struct PropertyDefinitions {
/// `snowy: Snowy(false)` or `axis: properties::Axis::Y`
#[derive(Debug)]
struct PropertyWithNameAndDefault {
// "snowy" / "axis"
name: Ident,
// Snowy / Axis
property_type: Ident,
property_value_type: Ident,
is_enum: bool,
// false / properties::Axis::Y
default: proc_macro2::TokenStream,
}

Expand All @@ -60,20 +64,22 @@ struct BlockDefinition {
}
impl Parse for PropertyWithNameAndDefault {
fn parse(input: ParseStream) -> Result<Self> {
// `snowy: false` or `axis: properties::Axis::Y`
// `snowy: Snowy(false)` or `axis: properties::Axis::Y`
let property_name = input.parse()?;
input.parse::<Token![:]>()?;

let first_ident = input.call(Ident::parse_any)?;
let mut property_default = quote! { #first_ident };

let property_type: Ident;
let property_value_type: Ident;
let mut is_enum = false;

if input.parse::<Token![::]>().is_ok() {
// enum
is_enum = true;
property_type = first_ident;
property_type = first_ident.clone();
property_value_type = first_ident;
let variant = input.parse::<Ident>()?;
property_default = quote! { properties::#property_default::#variant };
} else {
Expand All @@ -86,7 +92,8 @@ impl Parse for PropertyWithNameAndDefault {
let unit_struct_inner_string = unit_struct_inner.to_string();

if matches!(unit_struct_inner_string.as_str(), "true" | "false") {
property_type = Ident::new("bool", first_ident.span());
property_value_type = Ident::new("bool", first_ident.span());
property_type = first_ident;
property_default = quote! { #unit_struct_inner };
} else {
return Err(input.error("Expected a boolean or an enum variant"));
Expand All @@ -96,6 +103,7 @@ impl Parse for PropertyWithNameAndDefault {
Ok(PropertyWithNameAndDefault {
name: property_name,
property_type,
property_value_type,
is_enum,
default: property_default,
})
Expand Down Expand Up @@ -259,6 +267,12 @@ impl Parse for MakeBlockStates {
}
}

struct PropertyVariantData {
pub block_state_ids: Vec<u32>,
pub ident: Ident,
pub is_enum: bool,
}

#[proc_macro]
pub fn make_block_states(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as MakeBlockStates);
Expand Down Expand Up @@ -318,6 +332,12 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
#property_enum_variants
}

// impl Property for #property_struct_name {
// type Value = Self;

// fn try_from_block_state
// }

impl From<u32> for #property_struct_name {
fn from(value: u32) -> Self {
match value {
Expand All @@ -337,6 +357,10 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
#[derive(Debug, Clone, Copy)]
pub struct #property_struct_name(pub bool);

// impl Property for #property_struct_name {
// type Value = bool;
// }

impl From<u32> for #property_struct_name {
fn from(value: u32) -> Self {
match value {
Expand All @@ -360,14 +384,19 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let mut from_registry_block_to_blockstate_match = quote! {};
let mut from_registry_block_to_blockstates_match = quote! {};

// a list of block state ids that are waterlogged
let mut waterlogged_state_ids: Vec<u32> = Vec::new();
// {
// Waterlogged: [
// [ vec of waterlogged = true state ids ],
// [ vec of waterlogged = false state ids ]
// }
// }
let mut properties_to_state_ids: HashMap<String, Vec<PropertyVariantData>> = HashMap::new();

for block in &input.block_definitions.blocks {
let block_property_names = &block
.properties_and_defaults
.iter()
.map(|p| p.property_type.to_string())
.map(|p| p.property_value_type.to_string())
.collect::<Vec<_>>();
let mut block_properties_vec = Vec::new();
for property_name in block_property_names {
Expand Down Expand Up @@ -421,6 +450,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
properties_with_name.push(PropertyWithNameAndDefault {
name: Ident::new(&property_name, proc_macro2::Span::call_site()),
property_type: property.property_type.clone(),
property_value_type: property.property_value_type.clone(),
is_enum: property.is_enum,
default: property.default.clone(),
});
Expand All @@ -436,7 +466,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
// pub has_bottle_2: HasBottle,
let mut block_struct_fields = quote! {};
for PropertyWithNameAndDefault {
property_type: struct_name,
property_value_type,
name,
is_enum,
..
Expand All @@ -445,9 +475,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
// let property_name_snake =
// Ident::new(&property.to_string(), proc_macro2::Span::call_site());
block_struct_fields.extend(if *is_enum {
quote! { pub #name: properties::#struct_name, }
quote! { pub #name: properties::#property_value_type, }
} else {
quote! { pub #name: #struct_name, }
quote! { pub #name: #property_value_type, }
});
}

Expand Down Expand Up @@ -483,36 +513,47 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
for i in 0..properties_with_name.len() {
let property = &properties_with_name[i];
let property_name = &property.name;
let property_struct_name_ident = &property.property_type;
let property_value_name_ident = &property.property_type;
let variant =
Ident::new(&combination[i].to_string(), proc_macro2::Span::call_site());

// this terrible code just gets the property default as a string
let property_default_as_string = if let TokenTree::Ident(i) =
let property_default_as_string = if let TokenTree::Ident(ident) =
property.default.clone().into_iter().last().unwrap()
{
i.to_string()
ident.to_string()
} else {
panic!()
};
if property_default_as_string != combination[i] {
is_default = false;
}

let property_value = if property.is_enum {
quote! {properties::#property_struct_name_ident::#variant}
let property_variant = if property.is_enum {
quote! {properties::#property_value_name_ident::#variant}
} else {
quote! {#variant}
};

from_block_to_state_combination_match_inner.extend(quote! {
#property_name: #property_value,
#property_name: #property_variant,
});

// if "waterlogged" is a property and it's true for this state then add it to
// waterlogged_state_ids
if property_name == "waterlogged" && property_value.to_string() == "true" {
waterlogged_state_ids.push(state_id)
// add to properties_to_state_ids
let property_variants = properties_to_state_ids
.entry(property_value_name_ident.to_string())
.or_insert_with(Vec::new);

Check warning on line 545 in azalea-block/azalea-block-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

use of `or_insert_with` to construct default value

warning: use of `or_insert_with` to construct default value --> azalea-block/azalea-block-macros/src/lib.rs:545:22 | 545 | .or_insert_with(Vec::new); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_default = note: `#[warn(clippy::unwrap_or_default)]` on by default

Check warning on line 545 in azalea-block/azalea-block-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

use of `or_insert_with` to construct default value

warning: use of `or_insert_with` to construct default value --> azalea-block/azalea-block-macros/src/lib.rs:545:22 | 545 | .or_insert_with(Vec::new); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_default = note: `#[warn(clippy::unwrap_or_default)]` on by default
let property_variant_data = property_variants
.iter_mut()
.find(|v| v.ident.to_string() == variant.to_string());

Check warning on line 548 in azalea-block/azalea-block-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

this creates an owned instance just for comparison

warning: this creates an owned instance just for comparison --> azalea-block/azalea-block-macros/src/lib.rs:548:54 | 548 | .find(|v| v.ident.to_string() == variant.to_string()); | -----------------------^^^^^^^^^^^^^^^^^^^ | | | help: try: `variant == v.ident.to_string()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned

Check warning on line 548 in azalea-block/azalea-block-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

this creates an owned instance just for comparison

warning: this creates an owned instance just for comparison --> azalea-block/azalea-block-macros/src/lib.rs:548:31 | 548 | .find(|v| v.ident.to_string() == variant.to_string()); | ^^^^^^^^^^^^^^^^^^^ help: try: `v.ident` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned = note: `#[warn(clippy::cmp_owned)]` on by default

Check warning on line 548 in azalea-block/azalea-block-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

this creates an owned instance just for comparison

warning: this creates an owned instance just for comparison --> azalea-block/azalea-block-macros/src/lib.rs:548:54 | 548 | .find(|v| v.ident.to_string() == variant.to_string()); | -----------------------^^^^^^^^^^^^^^^^^^^ | | | help: try: `variant == v.ident.to_string()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned

Check warning on line 548 in azalea-block/azalea-block-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

this creates an owned instance just for comparison

warning: this creates an owned instance just for comparison --> azalea-block/azalea-block-macros/src/lib.rs:548:31 | 548 | .find(|v| v.ident.to_string() == variant.to_string()); | ^^^^^^^^^^^^^^^^^^^ help: try: `v.ident` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned = note: `#[warn(clippy::cmp_owned)]` on by default
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,
is_enum: property.is_enum,
});
}
}

Expand Down Expand Up @@ -557,13 +598,14 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let PropertyWithNameAndDefault {
property_type: property_struct_name_ident,
name: property_name,
property_value_type,
..
} = &properties_with_name[i];

let property_variants = &block_properties_vec[i];
let property_variants_count = property_variants.len() as u32;
let conversion_code = {
if &property_struct_name_ident.to_string() == "bool" {
if &property_value_type.to_string() == "bool" {
assert_eq!(property_variants_count, 2);
// this is not a mistake, it starts with true for some reason
quote! {(b / #division) % #property_variants_count == 0}
Expand Down Expand Up @@ -659,8 +701,6 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
block_structs.extend(block_struct);
}

let waterlogged_state_ids_match = quote! { #(#waterlogged_state_ids)|* };

let last_state_id = state_id - 1;
let mut generated = quote! {
impl BlockState {
Expand All @@ -670,21 +710,87 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
#last_state_id
}

/// Whether the given block state is waterlogged. This checks for
/// the `waterlogged` field, so it'll return false for water.
pub fn waterlogged(&self) -> bool {
matches!(self.id, #waterlogged_state_ids_match)
/// Get a property from this block state. Will be `None` if the block can't have the property.
///
/// ```
/// fn is_waterlogged(block_state: azalea_block::BlockState) -> bool {
/// block_state.property::<azalea_block::properties::Waterlogged>().unwrap_or_default()
/// }
/// ```
pub fn property<P: Property>(self) -> Option<P::Value> {
P::try_from_block_state(self)
}
}
};

// now impl Property for every property
// ```
// match state_id {
// // this is just an example of how it might look, these state ids are definitely not correct
// 0|3|6 => Some(Self::Axis::X),
// 1|4|7 => Some(Self::Axis::Y),
// 2|5|8 => Some(Self::Axis::Z),
// _ => None
// }
// ```
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;

for PropertyVariantData {
block_state_ids,
ident,
is_enum,
} in property_values
{
enum_inner_generated.extend(if is_enum {
quote! {
#(#block_state_ids)|* => Some(Self::#ident),
}
} else {
quote! {
#(#block_state_ids)|* => Some(#ident),
}
});
is_enum_ = is_enum;
}
let is_enum = is_enum_;

let property_struct_name =
Ident::new(&property_struct_name, proc_macro2::Span::call_site());

let value = if is_enum {
quote! { Self }
} else {
quote! { bool }
};

let property_impl = quote! {
impl Property for #property_struct_name {
type Value = #value;

fn try_from_block_state(block_state: BlockState) -> Option<Self::Value> {
match block_state.id {
#enum_inner_generated
_ => None
}
}
}
};
property_impls.extend(property_impl);
}

generated.extend(quote! {
pub mod properties {
use super::*;

#property_enums

#property_impls
}
};

generated.extend(quote! {
pub mod blocks {
use super::*;

Expand Down
3 changes: 2 additions & 1 deletion azalea-block/src/generated.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Block, BlockBehavior, BlockState, BlockStates};
use crate::{Block, BlockBehavior, BlockState, BlockStates, Property};
use azalea_block_macros::make_block_states;
use std::fmt::Debug;

Expand Down Expand Up @@ -5380,3 +5380,4 @@ make_block_states! {
},
}
}

11 changes: 10 additions & 1 deletion azalea-block/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ impl dyn Block {
}
}

pub trait Property {
type Value;

fn try_from_block_state(state: BlockState) -> Option<Self::Value>;
}

/// A representation of a state a block can be in.
///
/// For example, a stone block only has one state but each possible stair
Expand Down Expand Up @@ -113,7 +119,10 @@ impl Default for FluidState {

impl From<BlockState> for FluidState {
fn from(state: BlockState) -> Self {
if state.waterlogged() {
if state
.property::<crate::properties::Waterlogged>()
.unwrap_or_default()
{
Self {
fluid: azalea_registry::Fluid::Water,
height: 15,
Expand Down
5 changes: 4 additions & 1 deletion azalea-world/src/heightmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ fn blocks_motion(block_state: BlockState) -> bool {

fn motion_blocking(block_state: BlockState) -> bool {
// TODO
!block_state.is_air() || block_state.waterlogged()
!block_state.is_air()
|| block_state
.property::<azalea_block::properties::Waterlogged>()
.unwrap_or_default()
}

impl HeightmapKind {
Expand Down
5 changes: 4 additions & 1 deletion azalea/src/pathfinder/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,10 @@ fn is_block_state_passable(block: BlockState) -> bool {
if block == azalea_registry::Block::Water.into() {
return false;
}
if block.waterlogged() {
if block
.property::<azalea_block::properties::Waterlogged>()
.unwrap_or_default()
{
return false;
}
if block == azalea_registry::Block::Lava.into() {
Expand Down

0 comments on commit f15f032

Please sign in to comment.