2024-06-04 00:26:39 +00:00
|
|
|
# A synchronous and simple Maelstrom crate
|
|
|
|
|
|
|
|
`nebkor-maelstreom` is a lean and simple synchronous library for writing
|
|
|
|
[Maelstrom](https://github.com/jepsen-io/maelstrom/tree/0186f398f96564a0453dda97c07daeac67c3d8d7)-compatible
|
|
|
|
distributed actors. It has three dependencies:
|
|
|
|
|
|
|
|
- serde
|
|
|
|
- serde_json
|
|
|
|
- serde_repr
|
|
|
|
|
|
|
|
For a simple example, see the [gg-echo](https://git.kittencollective.com/nebkor/chatty-catties/src/branch/main/gg-echo/src/main.rs) program:
|
|
|
|
|
|
|
|
``` rust
|
|
|
|
use nebkor_maelstrom::{Body, Message, Node, Runner};
|
|
|
|
|
|
|
|
struct Echo;
|
|
|
|
|
|
|
|
impl Node for Echo {
|
|
|
|
fn handle(&mut self, runner: &Runner, msg: Message) {
|
|
|
|
let typ = &msg.body.typ;
|
|
|
|
if typ.as_str() == "echo" {
|
|
|
|
let body = Body::from_type("echo_ok").with_payload(msg.body.payload.clone());
|
|
|
|
runner.reply(&msg, body);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let node = Echo;
|
|
|
|
|
|
|
|
let runner = Runner::new(node);
|
|
|
|
|
|
|
|
runner.run(None);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## How to use
|
|
|
|
|
|
|
|
Create a struct and implement `nebkor_maelstrom::Node` for it, which involves a single method,
|
|
|
|
`handle(&mut self, &Runner, Message)`. This method is passed a `Runner` which contains methods like
|
|
|
|
`send`, `reply`, and `rpc`.
|
|
|
|
|
2024-06-04 17:21:36 +00:00
|
|
|
In your main function, instantiate that struct and pass that into `Runner::new()` to get a
|
|
|
|
Runner. The `run()` method takes an optional closure that will be run when the `init` Message is
|
|
|
|
received; see the
|
2024-06-04 00:26:39 +00:00
|
|
|
[broadcast](https://git.kittencollective.com/nebkor/chatty-catties/src/commit/fff7fdc2d52f7d4c2d4d9c581ea16cdf0e1e3f30/gg-broadcast/src/main.rs#L18-L33)
|
|
|
|
program for an example of that, where it spawns a thread to send periodic messages to the node.
|
|
|
|
|
|
|
|
## Design considerations
|
|
|
|
|
|
|
|
I wanted the client code to be as simple as possible, with the least amount of boilerplate. Using
|
|
|
|
`&mut self` as the receiver for the `handle()` method lets you easily mutate state in your node if
|
|
|
|
you need to, without the ceremony of `Rc<Mutex<>>` and the like. Eschewing `async` results in an
|
|
|
|
order of magnitude fewer dependencies, and the entire workspace (crate and clients) can be compiled
|
|
|
|
in a couple seconds.
|
|
|
|
|
|
|
|
## Acknowledgments
|
|
|
|
|
|
|
|
I straight-up stole the design of the IO/network system from
|
|
|
|
[Maelbreaker](https://github.com/rafibayer/maelbreaker/), which allowed me to get a working RPC
|
|
|
|
call. Thanks!
|
|
|
|
|
|
|
|
|
|
|
|
## TODO
|
|
|
|
|
|
|
|
- add error handling.
|