Skip to main content

steel_core/block_entity/entities/
raw.rs

1//! NBT-preserving fallback block entity.
2
3use std::sync::Weak;
4
5use simdnbt::borrow::{BaseNbtCompound as BorrowedNbtCompound, NbtCompound as NbtCompoundView};
6use simdnbt::owned::NbtCompound;
7use steel_registry::block_entity_type::BlockEntityTypeRef;
8use steel_utils::{BlockPos, BlockStateId, DowncastType, DowncastTypeKey, locks::SyncMutex};
9
10use crate::block_entity::{BlockEntity, BlockEntityBase};
11use crate::world::World;
12
13struct RawBlockEntityState {
14    data: NbtCompound,
15}
16
17/// Steel-specific fallback for block entity types whose runtime behavior is not implemented yet.
18///
19/// Vanilla has concrete classes for every block entity type. Steel uses this only to preserve
20/// worldgen and disk NBT until the corresponding typed implementation is added.
21pub struct RawBlockEntity {
22    base: BlockEntityBase,
23    state: SyncMutex<RawBlockEntityState>,
24}
25
26// SAFETY: This key identifies the Steel fallback implementation, independently
27// of the Minecraft block-entity registry entry stored inside it.
28unsafe impl DowncastType for RawBlockEntity {
29    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:block_entity/raw");
30}
31
32impl RawBlockEntity {
33    /// Creates a raw block entity without additional NBT.
34    #[must_use]
35    pub fn new(
36        block_entity_type: BlockEntityTypeRef,
37        level: Weak<World>,
38        pos: BlockPos,
39        state: BlockStateId,
40    ) -> Self {
41        Self::with_data(block_entity_type, level, pos, state, NbtCompound::new())
42    }
43
44    /// Creates a raw block entity with already-owned additional NBT.
45    #[must_use]
46    pub fn with_data(
47        block_entity_type: BlockEntityTypeRef,
48        level: Weak<World>,
49        pos: BlockPos,
50        state: BlockStateId,
51        data: NbtCompound,
52    ) -> Self {
53        Self {
54            base: BlockEntityBase::new(block_entity_type, level, pos, state),
55            state: SyncMutex::new(RawBlockEntityState { data }),
56        }
57    }
58}
59
60impl BlockEntity for RawBlockEntity {
61    fn base(&self) -> &BlockEntityBase {
62        &self.base
63    }
64
65    fn load_additional(&self, nbt: &BorrowedNbtCompound<'_>) {
66        let nbt_view: NbtCompoundView<'_, '_> = nbt.into();
67        self.state.lock().data = nbt_view.to_owned();
68    }
69
70    fn save_additional(&self, nbt: &mut NbtCompound) {
71        *nbt = self.state.lock().data.clone();
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use std::sync::Weak;
78
79    use steel_registry::{init_vanilla_registry, vanilla_block_entity_types, vanilla_blocks};
80
81    use super::*;
82
83    #[test]
84    fn full_metadata_replaces_stale_raw_metadata() {
85        init_vanilla_registry();
86        let mut data = NbtCompound::new();
87        data.insert("id", "minecraft:chest");
88        data.insert("x", 100_i32);
89        data.insert("custom", 7_i32);
90        let entity = RawBlockEntity::with_data(
91            &vanilla_block_entity_types::BARREL,
92            Weak::new(),
93            BlockPos::new(2, 70, -4),
94            vanilla_blocks::BARREL.default_state(),
95            data,
96        );
97
98        let saved = entity.save_with_full_metadata();
99        let custom = entity.save_custom_only();
100
101        assert_eq!(
102            saved.string("id").map(ToString::to_string),
103            Some("minecraft:barrel".to_owned())
104        );
105        assert_eq!(saved.int("x"), Some(2));
106        assert_eq!(saved.int("y"), Some(70));
107        assert_eq!(saved.int("z"), Some(-4));
108        assert_eq!(saved.int("custom"), Some(7));
109        assert!(!custom.contains("id"));
110        assert!(!custom.contains("x"));
111        assert_eq!(custom.int("custom"), Some(7));
112    }
113
114    #[test]
115    #[should_panic(expected = "invalid block entity minecraft:barrel state minecraft:stone")]
116    fn constructor_rejects_a_type_state_mismatch() {
117        init_vanilla_registry();
118        let _ = RawBlockEntity::new(
119            &vanilla_block_entity_types::BARREL,
120            Weak::new(),
121            BlockPos::new(2, 70, -4),
122            vanilla_blocks::STONE.default_state(),
123        );
124    }
125}