use std::collections::HashSet;
use std::{slice, vec};
use super::CompositionDiagnostic;
#[derive(Debug, Default, Clone)]
pub(crate) struct Diagnostics {
entries: Vec<CompositionDiagnostic>,
#[allow(clippy::box_collection)]
seen: Option<Box<HashSet<CompositionDiagnostic>>>,
}
impl Diagnostics {
const INDEX_AT: usize = 32;
pub(crate) fn report(&mut self, diagnostic: CompositionDiagnostic) {
if self.admit(&diagnostic) {
self.entries.push(diagnostic);
}
}
fn admit(&mut self, diagnostic: &CompositionDiagnostic) -> bool {
if let Some(seen) = &mut self.seen {
return seen.insert(diagnostic.clone());
}
if self.entries.contains(diagnostic) {
return false;
}
if self.entries.len() >= Self::INDEX_AT {
let mut seen: HashSet<CompositionDiagnostic> = self.entries.iter().cloned().collect();
seen.insert(diagnostic.clone());
self.seen = Some(Box::new(seen));
}
true
}
pub(crate) fn retain(&mut self, predicate: impl FnMut(&CompositionDiagnostic) -> bool) {
self.entries.retain(predicate);
self.seen = None;
}
pub(crate) fn clear(&mut self) {
self.entries.clear();
self.seen = None;
}
pub(crate) fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &CompositionDiagnostic> {
self.entries.iter()
}
pub(crate) fn into_vec(self) -> Vec<CompositionDiagnostic> {
self.entries
}
}
#[cfg(test)]
impl Diagnostics {
pub(crate) fn len(&self) -> usize {
self.entries.len()
}
}
impl Extend<CompositionDiagnostic> for Diagnostics {
fn extend<T: IntoIterator<Item = CompositionDiagnostic>>(&mut self, diagnostics: T) {
for diagnostic in diagnostics {
self.report(diagnostic);
}
}
}
impl FromIterator<CompositionDiagnostic> for Diagnostics {
fn from_iter<T: IntoIterator<Item = CompositionDiagnostic>>(diagnostics: T) -> Self {
let mut held = Self::default();
held.extend(diagnostics);
held
}
}
impl IntoIterator for Diagnostics {
type Item = CompositionDiagnostic;
type IntoIter = vec::IntoIter<CompositionDiagnostic>;
fn into_iter(self) -> Self::IntoIter {
self.entries.into_iter()
}
}
impl<'a> IntoIterator for &'a Diagnostics {
type Item = &'a CompositionDiagnostic;
type IntoIter = slice::Iter<'a, CompositionDiagnostic>;
fn into_iter(self) -> Self::IntoIter {
self.entries.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pcp::ArcType;
use crate::sdf;
fn unresolved(layer: &str) -> CompositionDiagnostic {
CompositionDiagnostic::UnresolvedLayer {
asset_path: layer.to_string(),
arc: ArcType::Reference,
introduced_by: "root.usd".to_string(),
site_path: sdf::path("/Prim").expect("valid path"),
}
}
#[test]
fn report_deduplicates() {
let mut held = Diagnostics::default();
held.report(unresolved("a.usd"));
held.report(unresolved("a.usd"));
assert_eq!(held.len(), 1);
}
#[test]
fn extend_preserves_order() {
let mut held = Diagnostics::default();
held.extend([unresolved("a.usd"), unresolved("b.usd"), unresolved("a.usd")]);
let order: Vec<&CompositionDiagnostic> = held.iter().collect();
assert_eq!(order, vec![&unresolved("a.usd"), &unresolved("b.usd")]);
}
#[test]
fn dedups_past_index_threshold() {
let many: Vec<CompositionDiagnostic> = (0..Diagnostics::INDEX_AT * 2)
.map(|i| unresolved(&format!("{i}.usd")))
.collect();
let mut held: Diagnostics = many.iter().cloned().collect();
assert_eq!(held.len(), many.len());
held.extend(many.iter().cloned());
assert_eq!(held.len(), many.len(), "every repeat is dropped, indexed or not");
assert_eq!(held.iter().next(), Some(&many[0]), "first-seen order survives");
}
#[test]
fn retain_forgets_dropped() {
let mut held: Diagnostics = [unresolved("a.usd"), unresolved("b.usd")].into_iter().collect();
held.retain(|diagnostic| diagnostic != &unresolved("a.usd"));
held.report(unresolved("a.usd"));
assert_eq!(held.len(), 2);
assert_eq!(
held.iter().collect::<Vec<_>>(),
vec![&unresolved("b.usd"), &unresolved("a.usd")],
"the re-report is a first appearance"
);
}
#[test]
fn rebuild_dedups_transformed() {
let held: Diagnostics = [unresolved("a.usd"), unresolved("b.usd")].into_iter().collect();
assert_eq!(held.len(), 2);
let stamped: Diagnostics = held
.into_iter()
.map(|mut diagnostic| {
if let CompositionDiagnostic::UnresolvedLayer { asset_path, .. } = &mut diagnostic {
*asset_path = "same.usd".to_string();
}
diagnostic
})
.collect();
assert_eq!(stamped.len(), 1, "the transformation made them equal");
}
}