1 use std::collections::{HashMap, HashSet};
2 
3 use ordered_float::NotNan;
4 
5 use crate::{
6     algorithms::{fitness_distance::SettingFitnessDistanceError, FitnessDistance, SettingFitnessDistanceErrorKind},
7     constraints::SanitizedAdvancedMediaTrackConstraints,
8     errors::OverconstrainedError,
9     BareOrMandatoryMediaTrackConstraints, MediaTrackSettings, MediaTrackSupportedConstraints,
10     SanitizedMandatoryMediaTrackConstraints, SanitizedMediaTrackConstraintSet,
11     SanitizedMediaTrackConstraints,
12 };
13 
14 pub trait SelectSettingsPolicy {
15     /// Selects a preferred candidate from a non-empty selection of optimal candidates.
16     ///
17     /// As specified in step 5 of the `SelectSettings` algorithm:
18     /// https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings
19     ///
20     /// > Select one settings dictionary from candidates, and return it as the result
21     /// > of the SelectSettings algorithm. The User Agent MUST use one with the
22     /// > smallest fitness distance, as calculated in step 3.
23     /// > If more than one settings dictionary have the smallest fitness distance,
24     /// > the User Agent chooses one of them based on system default property values
25     /// > and User Agent default property values.
26     fn select_candidate<'a, I>(&self, candidates: I) -> &'a MediaTrackSettings
27     where
28         I: Iterator<Item = &'a MediaTrackSettings>;
29 }
30 
31 /// A naïve settings selection policy that just picks the first item of the iterator.
32 pub struct SelectFirstSettingsPolicy;
33 
34 impl SelectFirstSettingsPolicy {
35     pub fn new() -> Self {
36         Self
37     }
38 }
39 
40 impl Default for SelectFirstSettingsPolicy {
41     fn default() -> Self {
42         Self::new()
43     }
44 }
45 
46 impl SelectSettingsPolicy for SelectFirstSettingsPolicy {
47     fn select_candidate<'a, I>(&self, mut candidates: I) -> &'a MediaTrackSettings
48     where
49         I: Iterator<Item = &'a MediaTrackSettings>,
50     {
51         // Safety: We know that `candidates is non-empty:
52         candidates
53             .next()
54             .expect("The `candidates` iterator should have produced at least one item.")
55     }
56 }
57 
58 /// A settings selection policy that picks the item that's closest to the ideal.
59 pub struct SelectIdealSettingsPolicy {
60     sanitized_constraints: SanitizedMandatoryMediaTrackConstraints,
61 }
62 
63 impl SelectIdealSettingsPolicy {
64     pub fn new(
65         ideal_settings: MediaTrackSettings,
66         supported_constraints: &MediaTrackSupportedConstraints,
67     ) -> Self {
68         let sanitized_constraints = BareOrMandatoryMediaTrackConstraints::from_iter(
69             ideal_settings
70                 .into_iter()
71                 .map(|(property, setting)| (property, setting.into())),
72         )
73         .into_resolved()
74         .into_sanitized(supported_constraints);
75 
76         Self {
77             sanitized_constraints,
78         }
79     }
80 }
81 
82 impl SelectSettingsPolicy for SelectIdealSettingsPolicy {
83     fn select_candidate<'b, I>(&self, candidates: I) -> &'b MediaTrackSettings
84     where
85         I: Iterator<Item = &'b MediaTrackSettings>,
86     {
87         candidates
88             .min_by_key(|settings| {
89                 let fitness_distance = self
90                     .sanitized_constraints
91                     .fitness_distance(settings)
92                     .expect("Fitness distance should be positive.");
93                 NotNan::new(fitness_distance).expect("Expected non-NaN fitness distance.")
94             })
95             .expect("The `candidates` iterator should have produced at least one item.")
96     }
97 }
98 
99 #[derive(Clone, Eq, PartialEq, Debug)]
100 pub enum SelectSettingsError {
101     Overconstrained(OverconstrainedError),
102 }
103 
104 impl From<OverconstrainedError> for SelectSettingsError {
105     fn from(error: OverconstrainedError) -> Self {
106         Self::Overconstrained(error)
107     }
108 }
109 
110 pub fn select_settings<'a, I, P>(
111     possible_settings: I,
112     constraints: &SanitizedMediaTrackConstraints,
113     policy: &P,
114 ) -> Result<&'a MediaTrackSettings, SelectSettingsError>
115 where
116     I: Iterator<Item = &'a MediaTrackSettings>,
117     P: SelectSettingsPolicy,
118 {
119     // As specified in step 1 of the `SelectSettings` algorithm:
120     // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings
121     //
122     // > Each constraint specifies one or more values (or a range of values) for its property.
123     // > A property MAY appear more than once in the list of 'advanced' ConstraintSets.
124     // > If an empty list has been given as the value for a constraint,
125     // > it MUST be interpreted as if the constraint were not specified
126     // > (in other words, an empty constraint == no constraint).
127     // >
128     // > Note that unknown properties are discarded by WebIDL,
129     // > which means that unknown/unsupported required constraints will silently disappear.
130     // > To avoid this being a surprise, application authors are expected to first use
131     // > the `getSupportedConstraints()` method […].
132 
133     // We can expect "sanitized" constraints to not contain empty constraints:
134     debug_assert!(constraints
135         .mandatory
136         .iter()
137         .all(|(_, constraint)| !constraint.is_empty()));
138 
139     // Obtain candidates by filtering possible settings, dropping those with infinite fitness distances:
140     //
141     // This function call corresponds to steps 3 & 4 of the `SelectSettings` algorithm:
142     // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings
143 
144     let mut candidates = select_feasible_candidates(possible_settings, &constraints.mandatory)?;
145 
146     // As specified in step 5 of the `SelectSettings` algorithm:
147     // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings
148     //
149     // > Iterate over the 'advanced' ConstraintSets in newConstraints in the order in which they were specified.
150     // >
151     // > For each ConstraintSet:
152     // >
153     // > 1. compute the fitness distance between it and each settings dictionary in candidates,
154     // >    treating bare values of properties as exact.
155     // >
156     // > 2. If the fitness distance is finite for one or more settings dictionaries in candidates,
157     // >    keep those settings dictionaries in candidates, discarding others.
158     // >
159     // >    If the fitness distance is infinite for all settings dictionaries in candidates,
160     // >    ignore this ConstraintSet.
161     sieve_by_advanced_constraints(&mut candidates, &constraints.advanced);
162 
163     // Sort candidates by their fitness distance, as obtained in an earlier step:
164     candidates.sort_by(|lhs, rhs| {
165         // Safety: We know (since we check for it in `fitness_distance()`) that `candidates` does
166         // not contain candidates with non-finite distances, so we can safely unwrap the comparison:
167         lhs.1
168             .partial_cmp(&rhs.1)
169             .expect("Fitness distances should be finite at this point.")
170     });
171 
172     // Safety:
173     // - We know that `select_candidates()` returns a non-empty vec of candidates.
174     // - We know that applying advanced constraint-sets will always keep at least one candidate.
175     // As such we can safely unwrap the first element of the vec:
176 
177     let best_fitness_distance = candidates[0].1;
178 
179     let best_candidates = candidates
180         .into_iter()
181         .take_while(|(_, fitness_distance)| *fitness_distance == best_fitness_distance)
182         .map(|(candidate, _)| candidate);
183 
184     // Select one settings value from candidates:
185     //
186     // This function call corresponds to step 5 of the `SelectSettings` algorithm:
187     // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings
188     let best_candidate = policy.select_candidate(best_candidates);
189 
190     Ok(best_candidate)
191 }
192 
193 #[derive(Default)]
194 struct ConstraintFailureInfo {
195     failures: usize,
196     errors: HashSet<SettingFitnessDistanceError>,
197 }
198 
199 fn select_feasible_candidates<'a, I>(
200     possible_settings: I,
201     basic_or_required_constraints: &SanitizedMediaTrackConstraintSet,
202 ) -> Result<Vec<(&'a MediaTrackSettings, f64)>, OverconstrainedError>
203 where
204     I: Iterator<Item = &'a MediaTrackSettings>,
205 {
206     // As specified in step 3 of the `SelectSettings` algorithm:
207     // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings
208     //
209     // > For every possible settings dictionary of copy compute its fitness distance,
210     // > treating bare values of properties as ideal values. Let candidates be the
211     // > set of settings dictionaries for which the fitness distance is finite.
212 
213     let mut settings_len = 0;
214     let mut candidates: Vec<(&'a MediaTrackSettings, f64)> = vec![];
215     let mut failed_constraints: HashMap<String, ConstraintFailureInfo> = Default::default();
216 
217     for settings in possible_settings {
218         settings_len += 1;
219         match basic_or_required_constraints.fitness_distance(settings) {
220             Ok(fitness_distance) => {
221                 candidates.push((settings, fitness_distance));
222             }
223             Err(error) => {
224                 for (property, setting_error) in error.setting_errors {
225                     let entry = failed_constraints
226                         .entry(property)
227                         .or_insert_with(Default::default);
228                     entry.failures += 1;
229                     entry.errors.insert(setting_error);
230                 }
231             }
232         }
233     }
234 
235     if candidates.is_empty() {
236         let error = select_failed_constraint(settings_len, failed_constraints);
237         return Err(error);
238     }
239 
240     Ok(candidates)
241 }
242 
243 /// Implements step 5 of the `SelectSettings` algorithm:
244 /// https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings
245 ///
246 /// # Note:
247 /// This may change the order of items in `feasible_candidates`.
248 /// In practice however this is not a problem as we have to sort
249 /// it by fitness-distance eventually anyway.
250 fn sieve_by_advanced_constraints<'a>(
251     feasible_candidates: &mut Vec<(&'a MediaTrackSettings, f64)>,
252     advanced_constraints: &SanitizedAdvancedMediaTrackConstraints,
253 ) {
254     // As specified in step 5 of the `SelectSettings` algorithm:
255     // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings
256     //
257     // > Iterate over the 'advanced' ConstraintSets in newConstraints in the order in which they were specified.
258     // >
259     // > For each ConstraintSet:
260     // >
261     // > 1. compute the fitness distance between it and each settings dictionary in candidates,
262     // >    treating bare values of properties as exact.
263     // >
264     // > 2. If the fitness distance is finite for one or more settings dictionaries in candidates,
265     // >    keep those settings dictionaries in candidates, discarding others.
266     // >
267     // >    If the fitness distance is infinite for all settings dictionaries in candidates,
268     // >    ignore this ConstraintSet.
269 
270     for advanced_constraint_set in advanced_constraints.iter() {
271         let results: Vec<bool> = feasible_candidates
272             .iter()
273             .map(
274                 |(candidate, _)| match advanced_constraint_set.fitness_distance(candidate) {
275                     Ok(fitness_distance) => {
276                         debug_assert!(fitness_distance.is_finite());
277                         true
278                     }
279                     Err(_) => false,
280                 },
281             )
282             .collect();
283 
284         if !results.iter().any(|is_finite| *is_finite) {
285             continue;
286         }
287 
288         for (index, is_match) in results.iter().enumerate() {
289             if !is_match {
290                 feasible_candidates.swap_remove(index);
291             }
292         }
293     }
294 }
295 
296 fn select_failed_constraint(
297     settings_len: usize,
298     failed_constraints: HashMap<String, ConstraintFailureInfo>,
299 ) -> OverconstrainedError {
300     let failed_constraint = failed_constraints
301         .into_iter()
302         .max_by_key(|(_, failure_info)| failure_info.failures);
303 
304     let (constraint, failure_info) =
305         failed_constraint.expect("Empty candidates implies non-empty failed constraints");
306 
307     if failure_info.failures == settings_len {
308         generate_overconstrained_error(constraint, failure_info.errors)
309     } else {
310         OverconstrainedError::default()
311     }
312 }
313 
314 fn generate_overconstrained_error(
315     constraint: String,
316     errors: HashSet<SettingFitnessDistanceError>,
317 ) -> OverconstrainedError {
318     struct Violation {
319         constraint: String,
320         settings: Vec<String>,
321     }
322     let mut violators_by_kind: HashMap<SettingFitnessDistanceErrorKind, Violation> =
323         HashMap::default();
324 
325     for error in errors {
326         let violation = violators_by_kind.entry(error.kind).or_insert(Violation {
327             constraint: error.constraint.clone(),
328             settings: vec![],
329         });
330         assert_eq!(violation.constraint, error.constraint);
331         if let Some(setting) = error.setting {
332             violation.settings.push(setting.clone());
333         }
334     }
335 
336     let formatted_reasons: Vec<_> = violators_by_kind
337         .into_iter()
338         .map(|(kind, violation)| {
339             let kind_str = match kind {
340                 SettingFitnessDistanceErrorKind::Missing => "missing",
341                 SettingFitnessDistanceErrorKind::Mismatch => "a mismatch",
342                 SettingFitnessDistanceErrorKind::TooSmall => "too small",
343                 SettingFitnessDistanceErrorKind::TooLarge => "too large",
344                 SettingFitnessDistanceErrorKind::Invalid => "invalid",
345             };
346 
347             let mut settings = violation.settings;
348 
349             if settings.is_empty() {
350                 return format!("{} (does not satisfy {})", kind_str, violation.constraint);
351             }
352 
353             settings.sort();
354 
355             format!(
356                 "{} ([{}] do not satisfy {})",
357                 kind_str,
358                 settings.join(", "),
359                 violation.constraint
360             )
361         })
362         .collect();
363 
364     let formatted_reason = match &formatted_reasons[..] {
365         [] => unreachable!(),
366         [reason] => reason.clone(),
367         [reasons @ .., reason] => {
368             let reasons = reasons.join(", ");
369             format!("either {}, or {}", reasons, reason)
370         }
371     };
372     let message = Some(format!("Setting was {}.", formatted_reason));
373     OverconstrainedError {
374         constraint,
375         message,
376     }
377 }
378