move resolver to event

This commit is contained in:
tezlm 2024-02-05 19:26:58 -08:00
parent c3a185fe8b
commit e208f62686
Signed by: tezlm
GPG key ID: 649733FCD94AFBBA
4 changed files with 196 additions and 92 deletions

View file

@ -1,10 +1,12 @@
use crate::atoms::EventId;
use std::fmt::Debug;
use crate::{atoms::EventId, resolver::Resolver};
#[derive(Debug)]
/// An event, is the basic building block/envolope containing all data.
pub struct Event<T> {
pub struct Event<T, S> {
pub id: EventId,
pub content: CoreContent<T>,
pub content: CoreContent<T, S>,
pub references: Vec<EventId>,
// pub author: ActorId,
// pub signature: ActorSignature,
@ -12,8 +14,8 @@ pub struct Event<T> {
#[derive(Debug)]
/// This defines content of an event as either a builtin event or application-specific event.
pub enum CoreContent<T> {
Create(CreateContent),
pub enum CoreContent<T, S> {
Create(CreateContent<T, S>),
Custom(T),
/*
@ -41,12 +43,14 @@ pub enum CoreContent<T> {
#[derive(Debug)]
/// Every room has exactly one immutable create event as the single root of the event dag.
pub struct CreateContent {
// TODO: move resolver stuff here
pub struct CreateContent<T, S> {
pub resolver: Box<dyn Resolver<Type = T, State = S>>,
}
impl CreateContent {
pub fn new() -> Self {
Self { }
impl<T, S> CreateContent<T, S> {
pub fn new(resolver: Box<dyn Resolver<Type = T, State = S>>) -> Self {
Self {
resolver,
}
}
}

View file

@ -1,40 +1,145 @@
#![allow(dead_code)] // TODO: remove this later!
use std::collections::HashMap;
use event::Event;
use std::{
cmp::Ordering,
collections::{BTreeMap, HashMap},
path::{Path, PathBuf},
};
use crate::{event::CoreContent, resolver::Resolver, room::Room};
mod atoms;
mod event;
mod room;
mod resolver;
mod room;
#[derive(Debug)]
enum EventContent {
/// A basic key value store
enum KVEventContent {
Set(String, String),
}
fn main() {
let resolver = Resolver::new(Box::new(|events| {
// resolve/reduce state from a sorted list of events, "full resolution"
#[derive(Debug)]
/// A basic key filesystem-like thing
enum FSEventContent {
Put(PathBuf, Vec<u8>),
Delete(PathBuf),
}
struct KVResolver;
struct FSResolver;
#[derive(Debug)]
struct FSState {
files: BTreeMap<PathBuf, Vec<u8>>,
}
impl Resolver for KVResolver {
type Type = KVEventContent;
type State = HashMap<String, String>;
fn resolve(&self, events: &[&Event<Self::Type, Self::State>]) -> Self::State {
dbg!(events);
let mut kv = HashMap::new();
for event in events {
match &event.content {
CoreContent::Create(_) => {},
CoreContent::Custom(EventContent::Set(k, v)) => {
CoreContent::Create(_) => {}
CoreContent::Custom(KVEventContent::Set(k, v)) => {
kv.insert(k.clone(), v.clone());
},
}
}
}
kv
}), Box::new(|_a, _b| {
// tiebreak two events
todo!()
}));
let mut room = Room::new(resolver);
room.create_event(CoreContent::Custom(EventContent::Set("foo".into(), "apple".into())));
room.create_event(CoreContent::Custom(EventContent::Set("bar".into(), "banana".into())));
room.create_event(CoreContent::Custom(EventContent::Set("baz".into(), "carrot".into())));
}
fn tiebreak(
&self,
_a: &Event<Self::Type, Self::State>,
_b: &Event<Self::Type, Self::State>,
) -> Ordering {
Ordering::Equal
}
}
impl Resolver for FSResolver {
type Type = FSEventContent;
type State = FSState;
fn resolve(&self, events: &[&Event<Self::Type, Self::State>]) -> Self::State {
dbg!(events);
let mut files = BTreeMap::new();
for event in events {
match &event.content {
CoreContent::Create(_) => {}
CoreContent::Custom(FSEventContent::Put(path, data)) => {
files.insert(path.clone(), data.clone());
}
CoreContent::Custom(FSEventContent::Delete(match_path)) => {
files.retain(|file_path, _| !file_path.starts_with(match_path))
}
}
}
FSState { files }
}
fn tiebreak(
&self,
_a: &Event<Self::Type, Self::State>,
_b: &Event<Self::Type, Self::State>,
) -> Ordering {
Ordering::Equal
}
}
impl FSState {
fn readstr(&self, path: &Path) -> Option<String> {
self.files
.get(path)
.map(|b| String::from_utf8_lossy(b).to_string())
}
fn list(&self, path: &Path) -> Vec<&Path> {
self.files
.keys()
.filter(|file_path| file_path.starts_with(path))
.map(|p| p.as_path())
.collect()
}
}
fn main() {
let mut room = Room::new(Box::new(KVResolver));
room.create_event(CoreContent::Custom(KVEventContent::Set(
"foo".into(),
"apple".into(),
)));
room.create_event(CoreContent::Custom(KVEventContent::Set(
"bar".into(),
"banana".into(),
)));
room.create_event(CoreContent::Custom(KVEventContent::Set(
"baz".into(),
"carrot".into(),
)));
dbg!(&room);
dbg!(&room.get_state());
let mut room = Room::new(Box::new(FSResolver));
room.create_event(CoreContent::Custom(FSEventContent::Put(
"/readme.md".into(),
b"Hello, world!".into(),
)));
room.create_event(CoreContent::Custom(FSEventContent::Put(
"/remove/me.txt".into(),
b"oops".into(),
)));
room.create_event(CoreContent::Custom(FSEventContent::Delete(
"/remove".into(),
)));
let state = room.get_state();
dbg!(&room);
dbg!(&state);
dbg!(&state.list(&Path::new("/")));
dbg!(&state.readstr(&Path::new("/readme.md")));
}

View file

@ -1,63 +1,54 @@
use crate::{atoms::EventId, event::Event};
use std::{cmp::Ordering, collections::HashSet, fmt::Debug, ops::Deref};
use std::{cmp::Ordering, collections::HashSet, fmt::Debug};
pub type TiebreakFn<T> = Box<dyn Fn(&Event<T>, &Event<T>) -> Ordering>;
pub type ResolveFn<T, S> = Box<dyn Fn(&[&Event<T>]) -> S>;
pub struct Resolver<T, S> {
/// Given two events, decide which one comes first
pub tiebreak_fn: TiebreakFn<T>,
pub trait Resolver {
type Type;
type State;
/// Given a set of ordered events, resolve the final state
pub resolve_fn: ResolveFn<T, S>,
fn resolve(&self, events: &[&Event<Self::Type, Self::State>]) -> Self::State;
/// Given two events, decide which one comes first
fn tiebreak(
&self,
a: &Event<Self::Type, Self::State>,
b: &Event<Self::Type, Self::State>,
) -> Ordering;
}
impl<T: Debug, S> Resolver<T, S> {
pub fn new(resolve_fn: ResolveFn<T, S>, tiebreak_fn: TiebreakFn<T>) -> Self {
Resolver {
tiebreak_fn,
resolve_fn,
}
}
/// topologically sort a list of events
pub fn sort<'a>(&'a self, events: &'a [Event<T>]) -> Vec<&Event<T>> {
let mut references: HashSet<(EventId, EventId)> = events
.iter()
.flat_map(|event| {
event
.references
.iter()
.map(|parent_id| (parent_id.clone(), event.id.clone()))
})
.collect();
let mut sorted = vec![];
let (mut heads, mut unsorted): (Vec<_>, Vec<_>) =
events.iter().partition(|event| event.references.is_empty());
assert!(heads.len() == 1);
while let Some(ev) = heads.pop() {
references.retain(|(parent_id, _)| parent_id != &ev.id);
sorted.push(ev);
let (new_unsorted, mut children) = unsorted.into_iter().partition(|candidate| {
references
.iter()
.any(|(_, child)| child == &candidate.id)
});
unsorted = new_unsorted;
children.sort_by(|a, b| (self.tiebreak_fn)(a, b).then_with(|| a.id.cmp(&b.id)));
heads.extend(children);
}
assert!(unsorted.is_empty());
sorted
}
/// resolve state from a set of unsorted events
pub fn resolve(&self, events: &[Event<T>]) -> S {
(self.resolve_fn)(self.sort(events).deref())
/// topologically sort a list of events
pub fn sort<T, S>(
tiebreak: impl Fn(&Event<T, S>, &Event<T, S>) -> Ordering,
events: &[Event<T, S>],
) -> Vec<&Event<T, S>> {
let mut references: HashSet<(EventId, EventId)> = events
.iter()
.flat_map(|event| {
event
.references
.iter()
.map(|parent_id| (parent_id.clone(), event.id.clone()))
})
.collect();
let mut sorted = vec![];
let (mut heads, mut unsorted): (Vec<_>, Vec<_>) =
events.iter().partition(|event| event.references.is_empty());
assert!(heads.len() == 1);
while let Some(ev) = heads.pop() {
references.retain(|(parent_id, _)| parent_id != &ev.id);
sorted.push(ev);
let (new_unsorted, mut children) = unsorted
.into_iter()
.partition(|candidate| references.iter().any(|(_, child)| child == &candidate.id));
unsorted = new_unsorted;
children.sort_by(|a, b| tiebreak(a, b).then_with(|| a.id.cmp(&b.id)));
heads.extend(children);
}
assert!(unsorted.is_empty());
sorted
}
impl<T, S> Debug for Resolver<T, S> {
impl<T, S> Debug for dyn Resolver<Type = T, State = S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,

View file

@ -1,40 +1,44 @@
use std::fmt::Debug;
use crate::{atoms::EventId, event::{CoreContent, CreateContent, Event}, resolver::Resolver};
use crate::{atoms::EventId, event::{CoreContent, CreateContent, Event}, resolver::{sort, Resolver}};
#[derive(Debug)]
pub struct Room<E, S> {
pub(super) resolver: Resolver<E, S>,
pub(super) events: Vec<Event<E>>,
pub(super) events: Vec<Event<E, S>>,
pub(super) heads: Vec<EventId>,
}
impl<T: Debug, S> Room<T, S> {
pub fn new(resolver: Resolver<T, S>) -> Self {
pub fn new(resolver: Box<dyn Resolver<Type = T, State = S>>) -> Self {
let event_id = EventId::random();
let base_event = Event {
id: event_id.clone(),
content: CoreContent::Create(CreateContent::new()),
content: CoreContent::Create(CreateContent::new(resolver)),
references: vec![],
};
Self {
resolver,
events: vec![base_event],
heads: vec![event_id],
}
}
pub fn get_root(&self) -> &Event<T> {
pub fn get_root(&self) -> &Event<T, S> {
self.events.first().as_ref().unwrap()
}
pub fn get_state(&mut self) -> S {
// self.state_cache
// .get_or_insert_with(|| self.resolver.resolve(&self.events))
self.resolver.resolve(&self.events)
pub fn get_resolver(&self) -> &dyn Resolver<Type = T, State = S> {
match &self.get_root().content {
CoreContent::Create(create) => create.resolver.as_ref(),
_ => unreachable!("the root event is always a create event"),
}
}
pub fn create_event(&mut self, event_content: CoreContent<T>) {
pub fn get_state(&mut self) -> S {
let resolver = self.get_resolver();
let sorted = sort(|a, b| resolver.tiebreak(a, b), &self.events);
resolver.resolve(&sorted)
}
pub fn create_event(&mut self, event_content: CoreContent<T, S>) {
let event = Event {
id: EventId::random(),
content: event_content,
@ -43,7 +47,7 @@ impl<T: Debug, S> Room<T, S> {
self.append_event(event);
}
pub fn append_event(&mut self, event: Event<T>) {
pub fn append_event(&mut self, event: Event<T, S>) {
// let parents: Vec<_> = self
// .events
// .iter()