1pub(crate) mod block_state_nbt;
27mod components;
28pub mod entities;
29mod registry;
30mod storage;
31
32use simdnbt::FromNbtTag;
33use simdnbt::borrow::{
34 BaseNbtCompound as BorrowedNbtCompound, NbtCompound as BorrowedNbtCompoundView,
35 read_compound as read_borrowed_compound,
36};
37use simdnbt::owned::NbtCompound;
38use smallvec::SmallVec;
39use std::{
40 io::Cursor,
41 ptr,
42 sync::{
43 Arc, Weak,
44 atomic::{AtomicBool, Ordering},
45 },
46};
47
48use steel_registry::blocks::block_state_ext::BlockStateExt as _;
49use steel_registry::{
50 block_entity_type::BlockEntityTypeRef, data_components::DataComponentMap, item_stack::ItemStack,
51};
52use steel_utils::{
53 BlockPos, BlockStateId, ErasedType,
54 locks::{SyncMutex, SyncRwLock},
55};
56
57pub use components::ImplicitComponentGetter;
58pub use registry::{BLOCK_ENTITIES, BlockEntityFactory, BlockEntityRegistry, init_block_entities};
59pub(crate) use storage::{
60 BlockEntityInsert, BlockEntityLookup, BlockEntityStorage, ClearedBlockEntities,
61 DetachedBlockEntity, LifecycleDispatchers,
62};
63
64use crate::block_entity::entities::Spawner;
65use crate::inventory::container::DEFAULT_DISTANCE_BUFFER;
66use crate::inventory::lock::ContainerRef;
67use crate::player::Player;
68
69use crate::world::World;
70use crate::world::game_event::SharedGameEventListener;
71
72#[derive(Clone, Copy)]
78pub struct BlockEntityTicker {
79 block_entity_type: BlockEntityTypeRef,
80 tick: fn(&Arc<World>, BlockPos, BlockStateId, &dyn BlockEntity),
81}
82
83impl BlockEntityTicker {
84 #[must_use]
86 pub const fn new(
87 block_entity_type: BlockEntityTypeRef,
88 tick: fn(&Arc<World>, BlockPos, BlockStateId, &dyn BlockEntity),
89 ) -> Self {
90 Self {
91 block_entity_type,
92 tick,
93 }
94 }
95
96 #[must_use]
98 pub const fn for_entity_tick(block_entity_type: BlockEntityTypeRef) -> Self {
99 Self::new(block_entity_type, Self::tick_entity)
100 }
101
102 #[must_use]
104 pub fn for_matching_entity_tick(
105 actual: BlockEntityTypeRef,
106 expected: BlockEntityTypeRef,
107 ) -> Option<Self> {
108 ptr::eq(actual, expected).then(|| Self::for_entity_tick(expected))
109 }
110
111 #[must_use]
113 pub fn accepts(self, block_entity_type: BlockEntityTypeRef) -> bool {
114 ptr::eq(self.block_entity_type, block_entity_type)
115 }
116
117 pub(crate) fn tick(
118 self,
119 world: &Arc<World>,
120 pos: BlockPos,
121 state: BlockStateId,
122 block_entity: &dyn BlockEntity,
123 ) {
124 (self.tick)(world, pos, state, block_entity);
125 }
126
127 fn tick_entity(
128 world: &Arc<World>,
129 _pos: BlockPos,
130 _state: BlockStateId,
131 block_entity: &dyn BlockEntity,
132 ) {
133 block_entity.tick(world);
134 }
135}
136
137struct BlockEntityLifecycle {
138 block_state: BlockStateId,
139 events: SmallVec<[BlockEntityLifecycleEvent; 2]>,
140 dispatching_events: bool,
141}
142
143#[derive(Clone, Copy)]
144enum BlockEntityLifecycleEvent {
145 SetRemoved,
146 ClearRemoved,
147 BlockStateChanged(BlockStateId),
148}
149
150pub struct BlockEntityBase {
155 block_entity_type: BlockEntityTypeRef,
156 level: Weak<World>,
157 pos: BlockPos,
158 removed: AtomicBool,
160 lifecycle: SyncMutex<BlockEntityLifecycle>,
161 components: SyncRwLock<DataComponentMap>,
162}
163
164struct BlockEntityLifecycleDispatchGuard<'a> {
165 base: &'a BlockEntityBase,
166 armed: bool,
167}
168
169impl Drop for BlockEntityLifecycleDispatchGuard<'_> {
170 fn drop(&mut self) {
171 if !self.armed {
172 return;
173 }
174 let mut lifecycle = self.base.lifecycle.lock();
175 lifecycle.events.clear();
176 lifecycle.dispatching_events = false;
177 }
178}
179
180impl BlockEntityBase {
181 #[must_use]
187 pub fn new(
188 block_entity_type: BlockEntityTypeRef,
189 level: Weak<World>,
190 pos: BlockPos,
191 block_state: BlockStateId,
192 ) -> Self {
193 assert!(
194 block_entity_type.is_valid(block_state.get_block()),
195 "invalid block entity {} state {} at {pos:?}",
196 block_entity_type.key,
197 block_state.get_block().key,
198 );
199 Self {
200 block_entity_type,
201 level,
202 pos,
203 removed: AtomicBool::new(false),
204 lifecycle: SyncMutex::new(BlockEntityLifecycle {
205 block_state,
206 events: SmallVec::new(),
207 dispatching_events: false,
208 }),
209 components: SyncRwLock::new(DataComponentMap::new()),
210 }
211 }
212
213 #[must_use]
214 const fn block_entity_type(&self) -> BlockEntityTypeRef {
215 self.block_entity_type
216 }
217
218 #[must_use]
219 const fn pos(&self) -> BlockPos {
220 self.pos
221 }
222
223 #[must_use]
224 fn block_state(&self) -> BlockStateId {
225 self.lifecycle.lock().block_state
226 }
227
228 fn queue_block_state_change(&self, state: BlockStateId) -> bool {
229 assert!(
230 self.block_entity_type.is_valid(state.get_block()),
231 "invalid block entity {} state {} at {:?}",
232 self.block_entity_type.key,
233 state.get_block().key,
234 self.pos,
235 );
236 let mut lifecycle = self.lifecycle.lock();
237 if lifecycle.block_state == state {
238 return false;
239 }
240 lifecycle.block_state = state;
241 lifecycle
242 .events
243 .push(BlockEntityLifecycleEvent::BlockStateChanged(state));
244 if lifecycle.dispatching_events {
245 false
246 } else {
247 lifecycle.dispatching_events = true;
248 true
249 }
250 }
251
252 #[must_use]
253 fn is_removed(&self) -> bool {
254 self.removed.load(Ordering::Relaxed)
255 }
256
257 fn queue_set_removed(&self) -> bool {
258 let mut lifecycle = self.lifecycle.lock();
259 self.removed.store(true, Ordering::Relaxed);
260 lifecycle.events.push(BlockEntityLifecycleEvent::SetRemoved);
261 if lifecycle.dispatching_events {
262 false
263 } else {
264 lifecycle.dispatching_events = true;
265 true
266 }
267 }
268
269 fn queue_clear_removed(&self) -> bool {
270 let mut lifecycle = self.lifecycle.lock();
271 if !self.removed.load(Ordering::Relaxed) {
272 return false;
273 }
274 self.removed.store(false, Ordering::Relaxed);
275 lifecycle
276 .events
277 .push(BlockEntityLifecycleEvent::ClearRemoved);
278 if lifecycle.dispatching_events {
279 false
280 } else {
281 lifecycle.dispatching_events = true;
282 true
283 }
284 }
285
286 #[must_use]
287 fn level(&self) -> Option<Arc<World>> {
288 self.level.upgrade()
289 }
290
291 pub(crate) fn set_changed(&self) {
292 let Some(world) = self.level() else {
293 return;
294 };
295 let state = self.block_state();
296 world.block_entity_changed(self.pos);
297 if !state.is_air() {
298 world.update_neighbor_for_output_signal(self.pos, state.get_block());
299 }
300 }
301
302 fn load_components(&self, nbt: &BorrowedNbtCompound<'_>) {
303 let nbt_view: BorrowedNbtCompoundView<'_, '_> = nbt.into();
304 let components = match nbt_view.get("components") {
305 Some(tag) => DataComponentMap::from_nbt_tag(tag).unwrap_or_else(|| {
306 log::warn!(
307 "Discarding malformed stored components for block entity {} at {:?}",
308 self.block_entity_type.key,
309 self.pos,
310 );
311 DataComponentMap::new()
312 }),
313 None => DataComponentMap::new(),
314 };
315 *self.components.write() = components;
316 }
317
318 fn stored_components(&self) -> DataComponentMap {
319 self.components.read().clone()
320 }
321
322 fn set_components(&self, components: DataComponentMap) {
323 *self.components.write() = components;
324 }
325
326 pub(crate) fn is_valid_container_for(&self, player: &Player) -> bool {
327 if self.is_removed() {
328 return false;
329 }
330 let Some(world) = self.level() else {
331 return false;
332 };
333 let Some(current) = world.get_block_entity(self.pos) else {
334 return false;
335 };
336 ptr::eq(current.base(), self)
337 && player.is_within_block_interaction_range_with_buffer(
338 self.pos,
339 f64::from(DEFAULT_DISTANCE_BUFFER),
340 )
341 }
342}
343
344pub trait BlockEntity: ErasedType + Send + Sync {
351 fn base(&self) -> &BlockEntityBase;
353
354 fn get_type(&self) -> BlockEntityTypeRef {
356 self.base().block_entity_type()
357 }
358
359 fn get_block_pos(&self) -> BlockPos {
361 self.base().pos()
362 }
363
364 fn get_block_state(&self) -> BlockStateId {
366 self.base().block_state()
367 }
368
369 fn is_valid_block_state(&self, state: BlockStateId) -> bool {
371 self.get_type().is_valid(state.get_block())
372 }
373
374 fn on_block_state_changed(&self, _state: BlockStateId) {}
380
381 fn on_set_removed(&self) {}
386
387 fn on_clear_removed(&self) {}
391
392 fn set_changed(&self) {
396 self.base().set_changed();
397 }
398
399 fn get_level(&self) -> Option<Arc<World>> {
403 self.base().level()
404 }
405
406 fn trigger_event(&self, _param_a: i32, _param_b: i32) -> bool {
408 false
409 }
410
411 fn as_spawner(&self) -> Option<&dyn Spawner> {
413 None
414 }
415
416 #[expect(
424 unused_variables,
425 reason = "default trait impl; parameters used by overrides"
426 )]
427 fn pre_remove_side_effects(&self, pos: BlockPos, state: BlockStateId) {
428 }
430
431 fn load_additional(&self, nbt: &BorrowedNbtCompound<'_>);
436
437 fn load_with_components(&self, nbt: &BorrowedNbtCompound<'_>) {
439 self.load_additional(nbt);
440 self.base().load_components(nbt);
441 }
442
443 fn apply_implicit_components(&self, _components: &mut ImplicitComponentGetter<'_>) {}
447
448 fn collect_implicit_components(&self, _components: &mut DataComponentMap) {}
452
453 fn apply_components_from_item_stack(&self, stack: &ItemStack) {
458 let mut getter = ImplicitComponentGetter::new(stack);
459 self.apply_implicit_components(&mut getter);
460 self.base()
461 .set_components(getter.remaining_stored_components());
462 }
463
464 fn collect_components(&self) -> DataComponentMap {
468 let mut components = self.base().stored_components();
469 self.collect_implicit_components(&mut components);
470 components
471 }
472
473 fn load_with_owned_components(&self, nbt: &NbtCompound) -> Result<(), simdnbt::Error> {
479 let mut encoded = Vec::new();
480 nbt.write(&mut encoded);
481 let borrowed = read_borrowed_compound(&mut Cursor::new(encoded.as_slice()))?;
482 self.load_with_components(&borrowed);
483 Ok(())
484 }
485
486 fn save_additional(&self, nbt: &mut NbtCompound);
490
491 fn save_custom_only(&self) -> NbtCompound {
493 let mut nbt = NbtCompound::new();
494 self.save_additional(&mut nbt);
495 for key in ["id", "x", "y", "z"] {
496 while nbt.remove(key).is_some() {}
497 }
498 nbt
499 }
500
501 fn save_without_metadata(&self) -> NbtCompound {
503 let mut nbt = self.save_custom_only();
504 nbt.insert(
505 "components",
506 self.base().stored_components().to_nbt_tag_ref(),
507 );
508 nbt
509 }
510
511 fn save_with_full_metadata(&self) -> NbtCompound {
513 let mut nbt = self.save_without_metadata();
514 let pos = self.get_block_pos();
515 nbt.insert("id", self.get_type().key.to_string());
516 nbt.insert("x", pos.x());
517 nbt.insert("y", pos.y());
518 nbt.insert("z", pos.z());
519 nbt
520 }
521
522 fn get_update_tag(&self) -> Option<NbtCompound> {
527 None
528 }
529
530 #[expect(
535 unused_variables,
536 reason = "default trait impl; parameter used by overrides"
537 )]
538 fn tick(&self, world: &Arc<World>) {}
539
540 fn container_ref(&self) -> Option<ContainerRef> {
542 None
543 }
544
545 fn game_event_listener(&self) -> Option<SharedGameEventListener> {
550 None
551 }
552}
553
554pub trait BlockEntityLifecycleExt: BlockEntity {
560 fn is_removed(&self) -> bool {
562 self.base().is_removed()
563 }
564
565 fn set_block_state(&self, state: BlockStateId) {
567 if self.base().queue_block_state_change(state) {
568 self.dispatch_lifecycle_events();
569 }
570 }
571
572 fn set_removed(&self) {
574 if self.base().queue_set_removed() {
575 self.dispatch_lifecycle_events();
576 }
577 }
578
579 fn clear_removed(&self) {
581 if self.base().queue_clear_removed() {
582 self.dispatch_lifecycle_events();
583 }
584 }
585
586 fn dispatch_lifecycle_events(&self) {
591 let mut guard = BlockEntityLifecycleDispatchGuard {
592 base: self.base(),
593 armed: true,
594 };
595 loop {
596 let event = {
597 let mut lifecycle = self.base().lifecycle.lock();
598 if lifecycle.events.is_empty() {
599 lifecycle.dispatching_events = false;
600 guard.armed = false;
601 return;
602 }
603 lifecycle.events.remove(0)
604 };
605 match event {
606 BlockEntityLifecycleEvent::SetRemoved => self.on_set_removed(),
607 BlockEntityLifecycleEvent::ClearRemoved => self.on_clear_removed(),
608 BlockEntityLifecycleEvent::BlockStateChanged(state) => {
609 self.on_block_state_changed(state);
610 }
611 }
612 }
613 }
614}
615
616impl<T: BlockEntity + ?Sized> BlockEntityLifecycleExt for T {}
617
618pub type SharedBlockEntity = Arc<dyn BlockEntity>;