Skip to content

Commit

Permalink
docs: Update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
cmleinz committed Oct 9, 2024
1 parent 9e1988c commit 8f1bea4
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 10 deletions.
70 changes: 60 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

A [minimal, stage](https://en.wikipedia.org/wiki/Black_box_theater) for actors.

API design is inspired by actix, but built around the convenience of `async fn`
in traits.
## About

Black-box's API design is inspired by actix, but built is built to be as minimal
as possible, and to integrate with the recently stabalized `async fn` in traits.

To get started just define an Actor and implement some message handlers:

Expand All @@ -19,7 +21,7 @@ struct Shutdown;
struct MyActor;
// All methods are provided, can be overridden for more control
// All methods are provided, but can be overridden for more control
impl Actor for MyActor {}
impl Handler<Event> for MyActor {
Expand All @@ -31,7 +33,9 @@ impl Handler<Event> for MyActor {
impl Handler<Shutdown> for MyActor {
async fn handle(&mut self, _msg: Shutdown, ctx: &Context<Self>) {
println!("INFO: Shutting down");
ctx.shutdown();
// shutdown the actor from within the handler
ctx.shutdown();
// await futures inline thanks to async fn
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
println!("INFO: Shut down");
}
Expand All @@ -57,20 +61,66 @@ async fn main() {
## Runtime

Black-box deliberatly does not ship with a runtime, instead it aims to work with
the users runtime of choice.
the user's runtime of choice.

## Send Bounds

While it likely won't always be the case, currently the futures return by
`Handler::handle` must be `Send`
`Handler::handle` must be `Send`.

## Message Trait

Black-box does have a message trait, but currently it is just a supertrait for
`'static + Send`. This makes it super easy to define new message types, no need
to implement a trait on it, just implement the `Handler` for it, and you're good
to go.
Black-box does have a message trait, but currently private, and just a
supertrait for `'static + Send`.

This means messages are not special types, any `'static + Send` type can
immediately be used as a message.

In the future, this might change to accomodate things like message responses,
however for now the same things can be accomplished by embedding a oneshot
channel in the message type to handle the response.

```rust, ignore
type Message = (String, tokio::sync::oneshot::Sender<String>);
impl Handler<Message> for MyActor {
async fn handle(&mut self, mut message: Message, _ctx: &Context<Self>) {
message.0.push_str("foo");
let _ = message.1.send(message.0);
}
}
```

## Limitations

Before adopting black-box, there are a few drawbacks to consider.

### Concurrency

The lack of a runtime and the decision to provide `&mut self` in `Handler` means
that **there is no native concurrency within actors in black-box**. Awaiting a
future within `Handler::handle` will not prevent messages from enqueuing, but it
will prevent the processing of those events.

For many applications this will be fine. However, for applications which require
high concurrency, you will likely want to either

1. Spawn many actors to handle the work
1. Offload the bulk of the asynchronous work to a task spawned onto some
executor.

### Allocations

An implementation of the actor pattern which is based purely around channels
would likely either:

1. Create many channels over which to send different message types
1. Collect all the message types for a given actor into an enum and descructure
the enum, and pass it down to functions

To get around these rough spots, black-box conducts type-erasure via
`Box<dyn Any>`, when the message is sent, then reconstructs the message type
before passing it to the appropriate message handler.

This unfortunately results in two allocation for every message, one for this,
and one for the handle future.
2 changes: 2 additions & 0 deletions src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ impl<A> Context<A> {
}

/// Retrieve the address for the executor's actor
///
/// This is useful when an actor wants to emit messages to itself.
pub fn address(&self) -> &Address<A> {
&self.address
}
Expand Down

0 comments on commit 8f1bea4

Please sign in to comment.