Skip to main content

steel_core/block_entity/
registry.rs

1//! Block entity registry for creating block entity instances.
2
3use std::io::Cursor;
4use std::ops::Deref;
5use std::sync::{Arc, OnceLock, Weak};
6
7use simdnbt::borrow::NbtCompound as BorrowedRootNbtCompound;
8use simdnbt::borrow::{
9    BaseNbtCompound as BorrowedNbtCompound, read_compound as read_borrowed_compound,
10};
11use simdnbt::owned::NbtCompound;
12use steel_registry::block_entity_type::BlockEntityTypeRef;
13use steel_registry::vanilla_block_entity_types;
14use steel_registry::{REGISTRY, RegistryEntry, RegistryExt};
15use steel_utils::{BlockPos, BlockStateId};
16
17use super::SharedBlockEntity;
18#[cfg_attr(
19    not(test),
20    expect(
21        clippy::wildcard_imports,
22        reason = "the registry intentionally imports every block entity implementation"
23    )
24)]
25use super::entities::*;
26use crate::world::World;
27
28/// Factory function type for creating block entities.
29///
30/// Takes the world, position and block state, returns a new block entity instance.
31pub type BlockEntityFactory = fn(Weak<World>, BlockPos, BlockStateId) -> SharedBlockEntity;
32
33/// Registry entry for a block entity type.
34struct BlockEntityEntry {
35    /// Factory function to create instances.
36    factory: Option<BlockEntityFactory>,
37}
38
39/// Registry for block entity factories.
40///
41/// Maps `BlockEntityType` to factory functions that can create block entity instances.
42/// This is used when loading block entities from disk or when blocks with entities
43/// are placed.
44pub struct BlockEntityRegistry {
45    entries: Vec<BlockEntityEntry>,
46}
47
48impl BlockEntityRegistry {
49    /// Creates a new empty registry with entries for all block entity types.
50    #[must_use]
51    pub fn new() -> Self {
52        let count = REGISTRY.block_entity_types.len();
53        let entries = (0..count)
54            .map(|_| BlockEntityEntry { factory: None })
55            .collect();
56
57        Self { entries }
58    }
59
60    /// Registers a factory function for a block entity type.
61    pub fn register(&mut self, block_entity_type: BlockEntityTypeRef, factory: BlockEntityFactory) {
62        let id = block_entity_type.id();
63        self.entries[id].factory = Some(factory);
64    }
65
66    /// Creates a new block entity instance.
67    ///
68    /// Returns `None` if no factory is registered for the given type.
69    #[must_use]
70    pub fn create(
71        &self,
72        block_entity_type: BlockEntityTypeRef,
73        level: Weak<World>,
74        pos: BlockPos,
75        state: BlockStateId,
76    ) -> Option<SharedBlockEntity> {
77        let id = block_entity_type.id();
78        self.entries.get(id)?.factory.map(|f| f(level, pos, state))
79    }
80
81    /// Creates a block entity, falling back to an NBT-preserving unimplemented entity.
82    ///
83    /// Use this for disk/worldgen paths where an unimplemented block entity type must still
84    /// survive save/load. Gameplay paths that require concrete behavior should call
85    /// [`Self::create`] and handle `None`.
86    #[must_use]
87    pub fn create_or_unimplemented(
88        &self,
89        block_entity_type: BlockEntityTypeRef,
90        level: Weak<World>,
91        pos: BlockPos,
92        state: BlockStateId,
93    ) -> SharedBlockEntity {
94        let id = block_entity_type.id();
95        if let Some(factory) = self.entries.get(id).and_then(|entry| entry.factory) {
96            factory(level, pos, state)
97        } else {
98            Arc::new(UnimplementedBlockEntity::new(
99                block_entity_type,
100                level,
101                pos,
102                state,
103            ))
104        }
105    }
106
107    /// Creates and loads a block entity, falling back to an unimplemented entity.
108    #[must_use]
109    pub fn create_and_load_or_unimplemented(
110        &self,
111        block_entity_type: BlockEntityTypeRef,
112        level: Weak<World>,
113        pos: BlockPos,
114        state: BlockStateId,
115        nbt: &BorrowedNbtCompound<'_>,
116    ) -> SharedBlockEntity {
117        let id = block_entity_type.id();
118        if let Some(factory) = self.entries.get(id).and_then(|entry| entry.factory) {
119            let entity = factory(level, pos, state);
120            entity.load_additional(nbt);
121            entity
122        } else {
123            let nbt_view: BorrowedRootNbtCompound<'_, '_> = nbt.into();
124            Arc::new(UnimplementedBlockEntity::with_data(
125                block_entity_type,
126                level,
127                pos,
128                state,
129                nbt_view.to_owned(),
130            ))
131        }
132    }
133
134    /// Creates and loads owned block entity NBT, falling back to an unimplemented entity.
135    #[must_use]
136    pub fn create_and_load_owned_or_unimplemented(
137        &self,
138        block_entity_type: BlockEntityTypeRef,
139        level: Weak<World>,
140        pos: BlockPos,
141        state: BlockStateId,
142        nbt: NbtCompound,
143    ) -> SharedBlockEntity {
144        let id = block_entity_type.id();
145        if let Some(factory) = self.entries.get(id).and_then(|entry| entry.factory) {
146            let entity = factory(level, pos, state);
147            let mut nbt_bytes = Vec::new();
148            nbt.write(&mut nbt_bytes);
149            if let Ok(borrowed) = read_borrowed_compound(&mut Cursor::new(&nbt_bytes)) {
150                entity.load_additional(&borrowed);
151            } else {
152                log::warn!(
153                    "failed to reborrow owned NBT for block entity {}",
154                    block_entity_type.key()
155                );
156            }
157            entity
158        } else {
159            Arc::new(UnimplementedBlockEntity::with_data(
160                block_entity_type,
161                level,
162                pos,
163                state,
164                nbt,
165            ))
166        }
167    }
168
169    /// Returns whether a factory is registered for the given type.
170    #[must_use]
171    pub fn has_factory(&self, block_entity_type: BlockEntityTypeRef) -> bool {
172        let id = block_entity_type.id();
173        self.entries.get(id).is_some_and(|e| e.factory.is_some())
174    }
175}
176
177impl Default for BlockEntityRegistry {
178    fn default() -> Self {
179        Self::new()
180    }
181}
182
183/// Wrapper for the global block entity registry that implements `Deref`.
184pub struct BlockEntityRegistryLock(OnceLock<BlockEntityRegistry>);
185
186impl Deref for BlockEntityRegistryLock {
187    type Target = BlockEntityRegistry;
188
189    fn deref(&self) -> &Self::Target {
190        self.0.get().expect("Block entity registry not initialized")
191    }
192}
193
194impl BlockEntityRegistryLock {
195    /// Sets the registry. Returns `Err` if already initialized.
196    pub fn set(&self, registry: BlockEntityRegistry) -> Result<(), BlockEntityRegistry> {
197        self.0.set(registry)
198    }
199
200    fn get_or_init(&self, init: impl FnOnce() -> BlockEntityRegistry) -> &BlockEntityRegistry {
201        self.0.get_or_init(init)
202    }
203}
204
205/// Global block entity registry.
206///
207/// Access via deref: `BLOCK_ENTITIES.create(type, pos, state)`
208pub static BLOCK_ENTITIES: BlockEntityRegistryLock = BlockEntityRegistryLock(OnceLock::new());
209
210/// Initializes the global block entity registry.
211///
212/// This should be called after the main registry is frozen. Repeated calls are a no-op.
213pub fn init_block_entities() {
214    BLOCK_ENTITIES.get_or_init(|| {
215        let mut registry = BlockEntityRegistry::new();
216
217        // Register sign block entity factory
218        registry.register(&vanilla_block_entity_types::SIGN, |level, pos, state| {
219            Arc::new(SignBlockEntity::new(level, pos, state))
220        });
221
222        // Register hanging sign block entity factory
223        registry.register(
224            &vanilla_block_entity_types::HANGING_SIGN,
225            |level, pos, state| Arc::new(SignBlockEntity::new_hanging(level, pos, state)),
226        );
227
228        // Register barrel block entity factory
229        registry.register(&vanilla_block_entity_types::BARREL, |level, pos, state| {
230            Arc::new(BarrelBlockEntity::new(level, pos, state))
231        });
232
233        registry.register(&vanilla_block_entity_types::BEACON, |level, pos, state| {
234            Arc::new(BeaconBlockEntity::new(level, pos, state))
235        });
236
237        registry.register(&vanilla_block_entity_types::FURNACE, |level, pos, state| {
238            Arc::new(FurnaceBlockEntity::new(level, pos, state))
239        });
240
241        registry.register(
242            &vanilla_block_entity_types::BLAST_FURNACE,
243            |level, pos, state| Arc::new(BlastFurnaceBlockEntity::new(level, pos, state)),
244        );
245
246        registry.register(&vanilla_block_entity_types::SMOKER, |level, pos, state| {
247            Arc::new(SmokerBlockEntity::new(level, pos, state))
248        });
249
250        registry.register(
251            &vanilla_block_entity_types::CAMPFIRE,
252            |level, pos, state| Arc::new(CampfireBlockEntity::new(level, pos, state)),
253        );
254
255        registry.register(
256            &vanilla_block_entity_types::CHISELED_BOOKSHELF,
257            |level, pos, state| Arc::new(ChiseledBookShelfBlockEntity::new(level, pos, state)),
258        );
259
260        // Register jukebox block entity factory
261        registry.register(&vanilla_block_entity_types::JUKEBOX, |level, pos, state| {
262            Arc::new(JukeboxBlockEntity::new(level, pos, state))
263        });
264
265        // Register beehive block entity factory
266        registry.register(&vanilla_block_entity_types::BEEHIVE, |level, pos, state| {
267            Arc::new(BeehiveBlockEntity::new(level, pos, state))
268        });
269
270        registry.register(&vanilla_block_entity_types::BELL, |level, pos, state| {
271            Arc::new(BellBlockEntity::new(level, pos, state))
272        });
273
274        // Register comparator block entity factory
275        registry.register(
276            &vanilla_block_entity_types::COMPARATOR,
277            |level, pos, state| Arc::new(ComparatorBlockEntity::new(level, pos, state)),
278        );
279
280        registry.register(
281            &vanilla_block_entity_types::DAYLIGHT_DETECTOR,
282            |level, pos, state| Arc::new(DaylightDetectorBlockEntity::new(level, pos, state)),
283        );
284
285        registry.register(&vanilla_block_entity_types::PISTON, |level, pos, state| {
286            Arc::new(PistonMovingBlockEntity::new(level, pos, state))
287        });
288
289        registry.register(
290            &vanilla_block_entity_types::BRUSHABLE_BLOCK,
291            |level, pos, state| Arc::new(BrushableBlockEntity::new(level, pos, state)),
292        );
293
294        // Register End gateway block entity factory
295        registry.register(
296            &vanilla_block_entity_types::END_GATEWAY,
297            |level, pos, state| Arc::new(EndGatewayBlockEntity::new(level, pos, state)),
298        );
299
300        // Register End portal block entity factory
301        registry.register(
302            &vanilla_block_entity_types::END_PORTAL,
303            |level, pos, state| Arc::new(EndPortalBlockEntity::new(level, pos, state)),
304        );
305
306        // Register ender chest block entity factory
307        registry.register(
308            &vanilla_block_entity_types::ENDER_CHEST,
309            |level, pos, state| Arc::new(EnderChestBlockEntity::new(level, pos, state)),
310        );
311
312        // Register potent sulfur block entity factory
313        registry.register(
314            &vanilla_block_entity_types::POTENT_SULFUR,
315            |level, pos, state| Arc::new(PotentSulfurBlockEntity::new(level, pos, state)),
316        );
317
318        registry.register(
319            &vanilla_block_entity_types::SHULKER_BOX,
320            |level, pos, state| Arc::new(ShulkerBoxBlockEntity::new(level, pos, state)),
321        );
322
323        registry
324    });
325}