Skip to content

Commit

Permalink
Merge pull request #2144 from nathan-folsom/object-ownership-validation
Browse files Browse the repository at this point in the history
Object Reference Validation #2133
  • Loading branch information
hannobraun authored Jan 14, 2024
2 parents 9d992aa + cdcf015 commit 39b6ca8
Show file tree
Hide file tree
Showing 5 changed files with 505 additions and 7 deletions.
7 changes: 7 additions & 0 deletions crates/fj-core/src/services/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ impl<S: State> Deref for Service<S> {
}
}

#[cfg(test)]
impl<S: State> std::ops::DerefMut for Service<S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.state
}
}

impl<S: State> Default for Service<S>
where
S: Default,
Expand Down
7 changes: 6 additions & 1 deletion crates/fj-core/src/validate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ mod curve;
mod cycle;
mod edge;
mod face;
mod references;
mod region;
mod shell;
mod sketch;
Expand All @@ -86,7 +87,7 @@ mod vertex;
pub use self::{
cycle::CycleValidationError, edge::EdgeValidationError,
face::FaceValidationError, shell::ShellValidationError,
solid::SolidValidationError,
sketch::SketchValidationError, solid::SolidValidationError,
};

use std::{convert::Infallible, fmt};
Expand Down Expand Up @@ -190,6 +191,10 @@ pub enum ValidationError {
/// `Solid` validation error
#[error("`Solid` validation error")]
Solid(#[from] SolidValidationError),

/// `Sketch` validation error
#[error("`Sketch` validation error")]
Sketch(#[from] SketchValidationError),
}

impl From<Infallible> for ValidationError {
Expand Down
107 changes: 107 additions & 0 deletions crates/fj-core/src/validate/references.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use std::collections::HashMap;
use std::hash::Hash;

use crate::objects::{Cycle, Face, HalfEdge, Region, Shell};
use crate::storage::Handle;

#[derive(Default)]
pub struct ReferenceCounter<T, U>(HashMap<Handle<T>, Vec<Handle<U>>>);

impl<T: Eq + PartialEq + Hash, U> ReferenceCounter<T, U> {
pub fn new() -> Self {
Self(HashMap::new())
}

pub fn add_reference(
&mut self,
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>> {
self.0
.iter()
.filter(|(_, references)| references.len() > 1)
.map(|(referenced, references)| MultipleReferences {
referenced: referenced.clone(),
references: references.to_vec(),
})
.collect()
}
}

/// Find errors and convert to [`crate::validate::ValidationError`]
#[macro_export]
macro_rules! validate_references {
($errors:ident, $error_ty:ty;$($counter:ident, $err:ident;)*) => {
$(
$counter.get_multiples().iter().for_each(|multiple| {
let reference_error = ReferenceCountError::$err { references: multiple.clone() };
$errors.push(Into::<$error_ty>::into(reference_error).into());
});
)*
};
}

/// Validation errors for when an object is referenced by multiple other objects. Each object
/// should only be referenced by a single other object
#[derive(Clone, Debug, thiserror::Error)]
pub enum ReferenceCountError {
/// [`crate::objects::Region`] referenced by more than one [`crate::objects::Face`]
#[error(
"[`Region`] referenced by more than one [`Face`]\n{references:#?}"
)]
Region {
references: MultipleReferences<Region, Face>,
},
/// [`crate::objects::Face`] referenced by more than one [`crate::objects::Shell`]
#[error("[`Face`] referenced by more than one [`Shell`]\n{references:#?}")]
Face {
references: MultipleReferences<Face, Shell>,
},
/// [`crate::objects::HalfEdge`] referenced by more than one [`crate::objects::Cycle`]
#[error(
"[`HalfEdge`] referenced by more than one [`Cycle`]\n{references:#?}"
)]
HalfEdge {
references: MultipleReferences<HalfEdge, Cycle>,
},
/// [`crate::objects::Cycle`] referenced by more than one [`crate::objects::Region`]
#[error(
"[`Cycle`] referenced by more than one [`Region`]\n{references:#?}"
)]
Cycle {
references: MultipleReferences<Cycle, Region>,
},
}

pub struct MultipleReferences<T, U> {
referenced: Handle<T>,
references: Vec<Handle<U>>,
}

use std::fmt::Debug;

