Skip to main content

steel_registry/data_components/
component_data.rs

1//! ABI-stable component data storage.
2//!
3//! This module provides the core types for storing component values in an ABI-stable way.
4//! Vanilla components get dedicated enum variants for zero-cost access, while plugin
5//! components use the `Other` variant with opaque bytes.
6use super::components::{Equippable, ItemEnchantments, Tool};
7use text_components::TextComponent;
8
9/// Discriminant for [`ComponentData`] variants.
10///
11/// Used for runtime type validation to ensure plugins don't
12/// set wrong types on vanilla components.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ComponentDataDiscriminant {
15    Empty,
16    Bool,
17    I32,
18    Float,
19    Tool,
20    Equippable,
21    Enchantments,
22    TextComponent,
23    Todo,
24    Other,
25}
26
27/// ABI-stable component value storage.
28///
29/// Each vanilla component type gets its own variant for type-safe, zero-cost access.
30/// Plugin-defined components use the `Other` variant with serialized bytes that
31/// the plugin is responsible for interpreting.
32///
33/// # Example (vanilla code)
34/// ```ignore
35/// let data = ComponentData::I32(10);
36/// if let ComponentData::I32(d) = data {
37///     println!("Value: {}", d);
38/// }
39/// ```
40///
41/// # Example (plugin code)
42/// ```ignore
43/// // Plugin stores its own serialized data
44/// let my_bytes = my_energy.serialize();
45/// let data = ComponentData::Other(my_bytes);
46///
47/// // Plugin retrieves and deserializes
48/// if let ComponentData::Other(bytes) = data {
49///     let energy = MyEnergy::deserialize(&bytes)?;
50/// }
51/// ```
52#[derive(Debug, Clone, PartialEq)]
53#[non_exhaustive]
54pub enum ComponentData {
55    /// Component with no data (e.g., Unbreakable, Glider, CreativeSlotLock)
56    Empty,
57
58    /// Boolean component (e.g., EnchantmentGlintOverride)
59    Bool(bool),
60    /// i32 component (e.g., MaxStackSize, MaxDamage, Damage, RepairCost)
61    /// Stored as VarInt on network.
62    I32(i32),
63    /// Float component (e.g., PotionDurationScale)
64    Float(f32),
65
66    /// minecraft:tool
67    Tool(Tool),
68    /// minecraft:equippable
69    Equippable(Equippable),
70    /// minecraft:enchantments / minecraft:stored_enchantments
71    Enchantments(ItemEnchantments),
72    /// TextComponent component (e.g., CustomName, ItemName)
73    TextComponent(Box<TextComponent>),
74
75    /// Placeholder for components that aren't implemented yet.
76    Todo,
77
78    /// Opaque bytes for plugin-defined components.
79    /// The plugin is responsible for serialization/deserialization.
80    Other(Vec<u8>),
81}
82
83impl ComponentData {
84    /// Returns true if this is the empty/unit variant.
85    #[must_use]
86    pub const fn is_empty(&self) -> bool {
87        matches!(self, Self::Empty)
88    }
89
90    /// Returns the raw bytes if this is an `Other` variant.
91    #[must_use]
92    pub fn as_other(&self) -> Option<&[u8]> {
93        match self {
94            Self::Other(bytes) => Some(bytes),
95            _ => None,
96        }
97    }
98
99    /// Returns the discriminant of this component data variant.
100    /// Used for runtime type validation.
101    #[must_use]
102    pub const fn discriminant(&self) -> ComponentDataDiscriminant {
103        match self {
104            Self::Empty => ComponentDataDiscriminant::Empty,
105            Self::Bool(_) => ComponentDataDiscriminant::Bool,
106            Self::I32(_) => ComponentDataDiscriminant::I32,
107            Self::Float(_) => ComponentDataDiscriminant::Float,
108            Self::Tool(_) => ComponentDataDiscriminant::Tool,
109            Self::Equippable(_) => ComponentDataDiscriminant::Equippable,
110            Self::Enchantments(_) => ComponentDataDiscriminant::Enchantments,
111            Self::TextComponent(_) => ComponentDataDiscriminant::TextComponent,
112            Self::Todo => ComponentDataDiscriminant::Todo,
113            Self::Other(_) => ComponentDataDiscriminant::Other,
114        }
115    }
116
117    /// Computes a hash of this component value for validation.
118    ///
119    /// Uses CRC32C hashing matching Minecraft's `HashOps` implementation.
120    #[must_use]
121    pub fn compute_hash(&self) -> i32 {
122        use steel_utils::hash::{ComponentHasher, HashComponent};
123
124        let mut hasher = ComponentHasher::new();
125
126        match self {
127            // Primitives
128            Self::Empty => hasher.put_empty(),
129            Self::Bool(v) => v.hash_component(&mut hasher),
130            Self::I32(v) => v.hash_component(&mut hasher),
131            Self::Float(v) => v.hash_component(&mut hasher),
132
133            // Complex types
134            Self::Tool(v) => v.hash_component(&mut hasher),
135            Self::Equippable(v) => v.hash_component(&mut hasher),
136            Self::Enchantments(v) => v.hash_component(&mut hasher),
137            Self::TextComponent(v) => v.hash_component(&mut hasher),
138
139            // Stub/plugin types - hash as empty map for now
140            // TODO: Implement proper hashing when these types are implemented
141            Self::Todo | Self::Other(_) => {
142                hasher.start_map();
143                hasher.end_map();
144            }
145        }
146
147        hasher.finish()
148    }
149}
150
151/// Trait for types that can be converted to/from [`ComponentData`].
152///
153/// This provides compile-time type safety for vanilla components while
154/// the actual storage uses the ABI-stable `ComponentData` enum.
155///
156/// # Example
157/// ```ignore
158/// impl Component for Damage {
159///     fn into_data(self) -> ComponentData {
160///         ComponentData::Damage(self)
161///     }
162///
163///     fn from_data(data: ComponentData) -> Option<Self> {
164///         match data {
165///             ComponentData::Damage(d) => Some(d),
166///             _ => None,
167///         }
168///     }
169/// }
170/// ```
171pub trait Component: Sized + Clone {
172    /// Converts this component value into `ComponentData`.
173    fn into_data(self) -> ComponentData;
174
175    /// Attempts to extract this component type from `ComponentData`.
176    /// Returns `None` if the data is a different variant.
177    fn from_data(data: ComponentData) -> Option<Self>;
178
179    /// Attempts to get a reference to this component type from `ComponentData`.
180    /// Returns `None` if the data is a different variant or if the type
181    /// cannot be referenced directly (e.g., needs conversion).
182    fn from_data_ref(data: &ComponentData) -> Option<&Self>;
183}
184
185// Unit type for marker components
186impl Component for () {
187    fn into_data(self) -> ComponentData {
188        ComponentData::Empty
189    }
190
191    fn from_data(data: ComponentData) -> Option<Self> {
192        match data {
193            ComponentData::Empty => Some(()),
194            _ => None,
195        }
196    }
197
198    fn from_data_ref(data: &ComponentData) -> Option<&Self> {
199        match data {
200            ComponentData::Empty => Some(&()),
201            _ => None,
202        }
203    }
204}
205
206impl Component for bool {
207    fn into_data(self) -> ComponentData {
208        ComponentData::Bool(self)
209    }
210
211    fn from_data(data: ComponentData) -> Option<Self> {
212        match data {
213            ComponentData::Bool(v) => Some(v),
214            _ => None,
215        }
216    }
217
218    fn from_data_ref(data: &ComponentData) -> Option<&Self> {
219        match data {
220            ComponentData::Bool(v) => Some(v),
221            _ => None,
222        }
223    }
224}
225
226impl Component for i32 {
227    fn into_data(self) -> ComponentData {
228        ComponentData::I32(self)
229    }
230
231    fn from_data(data: ComponentData) -> Option<Self> {
232        match data {
233            ComponentData::I32(v) => Some(v),
234            _ => None,
235        }
236    }
237
238    fn from_data_ref(data: &ComponentData) -> Option<&Self> {
239        match data {
240            ComponentData::I32(v) => Some(v),
241            _ => None,
242        }
243    }
244}
245
246impl Component for f32 {
247    fn into_data(self) -> ComponentData {
248        ComponentData::Float(self)
249    }
250
251    fn from_data(data: ComponentData) -> Option<Self> {
252        match data {
253            ComponentData::Float(v) => Some(v),
254            _ => None,
255        }
256    }
257
258    fn from_data_ref(data: &ComponentData) -> Option<&Self> {
259        match data {
260            ComponentData::Float(v) => Some(v),
261            _ => None,
262        }
263    }
264}
265
266impl Component for Tool {
267    fn into_data(self) -> ComponentData {
268        ComponentData::Tool(self)
269    }
270
271    fn from_data(data: ComponentData) -> Option<Self> {
272        match data {
273            ComponentData::Tool(v) => Some(v),
274            _ => None,
275        }
276    }
277
278    fn from_data_ref(data: &ComponentData) -> Option<&Self> {
279        match data {
280            ComponentData::Tool(v) => Some(v),
281            _ => None,
282        }
283    }
284}
285
286impl Component for ItemEnchantments {
287    fn into_data(self) -> ComponentData {
288        ComponentData::Enchantments(self)
289    }
290
291    fn from_data(data: ComponentData) -> Option<Self> {
292        match data {
293            ComponentData::Enchantments(v) => Some(v),
294            _ => None,
295        }
296    }
297
298    fn from_data_ref(data: &ComponentData) -> Option<&Self> {
299        match data {
300            ComponentData::Enchantments(v) => Some(v),
301            _ => None,
302        }
303    }
304}
305
306impl Component for Equippable {
307    fn into_data(self) -> ComponentData {
308        ComponentData::Equippable(self)
309    }
310
311    fn from_data(data: ComponentData) -> Option<Self> {
312        match data {
313            ComponentData::Equippable(v) => Some(v),
314            _ => None,
315        }
316    }
317
318    fn from_data_ref(data: &ComponentData) -> Option<&Self> {
319        match data {
320            ComponentData::Equippable(v) => Some(v),
321            _ => None,
322        }
323    }
324}
325
326impl Component for TextComponent {
327    fn into_data(self) -> ComponentData {
328        ComponentData::TextComponent(Box::new(self))
329    }
330
331    fn from_data(data: ComponentData) -> Option<Self> {
332        match data {
333            ComponentData::TextComponent(v) => Some(*v),
334            _ => None,
335        }
336    }
337
338    fn from_data_ref(data: &ComponentData) -> Option<&Self> {
339        match data {
340            ComponentData::TextComponent(v) => Some(v),
341            _ => None,
342        }
343    }
344}
345
346// TextComponent and Identifier need special handling since they're used
347// for multiple component types. We'll handle these through the DataComponentType
348// registration rather than a blanket Component impl.