1use 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;
18use super::entities::{
19 BarrelBlockEntity, BeehiveBlockEntity, ComparatorBlockEntity, DaylightDetectorBlockEntity,
20 EndGatewayBlockEntity, EndPortalBlockEntity, PistonMovingBlockEntity, PotentSulfurBlockEntity,
21 RawBlockEntity, SignBlockEntity,
22};
23use crate::world::World;
24
25pub type BlockEntityFactory = fn(Weak<World>, BlockPos, BlockStateId) -> SharedBlockEntity;
29
30struct BlockEntityEntry {
32 factory: Option<BlockEntityFactory>,
34}
35
36pub struct BlockEntityRegistry {
42 entries: Vec<BlockEntityEntry>,
43}
44
45impl BlockEntityRegistry {
46 #[must_use]
48 pub fn new() -> Self {
49 let count = REGISTRY.block_entity_types.len();
50 let entries = (0..count)
51 .map(|_| BlockEntityEntry { factory: None })
52 .collect();
53
54 Self { entries }
55 }
56
57 pub fn register(&mut self, block_entity_type: BlockEntityTypeRef, factory: BlockEntityFactory) {
59 let id = block_entity_type.id();
60 self.entries[id].factory = Some(factory);
61 }
62
63 #[must_use]
67 pub fn create(
68 &self,
69 block_entity_type: BlockEntityTypeRef,
70 level: Weak<World>,
71 pos: BlockPos,
72 state: BlockStateId,
73 ) -> Option<SharedBlockEntity> {
74 let id = block_entity_type.id();
75 self.entries.get(id)?.factory.map(|f| f(level, pos, state))
76 }
77
78 #[must_use]
84 pub fn create_or_raw(
85 &self,
86 block_entity_type: BlockEntityTypeRef,
87 level: Weak<World>,
88 pos: BlockPos,
89 state: BlockStateId,
90 ) -> SharedBlockEntity {
91 let id = block_entity_type.id();
92 if let Some(factory) = self.entries.get(id).and_then(|entry| entry.factory) {
93 factory(level, pos, state)
94 } else {
95 Arc::new(RawBlockEntity::new(block_entity_type, level, pos, state))
96 }
97 }
98
99 #[must_use]
101 pub fn create_and_load_or_raw(
102 &self,
103 block_entity_type: BlockEntityTypeRef,
104 level: Weak<World>,
105 pos: BlockPos,
106 state: BlockStateId,
107 nbt: &BorrowedNbtCompound<'_>,
108 ) -> SharedBlockEntity {
109 let id = block_entity_type.id();
110 if let Some(factory) = self.entries.get(id).and_then(|entry| entry.factory) {
111 let entity = factory(level, pos, state);
112 entity.load_additional(nbt);
113 entity
114 } else {
115 let nbt_view: BorrowedRootNbtCompound<'_, '_> = nbt.into();
116 Arc::new(RawBlockEntity::with_data(
117 block_entity_type,
118 level,
119 pos,
120 state,
121 nbt_view.to_owned(),
122 ))
123 }
124 }
125
126 #[must_use]
128 pub fn create_and_load_owned_or_raw(
129 &self,
130 block_entity_type: BlockEntityTypeRef,
131 level: Weak<World>,
132 pos: BlockPos,
133 state: BlockStateId,
134 nbt: NbtCompound,
135 ) -> SharedBlockEntity {
136 let id = block_entity_type.id();
137 if let Some(factory) = self.entries.get(id).and_then(|entry| entry.factory) {
138 let entity = factory(level, pos, state);
139 let mut nbt_bytes = Vec::new();
140 nbt.write(&mut nbt_bytes);
141 if let Ok(borrowed) = read_borrowed_compound(&mut Cursor::new(&nbt_bytes)) {
142 entity.load_additional(&borrowed);
143 } else {
144 log::warn!(
145 "failed to reborrow owned NBT for block entity {}",
146 block_entity_type.key()
147 );
148 }
149 entity
150 } else {
151 Arc::new(RawBlockEntity::with_data(
152 block_entity_type,
153 level,
154 pos,
155 state,
156 nbt,
157 ))
158 }
159 }
160
161 #[must_use]
163 pub fn has_factory(&self, block_entity_type: BlockEntityTypeRef) -> bool {
164 let id = block_entity_type.id();
165 self.entries.get(id).is_some_and(|e| e.factory.is_some())
166 }
167}
168
169impl Default for BlockEntityRegistry {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175pub struct BlockEntityRegistryLock(OnceLock<BlockEntityRegistry>);
177
178impl Deref for BlockEntityRegistryLock {
179 type Target = BlockEntityRegistry;
180
181 fn deref(&self) -> &Self::Target {
182 self.0.get().expect("Block entity registry not initialized")
183 }
184}
185
186impl BlockEntityRegistryLock {
187 pub fn set(&self, registry: BlockEntityRegistry) -> Result<(), BlockEntityRegistry> {
189 self.0.set(registry)
190 }
191
192 fn get_or_init(&self, init: impl FnOnce() -> BlockEntityRegistry) -> &BlockEntityRegistry {
193 self.0.get_or_init(init)
194 }
195}
196
197pub static BLOCK_ENTITIES: BlockEntityRegistryLock = BlockEntityRegistryLock(OnceLock::new());
201
202pub fn init_block_entities() {
206 BLOCK_ENTITIES.get_or_init(|| {
207 let mut registry = BlockEntityRegistry::new();
208
209 registry.register(&vanilla_block_entity_types::SIGN, |level, pos, state| {
211 Arc::new(SignBlockEntity::new(level, pos, state))
212 });
213
214 registry.register(
216 &vanilla_block_entity_types::HANGING_SIGN,
217 |level, pos, state| Arc::new(SignBlockEntity::new_hanging(level, pos, state)),
218 );
219
220 registry.register(&vanilla_block_entity_types::BARREL, |level, pos, state| {
222 Arc::new(BarrelBlockEntity::new(level, pos, state))
223 });
224
225 registry.register(&vanilla_block_entity_types::BEEHIVE, |level, pos, state| {
227 Arc::new(BeehiveBlockEntity::new(level, pos, state))
228 });
229
230 registry.register(
232 &vanilla_block_entity_types::COMPARATOR,
233 |level, pos, state| Arc::new(ComparatorBlockEntity::new(level, pos, state)),
234 );
235
236 registry.register(
237 &vanilla_block_entity_types::DAYLIGHT_DETECTOR,
238 |level, pos, state| Arc::new(DaylightDetectorBlockEntity::new(level, pos, state)),
239 );
240
241 registry.register(&vanilla_block_entity_types::PISTON, |level, pos, state| {
242 Arc::new(PistonMovingBlockEntity::new(level, pos, state))
243 });
244
245 registry.register(
247 &vanilla_block_entity_types::END_GATEWAY,
248 |level, pos, state| Arc::new(EndGatewayBlockEntity::new(level, pos, state)),
249 );
250
251 registry.register(
253 &vanilla_block_entity_types::END_PORTAL,
254 |level, pos, state| Arc::new(EndPortalBlockEntity::new(level, pos, state)),
255 );
256
257 registry.register(
259 &vanilla_block_entity_types::POTENT_SULFUR,
260 |level, pos, state| Arc::new(PotentSulfurBlockEntity::new(level, pos, state)),
261 );
262
263 registry
264 });
265}