Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up the layers code #2213

Merged
merged 26 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
907fdf9
Allow `lints.clippy.module_inception`
hannobraun Feb 14, 2024
069d3e5
Move `Layers` to dedicated module
hannobraun Feb 14, 2024
0834bc7
Refactor to prepare for follow-on change
hannobraun Feb 14, 2024
69e86ec
Remove unused re-export
hannobraun Feb 14, 2024
7a0a8f3
Refactor to prepare for follow-on change
hannobraun Feb 14, 2024
c92744e
Remove unused re-export
hannobraun Feb 14, 2024
edcf8cc
Refactor to prepare for follow-on change
hannobraun Feb 14, 2024
6ca6461
Remove unused re-export
hannobraun Feb 14, 2024
a7250b2
Remove unused re-exports
hannobraun Feb 14, 2024
7838855
Refactor to prepare for follow-on change
hannobraun Feb 14, 2024
8df1ac9
Move `Validation` to `validate`
hannobraun Feb 14, 2024
f0097dc
Refactor to prepare for follow-on change
hannobraun Feb 14, 2024
63ffd9d
Make modules public
hannobraun Feb 14, 2024
0bf6889
Rename `Operation` to `ObjectsCommand`
hannobraun Feb 14, 2024
e2e3a0d
Rename `InsertObject` to `ObjectsEvent`
hannobraun Feb 14, 2024
46b4468
Convert `ObjectsEvent` into enum
hannobraun Feb 14, 2024
2b4052a
Update variable name
hannobraun Feb 14, 2024
b467cee
Update variable name
hannobraun Feb 14, 2024
856a361
Add `Layer<Validation>::on_objects_event`
hannobraun Feb 14, 2024
19899d3
Add `Layer<Objects>::insert`
hannobraun Feb 14, 2024
0047d3a
Remove redundant method
hannobraun Feb 14, 2024
9eb035e
Add `Layer<Validation>::into_result`
hannobraun Feb 14, 2024
02c09fa
Remove redundant method
hannobraun Feb 14, 2024
12fc950
Refactor to simplify
hannobraun Feb 14, 2024
d3b44ae
Refactor to simplify
hannobraun Feb 14, 2024
2ca47a7
Inline redundant variable
hannobraun Feb 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ categories = ["encoding", "mathematics", "rendering"]
[workspace.lints.rust]
missing_docs = "warn"

[workspace.lints.clippy]
# I really don't see any point in this. It can make a public API ugly, but
# a) that will be obvious, even without a lint, and
# b) it provides benefits in private APIs with top-level re-exports.
module_inception = "allow"


[workspace.dependencies.fj]
version = "0.48.0"
Expand Down
49 changes: 49 additions & 0 deletions crates/fj-core/src/layers/layers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use crate::{
objects::Objects,
validate::{Validation, ValidationConfig},
};

use super::Layer;

/// # Loosely coupled layers, that together define shapes
///
/// Shapes are not a monolithic thing in Fornjot, but instead are defined by
/// several, loosely coupled layers. These layers are owned by this struct.
///
/// ## Implementation Note
///
/// It is totally conceivable that one day, this system of layers is extensible
/// and more layers can be defined by third-party code. The foundation for that,
/// the loose coupling and inter-layer communication via events, is already
/// there, conceptually.
///
/// For now, there is no need for this, and all layers are just hardcoded here.
/// That can be changed, once necessary.
#[derive(Default)]
pub struct Layers {
/// The objects layers
///
/// Manages the stores of topological and geometric objects that make up
/// shapes.
pub objects: Layer<Objects>,

/// The validation layer
///
/// Monitors objects and validates them, as they are inserted.
pub validation: Layer<Validation>,
}

impl Layers {
/// Construct an instance of `Layers`
pub fn new() -> Self {
Self::default()
}

/// Construct an instance of `Layers`, using the provided configuration
pub fn with_validation_config(config: ValidationConfig) -> Self {
Self {
validation: Layer::new(Validation::with_validation_config(config)),
..Default::default()
}
}
}
85 changes: 5 additions & 80 deletions crates/fj-core/src/layers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,88 +2,13 @@
//!
//! See [`Layers`].

mod layer;
mod objects;
mod validation;
pub mod objects;
pub mod validation;

