From fe0e5d93b40c01671d9bccd832d5808ee801238f Mon Sep 17 00:00:00 2001 From: Nathan McCarty Date: Thu, 24 Nov 2022 01:56:34 -0500 Subject: [PATCH] feat: Adaptor utility type Add a utility type that wraps an `EventConsumer`, allowing it to consume another type via a conversion closure. --- examples/grading-tests/main.rs | 41 ++++++++++++++++++++++++---- examples/grading-tests/professor.rs | 1 - examples/grading-tests/student.rs | 26 ++++++++++++------ examples/grading-tests/university.rs | 20 ++++++++++---- src/lib.rs | 2 +- src/util.rs | 2 ++ src/util/consumer_adaptor.rs | 40 +++++++++++++++++++++++++++ 7 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 src/util/consumer_adaptor.rs diff --git a/examples/grading-tests/main.rs b/examples/grading-tests/main.rs index 5fa0d02..3d5efa8 100644 --- a/examples/grading-tests/main.rs +++ b/examples/grading-tests/main.rs @@ -8,9 +8,12 @@ use nanorand::Rng; use professor::ProfessorOutputType; use crate::{ - professor::{AddStudent, Professor, ProfessorContext, ProfessorInputType}, - student::{Student, StudentContext, StudentInput}, - university::{University, UniversityContext}, + professor::{ + AddStudent, Professor, ProfessorContext, ProfessorInputType, ProfessorOutputEvent, + SubmitTest, + }, + student::{Student, StudentContext, StudentInput, StudentOutputEvent}, + university::{University, UniversityContext, UniversityInput}, }; pub mod professor; @@ -44,7 +47,18 @@ async fn main() { // Create a university and force it to listen to our professor let university = University::::new(UniversityContext::default(), None); professor - .register_consumer(university.clone()) + .register_consumer(Adaptor::wrap( + |e: ProfessorOutputEvent| { + let e = e.into_inner(); + match e { + ProfessorOutputType::Grade(name, grade) => { + Some(UniversityInput::Grade(name, grade).into()) + } + _ => None, + } + }, + university.clone(), + )) .await .unwrap(); professor.catchup().wait().await; @@ -63,7 +77,22 @@ async fn main() { ); // Connect it to the professor println!("Hooking {name} up to professor"); - student.register_consumer(professor.clone()).await.unwrap(); + student + .register_consumer(Adaptor::wrap( + |e: StudentOutputEvent| { + let e = e.into_inner(); + Some( + ProfessorInputType::SubmitTest(SubmitTest { + answers: e.answers, + student: e.name, + }) + .into(), + ) + }, + professor.clone(), + )) + .await + .unwrap(); // Tell the professor about it println!("Telling professor about {name}"); professor @@ -113,7 +142,7 @@ async fn main() { university.catchup().wait().await; // Collect the results from our students let grades = university - .call(ProfessorOutputType::Get.into()) + .call(UniversityInput::Get.into()) .await .unwrap() .unwrap() diff --git a/examples/grading-tests/professor.rs b/examples/grading-tests/professor.rs index ec3eafa..1a75c67 100644 --- a/examples/grading-tests/professor.rs +++ b/examples/grading-tests/professor.rs @@ -60,7 +60,6 @@ pub enum ProfessorInputType { pub enum ProfessorOutputType { Grade(String, u32), TestGraded, - Get, } // Now generate the impls diff --git a/examples/grading-tests/student.rs b/examples/grading-tests/student.rs index 97547b1..9a5bee3 100644 --- a/examples/grading-tests/student.rs +++ b/examples/grading-tests/student.rs @@ -1,10 +1,7 @@ use actm::prelude::*; use nanorand::Rng; -use crate::{ - professor::{ProfessorInputEvent, ProfessorInputType, SubmitTest}, - Answers, Test, -}; +use crate::{Answers, Test}; // First we define the context type @@ -21,14 +18,24 @@ pub struct StudentInput { pub test: Test, } +// Then the output event +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct StudentOutput { + /// The answers to the test that the student just completed + pub answers: Answers, + /// The student's name + pub name: String, +} + // Generate the impls wrapped_event!(StudentInputEvent, StudentInput); +wrapped_event!(StudentOutputEvent, StudentOutput); async fn student_event_handler( context: StudentContext, mut event: StudentInputEvent, -) -> (StudentContext, Option) { +) -> (StudentContext, Option) { let token = event.token(); let mut rng = nanorand::tls_rng(); let results = event @@ -39,10 +46,11 @@ async fn student_event_handler( .map(|x| -> bool { x > rng.generate_range(1_u32..=100) }) .collect::>(); let name = context.name.clone(); - let mut new_event = ProfessorInputEvent::from(ProfessorInputType::SubmitTest(SubmitTest { + let mut new_event: StudentOutputEvent = StudentOutput { answers: Answers { answers: results }, - student: name, - })); + name, + } + .into(); if let Some(token) = token { new_event.set_completion_token(token); } @@ -52,7 +60,7 @@ async fn student_event_handler( async_actor!( Student, StudentInputEvent, - ProfessorInputEvent, + StudentOutputEvent, StudentContext, student_event_handler ); diff --git a/examples/grading-tests/university.rs b/examples/grading-tests/university.rs index 3e6928e..3edde6a 100644 --- a/examples/grading-tests/university.rs +++ b/examples/grading-tests/university.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use actm::prelude::*; -use crate::professor::{ProfessorOutputEvent, ProfessorOutputType}; + // First we define the context type @@ -12,26 +12,34 @@ pub struct UniversityContext { pub students: BTreeMap>, } +// Then the input type + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum UniversityInput { + Get, + Grade(String, u32), +} + // Then the output type #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct UniversityOutput(pub BTreeMap>); wrapped_event!(UniversityOutputEvent, UniversityOutput); +wrapped_event!(UniveristyInputEvent, UniversityInput); async fn university_event_handler( mut context: UniversityContext, - mut event: ProfessorOutputEvent, + mut event: UniveristyInputEvent, ) -> (UniversityContext, Option) { let token = event.token(); match event.into_inner() { - ProfessorOutputType::Grade(student, grade) => { + UniversityInput::Grade(student, grade) => { let grades = context.students.entry(student).or_default(); grades.push(grade); (context, None) } - ProfessorOutputType::TestGraded => (context, None), - ProfessorOutputType::Get => { + UniversityInput::Get => { let mut event = UniversityOutputEvent::from(UniversityOutput(context.students.clone())); if let Some(token) = token { event.set_completion_token(token); @@ -43,7 +51,7 @@ async fn university_event_handler( async_actor!( University, - ProfessorOutputEvent, + UniveristyInputEvent, UniversityOutputEvent, UniversityContext, university_event_handler diff --git a/src/lib.rs b/src/lib.rs index ae5c5e1..5bbd9a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ pub mod prelude { sync_actor, traits::{Actor, Event, EventConsumer, EventProducer}, types::{CompletionToken, Trigger, Waiter}, - util::{AsyncActor, AsyncActorError, SyncActor, SyncActorError, WrappedEvent}, + util::{Adaptor, AsyncActor, AsyncActorError, SyncActor, SyncActorError, WrappedEvent}, wrapped_event, }; } diff --git a/src/util.rs b/src/util.rs index 864f651..e605c72 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,6 +2,7 @@ mod async_actor; mod collector; +mod consumer_adaptor; pub(crate) mod either_error; mod sync_actor; mod token_manager; @@ -10,6 +11,7 @@ mod wrapped_event; pub use async_actor::{AsyncActor, AsyncActorError}; pub use collector::{Collector, CollectorOutput}; +pub use consumer_adaptor::Adaptor; pub use either_error::EitherError; pub use sync_actor::{SyncActor, SyncActorError}; pub use token_manager::TokenManager; diff --git a/src/util/consumer_adaptor.rs b/src/util/consumer_adaptor.rs new file mode 100644 index 0000000..d819ab5 --- /dev/null +++ b/src/util/consumer_adaptor.rs @@ -0,0 +1,40 @@ +//! Adapter for changing the type of a consumed value when registering a consumer + +use async_trait::async_trait; + +use crate::traits::{Event, EventConsumer}; + +/// Adaptor for registering a consumer +pub struct Adaptor> { + /// The closure used to map the call + closure: Box Option + Send + Sync>, + /// The wrapped consumer + consumer: C, +} + +impl> Adaptor { + /// Wrap another event consumer with the provided closure + pub fn wrap(closure: impl Fn(I) -> Option + Send + Sync + 'static, consumer: C) -> Self { + Self { + closure: Box::new(closure), + consumer, + } + } +} + +#[async_trait] +impl> EventConsumer for Adaptor { + type Error = >::Error; + async fn accept(&self, event: I) -> Result<(), Self::Error> { + match (self.closure)(event) { + Some(x) => self.consumer.accept(x).await, + None => Ok(()), + } + } + fn accept_sync(&self, event: I) -> Result<(), Self::Error> { + match (self.closure)(event) { + Some(x) => self.consumer.accept_sync(x), + None => Ok(()), + } + } +} -- 2.45.2