Merge pull request #2363 from hannobraun/validation

Clean up validation code that counts object references
This commit is contained in:
Hanno Braun 2024-05-23 21:16:16 +02:00 committed by GitHub
commit 3d633f6821
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 69 additions and 73 deletions

View File

@ -1,7 +1,9 @@
use std::collections::HashMap; use std::{any::type_name_of_val, collections::HashMap, fmt};
use crate::storage::Handle; use crate::{
use crate::topology::{Cycle, Face, HalfEdge, Region, Shell}; storage::Handle,
topology::{Cycle, Face, HalfEdge, Region, Shell},
};
#[derive(Default)] #[derive(Default)]
pub struct ReferenceCounter<T, U>(HashMap<Handle<T>, Vec<Handle<U>>>); pub struct ReferenceCounter<T, U>(HashMap<Handle<T>, Vec<Handle<U>>>);
@ -11,24 +13,17 @@ impl<T, U> ReferenceCounter<T, U> {
Self(HashMap::new()) Self(HashMap::new())
} }
pub fn add_reference( pub fn count_reference(&mut self, to: Handle<T>, from: Handle<U>) {
&mut self, self.0.entry(to).or_default().push(from);
referenced: Handle<T>,
reference: Handle<U>,
) {
self.0
.entry(referenced)
.and_modify(|references| references.push(reference.clone()))
.or_insert(vec![reference]);
} }
pub fn get_multiples(&self) -> Vec<MultipleReferences<T, U>> { pub fn find_multiples(&self) -> Vec<MultipleReferences<T, U>> {
self.0 self.0
.iter() .iter()
.filter(|(_, references)| references.len() > 1) .filter(|(_, referenced_by)| referenced_by.len() > 1)
.map(|(referenced, references)| MultipleReferences { .map(|(object, referenced_by)| MultipleReferences {
referenced: referenced.clone(), object: object.clone(),
references: references.to_vec(), referenced_by: referenced_by.to_vec(),
}) })
.collect() .collect()
} }
@ -39,68 +34,62 @@ impl<T, U> ReferenceCounter<T, U> {
macro_rules! validate_references { macro_rules! validate_references {
($errors:ident, $error_ty:ty;$($counter:ident, $err:ident;)*) => { ($errors:ident, $error_ty:ty;$($counter:ident, $err:ident;)*) => {
$( $(
$counter.get_multiples().iter().for_each(|multiple| { $counter.find_multiples().iter().for_each(|multiple| {
let reference_error = ReferenceCountError::$err { references: multiple.clone() }; let reference_error = ObjectNotExclusivelyOwned::$err { references: multiple.clone() };
$errors.push(Into::<$error_ty>::into(reference_error).into()); $errors.push(Into::<$error_ty>::into(reference_error).into());
}); });
)* )*
}; };
} }
/// Validation errors for when an object is referenced by multiple other objects. Each object /// Object that should be exclusively owned by another, is not
/// should only be referenced by a single other object ///
/// Some objects are expected to be "owned" by a single other object. This means
/// that only one reference to these objects must exist within the topological
/// object graph.
#[derive(Clone, Debug, thiserror::Error)] #[derive(Clone, Debug, thiserror::Error)]
pub enum ReferenceCountError { pub enum ObjectNotExclusivelyOwned {
/// [`Region`] referenced by more than one [`Face`] /// [`Region`] referenced by more than one [`Face`]
#[error( #[error(transparent)]
"[`Region`] referenced by more than one [`Face`]\n{references:#?}"
)]
Region { Region {
references: MultipleReferences<Region, Face>, references: MultipleReferences<Region, Face>,
}, },
/// [`Face`] referenced by more than one [`Shell`] /// [`Face`] referenced by more than one [`Shell`]
#[error("[`Face`] referenced by more than one [`Shell`]\n{references:#?}")] #[error(transparent)]
Face { Face {
references: MultipleReferences<Face, Shell>, references: MultipleReferences<Face, Shell>,
}, },
/// [`HalfEdge`] referenced by more than one [`Cycle`] /// [`HalfEdge`] referenced by more than one [`Cycle`]
#[error( #[error(transparent)]
"[`HalfEdge`] referenced by more than one [`Cycle`]\n{references:#?}"
)]
HalfEdge { HalfEdge {
references: MultipleReferences<HalfEdge, Cycle>, references: MultipleReferences<HalfEdge, Cycle>,
}, },
/// [`Cycle`] referenced by more than one [`Region`] /// [`Cycle`] referenced by more than one [`Region`]
#[error( #[error(transparent)]
"[`Cycle`] referenced by more than one [`Region`]\n{references:#?}"
)]
Cycle { Cycle {
references: MultipleReferences<Cycle, Region>, references: MultipleReferences<Cycle, Region>,
}, },
} }
#[derive(Clone, Debug, thiserror::Error)]
pub struct MultipleReferences<T, U> { pub struct MultipleReferences<T, U> {
referenced: Handle<T>, object: Handle<T>,
references: Vec<Handle<U>>, referenced_by: Vec<Handle<U>>,
} }
use std::fmt::Debug; impl<T, U> fmt::Display for MultipleReferences<T, U>
where
impl<T: Debug, U: Debug> Debug for MultipleReferences<T, U> { T: fmt::Debug,
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { U: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!( write!(
f, f,
"{:?} referenced by {:?}", "`{}` ({:?}) referenced by multiple `{}` objects ({:?})",
self.referenced, self.references type_name_of_val(&self.object),
self.object,
type_name_of_val(&self.referenced_by),
self.referenced_by
) )
} }
} }
impl<T, U> Clone for MultipleReferences<T, U> {
fn clone(&self) -> Self {
Self {
referenced: self.referenced.clone(),
references: self.references.to_vec(),
}
}
}

