Skip to main content

steel_core/block_entity/
registry.rs

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