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