Skip to content

Commit

Permalink
Merge pull request #2214 from hannobraun/layers
Browse files Browse the repository at this point in the history
Further clean up layers code
  • Loading branch information
hannobraun authored Feb 15, 2024
2 parents 75e6a68 + b4e6e07 commit f5810a4
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 119 deletions.
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());
}
}

0 comments on commit f5810a4

Please sign in to comment.