View File

@ -9,7 +9,7 @@ use crate::{
}; };
use super::{ use super::{
references::{ReferenceCountError, ReferenceCounter}, references::{ObjectNotExclusivelyOwned, ReferenceCounter},
Validate, ValidationConfig, ValidationError, Validate, ValidationConfig, ValidationError,
}; };
@ -39,7 +39,8 @@ impl Validate for Sketch {
pub enum SketchValidationError { pub enum SketchValidationError {
/// Object within sketch referenced by more than one other object /// Object within sketch referenced by more than one other object
#[error("Object within sketch referenced by more than one other Object")] #[error("Object within sketch referenced by more than one other Object")]
MultipleReferences(#[from] ReferenceCountError), ObjectNotExclusivelyOwned(#[from] ObjectNotExclusivelyOwned),
/// Region within sketch has exterior cycle with clockwise winding /// Region within sketch has exterior cycle with clockwise winding
#[error( #[error(
"Exterior cycle within sketch region has clockwise winding\n "Exterior cycle within sketch region has clockwise winding\n
@ -49,6 +50,7 @@ pub enum SketchValidationError {
/// Cycle with clockwise winding /// Cycle with clockwise winding
cycle: Handle<Cycle>, cycle: Handle<Cycle>,
}, },
/// Region within sketch has interior cycle with counter-clockwise winding /// Region within sketch has interior cycle with counter-clockwise winding
#[error( #[error(
"Interior cycle within sketch region has counter-clockwise winding\n "Interior cycle within sketch region has counter-clockwise winding\n
@ -71,9 +73,9 @@ impl SketchValidationError {
sketch.regions().iter().for_each(|r| { sketch.regions().iter().for_each(|r| {
r.all_cycles().for_each(|c| { r.all_cycles().for_each(|c| {
referenced_cycles.add_reference(c.clone(), r.clone()); referenced_cycles.count_reference(c.clone(), r.clone());
c.half_edges().into_iter().for_each(|e| { c.half_edges().into_iter().for_each(|e| {
referenced_edges.add_reference(e.clone(), c.clone()); referenced_edges.count_reference(e.clone(), c.clone());
}) })
}) })
}); });
@ -134,8 +136,8 @@ mod tests {
}, },
topology::{Cycle, HalfEdge, Region, Sketch, Vertex}, topology::{Cycle, HalfEdge, Region, Sketch, Vertex},
validate::{ validate::{
references::ReferenceCountError, SketchValidationError, Validate, references::ObjectNotExclusivelyOwned, SketchValidationError,
ValidationError, Validate, ValidationError,
}, },
Core, Core,
}; };
@ -168,9 +170,11 @@ mod tests {
assert_contains_err!( assert_contains_err!(
core, core,
invalid_sketch, invalid_sketch,
ValidationError::Sketch(SketchValidationError::MultipleReferences( ValidationError::Sketch(
ReferenceCountError::Cycle { references: _ } SketchValidationError::ObjectNotExclusivelyOwned(
)) ObjectNotExclusivelyOwned::Cycle { references: _ }
)
)
); );
Ok(()) Ok(())
@ -206,9 +210,11 @@ mod tests {
assert_contains_err!( assert_contains_err!(
core, core,
invalid_sketch, invalid_sketch,
ValidationError::Sketch(SketchValidationError::MultipleReferences( ValidationError::Sketch(
ReferenceCountError::HalfEdge { references: _ } SketchValidationError::ObjectNotExclusivelyOwned(
)) ObjectNotExclusivelyOwned::HalfEdge { references: _ }
)
)
); );
Ok(()) Ok(())

View File

@ -9,7 +9,7 @@ use crate::{
use fj_math::Point; use fj_math::Point;
use super::{ use super::{
references::{ReferenceCountError, ReferenceCounter}, references::{ObjectNotExclusivelyOwned, ReferenceCounter},
Validate, ValidationConfig, ValidationError, Validate, ValidationConfig, ValidationError,
}; };
@ -70,7 +70,7 @@ pub enum SolidValidationError {
/// Object within solid referenced by more than one other object /// Object within solid referenced by more than one other object
#[error("Object within solid referenced by more than one other Object")] #[error("Object within solid referenced by more than one other Object")]
MultipleReferences(#[from] ReferenceCountError), MultipleReferences(#[from] ObjectNotExclusivelyOwned),
} }
impl SolidValidationError { impl SolidValidationError {
@ -154,13 +154,14 @@ impl SolidValidationError {
solid.shells().iter().for_each(|s| { solid.shells().iter().for_each(|s| {
s.faces().into_iter().for_each(|f| { s.faces().into_iter().for_each(|f| {
referenced_faces.add_reference(f.clone(), s.clone()); referenced_faces.count_reference(f.clone(), s.clone());
referenced_regions.add_reference(f.region().clone(), f.clone()); referenced_regions
.count_reference(f.region().clone(), f.clone());
f.region().all_cycles().for_each(|c| { f.region().all_cycles().for_each(|c| {
referenced_cycles referenced_cycles
.add_reference(c.clone(), f.region().clone()); .count_reference(c.clone(), f.region().clone());
c.half_edges().into_iter().for_each(|e| { c.half_edges().into_iter().for_each(|e| {
referenced_edges.add_reference(e.clone(), c.clone()); referenced_edges.count_reference(e.clone(), c.clone());
}) })
}) })
}) })
@ -187,8 +188,8 @@ mod tests {
}, },
topology::{Cycle, Face, HalfEdge, Region, Shell, Solid, Surface}, topology::{Cycle, Face, HalfEdge, Region, Shell, Solid, Surface},
validate::{ validate::{
references::ReferenceCountError, SolidValidationError, Validate, references::ObjectNotExclusivelyOwned, SolidValidationError,
ValidationError, Validate, ValidationError,
}, },
Core, Core,
}; };
@ -238,7 +239,7 @@ mod tests {
core, core,
invalid_solid, invalid_solid,
ValidationError::Solid(SolidValidationError::MultipleReferences( ValidationError::Solid(SolidValidationError::MultipleReferences(
ReferenceCountError::Face { references: _ } ObjectNotExclusivelyOwned::Face { references: _ }
)) ))
); );
@ -284,7 +285,7 @@ mod tests {
core, core,
invalid_solid, invalid_solid,
ValidationError::Solid(SolidValidationError::MultipleReferences( ValidationError::Solid(SolidValidationError::MultipleReferences(
ReferenceCountError::Region { references: _ } ObjectNotExclusivelyOwned::Region { references: _ }
)) ))
); );
@ -334,7 +335,7 @@ mod tests {
core, core,
invalid_solid, invalid_solid,
ValidationError::Solid(SolidValidationError::MultipleReferences( ValidationError::Solid(SolidValidationError::MultipleReferences(
ReferenceCountError::Cycle { references: _ } ObjectNotExclusivelyOwned::Cycle { references: _ }
)) ))
); );
@ -376,7 +377,7 @@ mod tests {
core, core,
invalid_solid, invalid_solid,
ValidationError::Solid(SolidValidationError::MultipleReferences( ValidationError::Solid(SolidValidationError::MultipleReferences(
ReferenceCountError::HalfEdge { references: _ } ObjectNotExclusivelyOwned::HalfEdge { references: _ }
)) ))
); );