Skip to content

Commit

Permalink
update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
paulgb committed Sep 5, 2024
1 parent a8ffb3a commit 6abe18e
Showing 1 changed file with 74 additions and 47 deletions.
121 changes: 74 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,74 +5,101 @@
[![docs.rs](https://img.shields.io/badge/docs-release-brightgreen)](https://docs.rs/aper/)
[![wokflow state](https://github.com/drifting-in-space/aper/workflows/build/badge.svg)](https://github.com/drifting-in-space/aper/actions/workflows/rust.yml)

<img src="https://aper.dev/ape.svg" alt="Cartoonized face of an ape." width="200px" />
Aper is a Rust library for data synchronization over a network.

Aper is a Rust library for data synchronization using **state machines**. Aper provides mechanisms to represent common data structures in terms of state machines, as well as a transport-agnostic protocol for keeping multiple instances of a state machine synchronized across a network.
Aper supports optimistic updates and arbitrary business logic, making it useful for real-time collabrative and agentic use cases.

Use-cases include real-time multiplayer applications that operate on shared state, client-server applications that want to share state updates incrementally and bidirectionally, and multiplayer turn-based games.
## Introduction

## What is a state machine?
(More docs coming soon)

For the purposes of Aper, a state machine is simply a `struct` or `enum` that
implements `StateMachine` and has the following properties:
- It defines a `StateMachine::Transition` type, through which every
possible change to the state can be described. It is usually useful,
though not required, that this be an `enum` type.
- It defines a `StateMachine::Conflict` type, which describes a conflict which
may occur when a transition is applied that is not valid at the time it is
applied. For simple types where a conflict is impossible, you can use
`NeverConflict` for this.
- All state updates are deterministic: if you clone a `StateMachine` and a
`Transition`, the result of applying the cloned transition to the cloned
state must be identical to applying the original transition to the original
state.
Types marked with the `AperSync` trait can be stored in the `Store`, Aper's synchronizable data store.
Aper includes several data structures that implement `AperSync` in the `aper::data_structures` module, which
can be used as building blocks to build your own synchronizable types.

Here's an example `StateMachine` implementing a counter:
You can use these, along with the `AperSync` derive macro, to compose structs that also implement `AperSync`.

```rust
use aper::{Aper, AperSync};
use serde::{Serialize, Deserialize};
use aper::{AperSync, data_structures::{Atom, Map}};
use uuid::Uuid;

#[derive(Serialize, Deserialize, Clone, Debug, Default, AperSync)]
struct Counter { value: i64 }
#[derive(AperSync)]
struct ToDoItem {
pub done: Atom<bool>,
pub name: Atom<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
enum CounterTransition {
Reset,
Increment(i64),
Decrement(i64),
#[derive(AperSync)]
struct ToDoList {
pub items: Map<Uuid, ToDoItem>,
}
```

To synchronize from the server to clients, Aper replicates changes to the `Store` when it receives them. To synchronize
from clients to servers, we instead send *intents* to the server.

Intents are represented as a serializable `enum` representing every possible action a user might take on the data.
For example, in our to-do list, that represents creating a task, renaming a task, marking a task as (not) done, or
removing completed items.

impl Aper for Counter {
type Transition = CounterTransition;
type Conflict = NeverConflict;
```rust
use aper::Aper;

#[derive(Serialize, Deserialize, Clone, std::cmp::PartialEq)]
enum ToDoIntent {
CreateTask {
id: Uuid,
name: String,
},
RenameTask {
id: Uuid,
name: String,
},
MarkDone {
id: Uuid,
done: bool,
},
RemoveCompleted,
}

fn apply(&self, event: &CounterTransition) -> Result<Counter, NeverConflict> {
match event {
CounterTransition::Reset => Ok(Counter {value: 0}),
CounterTransition::Increment(amount) => Ok(Counter {value: self.value + amount}),
CounterTransition::Decrement(amount) => Ok(Counter {value: self.value - amount}),
impl Aper for ToDoList {
type Intent = ToDoIntent;
type Error = ();

fn apply(&mut self, intent: &ToDoIntent) -> Result<(), ()> {
match intent {
ToDoIntent::CreateTask { id, name } => {
let mut item = self.items.get_or_create(id);
item.name.set(name.to_string());
item.done.set(false);
},
ToDoIntent::RenameTask { id, name } => {
// Unlike CreateTask, we bail early with an `Err` if
// the item doesn't exist. Most likely, the server has
// seen a `RemoveCompleted` that removed the item, but
// a client attempted to rename it before the removal
// was synced to it.
let mut item = self.items.get(id).ok_or(())?;
item.name.set(name.to_string());
}
ToDoIntent::MarkDone { id, done } => {
let mut item = self.items.get(id).ok_or(())?;
item.done.set(*done);
}
ToDoIntent::RemoveCompleted => {
// TODO: need to implement .iter() on Map first.
}
}

Ok(())
}
}
```

## Why not CRDT?
[Conflict-free replicated data types](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type)
are a really neat way of representing data that's shared between peers.
In order to avoid the need for a central “source of truth”, CRDTs require
that update operations (i.e. state transitions) be [commutative](https://en.wikipedia.org/wiki/Commutative_property).
This allows them to represent a bunch of common data structures, but doesn't
allow you to represent arbitrarily complex update logic.
By relying on a central authority, a state-machine approach allows you to
implement data structures with arbitrary update logic, such as atomic moves
of a value between two data structures, or the rules of a board game.

---

**Aper is rapidly evolving. Consider this a technology preview.** See the [list of issues outstanding for version 1.0](https://github.com/drifting-in-space/aper/labels/v1-milestone)

- [Documentation](https://docs.rs/aper/)
- [Getting Started with Aper guide](https://aper.dev/guide/)
- [Examples](https://github.com/drifting-in-space/aper/tree/main/examples)
- [Talk on Aper for Rust Berlin (20 minute video)](https://www.youtube.com/watch?v=HNzeouj0eKc&t=1852s)

0 comments on commit 6abe18e

Please sign in to comment.