Skip to main content

steel_registry/data_components/registry/
patch.rs

1use super::{
2    Component, ComponentData, DataComponentMap, DataComponentType, Debug, DowncastType, FxHashMap,
3    Identifier,
4};
5use rustc_hash::FxBuildHasher;
6
7static EMPTY: DataComponentPatch = DataComponentPatch {
8    entries: FxHashMap::with_hasher(FxBuildHasher),
9};
10
11/// Entry in a component patch.
12#[derive(Debug, Clone)]
13pub enum ComponentPatchEntry {
14    /// Component is set to this value
15    Set(ComponentData),
16    /// Component is explicitly removed
17    Removed,
18}
19
20impl PartialEq for ComponentPatchEntry {
21    fn eq(&self, other: &Self) -> bool {
22        match (self, other) {
23            (Self::Removed, Self::Removed) => true,
24            (Self::Set(a), Self::Set(b)) => a == b,
25            _ => false,
26        }
27    }
28}
29
30/// A patch representing modifications to a [`DataComponentMap`].
31///
32/// Stores differences from a prototype:
33/// - Components that are added or overridden (`Set`)
34/// - Components that are explicitly removed (`Removed`)
35#[derive(Debug, Default, Clone, PartialEq)]
36pub struct DataComponentPatch {
37    pub(super) entries: FxHashMap<Identifier, ComponentPatchEntry>,
38}
39
40impl DataComponentPatch {
41    /// Returns a shared patch with no entries.
42    #[must_use]
43    pub fn empty() -> &'static Self {
44        &EMPTY
45    }
46
47    #[must_use]
48    pub fn new() -> Self {
49        Self {
50            entries: FxHashMap::default(),
51        }
52    }
53
54    #[must_use]
55    pub fn is_empty(&self) -> bool {
56        self.entries.is_empty()
57    }
58
59    #[must_use]
60    pub fn len(&self) -> usize {
61        self.entries.len()
62    }
63
64    /// Sets a component value in the patch.
65    pub fn set<T: Component + DowncastType>(&mut self, component: DataComponentType<T>, value: T) {
66        self.entries.insert(
67            component.key,
68            ComponentPatchEntry::Set(ComponentData::new(value)),
69        );
70    }
71
72    pub(crate) fn set_component_data(&mut self, key: Identifier, data: ComponentData) {
73        self.entries.insert(key, ComponentPatchEntry::Set(data));
74    }
75
76    /// Sets raw component data (for plugin use).
77    ///
78    /// Returns `true` if the data was set successfully, or `false` if the key is
79    /// unregistered or the data type does not match it.
80    ///
81    /// This prevents plugins from setting invalid types on vanilla components.
82    pub fn set_raw(&mut self, key: Identifier, data: ComponentData) -> bool {
83        use crate::{REGISTRY, RegistryExt};
84
85        let Some(entry) = REGISTRY.data_components.by_key(&key) else {
86            return false;
87        };
88        if !entry.validates(&data) {
89            return false;
90        }
91
92        self.entries.insert(key, ComponentPatchEntry::Set(data));
93        true
94    }
95
96    /// Marks a component as removed.
97    pub fn remove<T>(&mut self, component: DataComponentType<T>) {
98        self.entries
99            .insert(component.key, ComponentPatchEntry::Removed);
100    }
101
102    /// Marks a dynamically resolved component as removed.
103    pub fn remove_raw(&mut self, key: Identifier) -> bool {
104        use crate::{REGISTRY, RegistryExt};
105
106        if REGISTRY.data_components.by_key(&key).is_none() {
107            return false;
108        }
109        self.entries.insert(key, ComponentPatchEntry::Removed);
110        true
111    }
112
113    /// Clears any patch entry for a component.
114    pub fn clear<T>(&mut self, component: DataComponentType<T>) {
115        self.entries.remove(&component.key);
116    }
117
118    /// Gets the patch entry for a key.
119    #[must_use]
120    pub fn get_entry(&self, key: &Identifier) -> Option<&ComponentPatchEntry> {
121        self.entries.get(key)
122    }
123
124    /// Checks if a component is marked as removed.
125    #[must_use]
126    pub fn is_removed(&self, key: &Identifier) -> bool {
127        matches!(self.entries.get(key), Some(ComponentPatchEntry::Removed))
128    }
129
130    /// Counts set entries.
131    #[must_use]
132    pub fn count_set(&self) -> usize {
133        self.entries
134            .values()
135            .filter(|e| matches!(e, ComponentPatchEntry::Set(_)))
136            .count()
137    }
138
139    /// Counts removed entries.
140    #[must_use]
141    pub fn count_removed(&self) -> usize {
142        self.entries
143            .values()
144            .filter(|e| matches!(e, ComponentPatchEntry::Removed))
145            .count()
146    }
147
148    /// Iterates over all entries.
149    pub fn iter(&self) -> impl Iterator<Item = (&Identifier, &ComponentPatchEntry)> {
150        self.entries.iter()
151    }
152
153    /// Applies another patch after this one, giving the added patch precedence.
154    pub fn apply(&mut self, added: &Self) {
155        self.entries.extend(
156            added
157                .entries
158                .iter()
159                .map(|(key, entry)| (key.clone(), entry.clone())),
160        );
161    }
162
163    pub(crate) fn sanitize_against(&mut self, prototype: &DataComponentMap) {
164        self.entries.retain(|key, entry| {
165            let default = prototype.get_raw(key);
166            match entry {
167                ComponentPatchEntry::Set(value) => default != Some(value),
168                ComponentPatchEntry::Removed => default.is_some(),
169            }
170        });
171    }
172}