Skip to main content

primase_core/shader/
select_variant.rs

1//! `select-variant`: the choice of one variant of a [`ShaderBinding`] that a source states and leaves to its consumer.
2
3use std::collections::BTreeSet;
4
5use crate::pass::{Given, OptionSpec, Options, Pass, PassLog, PassSpec};
6use crate::world::{Component, World};
7
8use super::ShaderBinding;
9
10/// `select-variant{field=NAME,value=N}` (both required: the choice is the caller's): in every [`ShaderBinding`] that
11/// has stages keyed by `field` ([`super::VariantKey`]), the stages of the variant `value` stay, without their key, and
12/// the stages of the other variants of that field are left out, counted. The binding then holds one stage of each kind
13/// under no key, as a consumer that draws one program of a stage reads it. A stage with no key or a key of another
14/// field is left as it is; a binding with no stage keyed by `field`, or none of the value `value` (a source numbers a
15/// binding's variants from 0, so bindings hold different numbers of them), is left as it is and counted. A `value`
16/// that no binding of the world carries, or a `field` none uses, is an error naming the fields and values the world
17/// does carry, and changes nothing. A program only the stages left out drew (its `core.shader` entity, which nothing
18/// in the world refers to any more) is despawned, counted.
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct SelectVariant {
21    pub field: String,
22    pub value: u32,
23}
24
25/// `select-variant`'s declaration ([`crate::pass::PassRegistry::add`]).
26pub 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
59/// The values as a range when they are consecutive from their first, else a list.
60fn 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    /// The stages of the other variants are taken out of the binding in place, and the chosen ones lose their key.
83    fn modifies(&self) -> &[&'static str] {
84        &[ShaderBinding::NAME]
85    }
86    /// The programs only the left-out stages drew.
87    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        // the programs of the stages left out, despawned below where nothing refers to them any more
122        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    /// A binding with a vertex and a pixel stage for each of `values` of `field`.
205    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    /// The programs only the left-out stages drew are despawned; one another binding still draws stays.
251    #[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        // another binding draws the first left-out program, unkeyed
261        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        // nothing changed
347        assert_eq!(w.get::<ShaderBinding>(a).unwrap(), &before.0);
348        assert_eq!(w.get::<ShaderBinding>(b).unwrap(), &before.1);
349        // the values of a world with gaps are listed
350        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        // a world with no keyed stage at all
357        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}