myth_resources/
shader_defines.rs1use std::collections::BTreeMap;
34use std::hash::{Hash, Hasher};
35
36use myth_core::utils::interner::{self, Symbol};
37
38#[derive(Debug, Clone, Default)]
49pub struct ShaderDefines {
50 defines: Vec<(Symbol, Symbol)>,
51}
52
53impl ShaderDefines {
54 #[inline]
56 #[must_use]
57 pub fn new() -> Self {
58 Self {
59 defines: Vec::new(),
60 }
61 }
62
63 #[inline]
65 #[must_use]
66 pub fn with_capacity(capacity: usize) -> Self {
67 Self {
68 defines: Vec::with_capacity(capacity),
69 }
70 }
71
72 pub fn set(&mut self, key: &str, value: &str) {
76 let key_sym = interner::intern(key);
77 let value_sym = interner::intern(value);
78 self.set_symbol(key_sym, value_sym);
79 }
80
81 #[inline]
83 pub fn set_symbol(&mut self, key: Symbol, value: Symbol) {
84 match self.defines.binary_search_by_key(&key, |&(k, _)| k) {
85 Ok(idx) => {
86 self.defines[idx].1 = value;
87 }
88 Err(idx) => {
89 self.defines.insert(idx, (key, value));
90 }
91 }
92 }
93
94 pub fn remove(&mut self, key: &str) -> bool {
96 if let Some(key_sym) = interner::get(key) {
97 self.remove_symbol(key_sym)
98 } else {
99 false
100 }
101 }
102
103 #[inline]
105 pub fn remove_symbol(&mut self, key: Symbol) -> bool {
106 if let Ok(idx) = self.defines.binary_search_by_key(&key, |&(k, _)| k) {
107 self.defines.remove(idx);
108 true
109 } else {
110 false
111 }
112 }
113
114 #[must_use]
116 pub fn contains(&self, key: &str) -> bool {
117 interner::get(key).is_some_and(|key_sym| self.contains_symbol(key_sym))
118 }
119
120 #[inline]
122 #[must_use]
123 pub fn contains_symbol(&self, key: Symbol) -> bool {
124 self.defines.binary_search_by_key(&key, |&(k, _)| k).is_ok()
125 }
126
127 #[must_use]
129 pub fn get(&self, key: &str) -> Option<String> {
130 interner::get(key).and_then(|key_sym| self.get_symbol(key_sym).map(|s| s.to_string()))
131 }
132
133 #[inline]
135 #[must_use]
136 pub fn get_symbol(&self, key: Symbol) -> Option<std::borrow::Cow<'static, str>> {
137 self.defines
138 .binary_search_by_key(&key, |&(k, _)| k)
139 .ok()
140 .map(|idx| interner::resolve(self.defines[idx].1))
141 }
142
143 #[inline]
145 pub fn clear(&mut self) {
146 self.defines.clear();
147 }
148
149 #[inline]
151 #[must_use]
152 pub fn len(&self) -> usize {
153 self.defines.len()
154 }
155
156 #[inline]
158 #[must_use]
159 pub fn is_empty(&self) -> bool {
160 self.defines.is_empty()
161 }
162
163 #[inline]
165 pub fn iter(&self) -> impl Iterator<Item = &(Symbol, Symbol)> {
166 self.defines.iter()
167 }
168
169 #[inline]
171 pub fn iter_strings(&self) -> impl Iterator<Item = (String, String)> + '_ {
172 self.defines.iter().map(|&(k, v)| {
173 (
174 interner::resolve(k).to_string(),
175 interner::resolve(v).to_string(),
176 )
177 })
178 }
179
180 #[must_use]
182 pub fn to_map(&self) -> BTreeMap<String, String> {
183 self.defines
184 .iter()
185 .map(|&(k, v)| {
186 (
187 interner::resolve(k).to_string(),
188 interner::resolve(v).to_string(),
189 )
190 })
191 .collect()
192 }
193
194 pub fn merge(&mut self, other: &ShaderDefines) {
198 for &(key, value) in &other.defines {
199 self.set_symbol(key, value);
200 }
201 }
202
203 #[must_use]
205 pub fn merged_with(&self, other: &ShaderDefines) -> ShaderDefines {
206 let mut result = self.clone();
207 result.merge(other);
208 result
209 }
210
211 #[must_use]
213 pub fn compute_hash(&self) -> u64 {
214 use std::hash::BuildHasher;
215
216 rustc_hash::FxBuildHasher.hash_one(self)
217 }
218
219 #[inline]
221 #[must_use]
222 pub fn as_slice(&self) -> &[(Symbol, Symbol)] {
223 &self.defines
224 }
225}
226
227impl Hash for ShaderDefines {
228 fn hash<H: Hasher>(&self, state: &mut H) {
229 self.defines.hash(state);
230 }
231}
232
233impl PartialEq for ShaderDefines {
234 fn eq(&self, other: &Self) -> bool {
235 self.defines == other.defines
236 }
237}
238
239impl Eq for ShaderDefines {}
240
241impl From<&[(&str, &str)]> for ShaderDefines {
243 fn from(defines: &[(&str, &str)]) -> Self {
244 let mut result = Self::with_capacity(defines.len());
245 for (k, v) in defines {
246 result.set(k, v);
247 }
248 result
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_set_and_get() {
258 let mut defines = ShaderDefines::new();
259 defines.set("USE_MAP", "1");
260 defines.set("USE_NORMAL_MAP", "1");
261
262 assert!(defines.contains("USE_MAP"));
263 assert!(defines.contains("USE_NORMAL_MAP"));
264 assert!(!defines.contains("USE_AO_MAP"));
265
266 assert_eq!(defines.get("USE_MAP"), Some("1".to_string()));
267 }
268
269 #[test]
270 fn test_ordering() {
271 let mut defines = ShaderDefines::new();
272 defines.set("B", "1");
273 defines.set("A", "1");
274 defines.set("C", "1");
275
276 let symbols: Vec<_> = defines.iter().map(|(k, _)| k).collect();
279 assert!(
280 symbols.windows(2).all(|w| w[0] < w[1]),
281 "Symbols should be sorted by numeric value"
282 );
283
284 assert!(defines.contains("A"));
286 assert!(defines.contains("B"));
287 assert!(defines.contains("C"));
288 }
289
290 #[test]
291 fn test_merge() {
292 let mut d1 = ShaderDefines::new();
293 d1.set("A", "1");
294 d1.set("B", "2");
295
296 let mut d2 = ShaderDefines::new();
297 d2.set("B", "3");
298 d2.set("C", "4");
299
300 d1.merge(&d2);
301
302 assert_eq!(d1.get("A"), Some("1".to_string()));
303 assert_eq!(d1.get("B"), Some("3".to_string())); assert_eq!(d1.get("C"), Some("4".to_string()));
305 }
306
307 #[test]
308 fn test_hash_consistency() {
309 let mut d1 = ShaderDefines::new();
310 d1.set("A", "1");
311 d1.set("B", "2");
312
313 let mut d2 = ShaderDefines::new();
314 d2.set("B", "2");
315 d2.set("A", "1");
316
317 assert_eq!(d1.compute_hash(), d2.compute_hash());
318 }
319}