diff --git a/.changeset/little-rivers-bow.md b/.changeset/little-rivers-bow.md new file mode 100644 index 0000000..a845151 --- /dev/null +++ b/.changeset/little-rivers-bow.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/pink-stingrays-learn.md b/.changeset/pink-stingrays-learn.md new file mode 100644 index 0000000..a845151 --- /dev/null +++ b/.changeset/pink-stingrays-learn.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/yellow-berries-march.md b/.changeset/yellow-berries-march.md new file mode 100644 index 0000000..a845151 --- /dev/null +++ b/.changeset/yellow-berries-march.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/Cargo.lock b/Cargo.lock index c41267f..7f2140d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,7 +128,7 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "servify" -version = "0.1.0" +version = "0.1.1" dependencies = [ "pretty_assertions", "servify_macro", diff --git a/Cargo.toml b/Cargo.toml index b85c5fa..a1f7d0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,7 @@ authors = ["AsPulse "] license = "MIT/Apache-2.0" edition = "2021" repository = "https://github.com/pulsuite/servify" + +[profile.size-optimized] +inherits = "release" +opt-level = "s" diff --git a/servify/src/lib.rs b/servify/src/lib.rs index 26052d9..5056513 100644 --- a/servify/src/lib.rs +++ b/servify/src/lib.rs @@ -1,6 +1,2 @@ -pub use servify_macro::{export, service}; - -pub trait ServifyExport { - type Request; - type Response; -} +pub mod processor; +pub mod serial; diff --git a/servify/src/processor/mod.rs b/servify/src/processor/mod.rs new file mode 100644 index 0000000..e28a928 --- /dev/null +++ b/servify/src/processor/mod.rs @@ -0,0 +1,56 @@ +use tokio::sync::oneshot; + +use crate::serial::ServifyMiddle; +use std::any::Any; + +pub type ServifyMessage = ( + ServifyMiddle<::Kind, ::Context>, + oneshot::Sender>, +); + +pub trait ServifyProcessor: Sized { + type Kind; + type Context; + + fn serve( + &mut self, + middle: ServifyMiddle, + ) -> impl std::future::Future>; + + fn channel(self, buffer_size: usize) -> (ServifyService, ServifyAccess) { + let (tx, rx) = tokio::sync::mpsc::channel(buffer_size); + ( + ServifyService { + processor: self, + rx, + }, + ServifyAccess { tx }, + ) + } +} + +pub struct ServifyService { + processor: T, + rx: tokio::sync::mpsc::Receiver>, +} + +impl ServifyService { + pub async fn listen(mut self) { + while let Some((middle, tx)) = self.rx.recv().await { + let response = self.processor.serve(middle).await; + tx.send(response).unwrap(); + } + } +} + +pub struct ServifyAccess { + pub tx: tokio::sync::mpsc::Sender>, +} + +impl Clone for ServifyAccess { + fn clone(&self) -> Self { + Self { + tx: self.tx.clone(), + } + } +} diff --git a/servify/src/serial/mod.rs b/servify/src/serial/mod.rs new file mode 100644 index 0000000..2e19452 --- /dev/null +++ b/servify/src/serial/mod.rs @@ -0,0 +1,17 @@ +use std::any::Any; + +pub struct ServifyMiddle { + pub ctx: Context, + pub request: ServifyRequest, +} + +pub struct ServifyRequest { + pub payload: Box, + pub kind: Kind, +} + +impl ServifyRequest { + pub fn with_ctx(self, ctx: Context) -> ServifyMiddle { + ServifyMiddle { ctx, request: self } + } +} diff --git a/servify/tests/expanded_1.rs b/servify/tests/expanded_1.rs deleted file mode 100644 index 161fd56..0000000 --- a/servify/tests/expanded_1.rs +++ /dev/null @@ -1,98 +0,0 @@ -use pretty_assertions::assert_eq; - -#[allow(non_snake_case)] -#[allow(unexpected_cfgs)] -mod SomeStruct { - - use super::some_other::some_struct_increment; - - pub struct Server { - pub count: u32, - } - - #[derive(Clone)] - pub struct Client { - tx: tokio::sync::mpsc::Sender, - } - - pub enum Message { - Increment( - some_struct_increment::Request, - tokio::sync::oneshot::Sender, - ), - } - - pub fn initiate_message_passing() -> (::tokio::sync::mpsc::Receiver, Client) { - let (tx, rx) = ::tokio::sync::mpsc::channel(64); - let client = Client { tx }; - (rx, client) - } - - impl Server { - pub async fn listen(&mut self, mut rx: ::tokio::sync::mpsc::Receiver) { - while let Some(msg) = rx.recv().await { - match msg { - Message::Increment(req, tx) => { - let res = self.increment(req).await; - tx.send(res).unwrap(); - } - } - } - } - } - - #[doc(hidden)] - pub async fn __internal_increment( - client: &Client, - req: some_struct_increment::Request, - ) -> some_struct_increment::Response { - let (tx, rx) = ::tokio::sync::oneshot::channel(); - client.tx.send(Message::Increment(req, tx)).await.unwrap(); - rx.await.unwrap() - } -} - -mod some_other { - use super::SomeStruct; - - #[allow(non_camel_case_types)] - pub type __increment_response = u32; - #[allow(non_camel_case_types)] - #[derive(Clone)] - pub struct __increment_request { - count: u32, - } - - impl SomeStruct::Server { - pub async fn increment(&mut self, req: __increment_request) -> __increment_response { - self.__internal_increment(req.count).await - } - - async fn __internal_increment(&mut self, count: u32) -> __increment_response { - self.count += count; - self.count - } - } - - impl SomeStruct::Client { - pub async fn increment(&self, count: u32) -> __increment_response { - SomeStruct::__internal_increment(self, __increment_request { count }).await - } - } - - pub mod some_struct_increment { - pub use super::{__increment_request as Request, __increment_response as Response}; - } -} - -#[tokio::test] -async fn test_manual_expanded() { - let (rx, client) = SomeStruct::initiate_message_passing(); - - tokio::spawn(async move { - SomeStruct::Server { count: 3 }.listen(rx).await; - }); - - assert_eq!(client.increment(5).await, 8); - assert_eq!(client.increment(3).await, 11); -} diff --git a/servify/tests/expanded_2.rs b/servify/tests/expanded_2.rs deleted file mode 100644 index e9c55d5..0000000 --- a/servify/tests/expanded_2.rs +++ /dev/null @@ -1,103 +0,0 @@ -use pretty_assertions::assert_eq; - -#[allow(non_snake_case)] -#[allow(unexpected_cfgs)] -mod SomeStruct { - - use super::some_other::SomeStruct_increment; - - pub struct Server { - pub count: u32, - } - - #[derive(Clone)] - pub struct Client { - tx: tokio::sync::mpsc::Sender, - } - - pub enum Message { - Increment( - ::Request, - tokio::sync::oneshot::Sender< - ::Response, - >, - ), - } - - pub fn initiate_message_passing() -> (::tokio::sync::mpsc::Receiver, Client) { - let (tx, rx) = ::tokio::sync::mpsc::channel(64); - let client = Client { tx }; - (rx, client) - } - - impl Server { - pub async fn listen(&mut self, mut rx: ::tokio::sync::mpsc::Receiver) { - while let Some(msg) = rx.recv().await { - match msg { - Message::Increment(req, tx) => { - let res = self.increment(req).await; - tx.send(res).unwrap(); - } - } - } - } - } - - #[doc(hidden)] - pub async fn __internal_increment( - client: &Client, - req: ::Request, - ) -> ::Response { - let (tx, rx) = ::tokio::sync::oneshot::channel(); - client.tx.send(Message::Increment(req, tx)).await.unwrap(); - rx.await.unwrap() - } -} - -mod some_other { - use super::SomeStruct; - - #[allow(non_camel_case_types)] - pub type __increment_response = u32; - #[allow(non_camel_case_types)] - #[derive(Clone)] - pub struct __increment_request { - count: u32, - } - - impl SomeStruct::Server { - pub async fn increment(&mut self, req: __increment_request) -> __increment_response { - self.__internal_increment(req.count).await - } - - async fn __internal_increment(&mut self, count: u32) -> __increment_response { - self.count += count; - self.count - } - } - - impl SomeStruct::Client { - pub async fn increment(&self, count: u32) -> __increment_response { - SomeStruct::__internal_increment(self, __increment_request { count }).await - } - } - - #[allow(non_camel_case_types)] - pub struct SomeStruct_increment(); - impl ::servify::ServifyExport for SomeStruct_increment { - type Request = __increment_request; - type Response = __increment_response; - } -} - -#[tokio::test] -async fn test_manual_expanded() { - let (rx, client) = SomeStruct::initiate_message_passing(); - - tokio::spawn(async move { - SomeStruct::Server { count: 3 }.listen(rx).await; - }); - - assert_eq!(client.increment(5).await, 8); - assert_eq!(client.increment(3).await, 11); -} diff --git a/servify/tests/expanded_counter_1.rs b/servify/tests/expanded_counter_1.rs new file mode 100644 index 0000000..89e2d44 --- /dev/null +++ b/servify/tests/expanded_counter_1.rs @@ -0,0 +1,274 @@ +use servify::processor::ServifyProcessor as _; +use tokio::task::JoinSet; + +mod counter { + use servify::processor::ServifyProcessor; + use servify::serial::{ServifyMiddle, ServifyRequest}; + use std::{any::Any, future::Future, pin::Pin}; + + pub(super) struct Processor { + pub(super) count: u32, + } + + impl ServifyProcessor for Processor { + type Context = Context; + type Kind = Kind; + async fn serve( + &mut self, + middle: ServifyMiddle, + ) -> Box { + match middle.request.kind { + Kind::IncrementAndGet => { + ::process_any( + self, + middle.ctx, + middle.request.payload, + ) + .await + } + Kind::Get => { + ::process_any(self, middle.ctx, middle.request.payload).await + } + } + } + } + + // Dispatchable - + // このモジュールを、「あたかも構造体に属するメソッドを呼びだせるかのように」呼びだせるトレイト。 + // このトレイトを実装すると、Counter が勝手に各メソッドを実装し、呼びだされた時にsendメソッドを呼びだしてくれる。 + pub trait Dispatchable { + fn send( + &self, + request: ServifyRequest, + ) -> Pin>>>; + } + + #[derive(Clone)] + pub struct Counter(T); + + impl Dispatchable for Counter { + fn send( + &self, + request: ServifyRequest, + ) -> Pin>>> { + self.0.send(request) + } + } + + // Kind - + // このモジュールで扱うリクエストの種類を表す列挙型。 + pub enum Kind { + IncrementAndGet, + Get, + } + + // Context - + // このモジュールで扱うリクエストが、呼ばれたクライアント(正確にはどのサーバーが受けとったか)によって + // 付け加わるコンテキスト情報。 + // + // Directは、Processor構造体から直接呼び出された場合。 + pub(super) enum Context { + Direct, + MessagePassing, + } + + // Impl(メソッド名) - + // 実際に実装を記述している別の場所に対して、定数や関数を要求するトレイト。 + pub(super) trait ImplIncrementAndGet { + async fn process_any( + &mut self, + ctx: Context, + payload: Box, + ) -> Box; + } + + pub(super) trait ImplGet { + async fn process_any( + &mut self, + ctx: Context, + payload: Box, + ) -> Box; + } + + // Modules - + // メッセージパッシングやHTTPサーバーなど、異なるリクエストの種類に対して、それぞれの処理を記述するモジュール。 + pub mod message_passing { + use std::{any::Any, future::Future, pin::Pin}; + + use servify::{processor::ServifyAccess, serial::ServifyRequest}; + + use super::{Context, Counter, Dispatchable, Kind}; + + #[derive(Clone)] + pub struct MessagePassing { + access: ServifyAccess, + } + + pub fn initiate(access: ServifyAccess) -> Counter { + Counter(MessagePassing { access }) + } + + impl Dispatchable for MessagePassing { + fn send( + &self, + request: ServifyRequest, + ) -> Pin>>> { + Box::pin(async move { + let (tx, rx) = tokio::sync::oneshot::channel(); + self.access + .tx + .send((request.with_ctx(Context::MessagePassing), tx)) + .await + .unwrap(); + rx.await.unwrap() + }) + } + } + } +} + +mod counter_increment_and_get { + use std::any::Any; + + use super::counter; + use servify::{processor::ServifyProcessor, serial::ServifyRequest}; + + struct Request { + amount: u32, + } + + trait Process { + async fn process_internal( + &mut self, + ctx: ::Context, + amount: u32, + ) -> u32; + } + + impl Process for counter::Processor { + async fn process_internal( + &mut self, + ctx: ::Context, + amount: u32, + ) -> u32 { + match ctx { + counter::Context::MessagePassing => {} + _ => unimplemented!(), + } + self.count += amount; + self.get().await + } + } + + impl counter::Processor { + #[allow(unused)] + #[inline(always)] + pub async fn increment_and_get(&mut self, amount: u32) -> u32 { + ::process_internal(self, counter::Context::Direct, amount).await + } + } + + impl counter::ImplIncrementAndGet for counter::Processor { + #[inline(always)] + async fn process_any( + &mut self, + ctx: counter::Context, + payload: Box, + ) -> Box { + let req = *payload.downcast::().unwrap(); + Box::new(::process_internal(self, ctx, req.amount).await) + } + } + + impl counter::Counter { + #[inline(always)] + pub async fn increment_and_get(&self, amount: u32) -> u32 { + let req = Request { amount }; + let request = ServifyRequest { + kind: counter::Kind::IncrementAndGet, + payload: Box::new(req), + }; + *::send(self, request) + .await + .downcast::() + .unwrap() + } + } +} + +mod get { + use std::any::Any; + + use super::counter; + use servify::serial::ServifyRequest; + + struct Request {} + + trait Process { + async fn process_internal(&mut self) -> u32; + } + + impl Process for counter::Processor { + async fn process_internal(&mut self) -> u32 { + self.count + } + } + + impl counter::Processor { + #[allow(unused)] + #[inline(always)] + pub async fn get(&mut self) -> u32 { + ::process_internal(self).await + } + } + + impl counter::ImplGet for counter::Processor { + #[inline(always)] + async fn process_any( + &mut self, + _ctx: counter::Context, + payload: Box, + ) -> Box { + let _req = *payload.downcast::().unwrap(); + Box::new(::process_internal(self).await) + } + } + + impl counter::Counter { + #[inline(always)] + pub async fn get(&self) -> u32 { + let req = Request {}; + let request = ServifyRequest { + kind: counter::Kind::Get, + payload: Box::new(req), + }; + *::send(self, request) + .await + .downcast::() + .unwrap() + } + } +} +#[tokio::test] +async fn main() { + let (counter_server, counter_access) = counter::Processor { count: 0 }.channel(32); + tokio::spawn(async move { + counter_server.listen().await; + }); + + let counter = counter::message_passing::initiate(counter_access); + + let mut set = JoinSet::new(); + + for _ in 0..10 { + let counter = counter.clone(); + set.spawn(async move { + for _ in 0..1000 { + counter.increment_and_get(1).await; + } + }); + } + set.join_all().await; + + assert_eq!(counter.get().await, 10000); +} diff --git a/servify/tests/expanded_counter_1_mod_split.rs b/servify/tests/expanded_counter_1_mod_split.rs new file mode 100644 index 0000000..c3aee95 --- /dev/null +++ b/servify/tests/expanded_counter_1_mod_split.rs @@ -0,0 +1,290 @@ +mod a { + + pub(crate) mod counter { + use servify::processor::ServifyProcessor; + use servify::serial::{ServifyMiddle, ServifyRequest}; + use std::{any::Any, future::Future, pin::Pin}; + + pub(crate) struct Processor { + pub(crate) count: u32, + } + + impl ServifyProcessor for Processor { + type Context = Context; + type Kind = Kind; + async fn serve( + &mut self, + middle: ServifyMiddle, + ) -> Box { + match middle.request.kind { + Kind::IncrementAndGet => { + ::process_any( + self, + middle.ctx, + middle.request.payload, + ) + .await + } + Kind::Get => { + ::process_any(self, middle.ctx, middle.request.payload) + .await + } + } + } + } + + // Dispatchable - + // このモジュールを、「あたかも構造体に属するメソッドを呼びだせるかのように」呼びだせるトレイト。 + // このトレイトを実装すると、Counter が勝手に各メソッドを実装し、呼びだされた時にsendメソッドを呼びだしてくれる。 + pub trait Dispatchable { + fn send( + &self, + request: ServifyRequest, + ) -> Pin>>>; + } + + #[derive(Clone)] + pub struct Counter(T); + + impl Dispatchable for Counter { + fn send( + &self, + request: ServifyRequest, + ) -> Pin>>> { + self.0.send(request) + } + } + + // Kind - + // このモジュールで扱うリクエストの種類を表す列挙型。 + pub enum Kind { + IncrementAndGet, + Get, + } + + // Context - + // このモジュールで扱うリクエストが、呼ばれたクライアント(正確にはどのサーバーが受けとったか)によって + // 付け加わるコンテキスト情報。 + // + // Directは、Processor構造体から直接呼び出された場合。 + pub(crate) enum Context { + Direct, + MessagePassing, + } + + // Impl(メソッド名) - + // 実際に実装を記述している別の場所に対して、定数や関数を要求するトレイト。 + pub(crate) trait ImplIncrementAndGet { + async fn process_any( + &mut self, + ctx: Context, + payload: Box, + ) -> Box; + } + + pub(crate) trait ImplGet { + async fn process_any( + &mut self, + ctx: Context, + payload: Box, + ) -> Box; + } + + // Modules - + // メッセージパッシングやHTTPサーバーなど、異なるリクエストの種類に対して、それぞれの処理を記述するモジュール。 + pub mod message_passing { + use std::{any::Any, future::Future, pin::Pin}; + + use servify::{processor::ServifyAccess, serial::ServifyRequest}; + + use super::{Context, Counter, Dispatchable, Kind}; + + #[derive(Clone)] + pub struct MessagePassing { + access: ServifyAccess, + } + + pub fn initiate(access: ServifyAccess) -> Counter { + Counter(MessagePassing { access }) + } + + impl Dispatchable for MessagePassing { + fn send( + &self, + request: ServifyRequest, + ) -> Pin>>> + { + Box::pin(async move { + let (tx, rx) = tokio::sync::oneshot::channel(); + self.access + .tx + .send((request.with_ctx(Context::MessagePassing), tx)) + .await + .unwrap(); + rx.await.unwrap() + }) + } + } + } + } +} + +mod b { + + use super::a::counter; + + mod counter_increment_and_get { + use std::any::Any; + + use super::counter; + use servify::{processor::ServifyProcessor, serial::ServifyRequest}; + + struct Request { + amount: u32, + } + + trait Process { + async fn process_internal( + &mut self, + ctx: ::Context, + amount: u32, + ) -> u32; + } + + impl Process for counter::Processor { + async fn process_internal( + &mut self, + ctx: ::Context, + amount: u32, + ) -> u32 { + match ctx { + counter::Context::MessagePassing => {} + _ => unimplemented!(), + } + self.count += amount; + self.get().await + } + } + + impl counter::Processor { + #[allow(unused)] + #[inline(always)] + pub async fn increment_and_get(&mut self, amount: u32) -> u32 { + ::process_internal(self, counter::Context::Direct, amount).await + } + } + + impl counter::ImplIncrementAndGet for counter::Processor { + #[inline(always)] + async fn process_any( + &mut self, + ctx: counter::Context, + payload: Box, + ) -> Box { + let req = *payload.downcast::().unwrap(); + Box::new(::process_internal(self, ctx, req.amount).await) + } + } + + impl counter::Counter { + #[inline(always)] + pub async fn increment_and_get(&self, amount: u32) -> u32 { + let req = Request { amount }; + let request = ServifyRequest { + kind: counter::Kind::IncrementAndGet, + payload: Box::new(req), + }; + *::send(self, request) + .await + .downcast::() + .unwrap() + } + } + } +} + +mod c { + + use super::a::counter; + mod get { + use std::any::Any; + + use super::counter; + use servify::serial::ServifyRequest; + + struct Request {} + + trait Process { + async fn process_internal(&mut self) -> u32; + } + + impl Process for counter::Processor { + async fn process_internal(&mut self) -> u32 { + self.count + } + } + + impl counter::Processor { + #[allow(unused)] + #[inline(always)] + pub async fn get(&mut self) -> u32 { + ::process_internal(self).await + } + } + + impl counter::ImplGet for counter::Processor { + #[inline(always)] + async fn process_any( + &mut self, + _ctx: counter::Context, + payload: Box, + ) -> Box { + let _req = *payload.downcast::().unwrap(); + Box::new(::process_internal(self).await) + } + } + + impl counter::Counter { + #[inline(always)] + pub async fn get(&self) -> u32 { + let req = Request {}; + let request = ServifyRequest { + kind: counter::Kind::Get, + payload: Box::new(req), + }; + *::send(self, request) + .await + .downcast::() + .unwrap() + } + } + } +} + +use a::counter; +use servify::processor::ServifyProcessor as _; +use tokio::task::JoinSet; + +#[tokio::test] +async fn main() { + let (counter_server, counter_access) = counter::Processor { count: 0 }.channel(32); + tokio::spawn(async move { + counter_server.listen().await; + }); + + let counter = counter::message_passing::initiate(counter_access); + + let mut set = JoinSet::new(); + + for _ in 0..10 { + let counter = counter.clone(); + set.spawn(async move { + for _ in 0..1000 { + counter.increment_and_get(1).await; + } + }); + } + set.join_all().await; + + assert_eq!(counter.get().await, 10000); +} diff --git a/servify/tests/mod.rs b/servify/tests/mod.rs deleted file mode 100644 index 8482af9..0000000 --- a/servify/tests/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod expanded_1; -mod expanded_2; -mod simple_counter; -mod simple_counter_2; -mod simple_counter_file_split; diff --git a/servify/tests/simple_counter.rs b/servify/tests/simple_counter.rs deleted file mode 100644 index 0cf8dd0..0000000 --- a/servify/tests/simple_counter.rs +++ /dev/null @@ -1,35 +0,0 @@ -#[servify::service( - impls = [ - Counter_increment_and_get, - Counter_get_value, - ] -)] -struct Counter { - pub count: u32, -} - -#[servify::export] -impl Counter { - fn increment_and_get(&mut self, count: u32) -> u32 { - self.count += count; - self.count - } - fn get_value(&self) -> u32 { - self.count - } -} - -#[tokio::test] -async fn count_up() { - let (rx, client) = Counter::initiate_message_passing(32); - - tokio::spawn(async move { - Counter::Server { count: 3 }.listen(rx).await; - }); - - assert_eq!(client.get_value().await, 3); - assert_eq!(client.increment_and_get(5).await, 8); - assert_eq!(client.get_value().await, 8); - assert_eq!(client.increment_and_get(3).await, 11); - assert_eq!(client.get_value().await, 11); -} diff --git a/servify/tests/simple_counter_2.rs b/servify/tests/simple_counter_2.rs deleted file mode 100644 index b2081f6..0000000 --- a/servify/tests/simple_counter_2.rs +++ /dev/null @@ -1,50 +0,0 @@ -#[servify::service( - impls = [ - SimpleCounter_increment_and_get, - SimpleCounter_get, - SimpleCounter_set, - SimpleCounter_reset, - ] -)] -struct SimpleCounter { - pub counter: u32, -} - -#[servify::export] -impl SimpleCounter { - fn increment_and_get(&mut self) -> u32 { - self.counter += 1; - self.counter - } - - fn get(&self) -> u32 { - self.counter - } - - fn set(&mut self, value: u32) { - self.counter = value; - } - - fn reset(&mut self) { - self.counter = 0; - } -} - -#[tokio::test] -async fn main() { - let (counter_rx, counter_client) = SimpleCounter::initiate_message_passing(32); - - tokio::spawn(async move { - SimpleCounter::Server { counter: 0 } - .listen(counter_rx) - .await; - }); - - assert_eq!(counter_client.increment_and_get().await, 1); - assert_eq!(counter_client.increment_and_get().await, 2); - assert_eq!(counter_client.get().await, 2); - counter_client.set(10).await; - assert_eq!(counter_client.get().await, 10); - counter_client.reset().await; - assert_eq!(counter_client.get().await, 0); -} diff --git a/servify/tests/simple_counter_file_split/get.rs b/servify/tests/simple_counter_file_split/get.rs deleted file mode 100644 index 26694e1..0000000 --- a/servify/tests/simple_counter_file_split/get.rs +++ /dev/null @@ -1,8 +0,0 @@ -use super::SimpleCounter; - -#[servify::export] -impl SimpleCounter { - fn get(&self) -> u32 { - self.counter - } -} diff --git a/servify/tests/simple_counter_file_split/increment.rs b/servify/tests/simple_counter_file_split/increment.rs deleted file mode 100644 index 18dc714..0000000 --- a/servify/tests/simple_counter_file_split/increment.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::SimpleCounter; - -#[servify::export] -impl SimpleCounter { - fn increment_and_get_ex(&mut self) -> u32 { - self.counter += 1; - self.counter - } -} diff --git a/servify/tests/simple_counter_file_split/mod.rs b/servify/tests/simple_counter_file_split/mod.rs deleted file mode 100644 index f0885ef..0000000 --- a/servify/tests/simple_counter_file_split/mod.rs +++ /dev/null @@ -1,40 +0,0 @@ -mod get; -mod increment; -mod reset; -mod set; - -use get::SimpleCounter_get; -use increment::SimpleCounter_increment_and_get_ex; -use reset::SimpleCounter_reset; -use set::SimpleCounter_set; - -#[servify::service( - impls = [ - SimpleCounter_increment_and_get_ex, - SimpleCounter_get, - SimpleCounter_set, - SimpleCounter_reset, - ] -)] -struct SimpleCounter { - pub counter: u32, -} - -#[tokio::test] -async fn main() { - let (counter_rx, counter_client) = SimpleCounter::initiate_message_passing(32); - - tokio::spawn(async move { - SimpleCounter::Server { counter: 0 } - .listen(counter_rx) - .await; - }); - - assert_eq!(counter_client.increment_and_get_ex().await, 1); - assert_eq!(counter_client.increment_and_get_ex().await, 2); - assert_eq!(counter_client.get().await, 2); - counter_client.set(10).await; - assert_eq!(counter_client.get().await, 10); - counter_client.reset().await; - assert_eq!(counter_client.get().await, 0); -} diff --git a/servify/tests/simple_counter_file_split/reset.rs b/servify/tests/simple_counter_file_split/reset.rs deleted file mode 100644 index 84f41fa..0000000 --- a/servify/tests/simple_counter_file_split/reset.rs +++ /dev/null @@ -1,8 +0,0 @@ -use super::SimpleCounter; - -#[servify::export] -impl SimpleCounter { - fn reset(&mut self) { - self.counter = 0; - } -} diff --git a/servify/tests/simple_counter_file_split/set.rs b/servify/tests/simple_counter_file_split/set.rs deleted file mode 100644 index 7d019bb..0000000 --- a/servify/tests/simple_counter_file_split/set.rs +++ /dev/null @@ -1,8 +0,0 @@ -use super::SimpleCounter; - -#[servify::export] -impl SimpleCounter { - fn set(&mut self, value: u32) { - self.counter = value; - } -} diff --git a/servify/tests/vanilla_counter_1.rs b/servify/tests/vanilla_counter_1.rs new file mode 100644 index 0000000..d2fafd2 --- /dev/null +++ b/servify/tests/vanilla_counter_1.rs @@ -0,0 +1,63 @@ +use tokio::{ + sync::{mpsc, oneshot}, + task::JoinSet, +}; + +pub struct Processor { + pub count: u32, +} + +enum Message { + IncrementAndGet { + amount: u32, + reply: oneshot::Sender, + }, + Get { + reply: oneshot::Sender, + }, +} + +fn spawn(mut rx: mpsc::Receiver) { + let mut processor = Processor { count: 0 }; + tokio::spawn(async move { + while let Some(message) = rx.recv().await { + match message { + Message::IncrementAndGet { amount, reply } => { + processor.count += amount; + reply.send(processor.count).unwrap(); + } + Message::Get { reply } => { + reply.send(processor.count).unwrap(); + } + } + } + }); +} + +#[tokio::test] +async fn main() { + let (tx, rx) = mpsc::channel(32); + spawn(rx); + let mut set = JoinSet::new(); + + for _ in 0..10 { + let tx = tx.clone(); + set.spawn(async move { + for _ in 0..1000 { + let (rep_tx, rep_rx) = oneshot::channel(); + tx.send(Message::IncrementAndGet { + amount: 1, + reply: rep_tx, + }) + .await + .unwrap(); + rep_rx.await.unwrap(); + } + }); + } + set.join_all().await; + + let (get_tx, get_rx) = oneshot::channel(); + tx.send(Message::Get { reply: get_tx }).await.unwrap(); + assert_eq!(get_rx.await, Ok(10000)); +} diff --git a/servify_macro/src/export.rs b/servify_macro/src/export.rs deleted file mode 100644 index 97da99c..0000000 --- a/servify_macro/src/export.rs +++ /dev/null @@ -1,250 +0,0 @@ -use case::CaseExt; -use proc_macro2::{Span, TokenStream}; -use quote::quote; -use syn::{ - parse::{ParseStream, Parser}, - punctuated::Punctuated, - spanned::Spanned, - Error, Expr, ExprField, ExprPath, Field, FieldMutability, FieldValue, FieldsNamed, FnArg, - Ident, ImplItem, ImplItemFn, ItemImpl, Member, Pat, PatType, Result, Token, TypePath, - Visibility, -}; - -use crate::util::{return_type_ext::ReturnTypeExt, type_path_ext::TypePathExt}; - -pub(crate) fn impl_export(_attrs: TokenStream, item: TokenStream) -> TokenStream { - parse.parse2(item).unwrap_or_else(Error::into_compile_error) -} - -struct ExportParent { - mod_path: TypePath, -} - -fn parse(input: ParseStream) -> Result { - let top: ItemImpl = input.parse()?; - let mod_path = match *top.self_ty { - syn::Type::Path(path) => path, - _ => Err(Error::new( - top.self_ty.span(), - "servify_macro::export can only be used on impl blocks with a TypePath.", - ))?, - }; - - let parent = ExportParent { mod_path }; - top.items - .iter() - .map(|item| match item { - ImplItem::Fn(item) => parse_method(item, &parent), - item => Err(Error::new( - item.span(), - "servify_macro::export cannot handle implementations other than functions.", - )), - }) - .collect() -} - -fn parse_method(input: &ImplItemFn, parent: &ExportParent) -> Result { - let mod_path = parent.mod_path.clone(); - - let struct_name = mod_path.path.segments.last().unwrap().ident.clone(); - - let fn_name = input.sig.ident.clone(); - - let export_name = Ident::new(&format!("{}_{}", struct_name, fn_name), Span::call_site()); - - let request_name = Ident::new( - &format!("__{}_request", fn_name.to_string().to_snake()), - Span::call_site(), - ); - - let response_name = Ident::new( - &format!("__{}_response", fn_name.to_string().to_snake()), - Span::call_site(), - ); - - let server_path = mod_path.clone().with_trail_ident("Server"); - let client_path = mod_path.clone().with_trail_ident("Client"); - - let internal_fn_name = Ident::new(&format!("__internal_{}", fn_name), Span::call_site()); - - let sig = input.sig.inputs.clone(); - - let sig_without_self = sig - .clone() - .into_iter() - .filter(|i| match i { - FnArg::Typed(_) => true, - FnArg::Receiver(_) => false, - }) - .collect::>(); - - let body = input.block.clone(); - let response = input.sig.output.clone().to_type(); - - let request_sig = sig_without_self - .clone() - .into_iter() - .filter_map(|i| match i { - FnArg::Typed(PatType { pat, ty, .. }) => match *pat { - Pat::Ident(ident) => { - Some((Ident::new(&ident.ident.to_string(), Span::call_site()), *ty)) - } - _ => None, - }, - _ => None, - }) - .collect::>(); - - let struct_block = FieldsNamed { - brace_token: Default::default(), - named: request_sig - .clone() - .into_iter() - .map(|(ident, ty)| Field { - attrs: Default::default(), - vis: Visibility::Inherited, - ident: Some(ident), - colon_token: Default::default(), - ty, - mutability: FieldMutability::None, - }) - .collect(), - }; - - let call_server_args: Punctuated = request_sig - .clone() - .into_iter() - .map(|(ident, _)| ExprField { - attrs: Default::default(), - member: Member::Named(Ident::new(&ident.to_string(), Span::call_site())), - dot_token: Default::default(), - base: Box::new(Expr::Path(ExprPath { - attrs: Default::default(), - qself: None, - path: Ident::new("req", Span::call_site()).into(), - })), - }) - .collect(); - - let call_client_args: Punctuated = request_sig - .clone() - .into_iter() - .map(|(ident, _)| FieldValue { - attrs: Default::default(), - member: Member::Named(Ident::new(&ident.to_string(), Span::call_site())), - colon_token: Default::default(), - expr: Expr::Path(ExprPath { - attrs: Default::default(), - qself: None, - path: Ident::new(&ident.to_string(), Span::call_site()).into(), - }), - }) - .collect(); - - Ok(quote! { - #[allow(non_camel_case_types)] - pub type #response_name = #response; - - #[allow(non_camel_case_types)] - #[derive(Clone)] - pub struct #request_name #struct_block - - impl #server_path { - pub async fn #fn_name(&mut self, req: #request_name) -> #response_name { - self.#internal_fn_name(#call_server_args).await - } - async fn #internal_fn_name(#sig) -> #response_name #body - } - - impl #client_path { - pub async fn #fn_name(&self, #sig_without_self) -> #response_name { - #mod_path::#internal_fn_name(self, #request_name { #call_client_args }).await - } - } - - #[allow(non_camel_case_types)] - pub struct #export_name (); - impl ::servify::ServifyExport for #export_name { - type Request = #request_name; - type Response = #response_name; - } - }) -} - -#[cfg(test)] -mod tests { - use super::impl_export; - use pretty_assertions::assert_eq; - use quote::quote; - - #[test] - fn fail_if_contains_const() { - assert_eq! { - impl_export(quote!{}, quote!{ - impl A { - const A: usize = 0; - fn a(&self) {} - } - }).to_string(), - r#":: core :: compile_error ! { "servify_macro::export cannot handle implementations other than functions." }"#, - }; - } - - #[test] - fn fail_if_impl_to_fn() { - assert_eq! { - impl_export(quote!{}, quote!{ - fn a() {} - }).to_string(), - r#":: core :: compile_error ! { "expected `impl`" }"# - }; - } - - #[test] - fn test_export() { - assert_eq! { - impl_export(quote!{}, quote!{ - impl SomeStruct { - fn increment(&mut self, count: u32) -> u32 { - self.count += count; - self.count - } - } - }).to_string(), - - quote!{ - #[allow(non_camel_case_types)] - pub type __increment_response = u32; - - #[allow(non_camel_case_types)] - #[derive(Clone)] - pub struct __increment_request { - count: u32 - } - - impl SomeStruct::Server { - pub async fn increment(&mut self, req: __increment_request) -> __increment_response { - self.__internal_increment(req.count).await - } - async fn __internal_increment(&mut self, count: u32) -> __increment_response { - self.count += count; - self.count - } - } - - impl SomeStruct::Client { - pub async fn increment(&self, count: u32) -> __increment_response { - SomeStruct::__internal_increment(self, __increment_request { count }).await - } - } - - #[allow(non_camel_case_types)] - pub struct SomeStruct_increment (); - impl ::servify::ServifyExport for SomeStruct_increment { - type Request = __increment_request; - type Response = __increment_response; - } - }.to_string() - }; - } -} diff --git a/servify_macro/src/lib.rs b/servify_macro/src/lib.rs index 90491bd..83c8c0a 100644 --- a/servify_macro/src/lib.rs +++ b/servify_macro/src/lib.rs @@ -1,21 +1 @@ -mod export; -mod service; mod util; -use export::impl_export; -use service::impl_service; - -#[proc_macro_attribute] -pub fn export( - attrs: proc_macro::TokenStream, - item: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - impl_export(attrs.into(), item.into()).into() -} - -#[proc_macro_attribute] -pub fn service( - attrs: proc_macro::TokenStream, - item: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - impl_service(attrs.into(), item.into()).into() -} diff --git a/servify_macro/src/service.rs b/servify_macro/src/service.rs deleted file mode 100644 index 3d7311f..0000000 --- a/servify_macro/src/service.rs +++ /dev/null @@ -1,238 +0,0 @@ -use std::vec; - -use case::CaseExt; -use proc_macro2::TokenStream; -use quote::quote; -use syn::bracketed; -use syn::parse::Parse; -use syn::parse::ParseStream; -use syn::parse2; -use syn::punctuated::Punctuated; -use syn::token::Comma; -use syn::Error; -use syn::Ident; -use syn::ItemStruct; -use syn::Result; -use syn::Token; -use syn::TypePath; - -use crate::util::type_path_ext::TypePathExt; - -pub(crate) fn impl_service(attrs: TokenStream, item: TokenStream) -> TokenStream { - parse2::(attrs) - .and_then(|attrs| attrs.parse_item(item)) - .unwrap_or_else(Error::into_compile_error) -} - -pub struct ServiceParentAttrs { - impls: Vec, -} - -impl Parse for ServiceParentAttrs { - fn parse(input: ParseStream) -> Result { - let mut impls = vec![]; - - while !input.is_empty() { - let property_name: Ident = input.parse()?; - match property_name.to_string().as_str() { - "impls" => { - let _eq: Token![=] = input.parse()?; - let group; - let _paren = bracketed!(group in input); - let paths = Punctuated::::parse_terminated(&group)?; - impls.extend(paths); - } - _ => { - return Err(Error::new( - property_name.span(), - "Unknown property. expected `impls`", - )) - } - } - if input.peek(Token![,]) { - input.parse::()?; - } - } - Ok(Self { impls }) - } -} - -struct ImplTokens { - internal_function: TokenStream, - enum_element: TokenStream, - server_arm: TokenStream, -} - -impl ServiceParentAttrs { - fn parse_item(self, item: TokenStream) -> Result { - let server: ItemStruct = parse2(item)?; - - let mod_name = server.ident.clone(); - let server_items = server.fields; - - let tokens: Vec = self - .impls - .clone() - .into_iter() - .filter_map(|path| { - let fn_name = path.path.segments.last().unwrap().ident.clone(); - let fn_name = Ident::new( - &fn_name.to_string() - .strip_prefix(&mod_name.to_string()) - .map(|p| p.trim_start_matches('_').to_string())?, - fn_name.span(), - ); - let internal_fn_name = Ident::new( - &format!("__internal_{}", fn_name), - fn_name.span(), - ); - - let enum_name = Ident::new(&fn_name.to_string().to_camel(), fn_name.span()); - - let super_path = path.clone().to_super(); - - let internal_function = quote! { - #[doc(hidden)] - pub async fn #internal_fn_name( - client: &Client, - req: <#super_path as ::servify::ServifyExport>::Request, - ) -> <#super_path as ::servify::ServifyExport>::Response { - let (tx, rx) = ::tokio::sync::oneshot::channel(); - client.tx.send(Message::#enum_name(req, tx)).await.unwrap(); - rx.await.unwrap() - } - }; - - let enum_element = quote! { - #enum_name( - <#super_path as ::servify::ServifyExport>::Request, - ::tokio::sync::oneshot::Sender<<#super_path as ::servify::ServifyExport>::Response>, - ), - }; - - let server_arm = quote! { - Message::#enum_name(req, tx) => { - let res = self.#fn_name(req).await; - tx.send(res).unwrap(); - }, - }; - - - Some(ImplTokens { - internal_function, - enum_element, - server_arm, - }) - }) - .collect(); - - let internal_functions: TokenStream = - tokens.iter().map(|t| t.internal_function.clone()).collect(); - let enum_elements: TokenStream = tokens.iter().map(|t| t.enum_element.clone()).collect(); - let server_arms: TokenStream = tokens.iter().map(|t| t.server_arm.clone()).collect(); - - Ok(quote! { - #[allow(non_snake_case)] - mod #mod_name { - - pub enum Message { - #enum_elements - } - - pub struct Server #server_items - - #[derive(Clone)] - pub struct Client { - tx: ::tokio::sync::mpsc::Sender, - } - - impl Server { - pub async fn listen(&mut self, mut rx: ::tokio::sync::mpsc::Receiver) { - while let Some(msg) = rx.recv().await { - match msg { - #server_arms - } - } - } - } - - #internal_functions - - pub fn initiate_message_passing(buffer: usize) -> (::tokio::sync::mpsc::Receiver, Client) { - let (tx, rx) = ::tokio::sync::mpsc::channel(buffer); - let client = Client { tx }; - (rx, client) - } - } - }) - } -} - -#[cfg(test)] -mod tests { - use super::impl_service; - use pretty_assertions::assert_eq; - use quote::quote; - - #[test] - fn single() { - assert_eq! { - impl_service(quote!{ - impls = [SomeStruct_increment], - }, quote!{ - struct SomeStruct { - pub count: u32, - } - }).to_string(), - quote!{ - #[allow(non_snake_case)] - mod SomeStruct { - pub enum Message { - Increment( - ::Request, - ::tokio::sync::oneshot::Sender<::Response>, - ), - } - - pub struct Server { - pub count: u32, - } - - #[derive(Clone)] - pub struct Client { - tx: ::tokio::sync::mpsc::Sender, - } - - impl Server { - pub async fn listen(&mut self, mut rx: ::tokio::sync::mpsc::Receiver) { - while let Some(msg) = rx.recv().await { - match msg { - Message::Increment(req, tx) => { - let res = self.increment(req).await; - tx.send(res).unwrap(); - }, - } - } - } - } - - #[doc(hidden)] - pub async fn __internal_increment( - client: &Client, - req: ::Request, - ) -> ::Response { - let (tx, rx) = ::tokio::sync::oneshot::channel(); - client.tx.send(Message::Increment(req, tx)).await.unwrap(); - rx.await.unwrap() - } - - pub fn initiate_message_passing(buffer: usize) -> (::tokio::sync::mpsc::Receiver, Client) { - let (tx, rx) = ::tokio::sync::mpsc::channel(buffer); - let client = Client { tx }; - (rx, client) - } - } - }.to_string(), - }; - } -} diff --git a/servify_macro/src/util/return_type_ext.rs b/servify_macro/src/util/return_type_ext.rs index 0d1f629..d386176 100644 --- a/servify_macro/src/util/return_type_ext.rs +++ b/servify_macro/src/util/return_type_ext.rs @@ -2,6 +2,7 @@ use syn::punctuated::Punctuated; use syn::token::Paren; use syn::{ReturnType, Type, TypeTuple}; +#[allow(dead_code)] pub(crate) trait ReturnTypeExt { fn to_type(self) -> Type; } diff --git a/servify_macro/src/util/type_path_ext.rs b/servify_macro/src/util/type_path_ext.rs index 1944b5a..ec6cc13 100644 --- a/servify_macro/src/util/type_path_ext.rs +++ b/servify_macro/src/util/type_path_ext.rs @@ -1,6 +1,7 @@ use proc_macro2::Span; use syn::{Ident, PathArguments, PathSegment, TypePath}; +#[allow(dead_code)] pub(crate) trait TypePathExt { fn with_inserted_ident(self, ident: &str, index: usize) -> TypePath; fn with_trail_ident(self, ident: &str) -> TypePath;