Skip to main content

steel_registry/data_components/registry/
mod.rs

1//! Data component registry and storage types.
2//!
3//! This module provides:
4//! - [`DataComponentRegistry`] - Registry of all component types with their serialization functions
5//! - [`DataComponentMap`] - Storage for component values on items/entities
6//! - [`DataComponentPatch`] - Diff representation for network/storage
7//! - [`DataComponentType`] - Type-safe handle for accessing components
8
9use rustc_hash::FxHashMap;
10use simdnbt::{
11    FromNbtTag, ToNbtTag,
12    borrow::{NbtTag as BorrowedNbtTag, read_tag},
13    owned::{NbtCompound, NbtTag as OwnedNbtTag},
14};
15use std::{
16    fmt::Debug,
17    io::{Cursor, Result, Write},
18    marker::PhantomData,
19};
20
21use steel_utils::{
22    DowncastType, DowncastTypeKey, Identifier,
23    codec::VarInt,
24    hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries},
25    serial::{ReadFrom, WriteTo},
26};
27use text_components::EmbeddedNbtCodec;
28
29use super::component_data::{Component, ComponentData};
30use super::components::{
31    ItemAttributeModifiers, ItemEnchantments, ItemLore, Rarity, SwingAnimation, TooltipDisplay,
32    UseEffects,
33};
34use super::vanilla_components::{
35    ATTRIBUTE_MODIFIERS, BREAK_SOUND, ENCHANTMENTS, LORE, MAX_STACK_SIZE, RARITY, REPAIR_COST,
36    SWING_ANIMATION, TOOLTIP_DISPLAY, USE_EFFECTS,
37};
38use crate::{sound_event::SoundEventHolder, sound_events};
39
40mod codecs;
41mod component_map;
42mod patch;
43mod patch_network;
44mod patch_persistence;
45
46pub(crate) use codecs::ValidatePersistentComponent;
47pub use codecs::{
48    ComponentEntry, ComponentEntryRef, NbtReader, NbtWriter, NetworkReader, NetworkWriter,
49};
50pub use component_map::DataComponentMap;
51pub use patch::{ComponentPatchEntry, DataComponentPatch};
52pub use patch_persistence::component_try_into;
53
54use codecs::{
55    PersistentCodecFns, hash_component, read_typed_nbt, read_typed_network, validate_component,
56    write_typed_nbt, write_typed_network,
57};
58
59/// A typed handle for a data component.
60///
61/// This provides compile-time type safety when getting/setting components.
62/// The actual storage uses keyed type erasure through [`ComponentData`].
63///
64/// # Example
65/// ```ignore
66/// pub const DAMAGE: DataComponentType<Damage> =
67///     DataComponentType::new(Identifier::vanilla_static("damage"));
68///
69/// // Type-safe access
70/// let damage: Option<Damage> = components.get(DAMAGE);
71/// components.set(DAMAGE, Damage(10));
72/// ```
73///
74/// Steel declares component handles alongside their registered codecs; external
75/// callers cannot construct a handle for an existing key with a different type.
76///
77/// ```compile_fail
78/// use steel_registry::data_components::DataComponentType;
79/// use steel_utils::Identifier;
80///
81/// let _forged = DataComponentType::<bool>::new(Identifier::vanilla_static("max_damage"));
82/// ```
83pub struct DataComponentType<T> {
84    pub(crate) key: Identifier,
85    ignore_swap_animation: bool,
86    _phantom: PhantomData<T>,
87}
88
89impl<T> Clone for DataComponentType<T> {
90    fn clone(&self) -> Self {
91        Self {
92            key: self.key.clone(),
93            ignore_swap_animation: self.ignore_swap_animation,
94            _phantom: PhantomData,
95        }
96    }
97}
98
99impl<T> DataComponentType<T> {
100    #[must_use]
101    pub(crate) const fn new(key: Identifier) -> Self {
102        Self {
103            key,
104            ignore_swap_animation: false,
105            _phantom: PhantomData,
106        }
107    }
108
109    /// Creates a component type whose changes do not restart the held-item swap animation.
110    #[must_use]
111    pub(crate) const fn new_ignoring_swap_animation(key: Identifier) -> Self {
112        Self {
113            key,
114            ignore_swap_animation: true,
115            _phantom: PhantomData,
116        }
117    }
118
119    /// Returns whether this component is ignored when comparing held items for swap animation.
120    #[must_use]
121    pub const fn ignore_swap_animation(&self) -> bool {
122        self.ignore_swap_animation
123    }
124
125    /// Returns this component type's registry key.
126    #[must_use]
127    pub const fn key(&self) -> &Identifier {
128        &self.key
129    }
130}
131
132/// Registry of all data component types.
133///
134/// Stores metadata about each component type including how to serialize/deserialize
135/// them for network and persistent storage.
136pub struct DataComponentRegistry {
137    /// Component entries indexed by network ID
138    entries: Vec<ComponentEntryRef>,
139    /// Map from component key to network ID
140    by_key: FxHashMap<Identifier, usize>,
141    /// Whether registration is still allowed
142    allows_registering: bool,
143}
144
145impl DataComponentRegistry {
146    #[must_use]
147    pub(crate) fn new() -> Self {
148        Self {
149            entries: Vec::new(),
150            by_key: FxHashMap::default(),
151            allows_registering: true,
152        }
153    }
154
155    /// Registers a vanilla component type.
156    ///
157    /// The component type `T` must implement the necessary serialization traits.
158    /// This creates the appropriate reader/writer functions automatically.
159    pub(crate) fn register<T>(&mut self, component: DataComponentType<T>)
160    where
161        T: Component
162            + DowncastType
163            + Clone
164            + WriteTo
165            + ReadFrom
166            + ToNbtTag
167            + FromNbtTag
168            + HashComponent,
169    {
170        self.register_persistent(component);
171    }
172
173    /// Registers a transient vanilla component type.
174    ///
175    /// Transient components have network data but no persistent component codec.
176    pub(crate) fn register_transient<T>(&mut self, component: DataComponentType<T>)
177    where
178        T: Component + DowncastType + WriteTo + ReadFrom,
179    {
180        self.register_implemented(
181            component,
182            read_typed_network::<T>,
183            write_typed_network::<T>,
184            None,
185        );
186    }
187
188    fn register_persistent<T>(&mut self, component: DataComponentType<T>)
189    where
190        T: Component
191            + DowncastType
192            + Clone
193            + WriteTo
194            + ReadFrom
195            + ToNbtTag
196            + FromNbtTag
197            + HashComponent,
198    {
199        self.register_implemented(
200            component,
201            read_typed_network::<T>,
202            write_typed_network::<T>,
203            Some((
204                read_typed_nbt::<T>,
205                write_typed_nbt::<T>,
206                hash_component::<T>,
207                None,
208            )),
209        );
210    }
211
212    pub(crate) fn register_validated<T>(&mut self, component: DataComponentType<T>)
213    where
214        T: Component
215            + DowncastType
216            + Clone
217            + WriteTo
218            + ReadFrom
219            + ToNbtTag
220            + FromNbtTag
221            + HashComponent
222            + ValidatePersistentComponent,
223    {
224        self.register_implemented(
225            component,
226            read_typed_network::<T>,
227            write_typed_network::<T>,
228            Some((
229                read_typed_nbt::<T>,
230                write_typed_nbt::<T>,
231                hash_component::<T>,
232                Some(validate_component::<T>),
233            )),
234        );
235    }
236
237    /// Registers a component with custom network reader/writer functions.
238    ///
239    /// Use this when the default `WriteTo`/`ReadFrom` implementations don't match
240    /// the network encoding (e.g., VarInt-encoded i32 components).
241    /// NBT serialization still uses the type's `ToNbtTag`/`FromNbtTag` impls.
242    pub(crate) fn register_custom_network<T>(
243        &mut self,
244        component: DataComponentType<T>,
245        network_reader: NetworkReader,
246        network_writer: NetworkWriter,
247    ) where
248        T: Component + DowncastType + Clone + ToNbtTag + FromNbtTag + HashComponent,
249    {
250        self.register_implemented(
251            component,
252            network_reader,
253            network_writer,
254            Some((
255                read_typed_nbt::<T>,
256                write_typed_nbt::<T>,
257                hash_component::<T>,
258                None,
259            )),
260        );
261    }
262
263    /// Registers a component with explicit network and persistent codecs.
264    pub(crate) fn register_with_codecs<T: Component + DowncastType + HashComponent>(
265        &mut self,
266        component: DataComponentType<T>,
267        network_reader: NetworkReader,
268        network_writer: NetworkWriter,
269        nbt_reader: NbtReader,
270        nbt_writer: NbtWriter,
271    ) -> usize {
272        self.register_implemented(
273            component,
274            network_reader,
275            network_writer,
276            Some((nbt_reader, nbt_writer, hash_component::<T>, None)),
277        )
278    }
279
280    /// Registers a transient component with explicit network codecs.
281    pub(crate) fn register_transient_with_codecs<T: Component + DowncastType>(
282        &mut self,
283        component: DataComponentType<T>,
284        network_reader: NetworkReader,
285        network_writer: NetworkWriter,
286    ) -> usize {
287        self.register_implemented(component, network_reader, network_writer, None)
288    }
289
290    fn register_implemented<T: Component + DowncastType>(
291        &mut self,
292        component: DataComponentType<T>,
293        network_reader: NetworkReader,
294        network_writer: NetworkWriter,
295        persistent_codecs: Option<PersistentCodecFns>,
296    ) -> usize {
297        assert!(
298            self.allows_registering,
299            "Cannot register data components after the registry has been frozen"
300        );
301
302        let ignore_swap_animation = component.ignore_swap_animation();
303        let key = component.key;
304        assert!(
305            !self.by_key.contains_key(&key),
306            "Cannot register duplicate data component key {key}"
307        );
308        let entry = Box::leak(Box::new(ComponentEntry::implemented(
309            key.clone(),
310            T::TYPE_KEY,
311            network_reader,
312            network_writer,
313            persistent_codecs,
314            ignore_swap_animation,
315        )));
316
317        let id = self.entries.len();
318        self.by_key.insert(key, id);
319        self.entries.push(entry);
320        id
321    }
322
323    /// Gets the network ID for a component type.
324    #[must_use]
325    pub fn get_id<T>(&self, component: DataComponentType<T>) -> Option<usize> {
326        self.by_key.get(&component.key).copied()
327    }
328
329    /// Gets the component key by network ID.
330    #[must_use]
331    pub fn get_key_by_id(&self, id: usize) -> Option<&Identifier> {
332        self.entries.get(id).map(|e| &e.key)
333    }
334}
335
336crate::impl_registry!(
337    DataComponentRegistry,
338    ComponentEntry,
339    entries,
340    by_key,
341    data_components
342);
343
344#[cfg(test)]
345mod tests;