7 releases

0.0.6 Oct 9, 2024
0.0.5 Oct 9, 2024

#404 in Asynchronous

Download history 564/week @ 2024-10-05 53/week @ 2024-10-12

617 downloads per month

MIT license

25KB
459 lines

NOTE: This is currently a proof of concept and is not ready for usage

Tactix

A Simple Actor Model Implementation inspired by Actix

Actix provides a great API for working with actors but it is missing some key features of the Actor Model that really need to be there such as heirarchical supervision. Also Actix was created as a solution to asynchrony before tokio was available and as a result implemented it's own runtime. Overtime it switched to tokio as a default async runtime however much of the baggage from the original runtime remains in the code which has led to complexity. Tactix attempts to follow the Actix API whilst fixing some issues inherent within it utilizing as much of tokio as it can.

Alice Ryhl a maintainer of tokio wrote a great article on creating an actor model in tokio where it is outlined how to create an actor model system using tokio channels. This however leads to relatively verbose code as events must be discriminated.

Tactix attempts to apply some techniques from Alice Ryhl's article and combine them with Actix's handler syntax whilst enabling safe async handlers in order to get the best of both worlds.

This is not a drop-in replacement for Actix but should be a relatively light lift and should improve developer erganomics for async handlers.

Usage

#[derive(Debug)]
pub struct Increment;
impl Message for Increment {
    type Response = ();
}

#[derive(Debug)]
pub struct Decrement;
impl Message for Decrement {
    type Response = ();
}

#[derive(Debug)]
pub struct GetCount;
impl Message for GetCount {
    type Response = u64;
}

pub struct Counter {
    count: u64,
}

impl Counter {
    pub fn new() -> Self {
        Self { count: 0 }
    }
}

impl Actor for Counter {
    type Context = Context<Self>;
}

#[async_trait]
impl Handler<Increment> for Counter {
    async fn handle(&mut self, _msg: Increment, _:Self::Context) {
        println!("INC");
        self.count += 1;
    }
}

#[async_trait]
impl Handler<Decrement> for Counter {
    async fn handle(&mut self, _msg: Decrement, _:Self::Context) {
        println!("DEC");
        self.count -= 1;
    }
}

#[async_trait]
impl Handler<GetCount> for Counter {
    async fn handle(&mut self, _: GetCount, _:Self::Context) -> u64 {
        println!("GET");
        let s = self.count;
        println!("SENDING: {}", s);
        s
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<String>> {
    let addr = Counter::new().start();
    let incrementor: Recipient<Increment> = addr.clone().recipient();
    let decrementor: Recipient<Decrement> = addr.clone().recipient();
    addr.do_send(Increment);
    incrementor.do_send(Increment);
    addr.do_send(Increment);
    addr.do_send(Increment);
    addr.do_send(Decrement);
    decrementor.do_send(Decrement);
    let count = addr.send(GetCount).await.unwrap();

    assert_eq!(count, 2);
    Ok(())
}
  • Async Handlers
  • Recipient
  • started()
  • Ensure handlers run in a non-blocking mutually exclusive way
  • Heirarchical Actor Supervision

Dependencies

~2.6–8.5MB
~75K SLoC