1use std::collections::BTreeSet;
4
5use crate::pass::{Given, OptionSpec, Options, Pass, PassLog, PassSpec};
6use crate::world::{Component, World};
7
8use super::ShaderBinding;
9
10#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct SelectVariant {
21 pub field: String,
22 pub value: u32,
23}
24
25pub const SELECT_VARIANT: PassSpec = PassSpec {
27 name: "select-variant",
28 options: &[
29 OptionSpec::required(
30 "field",
31 "dialect.variant",
32 "the field of the variant keys to choose by (VariantKey::field), e.g. dialect.variant",
33 ),
34 OptionSpec::required(
35 "value",
36 "0",
37 "the value of that field whose stages are kept: the caller's choice, which the source does not make",
38 ),
39 ],
40 make: select_variant,
41};
42
43fn select_variant(g: &Given) -> Result<Box<dyn Pass>, String> {
44 let field = g.required("field").trim();
45 if field.is_empty() {
46 return Err("field is empty".to_owned());
47 }
48 let value = g.required("value");
49 let value = value
50 .trim()
51 .parse()
52 .map_err(|_| format!("value={value} is not a number from 0"))?;
53 Ok(Box::new(SelectVariant {
54 field: field.to_owned(),
55 value,
56 }))
57}
58
59fn describe(values: &BTreeSet<u32>) -> String {
61 match (values.first(), values.last()) {
62 (Some(&first), Some(&last)) if (last - first) as usize + 1 == values.len() && values.len() > 1 => {
63 format!("{first} to {last}")
64 }
65 _ => values.iter().map(u32::to_string).collect::<Vec<_>>().join(", "),
66 }
67}
68
69impl Pass for SelectVariant {
70 fn name(&self) -> &'static str {
71 "select-variant"
72 }
73 fn options(&self) -> Options {
74 Options::from([
75 ("field".to_owned(), self.field.clone()),
76 ("value".to_owned(), self.value.to_string()),
77 ])
78 }
79 fn requires(&self) -> &[&'static str] {
80 &[ShaderBinding::NAME]
81 }
82 fn modifies(&self) -> &[&'static str] {
84 &[ShaderBinding::NAME]
85 }
86 fn despawns_entities(&self) -> bool {
88 true
89 }
90 fn run(&self, w: &mut World, log: &mut PassLog) -> Result<(), String> {
91 let mut fields: BTreeSet<&str> = BTreeSet::new();
92 let mut values: BTreeSet<u32> = BTreeSet::new();
93 for (_, binding) in w.iter::<ShaderBinding>() {
94 for key in binding.stages.iter().filter_map(|s| s.variant.as_ref()) {
95 fields.insert(key.field.as_str());
96 if key.field == self.field {
97 values.insert(key.value);
98 }
99 }
100 }
101 if values.is_empty() {
102 return Err(format!(
103 "no stage of any binding is keyed by {}; the fields keyed are {}",
104 self.field,
105 if fields.is_empty() {
106 "none".to_owned()
107 } else {
108 fields.into_iter().collect::<Vec<_>>().join(", ")
109 }
110 ));
111 }
112 if !values.contains(&self.value) {
113 return Err(format!(
114 "no stage of any binding has {}={}; the values are {}",
115 self.field,
116 self.value,
117 describe(&values)
118 ));
119 }
120 let chosen: Vec<_> = w.iter::<ShaderBinding>().map(|(e, b)| (e, b.clone())).collect();
121 let mut left_out = BTreeSet::new();
123 for (entity, mut binding) in chosen {
124 let keyed = |s: &super::StageBinding| s.variant.as_ref().is_some_and(|k| k.field == self.field);
125 if !binding.stages.iter().any(keyed) {
126 log.count("bindings with no stage keyed by the field, left as they are", 1);
127 continue;
128 }
129 if !binding.stages.iter().any(|s| {
130 s.variant
131 .as_ref()
132 .is_some_and(|k| k.field == self.field && k.value == self.value)
133 }) {
134 log.count(
135 "bindings with no stage of the chosen value (they hold fewer variants), left as they are",
136 1,
137 );
138 continue;
139 }
140 let before = binding.stages.len();
141 left_out.extend(
142 binding
143 .stages
144 .iter()
145 .filter(|s| {
146 s.variant
147 .as_ref()
148 .is_some_and(|k| k.field == self.field && k.value != self.value)
149 })
150 .map(|s| s.shader),
151 );
152 binding.stages.retain(|s| {
153 s.variant
154 .as_ref()
155 .is_none_or(|k| k.field != self.field || k.value == self.value)
156 });
157 for stage in &mut binding.stages {
158 if stage.variant.as_ref().is_some_and(|k| k.field == self.field) {
159 stage.variant = None;
160 }
161 }
162 log.count("bindings with one variant chosen", 1);
163 log.count(
164 "stages left out: another variant of the field",
165 (before - binding.stages.len()) as u64,
166 );
167 w.insert(entity, binding);
168 }
169 let unreferenced = w.unreferenced();
170 let gone: Vec<_> = left_out.intersection(&unreferenced).copied().collect();
171 for &e in &gone {
172 w.despawn(e);
173 }
174 log.count(
175 "shader programs only the left-out stages drew, despawned",
176 gone.len() as u64,
177 );
178 Ok(())
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use crate::shader::StageBinding;
186 use crate::shader::VariantKey;
187 use iridia_interface::Stage;
188
189 fn key(field: &str, value: u32) -> Option<VariantKey> {
190 Some(VariantKey {
191 field: field.to_owned(),
192 value,
193 parts: Vec::new(),
194 })
195 }
196
197 fn stage(w: &mut World, stage: Stage, variant: Option<VariantKey>) -> StageBinding {
198 StageBinding {
199 variant,
200 ..StageBinding::new(stage, w.spawn())
201 }
202 }
203
204 fn binding(w: &mut World, field: &str, values: &[u32]) -> crate::Entity {
206 let mut stages = Vec::new();
207 for &v in values {
208 stages.push(stage(w, Stage::Vertex, key(field, v)));
209 stages.push(stage(w, Stage::Pixel, key(field, v)));
210 }
211 let e = w.spawn();
212 w.insert(
213 e,
214 ShaderBinding {
215 stages,
216 ..ShaderBinding::default()
217 },
218 );
219 e
220 }
221
222 fn run(w: &mut World, field: &str, value: &str) -> Result<PassLog, String> {
223 let options = Options::from([
224 ("field".to_owned(), field.to_owned()),
225 ("value".to_owned(), value.to_owned()),
226 ]);
227 let mut log = PassLog::default();
228 SELECT_VARIANT.instance(&options)?.run(w, &mut log)?;
229 Ok(log)
230 }
231
232 #[test]
233 fn the_chosen_variants_stages_stay_without_their_key() {
234 let mut w = World::new();
235 let a = binding(&mut w, "dialect.variant", &[0, 1, 2]);
236 let kept: Vec<_> = w.get::<ShaderBinding>(a).unwrap().stages[2..4]
237 .iter()
238 .map(|s| s.shader)
239 .collect();
240 let log = run(&mut w, "dialect.variant", "1").unwrap();
241 let after = w.get::<ShaderBinding>(a).unwrap();
242 assert_eq!(after.stages.len(), 2);
243 assert_eq!(after.stages.iter().map(|s| s.shader).collect::<Vec<_>>(), kept);
244 assert!(after.stages.iter().all(|s| s.variant.is_none()));
245 assert_eq!(after.stages[0].stage, Stage::Vertex);
246 assert_eq!(log.counts["bindings with one variant chosen"], 1);
247 assert_eq!(log.counts["stages left out: another variant of the field"], 4);
248 }
249
250 #[test]
252 fn programs_only_the_left_out_stages_drew_are_despawned() {
253 let mut w = World::new();
254 let a = binding(&mut w, "dialect.variant", &[0, 1, 2]);
255 let stages = w.get::<ShaderBinding>(a).unwrap().stages.clone();
256 let (shared, gone) = (
257 stages[0].shader,
258 [stages[1].shader, stages[4].shader, stages[5].shader],
259 );
260 let other = w.spawn();
262 let mut drawn = stage(&mut w, Stage::Vertex, None);
263 drawn.shader = shared;
264 w.insert(
265 other,
266 ShaderBinding {
267 stages: vec![drawn],
268 ..ShaderBinding::default()
269 },
270 );
271 let log = run(&mut w, "dialect.variant", "1").unwrap();
272 assert!(w.is_alive(shared));
273 assert!(gone.iter().all(|&e| !w.is_alive(e)));
274 assert!(stages[2..4].iter().all(|s| w.is_alive(s.shader)));
275 assert_eq!(
276 log.counts["shader programs only the left-out stages drew, despawned"],
277 3
278 );
279 }
280
281 #[test]
282 fn a_binding_without_the_value_or_the_field_is_left_as_it_is_and_counted() {
283 let mut w = World::new();
284 let big = binding(&mut w, "dialect.variant", &[0, 1, 2]);
285 let small = binding(&mut w, "dialect.variant", &[0]);
286 let other = binding(&mut w, "dialect.layout", &[1, 2]);
287 let before = (
288 w.get::<ShaderBinding>(small).unwrap().clone(),
289 w.get::<ShaderBinding>(other).unwrap().clone(),
290 );
291 let log = run(&mut w, "dialect.variant", "2").unwrap();
292 assert_eq!(w.get::<ShaderBinding>(big).unwrap().stages.len(), 2);
293 assert_eq!(w.get::<ShaderBinding>(small).unwrap(), &before.0);
294 assert_eq!(w.get::<ShaderBinding>(other).unwrap(), &before.1);
295 assert_eq!(
296 log.counts["bindings with no stage of the chosen value (they hold fewer variants), left as they are"],
297 1
298 );
299 assert_eq!(
300 log.counts["bindings with no stage keyed by the field, left as they are"],
301 1
302 );
303 }
304
305 #[test]
306 fn stages_with_no_key_or_another_field_stay() {
307 let mut w = World::new();
308 let e = binding(&mut w, "dialect.variant", &[0, 1]);
309 let plain = stage(&mut w, Stage::Pixel, None);
310 let foreign = stage(&mut w, Stage::Vertex, key("dialect.layout", 7));
311 let mut b = w.get::<ShaderBinding>(e).unwrap().clone();
312 b.stages.push(plain);
313 b.stages.push(foreign);
314 w.insert(e, b);
315 run(&mut w, "dialect.variant", "0").unwrap();
316 let after = w.get::<ShaderBinding>(e).unwrap();
317 assert_eq!(after.stages.len(), 4);
318 assert_eq!(
319 after
320 .stages
321 .iter()
322 .filter_map(|s| s.variant.as_ref())
323 .map(|k| (k.field.as_str(), k.value))
324 .collect::<Vec<_>>(),
325 [("dialect.layout", 7)]
326 );
327 }
328
329 #[test]
330 fn a_value_or_field_no_stage_carries_is_refused_naming_what_is_there() {
331 let mut w = World::new();
332 let a = binding(&mut w, "dialect.variant", &[0, 1, 2, 3]);
333 let b = binding(&mut w, "dialect.variant", &[1, 3]);
334 let before = (
335 w.get::<ShaderBinding>(a).unwrap().clone(),
336 w.get::<ShaderBinding>(b).unwrap().clone(),
337 );
338 assert_eq!(
339 run(&mut w, "dialect.variant", "4").unwrap_err(),
340 "no stage of any binding has dialect.variant=4; the values are 0 to 3"
341 );
342 assert_eq!(
343 run(&mut w, "dialect.layout", "0").unwrap_err(),
344 "no stage of any binding is keyed by dialect.layout; the fields keyed are dialect.variant"
345 );
346 assert_eq!(w.get::<ShaderBinding>(a).unwrap(), &before.0);
348 assert_eq!(w.get::<ShaderBinding>(b).unwrap(), &before.1);
349 let mut gaps = World::new();
351 binding(&mut gaps, "dialect.variant", &[1, 3]);
352 assert_eq!(
353 run(&mut gaps, "dialect.variant", "2").unwrap_err(),
354 "no stage of any binding has dialect.variant=2; the values are 1, 3"
355 );
356 let mut none = World::new();
358 assert!(
359 run(&mut none, "dialect.variant", "0")
360 .unwrap_err()
361 .contains("the fields keyed are none")
362 );
363 }
364
365 #[test]
366 fn both_options_are_required_and_the_value_is_a_number() {
367 let missing = SELECT_VARIANT.instance(&Options::new()).err().unwrap();
368 assert!(missing.contains("field is required"), "{missing}");
369 let only_field = Options::from([("field".to_owned(), "dialect.variant".to_owned())]);
370 assert!(
371 SELECT_VARIANT
372 .instance(&only_field)
373 .err()
374 .unwrap()
375 .contains("value is required")
376 );
377 let bad = Options::from([
378 ("field".to_owned(), "dialect.variant".to_owned()),
379 ("value".to_owned(), "two".to_owned()),
380 ]);
381 assert!(
382 SELECT_VARIANT
383 .instance(&bad)
384 .err()
385 .unwrap()
386 .contains("value=two is not a number from 0")
387 );
388 }
389}