1pub(crate) mod block_state_nbt;
27pub mod entities;
28mod registry;
29mod storage;
30
31use std::{
32 ptr,
33 sync::{
34 Arc, Weak,
35 atomic::{AtomicBool, Ordering},
36 },
37};
38
39use simdnbt::borrow::BaseNbtCompound as BorrowedNbtCompound;
40use simdnbt::owned::NbtCompound;
41use smallvec::SmallVec;
42use steel_registry::block_entity_type::BlockEntityTypeRef;
43use steel_registry::blocks::block_state_ext::BlockStateExt as _;
44use steel_utils::{BlockPos, BlockStateId, ErasedType, locks::SyncMutex};
45
46pub use registry::{BLOCK_ENTITIES, BlockEntityFactory, BlockEntityRegistry, init_block_entities};
47pub(crate) use storage::{
48 BlockEntityInsert, BlockEntityLookup, BlockEntityStorage, ClearedBlockEntities,
49 DetachedBlockEntity, LifecycleDispatchers,
50};
51
52use crate::inventory::lock::ContainerRef;
53use crate::player::Player;
54
55use crate::world::World;
56use crate::world::game_event::SharedGameEventListener;
57
58#[derive(Clone, Copy)]
64pub struct BlockEntityTicker {
65 block_entity_type: BlockEntityTypeRef,
66 tick: fn(&Arc<World>, BlockPos, BlockStateId, &dyn BlockEntity),
67}
68
69impl BlockEntityTicker {
70 #[must_use]
72 pub const fn new(
73 block_entity_type: BlockEntityTypeRef,
74 tick: fn(&Arc<World>, BlockPos, BlockStateId, &dyn BlockEntity),
75 ) -> Self {
76 Self {
77 block_entity_type,
78 tick,
79 }
80 }
81
82 #[must_use]
84 pub const fn for_entity_tick(block_entity_type: BlockEntityTypeRef) -> Self {
85 Self::new(block_entity_type, Self::tick_entity)
86 }
87
88 #[must_use]
90 pub fn for_matching_entity_tick(
91 actual: BlockEntityTypeRef,
92 expected: BlockEntityTypeRef,
93 ) -> Option<Self> {
94 ptr::eq(actual, expected).then(|| Self::for_entity_tick(expected))
95 }
96
97 #[must_use]
99 pub fn accepts(self, block_entity_type: BlockEntityTypeRef) -> bool {
100 ptr::eq(self.block_entity_type, block_entity_type)
101 }
102
103 pub(crate) fn tick(
104 self,
105 world: &Arc<World>,
106 pos: BlockPos,
107 state: BlockStateId,
108 block_entity: &dyn BlockEntity,
109 ) {
110 (self.tick)(world, pos, state, block_entity);
111 }
112
113 fn tick_entity(
114 world: &Arc<World>,
115 _pos: BlockPos,
116 _state: BlockStateId,
117 block_entity: &dyn BlockEntity,
118 ) {
119 block_entity.tick(world);
120 }
121}
122
123struct BlockEntityLifecycle {
124 block_state: BlockStateId,
125 events: SmallVec<[BlockEntityLifecycleEvent; 2]>,
126 dispatching_events: bool,
127}
128
129#[derive(Clone, Copy)]
130enum BlockEntityLifecycleEvent {
131 SetRemoved,
132 ClearRemoved,
133 BlockStateChanged(BlockStateId),
134}
135
136pub struct BlockEntityBase {
141 block_entity_type: BlockEntityTypeRef,
142 level: Weak<World>,
143 pos: BlockPos,
144 removed: AtomicBool,
146 lifecycle: SyncMutex<BlockEntityLifecycle>,
147}
148
149struct BlockEntityLifecycleDispatchGuard<'a> {
150 base: &'a BlockEntityBase,
151 armed: bool,
152}
153
154impl Drop for BlockEntityLifecycleDispatchGuard<'_> {
155 fn drop(&mut self) {
156 if !self.armed {
157 return;
158 }
159 let mut lifecycle = self.base.lifecycle.lock();
160 lifecycle.events.clear();
161 lifecycle.dispatching_events = false;
162 }
163}
164
165impl BlockEntityBase {
166 #[must_use]
172 pub fn new(
173 block_entity_type: BlockEntityTypeRef,
174 level: Weak<World>,
175 pos: BlockPos,
176 block_state: BlockStateId,
177 ) -> Self {
178 assert!(
179 block_entity_type.is_valid(block_state.get_block()),
180 "invalid block entity {} state {} at {pos:?}",
181 block_entity_type.key,
182 block_state.get_block().key,
183 );
184 Self {
185 block_entity_type,
186 level,
187 pos,
188 removed: AtomicBool::new(false),
189 lifecycle: SyncMutex::new(BlockEntityLifecycle {
190 block_state,
191 events: SmallVec::new(),
192 dispatching_events: false,
193 }),
194 }
195 }
196
197 #[must_use]
198 const fn block_entity_type(&self) -> BlockEntityTypeRef {
199 self.block_entity_type
200 }
201
202 #[must_use]
203 const fn pos(&self) -> BlockPos {
204 self.pos
205 }
206
207 #[must_use]
208 fn block_state(&self) -> BlockStateId {
209 self.lifecycle.lock().block_state
210 }
211
212 fn queue_block_state_change(&self, state: BlockStateId) -> bool {
213 assert!(
214 self.block_entity_type.is_valid(state.get_block()),
215 "invalid block entity {} state {} at {:?}",
216 self.block_entity_type.key,
217 state.get_block().key,
218 self.pos,
219 );
220 let mut lifecycle = self.lifecycle.lock();
221 if lifecycle.block_state == state {
222 return false;
223 }
224 lifecycle.block_state = state;
225 lifecycle
226 .events
227 .push(BlockEntityLifecycleEvent::BlockStateChanged(state));
228 if lifecycle.dispatching_events {
229 false
230 } else {
231 lifecycle.dispatching_events = true;
232 true
233 }
234 }
235
236 #[must_use]
237 fn is_removed(&self) -> bool {
238 self.removed.load(Ordering::Relaxed)
239 }
240
241 fn queue_set_removed(&self) -> bool {
242 let mut lifecycle = self.lifecycle.lock();
243 self.removed.store(true, Ordering::Relaxed);
244 lifecycle.events.push(BlockEntityLifecycleEvent::SetRemoved);
245 if lifecycle.dispatching_events {
246 false
247 } else {
248 lifecycle.dispatching_events = true;
249 true
250 }
251 }
252
253 fn queue_clear_removed(&self) -> bool {
254 let mut lifecycle = self.lifecycle.lock();
255 if !self.removed.load(Ordering::Relaxed) {
256 return false;
257 }
258 self.removed.store(false, Ordering::Relaxed);
259 lifecycle
260 .events
261 .push(BlockEntityLifecycleEvent::ClearRemoved);
262 if lifecycle.dispatching_events {
263 false
264 } else {
265 lifecycle.dispatching_events = true;
266 true
267 }
268 }
269
270 #[must_use]
271 fn level(&self) -> Option<Arc<World>> {
272 self.level.upgrade()
273 }
274
275 pub(crate) fn set_changed(&self) {
276 let Some(world) = self.level() else {
277 return;
278 };
279 let state = self.block_state();
280 world.block_entity_changed(self.pos);
281 if !state.is_air() {
282 world.update_neighbor_for_output_signal(self.pos, state.get_block());
283 }
284 }
285
286 pub(crate) fn is_valid_container_for(&self, player: &Player) -> bool {
287 if self.is_removed() {
288 return false;
289 }
290 let Some(world) = self.level() else {
291 return false;
292 };
293 let Some(current) = world.get_block_entity(self.pos) else {
294 return false;
295 };
296 ptr::eq(current.base(), self)
297 && player.is_within_block_interaction_range_with_buffer(self.pos, 4.0)
298 }
299}
300
301pub trait BlockEntity: ErasedType + Send + Sync {
308 fn base(&self) -> &BlockEntityBase;
310
311 fn get_type(&self) -> BlockEntityTypeRef {
313 self.base().block_entity_type()
314 }
315
316 fn get_block_pos(&self) -> BlockPos {
318 self.base().pos()
319 }
320
321 fn get_block_state(&self) -> BlockStateId {
323 self.base().block_state()
324 }
325
326 fn is_valid_block_state(&self, state: BlockStateId) -> bool {
330 self.get_type().is_valid(state.get_block())
331 }
332
333 fn on_block_state_changed(&self, _state: BlockStateId) {}
339
340 fn on_set_removed(&self) {}
345
346 fn on_clear_removed(&self) {}
350
351 fn set_changed(&self) {
355 self.base().set_changed();
356 }
357
358 fn get_level(&self) -> Option<Arc<World>> {
362 self.base().level()
363 }
364
365 fn trigger_event(&self, _param_a: i32, _param_b: i32) -> bool {
369 false
370 }
371
372 #[expect(
380 unused_variables,
381 reason = "default trait impl; parameters used by overrides"
382 )]
383 fn pre_remove_side_effects(&self, pos: BlockPos, state: BlockStateId) {
384 }
386
387 fn load_additional(&self, nbt: &BorrowedNbtCompound<'_>);
392
393 fn save_additional(&self, nbt: &mut NbtCompound);
397
398 fn save_custom_only(&self) -> NbtCompound {
400 let mut nbt = NbtCompound::new();
401 self.save_additional(&mut nbt);
402 for key in ["id", "x", "y", "z"] {
403 while nbt.remove(key).is_some() {}
404 }
405 nbt
406 }
407
408 fn save_with_full_metadata(&self) -> NbtCompound {
410 let mut nbt = self.save_custom_only();
414 let pos = self.get_block_pos();
415 nbt.insert("id", self.get_type().key.to_string());
416 nbt.insert("x", pos.x());
417 nbt.insert("y", pos.y());
418 nbt.insert("z", pos.z());
419 nbt
420 }
421
422 fn get_update_tag(&self) -> Option<NbtCompound> {
427 None
428 }
429
430 #[expect(
435 unused_variables,
436 reason = "default trait impl; parameter used by overrides"
437 )]
438 fn tick(&self, world: &Arc<World>) {}
439
440 fn container_ref(&self) -> Option<ContainerRef> {
442 None
443 }
444
445 fn game_event_listener(&self) -> Option<SharedGameEventListener> {
450 None
451 }
452}
453
454pub trait BlockEntityLifecycleExt: BlockEntity {
460 fn is_removed(&self) -> bool {
462 self.base().is_removed()
463 }
464
465 fn set_block_state(&self, state: BlockStateId) {
467 if self.base().queue_block_state_change(state) {
468 self.dispatch_lifecycle_events();
469 }
470 }
471
472 fn set_removed(&self) {
474 if self.base().queue_set_removed() {
475 self.dispatch_lifecycle_events();
476 }
477 }
478
479 fn clear_removed(&self) {
481 if self.base().queue_clear_removed() {
482 self.dispatch_lifecycle_events();
483 }
484 }
485
486 fn dispatch_lifecycle_events(&self) {
491 let mut guard = BlockEntityLifecycleDispatchGuard {
492 base: self.base(),
493 armed: true,
494 };
495 loop {
496 let event = {
497 let mut lifecycle = self.base().lifecycle.lock();
498 if lifecycle.events.is_empty() {
499 lifecycle.dispatching_events = false;
500 guard.armed = false;
501 return;
502 }
503 lifecycle.events.remove(0)
504 };
505 match event {
506 BlockEntityLifecycleEvent::SetRemoved => self.on_set_removed(),
507 BlockEntityLifecycleEvent::ClearRemoved => self.on_clear_removed(),
508 BlockEntityLifecycleEvent::BlockStateChanged(state) => {
509 self.on_block_state_changed(state);
510 }
511 }
512 }
513 }
514}
515
516impl<T: BlockEntity + ?Sized> BlockEntityLifecycleExt for T {}
517
518pub type SharedBlockEntity = Arc<dyn BlockEntity>;