Black-Box
A minimal, stage for actors.
About
Black-box's API design is inspired by actix, but 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:
use *;
// Messages do not need to implement anything
;
;
;
// All methods are provided, but can be overridden for more control
// Spawn the actor on a runtime of your choosing
async
Runtime
Black-box deliberatly does not ship with a runtime, instead it aims to work with 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.
Message Trait
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.
type Message = ;
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
- Spawn many actors to handle the work
- 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:
- Create many channels over which to send different message types
- 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.