use crate::{
objects::{AboutToBeStored, AnyObject, Objects},
validate::{ValidationConfig, ValidationErrors},
};
mod layer;
mod layers;

pub use self::{
layer::{Layer, State},
objects::{InsertObject, Operation},
validation::{Validation, ValidationCommand, ValidationEvent},
layers::Layers,
};

/// # Loosely coupled layers, that together define shapes
///
/// Shapes are not a monolithic thing in Fornjot, but instead are defined by
/// several, loosely coupled layers. These layers are owned by this struct.
///
/// ## Implementation Note
///
/// It is totally conceivable that one day, this system of layers is extensible
/// and more layers can be defined by third-party code. The foundation for that,
/// the loose coupling and inter-layer communication via events, is already
/// there, conceptually.
///
/// For now, there is no need for this, and all layers are just hardcoded here.
/// That can be changed, once necessary.
#[derive(Default)]
pub struct Layers {
/// The objects layers
///
/// Manages the stores of topological and geometric objects that make up
/// shapes.
pub objects: Layer<Objects>,

/// The validation layer
///
/// Monitors objects and validates them, as they are inserted.
pub validation: Layer<Validation>,
}

impl Layers {
/// Construct an instance of `Layers`
pub fn new() -> Self {
Self::default()
}

/// Construct an instance of `Layers`, using the provided configuration
pub fn with_validation_config(config: ValidationConfig) -> Self {
let objects = Layer::default();
let validation = Layer::new(Validation::with_validation_config(config));

Self {
objects,
validation,
}
}

/// Insert an object into the stores
pub fn insert_object(&mut self, object: AnyObject<AboutToBeStored>) {
let mut object_events = Vec::new();
self.objects
.process(Operation::InsertObject { object }, &mut object_events);

for object_event in object_events {
let command = ValidationCommand::ValidateObject {
object: object_event.object.into(),
};
self.validation.process(command, &mut Vec::new());
}
}

/// Drop `Layers`; return any unhandled validation error
pub fn drop_and_validate(self) -> Result<(), ValidationErrors> {
let errors = self.validation.into_state().into_errors();

if errors.0.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
47 changes: 36 additions & 11 deletions crates/fj-core/src/layers/objects.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,46 @@
use crate::objects::{AboutToBeStored, AnyObject, Objects};
//! Layer infrastructure for [`Objects`]

use super::State;
use crate::{
objects::{AboutToBeStored, AnyObject, Objects},
validate::Validation,
};

use super::{Layer, State};

impl Layer<Objects> {
/// Insert and object into the stores
pub fn insert(
&mut self,
object: AnyObject<AboutToBeStored>,
validation: &mut Layer<Validation>,
) {
let mut events = Vec::new();
self.process(ObjectsCommand::InsertObject { object }, &mut events);

for event in events {
validation.on_objects_event(event);
}
}
}

impl State for Objects {
type Command = Operation;
type Event = InsertObject;
type Command = ObjectsCommand;
type Event = ObjectsEvent;

fn decide(&self, command: Self::Command, events: &mut Vec<Self::Event>) {
let Operation::InsertObject { object } = command;
events.push(InsertObject { object });
let ObjectsCommand::InsertObject { object } = command;
events.push(ObjectsEvent::InsertObject { object });
}

fn evolve(&mut self, event: &Self::Event) {
event.object.clone().insert(self);
let ObjectsEvent::InsertObject { object } = event;
object.clone().insert(self);
}
}

/// Command for `Layer<Objects>`
#[derive(Debug)]
pub enum Operation {
pub enum ObjectsCommand {
/// Insert an object into the stores
///
/// This is the one primitive operation that all other operations are built
Expand All @@ -31,7 +53,10 @@ pub enum Operation {

/// Event produced by `Layer<Objects>`
#[derive(Clone, Debug)]
pub struct InsertObject {
/// The object to insert
pub object: AnyObject<AboutToBeStored>,
pub enum ObjectsEvent {
/// Insert an object into the stores
InsertObject {
/// The object to insert
object: AnyObject<AboutToBeStored>,
},
}
68 changes: 18 additions & 50 deletions crates/fj-core/src/layers/validation.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,30 @@
use std::{collections::HashMap, error::Error, thread};
//! Layer infrastructure for [`Validation`]

use crate::{
objects::{AnyObject, Stored},
storage::ObjectId,
validate::{ValidationConfig, ValidationError, ValidationErrors},
validate::{Validation, ValidationError, ValidationErrors},
};

use super::State;
use super::{objects::ObjectsEvent, Layer, State};

/// Errors that occurred while validating the objects inserted into the stores
#[derive(Default)]
pub struct Validation {
/// All unhandled validation errors
errors: HashMap<ObjectId, ValidationError>,

/// Validation configuration for the validation service
config: ValidationConfig,
}

impl Validation {
/// Construct an instance of `Validation`, using the provided configuration
pub fn with_validation_config(config: ValidationConfig) -> Self {
let errors = HashMap::new();
Self { errors, config }
impl Layer<Validation> {
/// Handler for [`ObjectsEvent`]
pub fn on_objects_event(&mut self, event: ObjectsEvent) {
let ObjectsEvent::InsertObject { object } = event;
let command = ValidationCommand::ValidateObject {
object: object.into(),
};
self.process(command, &mut Vec::new());
}

/// Drop this instance, returning the errors it contained
pub fn into_errors(mut self) -> ValidationErrors {
ValidationErrors(self.errors.drain().map(|(_, error)| error).collect())
}
}

impl Drop for Validation {
fn drop(&mut self) {
let num_errors = self.errors.len();
if num_errors > 0 {
println!(
"Dropping `Validation` with {num_errors} unhandled validation \
errors:"
);
/// Consume the validation layer, returning any validation errors
pub fn into_result(self) -> Result<(), ValidationErrors> {
let errors = self.into_state().into_errors();

for err in self.errors.values() {
println!("{}", err);

// Once `Report` is stable, we can replace this:
// https://doc.rust-lang.org/std/error/struct.Report.html
let mut source = err.source();
while let Some(err) = source {
println!("\nCaused by:\n\t{err}");
source = err.source();
}

print!("\n\n");
}

if !thread::panicking() {
panic!();
}
if errors.0.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion crates/fj-core/src/operations/insert/insert_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ macro_rules! impl_insert {
fn insert(self, core: &mut Instance) -> Self::Inserted {
let handle = core.layers.objects.$store.reserve();
let object = (handle.clone(), self).into();
core.layers.insert_object(object);
core.layers.objects.insert(
object,
&mut core.layers.validation,
);
handle
}
}
Expand Down
59 changes: 58 additions & 1 deletion crates/fj-core/src/validate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,17 @@ mod solid;
mod surface;
mod vertex;

use crate::storage::ObjectId;

pub use self::{
cycle::CycleValidationError, edge::EdgeValidationError,
face::FaceValidationError, shell::ShellValidationError,
sketch::SketchValidationError, solid::SolidValidationError,
};

use std::{convert::Infallible, fmt};
use std::{
collections::HashMap, convert::Infallible, error::Error, fmt, thread,
};

use fj_math::Scalar;

Expand All @@ -99,6 +103,59 @@ macro_rules! assert_contains_err {
};
}

/// Errors that occurred while validating the objects inserted into the stores
#[derive(Default)]
pub struct Validation {
/// All unhandled validation errors
pub errors: HashMap<ObjectId, ValidationError>,

/// Validation configuration for the validation service
pub config: ValidationConfig,
}

impl Validation {
/// Construct an instance of `Validation`, using the provided configuration
pub fn with_validation_config(config: ValidationConfig) -> Self {
let errors = HashMap::new();
Self { errors, config }
}

/// Drop this instance, returning the errors it contained
pub fn into_errors(mut self) -> ValidationErrors {
ValidationErrors(self.errors.drain().map(|(_, error)| error).collect())
}
}

impl Drop for Validation {
fn drop(&mut self) {
let num_errors = self.errors.len();
if num_errors > 0 {
println!(
"Dropping `Validation` with {num_errors} unhandled validation \
errors:"
);

for err in self.errors.values() {
println!("{}", err);

// Once `Report` is stable, we can replace this:
// https://doc.rust-lang.org/std/error/struct.Report.html
let mut source = err.source();
while let Some(err) = source {
println!("\nCaused by:\n\t{err}");
source = err.source();
}

print!("\n\n");
}

if !thread::panicking() {
panic!();
}
}
}
}

/// Validate an object
///
/// This trait is used automatically when inserting an object into a store.
Expand Down
Loading