1pub(crate) mod block_state_nbt;
27pub mod entities;
28mod registry;
29mod storage;
30
31use simdnbt::FromNbtTag;
32use simdnbt::borrow::{
33 BaseNbtCompound as BorrowedNbtCompound, NbtCompound as BorrowedNbtCompoundView,
34 read_compound as read_borrowed_compound,
35};
36use simdnbt::owned::NbtCompound;
37use smallvec::SmallVec;
38use std::{
39 io::Cursor,
40 ptr,
41 sync::{
42 Arc, Weak,
43 atomic::{AtomicBool, Ordering},
44 },
45};
46
47use steel_registry::blocks::block_state_ext::BlockStateExt as _;
48use steel_registry::{
49 block_entity_type::BlockEntityTypeRef, data_components::DataComponentMap, item_stack::ItemStack,
50};
51use steel_utils::{
52 BlockPos, BlockStateId, ErasedType,
53 locks::{SyncMutex, SyncRwLock},
54};
55
56pub use registry::{BLOCK_ENTITIES, BlockEntityFactory, BlockEntityRegistry, init_block_entities};
57pub(crate) use storage::{
58 BlockEntityInsert, BlockEntityLookup, BlockEntityStorage, ClearedBlockEntities,
59 DetachedBlockEntity, LifecycleDispatchers,
60};
61
62use crate::inventory::lock::ContainerRef;
63use crate::player::Player;
64
65use crate::world::World;
66use crate::world::game_event::SharedGameEventListener;
67
68#[derive(Clone, Copy)]
74pub struct BlockEntityTicker {
75 block_entity_type: BlockEntityTypeRef,
76 tick: fn(&Arc<World>, BlockPos, BlockStateId, &dyn BlockEntity),
77}
78
79impl BlockEntityTicker {
80 #[must_use]
82 pub const fn new(
83 block_entity_type: BlockEntityTypeRef,
84 tick: fn(&Arc<World>, BlockPos, BlockStateId, &dyn BlockEntity),
85 ) -> Self {
86 Self {
87 block_entity_type,
88 tick,
89 }
90 }
91
92 #[must_use]
94 pub const fn for_entity_tick(block_entity_type: BlockEntityTypeRef) -> Self {
95 Self::new(block_entity_type, Self::tick_entity)
96 }
97
98 #[must_use]
100 pub fn for_matching_entity_tick(
101 actual: BlockEntityTypeRef,
102 expected: BlockEntityTypeRef,
103 ) -> Option<Self> {
104 ptr::eq(actual, expected).then(|| Self::for_entity_tick(expected))
105 }
106
107 #[must_use]
109 pub fn accepts(self, block_entity_type: BlockEntityTypeRef) -> bool {
110 ptr::eq(self.block_entity_type, block_entity_type)
111 }
112
113 pub(crate) fn tick(
114 self,
115 world: &Arc<World>,
116 pos: BlockPos,
117 state: BlockStateId,
118 block_entity: &dyn BlockEntity,
119 ) {
120 (self.tick)(world, pos, state, block_entity);
121 }
122
123 fn tick_entity(
124 world: &Arc<World>,
125 _pos: BlockPos,
126 _state: BlockStateId,
127 block_entity: &dyn BlockEntity,
128 ) {
129 block_entity.tick(world);
130 }
131}
132
133struct BlockEntityLifecycle {
134 block_state: BlockStateId,
135 events: SmallVec<[BlockEntityLifecycleEvent; 2]>,
136 dispatching_events: bool,
137}
138
139#[derive(Clone, Copy)]
140enum BlockEntityLifecycleEvent {
141 SetRemoved,
142 ClearRemoved,
143 BlockStateChanged(BlockStateId),
144}
145
146pub struct BlockEntityBase {
151 block_entity_type: BlockEntityTypeRef,
152 level: Weak<World>,
153 pos: BlockPos,
154 removed: AtomicBool,
156 lifecycle: SyncMutex<BlockEntityLifecycle>,
157 components: SyncRwLock<DataComponentMap>,
158}
159
160struct BlockEntityLifecycleDispatchGuard<'a> {
161 base: &'a BlockEntityBase,
162 armed: bool,
163}
164
165impl Drop for BlockEntityLifecycleDispatchGuard<'_> {
166 fn drop(&mut self) {
167 if !self.armed {
168 return;
169 }
170 let mut lifecycle = self.base.lifecycle.lock();
171 lifecycle.events.clear();
172 lifecycle.dispatching_events = false;
173 }
174}
175
176impl BlockEntityBase {
177 #[must_use]
183 pub fn new(
184 block_entity_type: BlockEntityTypeRef,
185 level: Weak<World>,
186 pos: BlockPos,
187 block_state: BlockStateId,
188 ) -> Self {
189 assert!(
190 block_entity_type.is_valid(block_state.get_block()),
191 "invalid block entity {} state {} at {pos:?}",
192 block_entity_type.key,
193 block_state.get_block().key,
194 );
195 Self {
196 block_entity_type,
197 level,
198 pos,
199 removed: AtomicBool::new(false),
200 lifecycle: SyncMutex::new(BlockEntityLifecycle {
201 block_state,
202 events: SmallVec::new(),
203 dispatching_events: false,
204 }),
205 components: SyncRwLock::new(DataComponentMap::new()),
206 }
207 }
208
209 #[must_use]
210 const fn block_entity_type(&self) -> BlockEntityTypeRef {
211 self.block_entity_type
212 }
213
214 #[must_use]
215 const fn pos(&self) -> BlockPos {
216 self.pos
217 }
218
219 #[must_use]
220 fn block_state(&self) -> BlockStateId {
221 self.lifecycle.lock().block_state
222 }
223
224 fn queue_block_state_change(&self, state: BlockStateId) -> bool {
225 assert!(
226 self.block_entity_type.is_valid(state.get_block()),
227 "invalid block entity {} state {} at {:?}",
228 self.block_entity_type.key,
229 state.get_block().key,
230 self.pos,
231 );
232 let mut lifecycle = self.lifecycle.lock();
233 if lifecycle.block_state == state {
234 return false;
235 }
236 lifecycle.block_state = state;
237 lifecycle
238 .events
239 .push(BlockEntityLifecycleEvent::BlockStateChanged(state));
240 if lifecycle.dispatching_events {
241 false
242 } else {
243 lifecycle.dispatching_events = true;
244 true
245 }
246 }
247
248 #[must_use]
249 fn is_removed(&self) -> bool {
250 self.removed.load(Ordering::Relaxed)
251 }
252
253 fn queue_set_removed(&self) -> bool {
254 let mut lifecycle = self.lifecycle.lock();
255 self.removed.store(true, Ordering::Relaxed);
256 lifecycle.events.push(BlockEntityLifecycleEvent::SetRemoved);
257 if lifecycle.dispatching_events {
258 false
259 } else {
260 lifecycle.dispatching_events = true;
261 true
262 }
263 }
264
265 fn queue_clear_removed(&self) -> bool {
266 let mut lifecycle = self.lifecycle.lock();
267 if !self.removed.load(Ordering::Relaxed) {
268 return false;
269 }
270 self.removed.store(false, Ordering::Relaxed);
271 lifecycle
272 .events
273 .push(BlockEntityLifecycleEvent::ClearRemoved);
274 if lifecycle.dispatching_events {
275 false
276 } else {
277 lifecycle.dispatching_events = true;
278 true
279 }
280 }
281
282 #[must_use]
283 fn level(&self) -> Option<Arc<World>> {
284 self.level.upgrade()
285 }
286
287 pub(crate) fn set_changed(&self) {
288 let Some(world) = self.level() else {
289 return;
290 };
291 let state = self.block_state();
292 world.block_entity_changed(self.pos);
293 if !state.is_air() {
294 world.update_neighbor_for_output_signal(self.pos, state.get_block());
295 }
296 }
297
298 fn load_components(&self, nbt: &BorrowedNbtCompound<'_>) {
299 let nbt_view: BorrowedNbtCompoundView<'_, '_> = nbt.into();
300 let components = match nbt_view.get("components") {
301 Some(tag) => DataComponentMap::from_nbt_tag(tag).unwrap_or_else(|| {
302 log::warn!(
303 "Discarding malformed stored components for block entity {} at {:?}",
304 self.block_entity_type.key,
305 self.pos,
306 );
307 DataComponentMap::new()
308 }),
309 None => DataComponentMap::new(),
310 };
311 *self.components.write() = components;
312 }
313
314 fn stored_components(&self) -> DataComponentMap {
315 self.components.read().clone()
316 }
317
318 pub(crate) fn is_valid_container_for(&self, player: &Player) -> bool {
319 if self.is_removed() {
320 return false;
321 }
322 let Some(world) = self.level() else {
323 return false;
324 };
325 let Some(current) = world.get_block_entity(self.pos) else {
326 return false;
327 };
328 ptr::eq(current.base(), self)
329 && player.is_within_block_interaction_range_with_buffer(self.pos, 4.0)
330 }
331}
332
333pub trait BlockEntity: ErasedType + Send + Sync {
340 fn base(&self) -> &BlockEntityBase;
342
343 fn get_type(&self) -> BlockEntityTypeRef {
345 self.base().block_entity_type()
346 }
347
348 fn get_block_pos(&self) -> BlockPos {
350 self.base().pos()
351 }
352
353 fn get_block_state(&self) -> BlockStateId {
355 self.base().block_state()
356 }
357
358 fn is_valid_block_state(&self, state: BlockStateId) -> bool {
362 self.get_type().is_valid(state.get_block())
363 }
364
365 fn on_block_state_changed(&self, _state: BlockStateId) {}
371
372 fn on_set_removed(&self) {}
377
378 fn on_clear_removed(&self) {}
382
383 fn set_changed(&self) {
387 self.base().set_changed();
388 }
389
390 fn get_level(&self) -> Option<Arc<World>> {
394 self.base().level()
395 }
396
397 fn trigger_event(&self, _param_a: i32, _param_b: i32) -> bool {
401 false
402 }
403
404 #[expect(
412 unused_variables,
413 reason = "default trait impl; parameters used by overrides"
414 )]
415 fn pre_remove_side_effects(&self, pos: BlockPos, state: BlockStateId) {
416 }
418
419 fn load_additional(&self, nbt: &BorrowedNbtCompound<'_>);
424
425 fn load_with_components(&self, nbt: &BorrowedNbtCompound<'_>) {
427 self.load_additional(nbt);
428 self.base().load_components(nbt);
429 }
430
431 fn load_with_owned_components(&self, nbt: &NbtCompound) -> Result<(), simdnbt::Error> {
437 let mut encoded = Vec::new();
438 nbt.write(&mut encoded);
439 let borrowed = read_borrowed_compound(&mut Cursor::new(encoded.as_slice()))?;
440 self.load_with_components(&borrowed);
441 Ok(())
442 }
443
444 fn save_additional(&self, nbt: &mut NbtCompound);
448
449 fn save_custom_only(&self) -> NbtCompound {
451 let mut nbt = NbtCompound::new();
452 self.save_additional(&mut nbt);
453 for key in ["id", "x", "y", "z"] {
454 while nbt.remove(key).is_some() {}
455 }
456 nbt
457 }
458
459 fn save_without_metadata(&self) -> NbtCompound {
461 let mut nbt = self.save_custom_only();
462 nbt.insert(
463 "components",
464 self.base().stored_components().to_nbt_tag_ref(),
465 );
466 nbt
467 }
468
469 fn save_with_full_metadata(&self) -> NbtCompound {
471 let mut nbt = self.save_custom_only();
475 let pos = self.get_block_pos();
476 nbt.insert("id", self.get_type().key.to_string());
477 nbt.insert("x", pos.x());
478 nbt.insert("y", pos.y());
479 nbt.insert("z", pos.z());
480 nbt
481 }
482
483 fn get_update_tag(&self) -> Option<NbtCompound> {
488 None
489 }
490
491 #[expect(
496 unused_variables,
497 reason = "default trait impl; parameter used by overrides"
498 )]
499 fn tick(&self, world: &Arc<World>) {}
500
501 fn container_ref(&self) -> Option<ContainerRef> {
503 None
504 }
505
506 fn game_event_listener(&self) -> Option<SharedGameEventListener> {
511 None
512 }
513
514 #[expect(
518 unused_variables,
519 reason = "default trait impl; parameters used by overrides"
520 )]
521 fn apply_components_from_item(&self, item: &ItemStack) {}
522}
523
524pub trait BlockEntityLifecycleExt: BlockEntity {
530 fn is_removed(&self) -> bool {
532 self.base().is_removed()
533 }
534
535 fn set_block_state(&self, state: BlockStateId) {
537 if self.base().queue_block_state_change(state) {
538 self.dispatch_lifecycle_events();
539 }
540 }
541
542 fn set_removed(&self) {
544 if self.base().queue_set_removed() {
545 self.dispatch_lifecycle_events();
546 }
547 }
548
549 fn clear_removed(&self) {
551 if self.base().queue_clear_removed() {
552 self.dispatch_lifecycle_events();
553 }
554 }
555
556 fn dispatch_lifecycle_events(&self) {
561 let mut guard = BlockEntityLifecycleDispatchGuard {
562 base: self.base(),
563 armed: true,
564 };
565 loop {
566 let event = {
567 let mut lifecycle = self.base().lifecycle.lock();
568 if lifecycle.events.is_empty() {
569 lifecycle.dispatching_events = false;
570 guard.armed = false;
571 return;
572 }
573 lifecycle.events.remove(0)
574 };
575 match event {
576 BlockEntityLifecycleEvent::SetRemoved => self.on_set_removed(),
577 BlockEntityLifecycleEvent::ClearRemoved => self.on_clear_removed(),
578 BlockEntityLifecycleEvent::BlockStateChanged(state) => {
579 self.on_block_state_changed(state);
580 }
581 }
582 }
583 }
584}
585
586impl<T: BlockEntity + ?Sized> BlockEntityLifecycleExt for T {}
587
588pub type SharedBlockEntity = Arc<dyn BlockEntity>;