diff --git a/crates/tytix-core/src/actor.rs b/crates/tytix-core/src/actor.rs new file mode 100644 index 0000000..1b9391f --- /dev/null +++ b/crates/tytix-core/src/actor.rs @@ -0,0 +1,147 @@ +use std::any::Any; +use std::marker::PhantomData; +use std::pin::Pin; + +use crate::InternalMessage; +use crate::IsContainedInBundle; +use crate::Message; +use crate::MessageBundle; + +pub trait Actor: Any +where + Self: ActorHandler, +{ + type HandledMessages: MessageBundle; +} + +pub trait IntoActorHandle { + fn into_actor_handle(self) -> ActorHandle; +} + +impl IntoActorHandle for A { + fn into_actor_handle(self) -> ActorHandle { + ActorHandle::new(self) + } +} + +pub trait Handle { + fn handle(&mut self, message: M) -> impl Future>; +} + +pub trait ActorHandler: Any { + fn handle_message( + &mut self, + message: InternalMessage, + ) -> impl Future>; +} + +macro_rules! impl_actor_handle { + ( $($ty:ident),* ) => { + impl ActorHandler<($($ty,)*)> for ACTOR + where + ACTOR: Actor, + $( ACTOR: Handle<$ty>, )* + $( $ty: Message, )* + { + fn handle_message( + &mut self, + msg: InternalMessage, + ) -> impl Future> { + + #[allow(unused_variables)] + async { + $( + let msg = match msg.into_inner::<$ty>() { + Ok(msg) => { + return >::handle(self, msg).await.map(InternalMessage::new); + } + Err(msg) => msg, + }; + )* + + Err(anyhow::anyhow!("Could not handle message")) + + } + } + + } + }; +} + +all_the_tuples!(impl_actor_handle); + +pub struct ActorHandle { + actor: Box, + #[allow(clippy::type_complexity)] + handle: for<'a> fn( + &'a mut dyn Any, + msg: InternalMessage, + ) -> Pin> + 'a>>, + _pd: PhantomData, +} + +impl ActorHandle { + pub fn new>(actor: A) -> ActorHandle { + ActorHandle { + actor: Box::new(actor), + handle: |actor, msg| { + let actor: &mut A = actor.downcast_mut().unwrap(); + + Box::pin(actor.handle_message(msg)) + }, + + _pd: PhantomData, + } + } + + pub async fn handle>( + &mut self, + message: M, + ) -> anyhow::Result { + const { + let true = >::IS_CONTAINED else { + panic!("Message is not contained in MessageBundle",); + }; + } + + (self.handle)(self.actor.as_mut(), InternalMessage::new(message)) + .await + .map(|msg| match msg.into_inner::() { + Ok(t) => t, + Err(msg) => panic!("Could not process reply of type: {}", msg.type_name()), + }) + } +} + +#[cfg(test)] +mod tests { + use macro_rules_attribute::apply; + use smol_macros::test; + + use super::*; + + struct Foo; + + impl Message for Foo { + type Reply = (); + } + + struct FActor; + + impl Actor for FActor { + type HandledMessages = (Foo,); + } + + impl Handle for FActor { + async fn handle(&mut self, _message: Foo) -> anyhow::Result<::Reply> { + Ok(()) + } + } + + #[apply(test!)] + async fn test_name() { + let mut actor = FActor.into_actor_handle(); + + actor.handle(Foo).await.unwrap(); + } +} diff --git a/crates/tytix-core/src/address.rs b/crates/tytix-core/src/address.rs new file mode 100644 index 0000000..c80671b --- /dev/null +++ b/crates/tytix-core/src/address.rs @@ -0,0 +1,87 @@ +use std::any::Any; +use std::marker::PhantomData; +use std::pin::Pin; + +use crate::InternalMessage; +use crate::IsContainedInBundle; +use crate::Message; +use crate::MessageBundle; + +pub trait Address { + fn send(&mut self, message: M) -> impl Future>; +} + +pub struct BoxedAddress { + addr: Box, + #[allow(clippy::type_complexity)] + send: for<'a> fn( + &'a mut dyn Any, + InternalMessage, + ) -> Pin> + 'a>>, + _pd: PhantomData, +} + +impl BoxedAddress { + pub fn new(addr: IMH) -> Self + where + IMH: InternalMessageHandler + 'static, + { + BoxedAddress { + addr: Box::new(addr), + send: |addr, msg| { + let addr: &mut IMH = addr.downcast_mut().unwrap(); + + Box::pin(addr.handle_message(msg)) + }, + _pd: PhantomData, + } + } +} + +impl InternalMessageHandler for BoxedAddress +where + MB: MessageBundle, +{ + type HandledMessages = MB; + fn handle_message( + &mut self, + msg: InternalMessage, + ) -> impl Future> { + (self.send)(self.addr.as_mut(), msg) + } +} + +pub trait InternalMessageHandler { + type HandledMessages: MessageBundle; + fn handle_message( + &mut self, + msg: InternalMessage, + ) -> impl Future>; +} + +impl Address for IMH +where + IMH: InternalMessageHandler, +{ + fn send(&mut self, message: M) -> impl Future> { + const { + let true = >::IS_CONTAINED else { + panic!("Message is not contained in MessageBundle",); + }; + } + + let message = self.handle_message(InternalMessage::new(message)); + + async { + message.await.and_then(|msg| { + msg.into_inner::().map_err(|e| { + anyhow::anyhow!( + "Expected a {}, but got a {}", + std::any::type_name::(), + e.type_name() + ) + }) + }) + } + } +} diff --git a/crates/tytix-core/src/lib.rs b/crates/tytix-core/src/lib.rs index 7651dee..3a8bd5f 100644 --- a/crates/tytix-core/src/lib.rs +++ b/crates/tytix-core/src/lib.rs @@ -1,114 +1,21 @@ #![feature(const_cmp)] #![feature(const_trait_impl)] -use std::any::Any; +#[macro_use] +mod macros; +pub mod address; +pub mod message; +pub mod actor; + use std::any::TypeId; +pub use address::Address; +pub use address::BoxedAddress; +pub use address::InternalMessageHandler; pub use anyhow; - -pub trait Message: Send + Any { - type Reply: Send + Any; -} - -pub trait MessageIdentifier { - const IDENT: BundleChain; -} - -impl MessageIdentifier for M { - const IDENT: BundleChain = BundleChain::of::(); -} - -pub struct InternalMessage { - value: Box, - name: &'static str, -} - -impl InternalMessage { - pub fn new(message: M) -> InternalMessage { - InternalMessage { - value: Box::new(message), - name: std::any::type_name::(), - } - } - - pub fn into_inner(self) -> Result { - self.value - .downcast() - .map(|v| *v) - .map_err(|value| InternalMessage { - value, - name: self.name, - }) - } - - pub fn as_ref(&self) -> Option<&M> { - self.value.downcast_ref() - } - - pub fn type_name(&self) -> &'static str { - self.name - } - - pub fn type_id(&self) -> TypeId { - self.value.as_ref().type_id() - } -} - -pub trait Address { - fn send(&mut self, message: M) -> impl Future>; -} - -pub trait InternalMessageHandler { - type HandledMessages: MessageBundle; - fn handle_message( - &mut self, - msg: InternalMessage, - ) -> impl Future>; -} - -impl Address for IMH -where - IMH: InternalMessageHandler, -{ - fn send(&mut self, message: M) -> impl Future> { - const { - let true = >::IS_CONTAINED else { - panic!("Message is not contained in MessageBundle",); - }; - } - - let message = self.handle_message(InternalMessage::new(message)); - - async { - message.await.and_then(|msg| { - msg.into_inner::().map_err(|e| { - anyhow::anyhow!( - "Expected a {}, but got a {}", - std::any::type_name::(), - e.type_name() - ) - }) - }) - } - } -} - -pub trait MessageReceiver {} - -pub trait MessageBundle { - const IDS: BundleChain; -} - -impl MessageBundle for () { - const IDS: BundleChain = BundleChain { - next: None, - op: BundleOp::Remove(TypeId::of::<()>()), - }; -} - -impl MessageBundle for (M,) { - const IDS: BundleChain = BundleChain::of::(); -} +pub use message::InternalMessage; +pub use message::Message; +pub use message::MessageBundle; #[derive(Debug, Clone, Copy)] pub struct BundleChain { @@ -117,13 +24,21 @@ pub struct BundleChain { } #[derive(Debug, Clone, Copy)] -pub enum BundleOp { +enum BundleOp { Add(TypeId), Remove(TypeId), Chain(&'static BundleChain), + None, } impl BundleChain { + pub const fn empty() -> BundleChain { + BundleChain { + op: BundleOp::None, + next: None, + } + } + pub const fn of() -> BundleChain { BundleChain { op: BundleOp::Add(TypeId::of::()), @@ -135,6 +50,40 @@ impl BundleChain { check_is_contained(self, id) } + pub const fn is_subset_of(&self, ids: &BundleChain) -> bool { + let mut current = self; + + loop { + match current.op { + BundleOp::Add(type_id) => { + if !ids.contains(type_id) { + return false; + } + } + BundleOp::Remove(..) => (), + BundleOp::Chain(bundle_chain) => { + let chain_result = bundle_chain.is_subset_of(ids); + let own_result = if let Some(bc) = self.next { + bc.is_subset_of(ids) + } else { + true + }; + + return chain_result && own_result; + } + BundleOp::None => (), + } + + if let Some(next) = current.next { + current = next; + } else { + break; + }; + } + + true + } + pub const fn with(&'static self) -> BundleChain { let to_add = TypeId::of::(); BundleChain { @@ -159,24 +108,6 @@ impl BundleChain { } } -impl MessageBundle for (A, B) -where - A: Message, - B: Message, -{ - const IDS: BundleChain = BundleChain::of::().with::(); -} - -impl MessageBundle for (A, B, C, D) -where - A: Message, - B: Message, - C: Message, - D: Message, -{ - const IDS: BundleChain = BundleChain::of::().with::().with::().with::(); -} - pub trait IsContainedInBundle { const IS_CONTAINED: bool; } @@ -186,7 +117,7 @@ where M: Message, MB: MessageBundle, { - const IS_CONTAINED: bool = check_is_contained(&MB::IDS, TypeId::of::()); + const IS_CONTAINED: bool = check_is_contained(&MB::CHAIN, TypeId::of::()); } const fn check_is_contained(ids: &BundleChain, id: TypeId) -> bool { @@ -206,6 +137,7 @@ const fn check_is_contained(ids: &BundleChain, id: TypeId) -> bool { return true; } } + BundleOp::None => (), } if let Some(next) = ids.next { @@ -269,6 +201,16 @@ mod tests { )); } + #[test] + fn check_subset() { + const CHAIN: BundleChain = BundleChain::of::().join(&BundleChain::of::()); + const SUB: BundleChain = BundleChain::of::().with::(); + + assert!(SUB.is_subset_of(&CHAIN)); + + assert!(!BundleChain::of::().is_subset_of(&CHAIN)); + } + #[apply(test!)] async fn check_sending_messages() { struct Sender; diff --git a/crates/tytix-core/src/macros.rs b/crates/tytix-core/src/macros.rs new file mode 100644 index 0000000..914ce3d --- /dev/null +++ b/crates/tytix-core/src/macros.rs @@ -0,0 +1,43 @@ +#[rustfmt::skip] +macro_rules! all_the_tuples { + ($name:ident) => { + $name!(M1); + $name!(M1, M2); + $name!(M1, M2, M3); + $name!(M1, M2, M3, M4); + $name!(M1, M2, M3, M4, M5); + $name!(M1, M2, M3, M4, M5, M6); + $name!(M1, M2, M3, M4, M5, M6, M7); + $name!(M1, M2, M3, M4, M5, M6, M7, M8); + $name!(M1, M2, M3, M4, M5, M6, M7, M8, M9); + $name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10); + $name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11); + $name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12); + $name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13); + $name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14); + $name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15); + $name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15, M16); + }; +} + +#[rustfmt::skip] +macro_rules! all_the_tuples_special_first { + ($name:ident) => { + $name!([], M1); + $name!([M1], M2); + $name!([M1, M2], M3); + $name!([M1, M2, M3], M4); + $name!([M1, M2, M3, M4], M5); + $name!([M1, M2, M3, M4, M5], M6); + $name!([M1, M2, M3, M4, M5, M6], M7); + $name!([M1, M2, M3, M4, M5, M6, M7], M8); + $name!([M1, M2, M3, M4, M5, M6, M7, M8], M9); + $name!([M1, M2, M3, M4, M5, M6, M7, M8, M9], M10); + $name!([M1, M2, M3, M4, M5, M6, M7, M8, M9, M10], M11); + $name!([M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11], M12); + $name!([M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12], M13); + $name!([M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13], M14); + $name!([M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14], M15); + $name!([M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15], M16); + }; +} diff --git a/crates/tytix-core/src/message.rs b/crates/tytix-core/src/message.rs new file mode 100644 index 0000000..e91d29f --- /dev/null +++ b/crates/tytix-core/src/message.rs @@ -0,0 +1,66 @@ +use std::any::Any; +use std::any::TypeId; + +use crate::BundleChain; + +pub trait Message: Send + Any { + type Reply: Send + Any; +} + +pub struct InternalMessage { + value: Box, + name: &'static str, +} + +impl InternalMessage { + pub fn new(message: M) -> InternalMessage { + InternalMessage { + value: Box::new(message), + name: std::any::type_name::(), + } + } + + pub fn into_inner(self) -> Result { + self.value + .downcast() + .map(|v| *v) + .map_err(|value| InternalMessage { + value, + name: self.name, + }) + } + + pub fn as_ref(&self) -> Option<&M> { + self.value.downcast_ref() + } + + pub fn type_name(&self) -> &'static str { + self.name + } + + pub fn type_id(&self) -> TypeId { + self.value.as_ref().type_id() + } +} + +pub trait MessageBundle { + const CHAIN: BundleChain; +} + +impl MessageBundle for () { + const CHAIN: BundleChain = BundleChain::empty(); +} + +macro_rules! impl_message_bundles { + ( [ $($ty:ident),* ] , $last:ident ) => { + impl<$($ty,)* $last> MessageBundle for ($($ty,)* $last,) + where + $( $ty: Message, )* + $last: Message, + { + const CHAIN: BundleChain = <($($ty,)*) as MessageBundle>::CHAIN.with::<$last>(); + } + }; +} + +all_the_tuples_special_first!(impl_message_bundles); diff --git a/crates/tytix/src/lib.rs b/crates/tytix/src/lib.rs index f1222ef..523c5df 100644 --- a/crates/tytix/src/lib.rs +++ b/crates/tytix/src/lib.rs @@ -1,6 +1,7 @@ use std::marker::PhantomData; use futures::FutureExt; +use tytix_core::BoxedAddress; use tytix_core::InternalMessage; use tytix_core::InternalMessageHandler; use tytix_core::IsContainedInBundle; @@ -18,17 +19,29 @@ pub trait AddressExt { fn inspect(self, f: F) -> Inspect where - F: Fn(&M) -> U, - M: Message + IsContainedInBundle, + Inspect: InternalMessageHandler, Self: Sized; fn join(self, o: Other) -> Joined where Joined: InternalMessageHandler, Self: Sized; + + fn simplify(self) -> SimpleAddress + where + SimpleAddress: InternalMessageHandler, + Self: Sized; + + fn boxed(self) -> BoxedAddress + where + BoxedAddress: InternalMessageHandler; } -impl> AddressExt for A { +impl AddressExt for A +where + MB: MessageBundle, + A: InternalMessageHandler + 'static, +{ fn map(self, f: F, r: RF) -> MappedMessage where MappedMessage: InternalMessageHandler, @@ -44,16 +57,9 @@ impl> Address fn inspect(self, f: F) -> Inspect where - F: Fn(&M) -> U, + Inspect: InternalMessageHandler, Self: Sized, - M: Message + IsContainedInBundle, { - const { - let true = >::IS_CONTAINED else { - panic!("Message is not contained in MessageBundle",); - }; - } - Inspect { address: self, func: f, @@ -71,6 +77,47 @@ impl> Address right: o, } } + + fn simplify(self) -> SimpleAddress + where + SimpleAddress: InternalMessageHandler, + Self: Sized, + { + SimpleAddress { + inner: self, + _pd: PhantomData, + } + } + + fn boxed(self) -> BoxedAddress { + BoxedAddress::new(self) + } +} + +pub struct SimpleAddress { + inner: A, + _pd: PhantomData, +} + +impl InternalMessageHandler for SimpleAddress +where + SMB: MessageBundle, + MB: MessageBundle, + A: InternalMessageHandler, +{ + type HandledMessages = SMB; + + fn handle_message( + &mut self, + msg: InternalMessage, + ) -> impl Future> { + const { + let true = SMB::CHAIN.is_subset_of(&MB::CHAIN) else { + panic!("Message is not contained in MessageBundle",); + }; + } + self.inner.handle_message(msg) + } } pub struct Joined { @@ -87,7 +134,8 @@ where L: MessageBundle, R: MessageBundle, { - const IDS: tytix_core::BundleChain = ::IDS.join(&::IDS); + const CHAIN: tytix_core::BundleChain = + ::CHAIN.join(&::CHAIN); } impl InternalMessageHandler for Joined @@ -100,7 +148,7 @@ where async fn handle_message(&mut self, msg: InternalMessage) -> anyhow::Result { let message_id = msg.type_id(); - if L::HandledMessages::IDS.contains(message_id) { + if L::HandledMessages::CHAIN.contains(message_id) { self.left.handle_message(msg).await } else { self.right.handle_message(msg).await @@ -148,7 +196,7 @@ where M: Message, R: Message + IsContainedInBundle, { - const IDS: tytix_core::BundleChain = MB::IDS.without::().with::(); + const CHAIN: tytix_core::BundleChain = MB::CHAIN.without::().with::(); } impl InternalMessageHandler for MappedMessage @@ -219,9 +267,9 @@ mod tests { type Reply = usize; } - struct SimpleAddress; + struct FooBarAddress; - impl InternalMessageHandler for SimpleAddress { + impl InternalMessageHandler for FooBarAddress { type HandledMessages = (Foo, Bar); async fn handle_message( @@ -253,7 +301,7 @@ mod tests { async fn check_mapping() { static MSG: OnceLock = OnceLock::new(); - let mut sa = SimpleAddress.map( + let mut sa = FooBarAddress.map( |_b: Bar| { let _ = MSG.set(true); async { Foo } @@ -270,7 +318,7 @@ mod tests { async fn check_inspect() { static MSG: OnceLock = OnceLock::new(); - let mut sa = SimpleAddress.inspect(|_b: &Bar| { + let mut sa = FooBarAddress.inspect(|_b: &Bar| { let _ = MSG.set(true); async {} }); @@ -285,11 +333,11 @@ mod tests { } #[apply(test!)] - async fn check_join() { + async fn check_simplify() { static MSG_SA: OnceLock = OnceLock::new(); static MSG_ZAP: OnceLock = OnceLock::new(); - let sa = SimpleAddress.inspect(|_b: &Bar| { + let sa = FooBarAddress.inspect(|_b: &Bar| { MSG_SA.set(true).unwrap(); async {} }); @@ -308,4 +356,42 @@ mod tests { MSG_ZAP.get().expect("The message was NOT inspected!"); } + + #[apply(test!)] + async fn check_join() { + static MSG_SA: OnceLock = OnceLock::new(); + + let sa = FooBarAddress.inspect(|_b: &Bar| { + MSG_SA.set(true).unwrap(); + async {} + }); + + let zap = ZapAddress.join(ZapAddress).join(ZapAddress); + + let joined = sa.join(zap); + + let mut simple = joined.simplify::<(Foo, Bar, Zap)>(); + + simple.send(Bar).await.unwrap(); + + MSG_SA.get().expect("The message was not :CC inspected!"); + } + + #[apply(test!)] + async fn check_boxed() { + static MSG_SA: OnceLock = OnceLock::new(); + + let sa = FooBarAddress.inspect(|_b: &Bar| { + MSG_SA.set(true).unwrap(); + async {} + }); + + let zap = ZapAddress.join(ZapAddress).join(ZapAddress); + + let mut boxed = zap.join(sa).simplify::<(Bar, Zap, Foo)>().boxed(); + + boxed.send(Bar).await.unwrap(); + + MSG_SA.get().expect("The message was not :CC inspected!"); + } }