Skip to main content

steel_registry/data_components/registry/
codecs.rs

1use super::{
2    BorrowedNbtTag, Component, ComponentData, Cursor, DowncastType, DowncastTypeKey, FromNbtTag,
3    HashComponent, Identifier, OwnedNbtTag, ReadFrom, Result, ToNbtTag, WriteTo, read_tag,
4};
5
6pub type NetworkReader = fn(&mut Cursor<&[u8]>) -> Result<ComponentData>;
7
8/// Writer function for serializing a component to network format.
9pub type NetworkWriter = fn(&ComponentData, &mut Vec<u8>) -> Result<()>;
10
11/// Reader function for deserializing a component from NBT format.
12pub type NbtReader = fn(BorrowedNbtTag) -> Option<ComponentData>;
13
14/// Writer function for serializing a component to NBT format.
15pub type NbtWriter = fn(&ComponentData) -> Result<OwnedNbtTag>;
16
17/// Function for hashing a component through its persistent codec shape.
18pub(super) type ComponentHash = fn(&ComponentData) -> Result<i32>;
19pub(super) type ComponentValidator = fn(&ComponentData) -> Result<()>;
20pub(super) type PersistentCodecFns = (
21    NbtReader,
22    NbtWriter,
23    ComponentHash,
24    Option<ComponentValidator>,
25);
26
27/// Additional source-value validation required before persistent encoding.
28pub(crate) trait ValidatePersistentComponent {
29    fn validate_persistent(&self) -> Result<()>;
30}
31
32pub(super) fn hash_component<T: DowncastType + HashComponent>(data: &ComponentData) -> Result<i32> {
33    let Some(value) = data.downcast_ref::<T>() else {
34        return Err(std::io::Error::other("Component type mismatch"));
35    };
36    Ok(value.compute_hash())
37}
38
39pub(super) fn validate_component<T: DowncastType + ValidatePersistentComponent>(
40    data: &ComponentData,
41) -> Result<()> {
42    let Some(value) = data.downcast_ref::<T>() else {
43        return Err(std::io::Error::other("Component type mismatch"));
44    };
45    value.validate_persistent()
46}
47
48pub(super) fn read_typed_network<T: Component + ReadFrom>(
49    cursor: &mut Cursor<&[u8]>,
50) -> Result<ComponentData> {
51    Ok(ComponentData::new(T::read(cursor)?))
52}
53
54pub(super) fn write_typed_network<T: DowncastType + WriteTo>(
55    data: &ComponentData,
56    writer: &mut Vec<u8>,
57) -> Result<()> {
58    let Some(value) = data.downcast_ref::<T>() else {
59        return Err(std::io::Error::other("Component type mismatch"));
60    };
61    value.write(writer)
62}
63
64pub(super) fn read_typed_nbt<T: Component + FromNbtTag>(
65    tag: BorrowedNbtTag,
66) -> Option<ComponentData> {
67    T::from_nbt_tag(tag).map(ComponentData::new)
68}
69
70pub(super) fn write_typed_nbt<T: DowncastType + ToNbtTag + Clone>(
71    data: &ComponentData,
72) -> Result<OwnedNbtTag> {
73    let Some(value) = data.downcast_ref::<T>() else {
74        return Err(std::io::Error::other("Component type mismatch"));
75    };
76    Ok(value.clone().to_nbt_tag())
77}
78
79struct NetworkCodecs {
80    reader: NetworkReader,
81    writer: NetworkWriter,
82}
83
84struct PersistentCodecs {
85    reader: NbtReader,
86    writer: NbtWriter,
87    hash: ComponentHash,
88    validator: Option<fn(&ComponentData) -> Result<()>>,
89}
90
91struct ComponentCodecs {
92    expected_type_key: DowncastTypeKey,
93    network: NetworkCodecs,
94    persistent: Option<PersistentCodecs>,
95}
96
97/// Metadata for a registered component type.
98///
99/// Contains the component's key and all serialization functions needed
100/// to read/write the component for network and persistent storage.
101pub struct ComponentEntry {
102    /// The component's identifier (e.g., "minecraft:damage")
103    pub key: Identifier,
104    codecs: ComponentCodecs,
105    ignore_swap_animation: bool,
106}
107
108impl ComponentEntry {
109    #[must_use]
110    pub(super) fn implemented(
111        key: Identifier,
112        expected_type_key: DowncastTypeKey,
113        network_reader: NetworkReader,
114        network_writer: NetworkWriter,
115        persistent_codecs: Option<PersistentCodecFns>,
116        ignore_swap_animation: bool,
117    ) -> Self {
118        Self {
119            key,
120            codecs: ComponentCodecs {
121                expected_type_key,
122                network: NetworkCodecs {
123                    reader: network_reader,
124                    writer: network_writer,
125                },
126                persistent: persistent_codecs.map(|(reader, writer, hash, validator)| {
127                    PersistentCodecs {
128                        reader,
129                        writer,
130                        hash,
131                        validator,
132                    }
133                }),
134            },
135            ignore_swap_animation,
136        }
137    }
138
139    /// Validates that a `ComponentData` value matches the concrete type for this component.
140    ///
141    /// Returns `true` if the data is valid for this component type, `false` otherwise.
142    /// This prevents plugins from setting wrong types on vanilla components.
143    #[must_use]
144    pub fn validates(&self, data: &ComponentData) -> bool {
145        data.type_key() == self.codecs.expected_type_key
146    }
147
148    /// Decodes this component's network value.
149    pub fn read_network(&self, data: &mut Cursor<&[u8]>) -> Result<ComponentData> {
150        let ComponentCodecs {
151            network,
152            expected_type_key,
153            ..
154        } = &self.codecs;
155        let value = (network.reader)(data)?;
156        if value.type_key() != *expected_type_key {
157            return Err(std::io::Error::other(format!(
158                "Network codec returned the wrong value type for {}",
159                self.key
160            )));
161        }
162        Ok(value)
163    }
164
165    /// Encodes this component's network value after validating its concrete type.
166    pub fn write_network(&self, data: &ComponentData, writer: &mut Vec<u8>) -> Result<()> {
167        if !self.validates(data) {
168            return Err(std::io::Error::other(format!(
169                "Component value type does not match {}",
170                self.key
171            )));
172        }
173        (self.codecs.network.writer)(data, writer)
174    }
175
176    /// Decodes this component's persistent NBT value.
177    #[must_use]
178    pub fn read_nbt(&self, tag: BorrowedNbtTag) -> Option<ComponentData> {
179        let Some(persistent) = &self.codecs.persistent else {
180            return None;
181        };
182        let value = (persistent.reader)(tag)?;
183        (value.type_key() == self.codecs.expected_type_key).then_some(value)
184    }
185
186    /// Encodes this component's persistent NBT value after validating its concrete type.
187    pub fn write_nbt(&self, data: &ComponentData) -> Result<OwnedNbtTag> {
188        if !self.validates(data) {
189            return Err(std::io::Error::other(format!(
190                "Component value type does not match {}",
191                self.key
192            )));
193        }
194        let Some(persistent) = &self.codecs.persistent else {
195            return Err(std::io::Error::other(format!(
196                "Transient component {} has no persistent codec",
197                self.key
198            )));
199        };
200        (persistent.writer)(data)
201    }
202
203    /// Checks that a value accepted by the stream codec is also accepted by
204    /// the persistent codec.
205    pub fn validate_persistent_encoding(&self, data: &ComponentData) -> Result<OwnedNbtTag> {
206        if let Some(validator) = self
207            .codecs
208            .persistent
209            .as_ref()
210            .and_then(|persistent| persistent.validator)
211        {
212            validator(data)?;
213        }
214        let tag = self.write_nbt(data)?;
215        if self.read_nbt_owned(&tag).is_none() {
216            return Err(std::io::Error::other(format!(
217                "Persistent codec for component {} rejected its encoded value",
218                self.key
219            )));
220        }
221        Ok(tag)
222    }
223
224    /// Computes the vanilla `HashOps` value through this component's persistent codec.
225    pub fn compute_hash(&self, data: &ComponentData) -> Result<i32> {
226        if !self.validates(data) {
227            return Err(std::io::Error::other(format!(
228                "Component value type does not match {}",
229                self.key
230            )));
231        }
232        if !self.is_persistent() {
233            return Err(std::io::Error::other(format!(
234                "Transient component {} has no persistent hash codec",
235                self.key
236            )));
237        }
238        let Some(persistent) = &self.codecs.persistent else {
239            return Err(std::io::Error::other(format!(
240                "Transient component {} has no persistent hash codec",
241                self.key
242            )));
243        };
244        self.validate_persistent_encoding(data)?;
245        (persistent.hash)(data)
246    }
247
248    /// Returns whether vanilla defines this as a persistent component.
249    #[must_use]
250    pub const fn is_persistent(&self) -> bool {
251        self.codecs.persistent.is_some()
252    }
253
254    /// Returns whether changes to this component are ignored for held-item swap animation.
255    #[must_use]
256    pub const fn ignore_swap_animation(&self) -> bool {
257        self.ignore_swap_animation
258    }
259
260    /// Decodes an owned NBT value with this component's registered persistent codec.
261    #[must_use]
262    pub fn read_nbt_owned(&self, tag: &OwnedNbtTag) -> Option<ComponentData> {
263        if !self.is_persistent() {
264            return None;
265        }
266        let mut bytes = Vec::new();
267        tag.write(&mut bytes);
268        let borrowed = read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
269        self.read_nbt(borrowed.as_tag())
270    }
271}
272
273pub type ComponentEntryRef = &'static ComponentEntry;