Skip to main content

mc_core/block/
property.rs

1use std::fmt::{Debug, Formatter, Result as FmtResult};
2use std::str::FromStr;
3use std::any::Any;
4
5use crate::pos::{Direction, Axis};
6
7
8/// All valid property values types must implement this trait.
9pub trait PropertySerializable: 'static + Copy {
10    fn prop_to_string(self) -> String;
11    fn prop_from_string(value: &str) -> Option<Self>;
12}
13
14
15/// An untyped property trait used for storage in shared property.
16pub trait UntypedProperty: Any + Sync {
17    fn name(&self) -> &'static str;
18    fn len(&self) -> u8;
19    fn prop_to_string(&self, index: u8) -> Option<String>;
20    fn prop_from_string(&self, value: &str) -> Option<u8>;
21}
22
23
24/// Trait for all properties stored in a block state.
25pub trait Property<T: PropertySerializable>: UntypedProperty {
26    fn encode(&self, value: T) -> Option<u8>;
27    fn decode(&self, index: u8) -> Option<T>;
28}
29
30
31impl<T> PropertySerializable for T
32where
33    T: 'static + Copy + ToString + FromStr
34{
35
36    fn prop_to_string(self) -> String {
37        self.to_string()
38    }
39
40    fn prop_from_string(value: &str) -> Option<Self> {
41        Self::from_str(value).ok()
42    }
43
44}
45
46
47/// A boolean block property.
48pub struct BoolProperty(pub &'static str);
49
50impl UntypedProperty for BoolProperty {
51
52    fn name(&self) -> &'static str { self.0 }
53    fn len(&self) -> u8 { 2 }
54
55    fn prop_to_string(&self, index: u8) -> Option<String> {
56        Some((index != 0).to_string())
57    }
58
59    fn prop_from_string(&self, value: &str) -> Option<u8> {
60        Some(if bool::from_str(value).ok()? { 1 } else { 0 })
61    }
62
63}
64
65impl Property<bool> for BoolProperty {
66    fn encode(&self, value: bool) -> Option<u8> {
67        Some(if value { 1 } else { 0 })
68    }
69    fn decode(&self, index: u8) -> Option<bool> {
70        Some(index != 0)
71    }
72}
73
74
75/// An unsigned 8-bits integer block property.
76pub struct IntProperty(pub &'static str, pub u8);
77
78impl UntypedProperty for IntProperty {
79
80    fn name(&self) -> &'static str { self.0 }
81    fn len(&self) -> u8 { self.1 }
82
83    fn prop_to_string(&self, index: u8) -> Option<String> {
84        if index < self.1 { Some(index.to_string()) } else { None }
85    }
86
87    fn prop_from_string(&self, value: &str) -> Option<u8> {
88        let value = u8::from_str(value).ok()?;
89        if value < self.1 { Some(value) } else { None }
90    }
91
92}
93
94impl Property<u8> for IntProperty {
95    fn encode(&self, value: u8) -> Option<u8> {
96        if value < self.1 { Some(value) } else { None }
97    }
98    fn decode(&self, index: u8) -> Option<u8> {
99        if index < self.1 { Some(index) } else { None }
100    }
101}
102
103
104/// An unsigned 8-bits integer block property with offset for first value.
105pub struct RangeProperty(pub &'static str, pub u8, pub u8);
106
107impl UntypedProperty for RangeProperty {
108
109    fn name(&self) -> &'static str { self.0 }
110    fn len(&self) -> u8 { self.2 }
111
112    fn prop_to_string(&self, index: u8) -> Option<String> {
113        if index < self.2 { Some((self.1 + index).to_string()) } else { None }
114    }
115
116    fn prop_from_string(&self, value: &str) -> Option<u8> {
117        let value = u8::from_str(value).ok()?.wrapping_sub(self.1);
118        if value < self.2 { Some(value) } else { None }
119    }
120
121}
122
123impl Property<u8> for RangeProperty {
124    fn encode(&self, value: u8) -> Option<u8> {
125        let value = value.wrapping_sub(self.1);
126        if value < self.2 { Some(value) } else { None }
127    }
128    fn decode(&self, index: u8) -> Option<u8> {
129        if index < self.2 { Some(index + self.1) } else { None }
130    }
131}
132
133
134/// An enum block property, this property use an external statically defined array of values.
135pub struct EnumProperty<T: PropertySerializable + Eq>(pub &'static str, pub &'static [T]);
136
137impl<T> UntypedProperty for EnumProperty<T>
138where
139    T: PropertySerializable + Eq + Sync
140{
141
142    fn name(&self) -> &'static str { self.0 }
143    fn len(&self) -> u8 { self.1.len() as u8 }
144
145    fn prop_to_string(&self, index: u8) -> Option<String> {
146        Some(self.1.get(index as usize)?.prop_to_string())
147    }
148
149    fn prop_from_string(&self, value: &str) -> Option<u8> {
150        let value = T::prop_from_string(value)?;
151        Some(self.1.iter().position(|v| *v == value)? as u8)
152    }
153
154}
155
156impl<T> Property<T> for EnumProperty<T>
157where
158    T: PropertySerializable + Eq + Sync
159{
160    fn encode(&self, value: T) -> Option<u8> {
161        Some(self.1.iter().position(|v| *v == value)? as u8)
162    }
163    fn decode(&self, index: u8) -> Option<T> {
164        Some(*(self.1.get(index as usize)?))
165    }
166}
167
168
169/// An enum block property, the difference with `EnumProperty` is that it uses a const generic to
170/// store values directly inside the structure.
171pub struct ArrayEnumProperty<T: PropertySerializable + Eq, const N: usize>(pub &'static str, pub [T; N]);
172
173impl<T, const N: usize> UntypedProperty for ArrayEnumProperty<T, N>
174where
175    T: PropertySerializable + Eq + Sync
176{
177
178    fn name(&self) -> &'static str { self.0 }
179    fn len(&self) -> u8 { N as u8 }
180
181    fn prop_to_string(&self, index: u8) -> Option<String> {
182        Some(self.1.get(index as usize)?.prop_to_string())
183    }
184
185    fn prop_from_string(&self, value: &str) -> Option<u8> {
186        let value = T::prop_from_string(value)?;
187        Some(self.1.iter().position(|v| *v == value)? as u8)
188    }
189
190}
191
192impl<T, const N: usize> Property<T> for ArrayEnumProperty<T, N>
193where
194    T: PropertySerializable + Eq + Sync
195{
196    fn encode(&self, value: T) -> Option<u8> {
197        Some(self.1.iter().position(|v| *v == value)? as u8)
198    }
199    fn decode(&self, index: u8) -> Option<T> {
200        Some(*(self.1.get(index as usize)?))
201    }
202}
203
204
205impl Debug for dyn UntypedProperty {
206    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
207        f.debug_struct("UntypedProperty").field("name", &self.name()).finish()
208    }
209}
210
211
212#[macro_export]
213macro_rules! blocks_properties {
214    ($($v:vis $id:ident: $type_token:tt $params_token:tt;)*) => {
215        $($crate::_blocks_properties_prop!($v $id: $type_token $params_token);)*
216    };
217}
218
219#[macro_export]
220macro_rules! _blocks_properties_prop {
221    ($v:vis $id:ident: int($name:literal, $count:literal)) => {
222        $v static $id: $crate::block::IntProperty = $crate::block::IntProperty($name, $count);
223    };
224    ($v:vis $id:ident: range($name:literal, $offset:literal, $count:literal)) => {
225        $v static $id: $crate::block::RangeProperty = $crate::block::RangeProperty($name, $offset, $count);
226    };
227    ($v:vis $id:ident: bool($name:literal)) => {
228        $v static $id: $crate::block::BoolProperty = $crate::block::BoolProperty($name);
229    };
230    ($v:vis $id:ident: enum($name:literal, $enum_type:ty, [$($value:ident),+])) => {
231        $v static $id: $crate::block::ArrayEnumProperty<$enum_type, {$crate::count!($($value)+)}> = $crate::block::ArrayEnumProperty($name, [$(<$enum_type>::$value),+]);
232    };
233    ($v:vis $id:ident: enum($name:literal, $enum_type:ty, $values_id:ident)) => {
234        $v static $id: $crate::block::EnumProperty<$enum_type> = $crate::block::EnumProperty($name, &$values_id);
235    };
236}
237
238
239#[macro_export]
240macro_rules! impl_enum_serializable {
241    ($enum_id:ident { $($item_id:ident: $item_name:literal),* }) => {
242        impl $crate::block::PropertySerializable for $enum_id {
243            fn prop_to_string(self) -> String {
244                match self {
245                    $(Self::$item_id => $item_name),*
246                }.to_string()
247            }
248            fn prop_from_string(value: &str) -> Option<Self> {
249                match value {
250                    $($item_name => Some(Self::$item_id),)*
251                    _ => None
252                }
253            }
254        }
255    };
256}
257
258
259#[macro_export]
260macro_rules! def_enum_serializable {
261    ($enum_id:ident { $($item_id:ident: $item_name:literal),* }) => {
262
263        #[derive(Debug, Copy, Clone, Eq, PartialEq)]
264        pub enum $enum_id {
265            $($item_id),*
266        }
267
268        $crate::impl_enum_serializable!($enum_id { $($item_id: $item_name),* });
269
270    };
271}
272
273
274impl_enum_serializable!(Direction {
275    East: "east",
276    West: "west",
277    South: "south",
278    North: "north",
279    Up: "up",
280    Down: "down"
281});
282
283impl_enum_serializable!(Axis {
284    X: "x",
285    Y: "y",
286    Z: "z"
287});