Skip to main content

steel_core/block_entity/entities/
comparator.rs

1//! Comparator block-entity output storage.
2
3use std::sync::Weak;
4
5use simdnbt::borrow::{BaseNbtCompound as BorrowedNbtCompound, NbtCompound as NbtCompoundView};
6use simdnbt::owned::NbtCompound;
7use steel_registry::vanilla_block_entity_types;
8use steel_utils::nbt::NbtValueInput as _;
9use steel_utils::{BlockPos, BlockStateId, DowncastType, DowncastTypeKey, locks::SyncMutex};
10
11use crate::block_entity::{BlockEntity, BlockEntityBase};
12use crate::world::World;
13
14struct ComparatorState {
15    output_signal: i32,
16}
17
18/// Block entity for comparator blocks, storing the cached output signal
19/// strength between recalculations.
20pub struct ComparatorBlockEntity {
21    base: BlockEntityBase,
22    state: SyncMutex<ComparatorState>,
23}
24
25// SAFETY: This key is owned by Steel and uniquely identifies `ComparatorBlockEntity`.
26unsafe impl DowncastType for ComparatorBlockEntity {
27    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:block_entity/comparator");
28}
29
30impl ComparatorBlockEntity {
31    /// Creates comparator storage with vanilla's zero output.
32    #[must_use]
33    pub fn new(world: Weak<World>, pos: BlockPos, state: BlockStateId) -> Self {
34        Self {
35            base: BlockEntityBase::new(&vanilla_block_entity_types::COMPARATOR, world, pos, state),
36            state: SyncMutex::new(ComparatorState { output_signal: 0 }),
37        }
38    }
39
40    /// Returns the comparator's cached output signal.
41    #[must_use]
42    pub fn output_signal(&self) -> i32 {
43        self.state.lock().output_signal
44    }
45
46    /// Replaces the comparator's cached output signal.
47    pub fn set_output_signal(&self, output_signal: i32) {
48        self.state.lock().output_signal = output_signal;
49    }
50}
51
52impl BlockEntity for ComparatorBlockEntity {
53    fn base(&self) -> &BlockEntityBase {
54        &self.base
55    }
56
57    fn load_additional(&self, nbt: &BorrowedNbtCompound<'_>) {
58        let nbt: NbtCompoundView<'_, '_> = nbt.into();
59        self.state.lock().output_signal = nbt.int_value("OutputSignal").unwrap_or(0);
60    }
61
62    fn save_additional(&self, nbt: &mut NbtCompound) {
63        nbt.insert("OutputSignal", self.output_signal());
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use std::io::Cursor;
70
71    use simdnbt::borrow::read_compound as read_borrowed_compound;
72    use steel_registry::{init_vanilla_registry, vanilla_blocks};
73
74    use super::*;
75
76    fn comparator() -> ComparatorBlockEntity {
77        init_vanilla_registry();
78        ComparatorBlockEntity::new(
79            Weak::new(),
80            BlockPos::new(4, 65, -9),
81            vanilla_blocks::COMPARATOR.default_state(),
82        )
83    }
84
85    #[test]
86    fn output_signal_round_trips_with_vanilla_nbt_key() {
87        let source = comparator();
88        source.set_output_signal(11);
89        let mut nbt = NbtCompound::new();
90        source.save_additional(&mut nbt);
91        assert_eq!(nbt.int("OutputSignal"), Some(11));
92
93        let mut bytes = Vec::new();
94        nbt.write(&mut bytes);
95        let borrowed = read_borrowed_compound(&mut Cursor::new(bytes.as_slice()))
96            .expect("test NBT should reborrow");
97        let loaded = comparator();
98        loaded.load_additional(&borrowed);
99        assert_eq!(loaded.output_signal(), 11);
100    }
101
102    #[test]
103    fn output_signal_loads_from_any_numeric_tag() {
104        let mut nbt = NbtCompound::new();
105        nbt.insert("OutputSignal", 11i8);
106        let mut bytes = Vec::new();
107        nbt.write(&mut bytes);
108        let borrowed = read_borrowed_compound(&mut Cursor::new(bytes.as_slice()))
109            .expect("test NBT should reborrow");
110        let loaded = comparator();
111        loaded.load_additional(&borrowed);
112        assert_eq!(loaded.output_signal(), 11);
113    }
114
115    #[test]
116    fn missing_output_signal_loads_vanilla_default() {
117        let nbt = NbtCompound::new();
118        let mut bytes = Vec::new();
119        nbt.write(&mut bytes);
120        let borrowed = read_borrowed_compound(&mut Cursor::new(bytes.as_slice()))
121            .expect("test NBT should reborrow");
122        let loaded = comparator();
123        loaded.set_output_signal(15);
124        loaded.load_additional(&borrowed);
125        assert_eq!(loaded.output_signal(), 0);
126    }
127}