impl<T: Debug, U: Debug> Debug for MultipleReferences<T, U> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:?} referenced by {:?}",
self.referenced, self.references
)
}
}

impl<T, U> Clone for MultipleReferences<T, U> {
fn clone(&self) -> Self {
Self {
referenced: self.referenced.clone(),
references: self.references.to_vec(),
}
}
}
136 changes: 132 additions & 4 deletions crates/fj-core/src/validate/sketch.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,140 @@
use crate::objects::Sketch;
use crate::{objects::Sketch, validate_references};

use super::{Validate, ValidationConfig, ValidationError};
use super::{
references::{ReferenceCountError, ReferenceCounter},
Validate, ValidationConfig, ValidationError,
};

impl Validate for Sketch {
fn validate_with_config(
&self,
_: &ValidationConfig,
_: &mut Vec<ValidationError>,
config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
SketchValidationError::check_object_references(self, config, errors);
}
}

/// [`Sketch`] validation failed
#[derive(Clone, Debug, thiserror::Error)]
pub enum SketchValidationError {
/// Object within sketch referenced by more than one other object
#[error("Object within sketch referenced by more than one other Object")]
MultipleReferences(#[from] ReferenceCountError),
}

impl SketchValidationError {
fn check_object_references(
sketch: &Sketch,
_config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
let mut referenced_edges = ReferenceCounter::new();
let mut referenced_cycles = ReferenceCounter::new();

sketch.regions().iter().for_each(|r| {
r.all_cycles().for_each(|c| {
referenced_cycles.add_reference(c.clone(), r.clone());
c.half_edges().into_iter().for_each(|e| {
referenced_edges.add_reference(e.clone(), c.clone());
})
})
});

validate_references!(
errors, SketchValidationError;
referenced_edges, HalfEdge;
referenced_cycles, Cycle;
);
}
}

#[cfg(test)]
mod tests {
use crate::{
assert_contains_err,
objects::{Cycle, HalfEdge, Region, Sketch, Vertex},
operations::{build::BuildHalfEdge, insert::Insert},
services::Services,
validate::{
references::ReferenceCountError, SketchValidationError, Validate,
ValidationError,
},
};

#[test]
fn should_find_cycle_multiple_references() -> anyhow::Result<()> {
let mut services = Services::new();

let shared_cycle = Cycle::new(vec![]).insert(&mut services);

let invalid_sketch = Sketch::new(vec![
Region::new(
Cycle::new(vec![]).insert(&mut services),
vec![shared_cycle.clone()],
None,
)
.insert(&mut services),
Region::new(shared_cycle, vec![], None).insert(&mut services),
]);
assert_contains_err!(
invalid_sketch,
ValidationError::Sketch(SketchValidationError::MultipleReferences(
ReferenceCountError::Cycle { references: _ }
))
);

let valid_sketch = Sketch::new(vec![Region::new(
Cycle::new(vec![]).insert(&mut services),
vec![],
None,
)
.insert(&mut services)]);
valid_sketch.validate_and_return_first_error()?;

Ok(())
}

#[test]
fn should_find_half_edge_multiple_references() -> anyhow::Result<()> {
let mut services = Services::new();

let half_edge =
HalfEdge::line_segment([[0., 0.], [1., 0.]], None, &mut services)
.insert(&mut services);
let sibling_edge = HalfEdge::from_sibling(
&half_edge,
Vertex::new().insert(&mut services),
)
.insert(&mut services);

let exterior =
Cycle::new(vec![half_edge.clone(), sibling_edge.clone()])
.insert(&mut services);

let interior =
Cycle::new(vec![half_edge.clone(), sibling_edge.clone()])
.insert(&mut services);

let invalid_sketch = Sketch::new(vec![Region::new(
exterior.clone(),
vec![interior],
None,
)
.insert(&mut services)]);
assert_contains_err!(
invalid_sketch,
ValidationError::Sketch(SketchValidationError::MultipleReferences(
ReferenceCountError::HalfEdge { references: _ }
))
);

let valid_sketch =
Sketch::new(vec![
Region::new(exterior, vec![], None).insert(&mut services)
]);
valid_sketch.validate_and_return_first_error()?;

Ok(())
}
}
Loading

0 comments on commit 39b6ca8

Please sign in to comment.