basalt_api/world/block_entity.rs
1//! Block entities — persistent per-block state.
2//!
3//! Block entities store data that standard block states cannot represent,
4//! such as chest inventories, furnace cook progress, or sign text.
5//! They are keyed by absolute world position and persisted with the chunk.
6
7use basalt_types::Slot;
8
9/// A block entity with typed data.
10///
11/// Each variant holds the state specific to that block type.
12/// New variants are added as more interactive blocks are implemented.
13#[derive(Debug, Clone)]
14pub enum BlockEntity {
15 /// A chest with 27 item slots (3 rows of 9).
16 Chest {
17 /// The 27 inventory slots.
18 slots: Box<[Slot; 27]>,
19 },
20}
21
22/// Type discriminator for [`BlockEntity`].
23///
24/// A small `Copy`/`Eq`/`Hash` enum used to identify the kind of a
25/// block entity without owning its data. Use [`BlockEntity::kind`] to
26/// extract the discriminator.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum BlockEntityKind {
29 /// A chest block entity.
30 Chest,
31}
32
33impl BlockEntity {
34 /// Creates a new empty chest block entity.
35 pub fn empty_chest() -> Self {
36 Self::Chest {
37 slots: Box::new(std::array::from_fn(|_| Slot::empty())),
38 }
39 }
40
41 /// Returns the [`BlockEntityKind`] discriminator.
42 pub fn kind(&self) -> BlockEntityKind {
43 match self {
44 Self::Chest { .. } => BlockEntityKind::Chest,
45 }
46 }
47}
48
49impl From<&BlockEntity> for BlockEntityKind {
50 fn from(be: &BlockEntity) -> Self {
51 be.kind()
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn empty_chest_has_27_empty_slots() {
61 let be = BlockEntity::empty_chest();
62 match &be {
63 BlockEntity::Chest { slots } => {
64 assert_eq!(slots.len(), 27);
65 assert!(slots.iter().all(|s| s.is_empty()));
66 }
67 }
68 }
69
70 #[test]
71 fn empty_chest_has_chest_kind() {
72 let be = BlockEntity::empty_chest();
73 assert_eq!(be.kind(), BlockEntityKind::Chest);
74 assert_eq!(BlockEntityKind::from(&be), BlockEntityKind::Chest);
75 }
76}