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

Further clean up layers code #2214

Merged
merged 27 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7265180
Add `Event` trait
hannobraun Feb 15, 2024
9a9877d
Require `State` to be `Sized`
hannobraun Feb 15, 2024
ce95d0b
Require `State::Event` to implement `Event`
hannobraun Feb 15, 2024
692b325
Convert `State::evolve` into `Event::evolve`
hannobraun Feb 15, 2024
7484a10
Refactor to simplify
hannobraun Feb 15, 2024
310b437
Simplify `ValidationEvent`
hannobraun Feb 15, 2024
3154f99
Update documentation of `ValidationEvent`
hannobraun Feb 15, 2024
5b5a802
Rename `ValidationEvent` to `ValidationFailed`
hannobraun Feb 15, 2024
00b54dc
Simplify `ObjectsEvent`
hannobraun Feb 15, 2024
f03cd50
Update documentation of `ObjectsEvent`
hannobraun Feb 15, 2024
7beaa99
Rename `ObjectsEvent` to `InsertObject`
hannobraun Feb 15, 2024
f088fec
Update method name
hannobraun Feb 15, 2024
2915051
Add `Command` trait
hannobraun Feb 15, 2024
9555364
Implement `Command` for layer change commands
hannobraun Feb 15, 2024
0ae7979
Convert `State::decide` into `Command::decide`
hannobraun Feb 15, 2024
39edbab
Move `State::Event` to `Command`
hannobraun Feb 15, 2024
2400b66
Refactor to simplify
hannobraun Feb 15, 2024
8cd1caa
Remove redundant associated type
hannobraun Feb 15, 2024
8bd5e99
Remove redundant trait bound
hannobraun Feb 15, 2024
e38716a
Remove redundant trait bounds
hannobraun Feb 15, 2024
dd49959
Remove redundant trait
hannobraun Feb 15, 2024
3e3c49e
Refactor to simplify
hannobraun Feb 15, 2024
01247c3
Remove redundant command
hannobraun Feb 15, 2024
bf633a6
Inline redundant variable
hannobraun Feb 15, 2024
0e5ab8d
Inline redundant method
hannobraun Feb 15, 2024
421bdb9
Remove redundant command
hannobraun Feb 15, 2024
b4e6e07
Update documentation of `layers`
hannobraun Feb 15, 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
57 changes: 25 additions & 32 deletions crates/fj-core/src/layers/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,14 @@ use std::ops::Deref;
/// processed by [`Layer::process`]. Processing a command can result in any
/// number of events, which can then be used as commands for other layers.
///
/// All of this is mediated through [`State`], which the wrapped state must
/// implement.
///
/// This design takes inspiration from, and uses the nomenclature of, this
/// article:
/// <https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider>
pub struct Layer<S: State> {
pub struct Layer<S> {
state: S,
}

impl<S: State> Layer<S> {
impl<S> Layer<S> {
/// Create an instance of `Layer`
pub fn new(state: S) -> Self {
Self { state }
Expand All @@ -30,11 +27,14 @@ impl<S: State> Layer<S> {
///
/// The command is processed synchronously. When this method returns, the
/// state has been updated.
pub fn process(&mut self, command: S::Command, events: &mut Vec<S::Event>) {
self.state.decide(command, events);
pub fn process<C>(&mut self, command: C, events: &mut Vec<C::Event>)
where
C: Command<S>,
{
command.decide(&self.state, events);

for event in events {
self.state.evolve(event);
event.evolve(&mut self.state);
}
}

Expand All @@ -44,15 +44,15 @@ impl<S: State> Layer<S> {
}
}

impl<S: State> Deref for Layer<S> {
impl<S> Deref for Layer<S> {
type Target = S;

fn deref(&self) -> &Self::Target {
&self.state
}
}

impl<S: State> Default for Layer<S>
impl<S> Default for Layer<S>
where
S: Default,
{
Expand All @@ -61,37 +61,30 @@ where
}
}

/// The state of a specific layer
///
/// Implementations of this trait are wrapped by the generic [`Layer`], which is
/// the consumer of this trait's API.
///
/// See [`Layer`] for a more detailed explanation.
pub trait State {
/// A command that encodes a request to update the state
///
/// Commands are processed by [`State::decide`].
type Command;

/// A command that encodes a request to update a layer's state
pub trait Command<S> {
/// An event that encodes a change to the state
///
/// Events are produced by [`State::decide`] and processed by
/// [`State::evolve`].
type Event;
/// Events are produced by [`Command::decide`] and processed by
/// [`Event::evolve`].
type Event: Event<S>;

/// Decide how to react to the provided command
/// Decide which events to produce, given the command and provided state
///
/// If the command must result in changes to the state, any number of events
/// that describe these state changes can be produced.
fn decide(&self, command: Self::Command, events: &mut Vec<Self::Event>);
fn decide(self, state: &S, events: &mut Vec<Self::Event>);
}

/// Evolve the state according to the provided event
/// An event that encodes a change to a layer's state
pub trait Event<S> {
/// Evolve the provided state
///
/// This is the only method that gets mutable access to the state, making
/// sure that all changes to the state are captured as events.
/// This is the only method that [`Layer`] gives mutable access to the
/// state, making sure that all changes to the state are captured as events.
///
/// Implementations of this method are supposed to be relatively dumb. Any
/// decisions that go into updating the state should be made in
/// [`State::decide`], and encoded into the event.
fn evolve(&mut self, event: &Self::Event);
/// [`Command::decide`], and encoded into the event.
fn evolve(&self, state: &mut S);
}
2 changes: 1 addition & 1 deletion crates/fj-core/src/layers/layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use super::Layer;
/// That can be changed, once necessary.
#[derive(Default)]
pub struct Layers {
/// The objects layers
/// The objects layer
///
/// Manages the stores of topological and geometric objects that make up
/// shapes.
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-core/src/layers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ mod layer;
mod layers;

pub use self::{
layer::{Layer, State},
layer::{Command, Event, Layer},
layers::Layers,
};
58 changes: 23 additions & 35 deletions crates/fj-core/src/layers/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,46 @@ use crate::{
validate::Validation,
};

use super::{Layer, State};
use super::{Command, Event, Layer};

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

for event in events {
validation.on_objects_event(event);
validation.process(event, &mut Vec::new());
}
}
}

impl State for Objects {
type Command = ObjectsCommand;
type Event = ObjectsEvent;
/// Insert an object into the stores
///
/// This struct serves as both event and command for `Layer<Objects>`, as well
/// as a command for `Layer<Validation>`.
#[derive(Clone, Debug)]
pub struct InsertObject {
/// The object to insert
pub object: AnyObject<AboutToBeStored>,
}

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

fn evolve(&mut self, event: &Self::Event) {
let ObjectsEvent::InsertObject { object } = event;
object.clone().insert(self);
fn decide(self, _: &Objects, events: &mut Vec<Self::Event>) {
events.push(self);
}
}

/// Command for `Layer<Objects>`
#[derive(Debug)]
pub enum ObjectsCommand {
/// Insert an object into the stores
///
/// This is the one primitive operation that all other operations are built
/// upon.
InsertObject {
/// The object to insert
object: AnyObject<AboutToBeStored>,
},
}

/// Event produced by `Layer<Objects>`
#[derive(Clone, Debug)]
pub enum ObjectsEvent {
/// Insert an object into the stores
InsertObject {
/// The object to insert
object: AnyObject<AboutToBeStored>,
},
impl Event<Objects> for InsertObject {
fn evolve(&self, state: &mut Objects) {
self.object.clone().insert(state);
}
}
75 changes: 25 additions & 50 deletions crates/fj-core/src/layers/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,9 @@ use crate::{
validate::{Validation, ValidationError, ValidationErrors},
};

use super::{objects::ObjectsEvent, Layer, State};
use super::{objects::InsertObject, Command, Event, Layer};

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());
}

/// Consume the validation layer, returning any validation errors
pub fn into_result(self) -> Result<(), ValidationErrors> {
let errors = self.into_state().into_errors();
Expand All @@ -29,54 +20,38 @@ impl Layer<Validation> {
}
}

impl State for Validation {
type Command = ValidationCommand;
type Event = ValidationEvent;
impl Command<Validation> for InsertObject {
type Event = ValidationFailed;

fn decide(&self, command: Self::Command, events: &mut Vec<Self::Event>) {
fn decide(self, state: &Validation, events: &mut Vec<Self::Event>) {
let mut errors = Vec::new();

match command {
ValidationCommand::ValidateObject { object } => {
object.validate_with_config(&self.config, &mut errors);

for err in errors {
events.push(ValidationEvent::ValidationFailed {
object: object.clone(),
err,
});
}
}
}
}
let object: AnyObject<Stored> = self.object.into();
object.validate_with_config(&state.config, &mut errors);

fn evolve(&mut self, event: &Self::Event) {
match event {
ValidationEvent::ValidationFailed { object, err } => {
self.errors.insert(object.id(), err.clone());
}
for err in errors {
events.push(ValidationFailed {
object: object.clone(),
err,
});
}
}
}

/// Command for `Layer<Validation>`
pub enum ValidationCommand {
/// Validate the provided object
ValidateObject {
/// The object to validate
object: AnyObject<Stored>,
},
}

/// Event produced by `Layer<Validation>`
/// Validation of an object failed
///
/// Event produced by `Layer<Validation>`.
#[derive(Clone)]
pub enum ValidationEvent {
/// Validation of an object failed
ValidationFailed {
/// The object for which validation failed
object: AnyObject<Stored>,
pub struct ValidationFailed {
/// The object for which validation failed
pub object: AnyObject<Stored>,

/// The validation error
err: ValidationError,
},
/// The validation error
pub err: ValidationError,
}

impl Event<Validation> for ValidationFailed {
fn evolve(&self, state: &mut Validation) {
state.errors.insert(self.object.id(), self.err.clone());
}
}