mirror of
https://github.com/sinui0/ludi.git
synced 2026-01-09 04:47:56 -05:00
first commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
**/target
|
||||
|
||||
Cargo.lock
|
||||
2
Cargo.toml
Normal file
2
Cargo.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[workspace]
|
||||
members = ["ludi-core", "ludi-macros", "ludi"]
|
||||
10
ludi-core/Cargo.toml
Normal file
10
ludi-core/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "ludi-core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
futures = { version = "0.3" }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||
53
ludi-core/src/envelope.rs
Normal file
53
ludi-core/src/envelope.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use futures::channel::oneshot::{self, Receiver, Sender};
|
||||
|
||||
use crate::{Actor, Context, Mailbox, Message};
|
||||
|
||||
pub type ActorEnvelope<A> =
|
||||
Envelope<<A as Actor>::Message, <<A as Actor>::Message as Message<A>>::Return, A>;
|
||||
|
||||
pub struct Envelope<M, R, A> {
|
||||
msg: M,
|
||||
send: Option<Sender<R>>,
|
||||
_pd: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<M, R, A> Envelope<M, R, A> {
|
||||
pub fn new(msg: M) -> Self {
|
||||
Self {
|
||||
msg,
|
||||
send: None,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_returning(msg: M) -> (Self, Receiver<R>) {
|
||||
let (send, recv) = oneshot::channel();
|
||||
(
|
||||
Self {
|
||||
msg,
|
||||
send: Some(send),
|
||||
_pd: PhantomData,
|
||||
},
|
||||
recv,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, R, A> Envelope<M, R, A>
|
||||
where
|
||||
A: Actor,
|
||||
M: Message<A, Return = R> + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
pub async fn handle<T: Mailbox<A>>(mut self, actor: &mut A, ctx: &mut Context<'_, A, T>) {
|
||||
self.msg
|
||||
.handle(actor, ctx, move |ret| {
|
||||
if let Some(send) = self.send.take() {
|
||||
let _ = send.send(ret);
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
115
ludi-core/src/lib.rs
Normal file
115
ludi-core/src/lib.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
pub mod envelope;
|
||||
pub mod mailbox;
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use envelope::ActorEnvelope;
|
||||
use futures::{Future, Stream, StreamExt};
|
||||
|
||||
pub use envelope::Envelope;
|
||||
|
||||
pub trait Message<A: Actor>: Send + Sized + 'static {
|
||||
type Return: Send + 'static;
|
||||
|
||||
fn handle<M: Mailbox<A>, R: FnOnce(Self::Return) + Send>(
|
||||
self,
|
||||
actor: &mut A,
|
||||
ctx: &mut Context<A, M>,
|
||||
ret: R,
|
||||
) -> impl Future<Output = ()> + Send;
|
||||
}
|
||||
|
||||
pub trait Actor: Send + Sized + 'static {
|
||||
type Message: Message<Self>;
|
||||
type Stop;
|
||||
|
||||
fn started(
|
||||
&mut self,
|
||||
_ctx: &mut Context<'_, Self, impl Mailbox<Self>>,
|
||||
) -> Result<(), Self::Stop> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stopped(&mut self) -> impl Future<Output = Self::Stop> + Send;
|
||||
|
||||
fn run(&mut self, mut mailbox: impl Mailbox<Self>) -> impl Future<Output = Self::Stop> + Send {
|
||||
async move {
|
||||
let mut ctx = Context::new(&mut mailbox);
|
||||
if let Err(stop) = self.started(&mut ctx) {
|
||||
return stop;
|
||||
}
|
||||
|
||||
while let Some(msg) = mailbox.next().await {
|
||||
let mut ctx = Context::new(&mut mailbox);
|
||||
msg.handle(self, &mut ctx).await;
|
||||
|
||||
if ctx.stopped() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.stopped().await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Handler<T>: Actor {
|
||||
type Return: Send + 'static;
|
||||
|
||||
fn handle<M: Mailbox<Self>>(
|
||||
&mut self,
|
||||
msg: T,
|
||||
ctx: &mut Context<Self, M>,
|
||||
) -> impl Future<Output = Self::Return> + Send;
|
||||
|
||||
fn after<M: Mailbox<Self>>(
|
||||
&mut self,
|
||||
_ctx: &mut Context<Self, M>,
|
||||
) -> impl Future<Output = ()> + Send {
|
||||
async {}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Mailbox<A: Actor>: Stream<Item = ActorEnvelope<A>> + Send + Unpin + 'static {
|
||||
type Address: Address<A>;
|
||||
|
||||
fn address(&self) -> &Self::Address;
|
||||
}
|
||||
|
||||
pub trait Address<A: Actor>: Clone + Send + 'static {
|
||||
fn send<M>(&self, msg: M) -> impl Future<Output = <A as Handler<M>>::Return> + Send
|
||||
where
|
||||
A: Handler<M>,
|
||||
<A::Message as Message<A>>::Return: Into<<A as Handler<M>>::Return>,
|
||||
M: Into<A::Message> + Send;
|
||||
}
|
||||
|
||||
pub struct Context<'a, A: Actor, M: Mailbox<A>> {
|
||||
running: bool,
|
||||
mailbox: &'a mut M,
|
||||
_pd: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<'a, A: Actor, M: Mailbox<A>> Context<'a, A, M> {
|
||||
pub fn new(mailbox: &'a mut M) -> Self {
|
||||
Self {
|
||||
running: true,
|
||||
mailbox,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mailbox(&mut self) -> &mut M {
|
||||
self.mailbox
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
self.running = false;
|
||||
}
|
||||
|
||||
pub fn stopped(&self) -> bool {
|
||||
!self.running
|
||||
}
|
||||
}
|
||||
71
ludi-core/src/mailbox.rs
Normal file
71
ludi-core/src/mailbox.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use futures::{channel::mpsc, SinkExt, Stream};
|
||||
|
||||
use crate::{Actor, Address, Envelope, Handler, Mailbox, Message};
|
||||
|
||||
pub struct FuturesMailbox<A: Actor> {
|
||||
addr: FuturesAddress<A>,
|
||||
recv: mpsc::Receiver<Envelope<A::Message, <A::Message as Message<A>>::Return, A>>,
|
||||
}
|
||||
|
||||
impl<A: Actor> FuturesMailbox<A> {
|
||||
pub fn new() -> Self {
|
||||
let (send, recv) = mpsc::channel(100);
|
||||
|
||||
Self {
|
||||
addr: FuturesAddress { send },
|
||||
recv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Actor> Mailbox<A> for FuturesMailbox<A> {
|
||||
type Address = FuturesAddress<A>;
|
||||
|
||||
fn address(&self) -> &Self::Address {
|
||||
&self.addr
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Actor> Stream for FuturesMailbox<A> {
|
||||
type Item = Envelope<A::Message, <A::Message as Message<A>>::Return, A>;
|
||||
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
std::pin::Pin::new(&mut self.recv).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FuturesAddress<A: Actor> {
|
||||
send: mpsc::Sender<Envelope<A::Message, <A::Message as Message<A>>::Return, A>>,
|
||||
}
|
||||
|
||||
impl<A: Actor> Clone for FuturesAddress<A> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
send: self.send.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Actor> Address<A> for FuturesAddress<A> {
|
||||
async fn send<M>(&self, msg: M) -> <A as Handler<M>>::Return
|
||||
where
|
||||
A: Handler<M>,
|
||||
<A::Message as Message<A>>::Return: Into<<A as Handler<M>>::Return>,
|
||||
M: Into<A::Message> + Send,
|
||||
{
|
||||
let msg: A::Message = msg.into();
|
||||
|
||||
let (env, ret) = Envelope::new_returning(msg.into());
|
||||
|
||||
let mut send = self.send.clone();
|
||||
|
||||
send.send(env).await.unwrap();
|
||||
|
||||
let ret: <A::Message as Message<A>>::Return = ret.await.unwrap();
|
||||
|
||||
ret.into()
|
||||
}
|
||||
}
|
||||
119
ludi-core/tests/api.rs
Normal file
119
ludi-core/tests/api.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use ludi_core::{mailbox::FuturesMailbox, *};
|
||||
|
||||
struct PingActor;
|
||||
|
||||
impl Actor for PingActor {
|
||||
type Message = PingMessage;
|
||||
|
||||
type Stop = ();
|
||||
|
||||
async fn stopped(&mut self) -> Self::Stop {}
|
||||
}
|
||||
|
||||
impl Handler<Ping> for PingActor {
|
||||
type Return = String;
|
||||
|
||||
async fn handle<M: Mailbox<Self>>(
|
||||
&mut self,
|
||||
_msg: Ping,
|
||||
_ctx: &mut Context<'_, Self, M>,
|
||||
) -> Self::Return {
|
||||
println!("ping");
|
||||
"pong".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<Pong> for PingActor {
|
||||
type Return = ();
|
||||
|
||||
async fn handle<M: Mailbox<Self>>(
|
||||
&mut self,
|
||||
_msg: Pong,
|
||||
_ctx: &mut Context<'_, Self, M>,
|
||||
) -> Self::Return {
|
||||
println!("pong");
|
||||
}
|
||||
|
||||
async fn after<M: Mailbox<Self>>(&mut self, _ctx: &mut Context<'_, Self, M>) {
|
||||
println!("sent pong");
|
||||
}
|
||||
}
|
||||
|
||||
enum PingMessage {
|
||||
Ping(Ping),
|
||||
Pong(Pong),
|
||||
}
|
||||
|
||||
enum PingReturn {
|
||||
Ping(String),
|
||||
Pong(()),
|
||||
}
|
||||
|
||||
impl Into<()> for PingReturn {
|
||||
fn into(self) -> () {
|
||||
match self {
|
||||
PingReturn::Pong(()) => (),
|
||||
_ => unreachable!("handler returned unexpected type, this indicates the `Message` implementation is incorrect"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for PingReturn {
|
||||
fn into(self) -> String {
|
||||
match self {
|
||||
PingReturn::Ping(s) => s,
|
||||
_ => unreachable!("handler returned unexpected type, this indicates the `Message` implementation is incorrect"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Message<PingActor> for PingMessage {
|
||||
type Return = PingReturn;
|
||||
|
||||
async fn handle<M: Mailbox<PingActor>, R: FnOnce(PingReturn)>(
|
||||
self,
|
||||
actor: &mut PingActor,
|
||||
ctx: &mut Context<'_, PingActor, M>,
|
||||
ret: R,
|
||||
) {
|
||||
match self {
|
||||
PingMessage::Ping(ping) => {
|
||||
let value = PingReturn::Ping(Handler::<Ping>::handle(actor, ping, ctx).await);
|
||||
ret(value);
|
||||
Handler::<Ping>::after(actor, ctx).await;
|
||||
}
|
||||
PingMessage::Pong(pong) => {
|
||||
let value = PingReturn::Pong(Handler::<Pong>::handle(actor, pong, ctx).await);
|
||||
ret(value);
|
||||
Handler::<Pong>::after(actor, ctx).await;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
struct Ping;
|
||||
|
||||
impl From<Ping> for PingMessage {
|
||||
fn from(value: Ping) -> Self {
|
||||
PingMessage::Ping(value)
|
||||
}
|
||||
}
|
||||
|
||||
struct Pong;
|
||||
|
||||
impl From<Pong> for PingMessage {
|
||||
fn from(value: Pong) -> Self {
|
||||
PingMessage::Pong(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_api() {
|
||||
let mailbox = FuturesMailbox::new();
|
||||
let addr = mailbox.address().clone();
|
||||
let mut actor = PingActor;
|
||||
|
||||
tokio::spawn(async move { actor.run(mailbox).await });
|
||||
|
||||
addr.send(Pong).await;
|
||||
}
|
||||
13
ludi-macros/Cargo.toml
Normal file
13
ludi-macros/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "ludi-macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "1.0", features = ["full", "extra-traits", "visit"] }
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
heck = "0.4.1"
|
||||
71
ludi-macros/src/implement.rs
Normal file
71
ludi-macros/src/implement.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use heck::{ToSnakeCase, ToUpperCamelCase};
|
||||
use quote::quote;
|
||||
use syn::{parse_str, Ident, ItemImpl, Path};
|
||||
|
||||
use crate::types::MethodSig;
|
||||
|
||||
pub(crate) fn impl_implement(item: ItemImpl) -> proc_macro::TokenStream {
|
||||
let self_ty = *item.self_ty;
|
||||
let trait_path = item.trait_.expect("expected trait implementation").1;
|
||||
let msgs_module: Path = parse_str(&format!(
|
||||
"{}_msgs",
|
||||
quote!(#trait_path).to_string().to_snake_case()
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let (method_blocks, impl_blocks): (Vec<_>, Vec<_>) = item
|
||||
.items
|
||||
.into_iter()
|
||||
.filter_map(|item| match item {
|
||||
syn::ImplItem::Method(method) => Some(method),
|
||||
_ => None,
|
||||
})
|
||||
.map(|method| {
|
||||
let full_sig = method.sig.clone();
|
||||
let sig = MethodSig::from(method.sig);
|
||||
let msg_ident: Ident = parse_str(&sig.ident.to_string().to_upper_camel_case()).unwrap();
|
||||
let block = method.block;
|
||||
let ret = sig.ret;
|
||||
|
||||
let structure = if sig.args.is_empty() {
|
||||
quote!(#msgs_module :: #msg_ident)
|
||||
} else {
|
||||
let arg_idents = sig.args.iter().map(|(ident, _)| ident);
|
||||
quote!(#msgs_module :: #msg_ident { #( #arg_idents ),* })
|
||||
};
|
||||
|
||||
let method_block = quote!(
|
||||
#full_sig {
|
||||
self.send(#structure).await
|
||||
}
|
||||
);
|
||||
|
||||
let impl_block = quote!(
|
||||
impl ::ludi::Handler<#msgs_module :: #msg_ident> for #self_ty {
|
||||
type Return = #ret;
|
||||
|
||||
async fn handle<M: ::ludi::Mailbox<Self>>(
|
||||
&mut self,
|
||||
msg: #msgs_module :: #msg_ident,
|
||||
ctx: &mut ::ludi::Context<'_, Self, M>,
|
||||
) -> Self::Return {
|
||||
let #structure = msg;
|
||||
|
||||
#block
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
(method_block, impl_block)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
quote!(
|
||||
impl<A: ::ludi::Address<#self_ty>> #trait_path for A {
|
||||
#( #method_blocks )*
|
||||
}
|
||||
|
||||
#( #impl_blocks )*
|
||||
)
|
||||
.into()
|
||||
}
|
||||
133
ludi-macros/src/interface.rs
Normal file
133
ludi-macros/src/interface.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use heck::{ToSnakeCase, ToUpperCamelCase};
|
||||
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::{parse_str, Ident, ItemTrait, Path, Type};
|
||||
|
||||
use crate::types::MethodSig;
|
||||
|
||||
pub(crate) fn impl_interface(item: ItemTrait) -> proc_macro::TokenStream {
|
||||
let ident = &item.ident;
|
||||
let msgs_module: Path =
|
||||
parse_str(&format!("{}_msgs", ident.to_string().to_snake_case())).unwrap();
|
||||
|
||||
let sigs = item.items.into_iter().filter_map(|item| match item {
|
||||
syn::TraitItem::Method(method) => Some(MethodSig::from(method.sig)),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let msg_enum_name = Ident::new(&format!("{}Message", ident), Span::call_site());
|
||||
let msg_return_enum_name = Ident::new(&format!("{}MessageReturn", ident), Span::call_site());
|
||||
|
||||
let mut msg_idents = Vec::new();
|
||||
let mut msg_arg_idents = Vec::new();
|
||||
let mut msg_arg_types = Vec::new();
|
||||
let mut msg_rets = Vec::new();
|
||||
let mut msgs = Vec::new();
|
||||
let mut ret_map: HashMap<Type, Vec<Ident>> = HashMap::new();
|
||||
for sig in sigs {
|
||||
let MethodSig { ident, args, ret } = sig;
|
||||
|
||||
let ident: Ident = parse_str(&ident.to_string().to_upper_camel_case()).unwrap();
|
||||
|
||||
let msg = if args.is_empty() {
|
||||
quote!(
|
||||
pub struct #ident;
|
||||
)
|
||||
} else {
|
||||
let arg_idents = args.iter().map(|(ident, _)| ident);
|
||||
let arg_types = args.iter().map(|(_, ty)| ty);
|
||||
quote!(
|
||||
pub struct #ident {
|
||||
#( pub #arg_idents: #arg_types ),*
|
||||
}
|
||||
)
|
||||
};
|
||||
msgs.push(msg);
|
||||
|
||||
msg_idents.push(ident.clone());
|
||||
for arg in args {
|
||||
msg_arg_idents.push(arg.0);
|
||||
msg_arg_types.push(arg.1);
|
||||
}
|
||||
|
||||
if let Some(variants) = ret_map.get_mut(&ret) {
|
||||
variants.push(ident);
|
||||
} else {
|
||||
ret_map.insert(ret.clone(), vec![ident]);
|
||||
}
|
||||
|
||||
msg_rets.push(ret);
|
||||
}
|
||||
|
||||
let ret_into = ret_map
|
||||
.iter()
|
||||
.map(|(ty, variants)| {
|
||||
quote! {
|
||||
impl Into<#ty> for #msg_return_enum_name {
|
||||
fn into(self) -> #ty {
|
||||
match self {
|
||||
#( #msg_return_enum_name :: #variants (value) => value, )*
|
||||
_ => unreachable!("handler returned unexpected type, this indicates the `Message` implementation is incorrect"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
use #msgs_module :: #msg_enum_name;
|
||||
|
||||
pub mod #msgs_module {
|
||||
pub enum #msg_enum_name {
|
||||
#( #msg_idents ( #msg_idents ) ),*
|
||||
}
|
||||
|
||||
pub enum #msg_return_enum_name {
|
||||
#( #msg_idents ( #msg_rets ) ),*
|
||||
}
|
||||
|
||||
#(
|
||||
#msgs
|
||||
)*
|
||||
|
||||
#(
|
||||
impl From<#msg_idents> for #msg_enum_name {
|
||||
fn from(value: #msg_idents) -> Self {
|
||||
#msg_enum_name :: #msg_idents (value)
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
#(
|
||||
#ret_into
|
||||
)*
|
||||
|
||||
impl<A> ::ludi::Message<A> for #msg_enum_name where
|
||||
A: ::ludi::Actor,
|
||||
#( A: ::ludi::Handler<#msg_idents, Return = #msg_rets>, )*
|
||||
{
|
||||
type Return = #msg_return_enum_name;
|
||||
|
||||
async fn handle<M: ::ludi::Mailbox<A>, R: FnOnce(Self::Return)>(
|
||||
self,
|
||||
actor: &mut A,
|
||||
ctx: &mut ::ludi::Context<'_, A, M>,
|
||||
ret: R,
|
||||
) {
|
||||
match self {
|
||||
#(
|
||||
#msg_enum_name :: #msg_idents (msg) => {
|
||||
let value = #msg_return_enum_name :: #msg_idents (::ludi::Handler::<#msg_idents>::handle(actor, msg, ctx).await);
|
||||
ret(value);
|
||||
::ludi::Handler::<#msg_idents>::after(actor, ctx).await;
|
||||
}
|
||||
),*
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}.into()
|
||||
}
|
||||
22
ludi-macros/src/lib.rs
Normal file
22
ludi-macros/src/lib.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
mod implement;
|
||||
mod interface;
|
||||
pub(crate) mod types;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn interface(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut tokens = item.clone();
|
||||
let item_trait = syn::parse_macro_input!(item as syn::ItemTrait);
|
||||
|
||||
tokens.extend(interface::impl_interface(item_trait));
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn implement(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let item_impl = syn::parse_macro_input!(item as syn::ItemImpl);
|
||||
|
||||
implement::impl_implement(item_impl)
|
||||
}
|
||||
48
ludi-macros/src/types.rs
Normal file
48
ludi-macros/src/types.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use syn::{parse_quote, FnArg, Ident, Pat, ReturnType, Signature, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct MethodSig {
|
||||
pub ident: Ident,
|
||||
pub args: Vec<(Ident, Type)>,
|
||||
pub ret: Type,
|
||||
}
|
||||
|
||||
impl From<Signature> for MethodSig {
|
||||
fn from(sig: Signature) -> Self {
|
||||
let Signature {
|
||||
ident,
|
||||
generics,
|
||||
inputs,
|
||||
output,
|
||||
..
|
||||
} = sig;
|
||||
|
||||
let ret = match output {
|
||||
ReturnType::Default => parse_quote!(()),
|
||||
ReturnType::Type(_, ty) => *ty,
|
||||
};
|
||||
|
||||
if !generics.params.is_empty() {
|
||||
panic!("generic methods are not supported");
|
||||
}
|
||||
|
||||
let args = inputs
|
||||
.into_iter()
|
||||
.filter_map(|arg| {
|
||||
let FnArg::Typed(pat) = arg else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let ty = *pat.ty;
|
||||
|
||||
let Pat::Ident(pat) = *pat.pat else {
|
||||
panic!("only support named arguments");
|
||||
};
|
||||
|
||||
Some((pat.ident, ty))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self { ident, args, ret }
|
||||
}
|
||||
}
|
||||
19
ludi/Cargo.toml
Normal file
19
ludi/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "ludi"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["macros"]
|
||||
macros = ["dep:ludi-macros"]
|
||||
|
||||
[dependencies]
|
||||
ludi-core = { path = "../ludi-core" }
|
||||
ludi-macros = { path = "../ludi-macros", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
[[example]]
|
||||
name = "simple"
|
||||
required-features = ["macros"]
|
||||
59
ludi/examples/simple.rs
Normal file
59
ludi/examples/simple.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use ludi::{implement, interface, mailbox::FuturesMailbox, prelude::*};
|
||||
|
||||
// Slap `#[interface]` on a trait to generate a message types for it.
|
||||
//
|
||||
// This also generates a blanket implementation for all addresses of actors which implement
|
||||
// all the `Handler` impls for the messages.
|
||||
//
|
||||
// The original trait is not modified in any way.
|
||||
#[interface]
|
||||
trait Counter {
|
||||
/// Increment the counter by `increment` and return the new value.
|
||||
async fn increment(&self, increment: usize) -> usize;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CounterBoi {
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl Actor for CounterBoi {
|
||||
type Message = CounterMessage;
|
||||
type Stop = ();
|
||||
|
||||
async fn stopped(&mut self) -> Self::Stop {}
|
||||
}
|
||||
|
||||
// Implement a trait for an actor as if it were a normal implementation block.
|
||||
//
|
||||
// Code navigation works as expected, at least in VSCode. As in, you can jump to this
|
||||
// implementation from the trait definition.
|
||||
#[implement]
|
||||
impl Counter for CounterBoi {
|
||||
async fn increment(&self, increment: usize) -> usize {
|
||||
self.count += increment;
|
||||
self.count
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let mailbox = FuturesMailbox::new();
|
||||
let addr = mailbox.address().clone();
|
||||
let mut actor = CounterBoi::default();
|
||||
|
||||
tokio::spawn(async move { actor.run(mailbox).await });
|
||||
|
||||
// Because of the blanket implementation, this actor's address implements
|
||||
// the trait directly and can be used as normal.
|
||||
//
|
||||
// Also because it implements the actual trait documentation, highlighting,
|
||||
// etc. works as expected.
|
||||
let _count: usize = addr.increment(1).await;
|
||||
|
||||
// And can of course use the address as normal as well.
|
||||
let _count: usize = addr.send(counter_msgs::Increment { increment: 1 }).await;
|
||||
|
||||
println!("adding: {}, result: {}", 2, addr.increment(2).await);
|
||||
println!("adding: {}, result: {}", 3, addr.increment(3).await);
|
||||
}
|
||||
7
ludi/src/lib.rs
Normal file
7
ludi/src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub use ludi_core::*;
|
||||
#[cfg(feature = "macros")]
|
||||
pub use ludi_macros::*;
|
||||
|
||||
pub mod prelude {
|
||||
pub use ludi_core::{Actor, Address, Context, Handler, Mailbox};
|
||||
}
|
||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
Reference in New Issue
Block a user