Skip to main content

steel_registry/
block_entity_type.rs

1use rustc_hash::FxHashMap;
2use steel_utils::Identifier;
3
4use crate::blocks::BlockRef;
5
6/// Represents a block entity type in Minecraft.
7/// Block entities are used for blocks that need to store additional data
8/// beyond their block state, such as chests, furnaces, signs, etc.
9#[derive(Debug)]
10pub struct BlockEntityType {
11    pub key: Identifier,
12    /// Blocks for which vanilla accepts this block entity type.
13    pub valid_blocks: &'static [BlockRef],
14}
15
16impl BlockEntityType {
17    #[must_use]
18    pub fn is_valid(&self, block: BlockRef) -> bool {
19        self.valid_blocks
20            .iter()
21            .any(|valid_block| std::ptr::eq(*valid_block, block))
22    }
23}
24
25pub type BlockEntityTypeRef = &'static BlockEntityType;
26
27pub struct BlockEntityTypeRegistry {
28    block_entity_types_by_id: Vec<BlockEntityTypeRef>,
29    block_entity_types_by_key: FxHashMap<Identifier, usize>,
30    blocks_with_block_entities: Vec<bool>,
31    allows_registering: bool,
32}
33
34impl BlockEntityTypeRegistry {
35    #[must_use]
36    pub fn new() -> Self {
37        Self {
38            block_entity_types_by_id: Vec::new(),
39            block_entity_types_by_key: FxHashMap::default(),
40            blocks_with_block_entities: Vec::new(),
41            allows_registering: true,
42        }
43    }
44
45    pub fn register(&mut self, block_entity_type: BlockEntityTypeRef) -> usize {
46        assert!(
47            self.allows_registering,
48            "Cannot register block entity types after registry has been frozen"
49        );
50
51        let id = self.block_entity_types_by_id.len();
52        self.block_entity_types_by_id.push(block_entity_type);
53        self.block_entity_types_by_key
54            .insert(block_entity_type.key.clone(), id);
55
56        for block in block_entity_type.valid_blocks {
57            let Some(block_id) = block.id.get().copied() else {
58                panic!(
59                    "block {} must be registered before block entity type {}",
60                    block.key, block_entity_type.key
61                );
62            };
63            if self.blocks_with_block_entities.len() <= block_id {
64                self.blocks_with_block_entities.resize(block_id + 1, false);
65            }
66            self.blocks_with_block_entities[block_id] = true;
67        }
68
69        id
70    }
71
72    pub fn iter(&self) -> impl Iterator<Item = (usize, BlockEntityTypeRef)> + '_ {
73        self.block_entity_types_by_id.iter().copied().enumerate()
74    }
75
76    /// Returns whether at least one block entity type accepts `block`.
77    ///
78    /// Vanilla does not require those accepted-block sets to be globally disjoint, so creation
79    /// remains the owning block behavior's responsibility.
80    #[must_use]
81    pub fn has_block_entity(&self, block: BlockRef) -> bool {
82        let Some(block_id) = block.id.get().copied() else {
83            return false;
84        };
85        self.blocks_with_block_entities
86            .get(block_id)
87            .copied()
88            .unwrap_or(false)
89    }
90}
91
92impl Default for BlockEntityTypeRegistry {
93    fn default() -> Self {
94        Self::new()
95    }
96}
97
98crate::impl_registry!(
99    BlockEntityTypeRegistry,
100    BlockEntityType,
101    block_entity_types_by_id,
102    block_entity_types_by_key,
103    block_entity_types
104);
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use crate::{init_vanilla_registry, vanilla_block_entity_types, vanilla_blocks};
110
111    #[test]
112    fn overlapping_type_memberships_preserve_structural_presence() {
113        init_vanilla_registry();
114        let valid_blocks = Box::leak(vec![&vanilla_blocks::BARREL].into_boxed_slice());
115        let alternate = Box::leak(Box::new(BlockEntityType {
116            key: Identifier::new_static("test", "alternate_barrel"),
117            valid_blocks,
118        }));
119        let mut registry = BlockEntityTypeRegistry::new();
120
121        registry.register(&vanilla_block_entity_types::BARREL);
122        registry.register(alternate);
123
124        assert!(registry.has_block_entity(&vanilla_blocks::BARREL));
125        assert_eq!(registry.iter().count(), 2);
126    }
127}