Skip to content

Commit 6abe18e

Browse files
committed
update readme
1 parent a8ffb3a commit 6abe18e

File tree

1 file changed

+74
-47
lines changed

1 file changed

+74
-47
lines changed

README.md

Lines changed: 74 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,74 +5,101 @@
55
[![docs.rs](https://img.shields.io/badge/docs-release-brightgreen)](https://docs.rs/aper/)
66
[![wokflow state](https://github.com/drifting-in-space/aper/workflows/build/badge.svg)](https://github.com/drifting-in-space/aper/actions/workflows/rust.yml)
77

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

10-
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.
10+
Aper supports optimistic updates and arbitrary business logic, making it useful for real-time collabrative and agentic use cases.
1111

12-
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.
12+
## Introduction
1313

14-
## What is a state machine?
14+
(More docs coming soon)
1515

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

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

3222
```rust
33-
use aper::{Aper, AperSync};
34-
use serde::{Serialize, Deserialize};
23+
use aper::{AperSync, data_structures::{Atom, Map}};
24+
use uuid::Uuid;
3525

36-
#[derive(Serialize, Deserialize, Clone, Debug, Default, AperSync)]
37-
struct Counter { value: i64 }
26+
#[derive(AperSync)]
27+
struct ToDoItem {
28+
pub done: Atom<bool>,
29+
pub name: Atom<String>,
30+
}
3831

39-
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
40-
enum CounterTransition {
41-
Reset,
42-
Increment(i64),
43-
Decrement(i64),
32+
#[derive(AperSync)]
33+
struct ToDoList {
34+
pub items: Map<Uuid, ToDoItem>,
4435
}
36+
```
37+
38+
To synchronize from the server to clients, Aper replicates changes to the `Store` when it receives them. To synchronize
39+
from clients to servers, we instead send *intents* to the server.
40+
41+
Intents are represented as a serializable `enum` representing every possible action a user might take on the data.
42+
For example, in our to-do list, that represents creating a task, renaming a task, marking a task as (not) done, or
43+
removing completed items.
4544

46-
impl Aper for Counter {
47-
type Transition = CounterTransition;
48-
type Conflict = NeverConflict;
45+
```rust
46+
use aper::Aper;
47+
48+
#[derive(Serialize, Deserialize, Clone, std::cmp::PartialEq)]
49+
enum ToDoIntent {
50+
CreateTask {
51+
id: Uuid,
52+
name: String,
53+
},
54+
RenameTask {
55+
id: Uuid,
56+
name: String,
57+
},
58+
MarkDone {
59+
id: Uuid,
60+
done: bool,
61+
},
62+
RemoveCompleted,
63+
}
4964

50-
fn apply(&self, event: &CounterTransition) -> Result<Counter, NeverConflict> {
51-
match event {
52-
CounterTransition::Reset => Ok(Counter {value: 0}),
53-
CounterTransition::Increment(amount) => Ok(Counter {value: self.value + amount}),
54-
CounterTransition::Decrement(amount) => Ok(Counter {value: self.value - amount}),
65+
impl Aper for ToDoList {
66+
type Intent = ToDoIntent;
67+
type Error = ();
68+
69+
fn apply(&mut self, intent: &ToDoIntent) -> Result<(), ()> {
70+
match intent {
71+
ToDoIntent::CreateTask { id, name } => {
72+
let mut item = self.items.get_or_create(id);
73+
item.name.set(name.to_string());
74+
item.done.set(false);
75+
},
76+
ToDoIntent::RenameTask { id, name } => {
77+
// Unlike CreateTask, we bail early with an `Err` if
78+
// the item doesn't exist. Most likely, the server has
79+
// seen a `RemoveCompleted` that removed the item, but
80+
// a client attempted to rename it before the removal
81+
// was synced to it.
82+
let mut item = self.items.get(id).ok_or(())?;
83+
item.name.set(name.to_string());
84+
}
85+
ToDoIntent::MarkDone { id, done } => {
86+
let mut item = self.items.get(id).ok_or(())?;
87+
item.done.set(*done);
88+
}
89+
ToDoIntent::RemoveCompleted => {
90+
// TODO: need to implement .iter() on Map first.
91+
}
5592
}
93+
94+
Ok(())
5695
}
5796
}
5897
```
5998

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

73101
**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)
74102

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

0 commit comments

Comments
 (0)