Skip to main content

steel_registry/data_components/registry/
patch.rs

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