diff --git a/nebkor-maelstrom/README.md b/nebkor-maelstrom/README.md new file mode 100644 index 0000000..7de1e10 --- /dev/null +++ b/nebkor-maelstrom/README.md @@ -0,0 +1,70 @@ +# 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 std::sync::{Arc, Mutex}; + +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 node = Arc::new(Mutex::new(node)); + + 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`. + +In your main function, instantiate that struct and wrap it in an `Arc>`, then 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 +[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>` 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.