parent
4d9441e57b
commit
5cb807a107
@ -0,0 +1,95 @@
|
|||||||
|
//! Caching interfaces
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
borrow::{
|
||||||
|
Borrow,
|
||||||
|
ToOwned,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait Cache<T>
|
||||||
|
{
|
||||||
|
fn cap(&self) -> Option<usize>;
|
||||||
|
fn len(&self) -> usize;
|
||||||
|
|
||||||
|
fn insert(&mut self, value: T) -> &T;
|
||||||
|
fn get<Q: ?Sized + PartialEq<T>>(&self, value: &Q) -> Option<&T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MemCache<T>(Vec<T>);
|
||||||
|
pub struct UnlimitedMemCache<T>(Vec<T>);
|
||||||
|
|
||||||
|
impl<T> MemCache<T>
|
||||||
|
{
|
||||||
|
/// Create a new cache with a max size
|
||||||
|
pub fn new(cap: usize) -> Self
|
||||||
|
{
|
||||||
|
Self(Vec::with_capacity(cap))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Cache<T> for MemCache<T>
|
||||||
|
{
|
||||||
|
fn cap(&self) -> Option<usize>
|
||||||
|
{
|
||||||
|
Some(self.0.capacity())
|
||||||
|
}
|
||||||
|
fn len(&self) -> usize
|
||||||
|
{
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&mut self, value: T) -> &T
|
||||||
|
{
|
||||||
|
if self.0.len() == self.0.capacity() {
|
||||||
|
self.0.remove(self.0.len()-1);
|
||||||
|
}
|
||||||
|
self.0.insert(0, value);
|
||||||
|
&self.0[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get<Q: ?Sized + PartialEq<T>>(&self, value: &Q) -> Option<&T>
|
||||||
|
{
|
||||||
|
for x in self.0.iter() {
|
||||||
|
if value.eq(x) {
|
||||||
|
return Some(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> UnlimitedMemCache<T>
|
||||||
|
{
|
||||||
|
/// Create a new cache
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extension
|
||||||
|
|
||||||
|
pub trait CacheExt<T>
|
||||||
|
{
|
||||||
|
/// Insert into the cache if borrowed value is not present,
|
||||||
|
fn clone_insert<'a, Q>(&'a mut self, refer: &Q) -> &'a T
|
||||||
|
where Q: ToOwned<Owned=T> + ?Sized + PartialEq<T>,
|
||||||
|
T: Borrow<Q>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, T> CacheExt<T> for S
|
||||||
|
where S: Cache<T>,
|
||||||
|
T: Clone
|
||||||
|
{
|
||||||
|
fn clone_insert<'a, Q>(&'a mut self, refer: &Q) -> &'a T
|
||||||
|
where Q: ToOwned<Owned=T> + ?Sized + PartialEq<T>,
|
||||||
|
T: Borrow<Q>
|
||||||
|
{
|
||||||
|
if let Some(get) = self.get(refer) {
|
||||||
|
return get;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.insert(refer.to_owned())
|
||||||
|
}
|
||||||
|
}
|
@ -1,94 +0,0 @@
|
|||||||
//! Handles updating posts
|
|
||||||
use super::*;
|
|
||||||
use std::{
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
use tokio::{
|
|
||||||
sync::{
|
|
||||||
RwLock,
|
|
||||||
watch,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const MAX_SINGLE_DELTA_SIZE: usize = 16;
|
|
||||||
|
|
||||||
/// Information about the delta to be applied in `Delta`.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum DeltaKind
|
|
||||||
{
|
|
||||||
/// Append to the end of body. Equivilant to `Insert` with a location at `karada.scape.len()` (the end of the buffer). Might be removed idk
|
|
||||||
Append{
|
|
||||||
span: [char; MAX_SINGLE_DELTA_SIZE],
|
|
||||||
span_len: u8,
|
|
||||||
},
|
|
||||||
/// Insert `span_len` chars from `span` into body starting ahead of `location` and moving ahead
|
|
||||||
Insert{
|
|
||||||
span: [char; MAX_SINGLE_DELTA_SIZE],
|
|
||||||
span_len: u8,
|
|
||||||
},
|
|
||||||
/// Remove `span_len` chars ahead of `location`. (INCLUSIVE)
|
|
||||||
RemoveAhead{
|
|
||||||
span_len: usize,
|
|
||||||
},
|
|
||||||
/// Remove `span_len` chars behind this `location`. (EXCLUSIVE)
|
|
||||||
RemoveBehind{
|
|
||||||
span_len: usize,
|
|
||||||
},
|
|
||||||
/// Remove char at `location`
|
|
||||||
RemoveSingle,
|
|
||||||
/// Remove entire post body
|
|
||||||
Clear,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A delta to apply to `Karada`.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Delta
|
|
||||||
{
|
|
||||||
/// Location to insert into. This is the INclusive range of: 0..=(karada.scape.len()).
|
|
||||||
///
|
|
||||||
/// Insertions off the end of the buffer are to be appened instead.
|
|
||||||
location: usize,
|
|
||||||
/// The kind of delta t oinsert
|
|
||||||
kind: DeltaKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Static assertion: `MAX_SINGLE_DELTA_SIZE` can fit into `u8`.
|
|
||||||
const _: [u8;(MAX_SINGLE_DELTA_SIZE < (!0u8 as usize)) as usize] = [0];
|
|
||||||
|
|
||||||
/// Contains post deltas and an intermediate representation of the still-open post body.
|
|
||||||
/// Created and modified with `Kokoro` worker instances.
|
|
||||||
///
|
|
||||||
/// This should not be created by itself, instead `Kokoro` should create instances of this, so that it can retain the `watch::Sender` and other such things.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Karada
|
|
||||||
{
|
|
||||||
/// The post body so far as a vector of `char`s.
|
|
||||||
scape: Arc<RwLock<Vec<char>>>,
|
|
||||||
/// All applied deltas so far. Last applied one is at the end.
|
|
||||||
deltas: Arc<RwLock<Vec<Delta>>>,
|
|
||||||
|
|
||||||
/// the latest render of the whole body string. Updated whenever a delta(s) are applied atomically.
|
|
||||||
current_body: watch::Receiver<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles working on `Karada` instances.
|
|
||||||
pub struct Kokoro
|
|
||||||
{
|
|
||||||
// ...//TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An open, as yet unfinied post
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Imouto
|
|
||||||
{
|
|
||||||
id: identity::PostID,
|
|
||||||
|
|
||||||
user: identity::User,
|
|
||||||
|
|
||||||
title: Option<String>,
|
|
||||||
karada: Karada,
|
|
||||||
|
|
||||||
/// Hash of the current post data
|
|
||||||
hash: crypto::sha256::Sha256Hash,
|
|
||||||
timestamp: post::PostTimestamp, //Note that `closed` should always be `None` in `Imouto`. We use this for post lifetimes and such
|
|
||||||
}
|
|
@ -0,0 +1,230 @@
|
|||||||
|
//! Deltas and applying them
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const MAX_SINGLE_DELTA_SIZE: usize = 16;
|
||||||
|
|
||||||
|
/// Create a delta span from an input iterator.
|
||||||
|
///
|
||||||
|
/// This function can take no more than 255 chars from the input. The number of chars inserted is also returned as `u8`.
|
||||||
|
pub(super) fn delta_span<I>(from: I) -> ([char; MAX_SINGLE_DELTA_SIZE], u8)
|
||||||
|
where I: IntoIterator<Item = char>
|
||||||
|
{
|
||||||
|
let mut output: [char; MAX_SINGLE_DELTA_SIZE] = Default::default();
|
||||||
|
let mut sz: u8 = 0;
|
||||||
|
|
||||||
|
for (d, s) in output.iter_mut().zip(from.into_iter().take(usize::from(u8::MAX)))
|
||||||
|
{
|
||||||
|
*d = s;
|
||||||
|
sz += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(output, sz)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about the delta to be applied in `Delta`.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||||
|
pub enum DeltaKind
|
||||||
|
{
|
||||||
|
/// Append to the end of body. Equivilant to `Insert` with a location at `karada.scape.len()` (the end of the buffer). Might be removed idk
|
||||||
|
Append{
|
||||||
|
span: [char; MAX_SINGLE_DELTA_SIZE],
|
||||||
|
span_len: u8,
|
||||||
|
},
|
||||||
|
/// Insert `span_len` chars from `span` into body starting *at* `location` and moving ahead
|
||||||
|
Insert{
|
||||||
|
span: [char; MAX_SINGLE_DELTA_SIZE],
|
||||||
|
span_len: u8,
|
||||||
|
},
|
||||||
|
/// Remove `span_len` chars ahead of `location`. (INCLUSIVE)
|
||||||
|
RemoveAhead{
|
||||||
|
span_len: usize,
|
||||||
|
},
|
||||||
|
/// Remove `span_len` chars behind this `location`. (EXCLUSIVE)
|
||||||
|
RemoveBehind{
|
||||||
|
span_len: usize,
|
||||||
|
},
|
||||||
|
/// Remove char at `location`
|
||||||
|
RemoveSingle,
|
||||||
|
/// Remove entire post body
|
||||||
|
Clear,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A delta to apply to `Karada`.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||||
|
pub struct Delta
|
||||||
|
{
|
||||||
|
/// Location to insert into. This is the INclusive range of: 0..=(karada.scape.len()).
|
||||||
|
///
|
||||||
|
/// Insertions off the end of the buffer are to be appened instead.
|
||||||
|
location: usize,
|
||||||
|
/// The kind of delta t oinsert
|
||||||
|
kind: DeltaKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Static assertion: `MAX_SINGLE_DELTA_SIZE` can fit into `u8`.
|
||||||
|
const _: [u8;(MAX_SINGLE_DELTA_SIZE < (!0u8 as usize)) as usize] = [0];
|
||||||
|
|
||||||
|
impl Delta
|
||||||
|
{
|
||||||
|
pub fn insert(&self, inserter: &mut MessageSpan)
|
||||||
|
{
|
||||||
|
match self.kind {
|
||||||
|
DeltaKind::Append{span, span_len} => {
|
||||||
|
inserter.extend_from_slice(&span[..usize::from(span_len)]);
|
||||||
|
},
|
||||||
|
DeltaKind::Insert{span, span_len} => {
|
||||||
|
let span = &span[..usize::from(span_len)];
|
||||||
|
if self.location == inserter.len() {
|
||||||
|
inserter.extend_from_slice(span);
|
||||||
|
} else {
|
||||||
|
// reserve the extra space
|
||||||
|
inserter.reserve(span.len());
|
||||||
|
// shift everything across, replacing with the new values
|
||||||
|
let splice: Vec<_> = inserter.splice(self.location.., span.iter().cloned()).collect();
|
||||||
|
// add tail back
|
||||||
|
inserter.extend(splice);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests
|
||||||
|
{
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn insert_body()
|
||||||
|
{
|
||||||
|
let mut message: Vec<char> = "126789".chars().collect();
|
||||||
|
|
||||||
|
let delta = {
|
||||||
|
let (span, span_len) = delta_span("345".chars());
|
||||||
|
Delta {
|
||||||
|
location: 2,
|
||||||
|
kind: DeltaKind::Insert{span, span_len},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("from: {:?}", message);
|
||||||
|
println!("delta: {:?}", delta);
|
||||||
|
delta.insert(&mut message);
|
||||||
|
|
||||||
|
assert_eq!(&message.into_iter().collect::<String>()[..], "123456789");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn insert_end()
|
||||||
|
{
|
||||||
|
let mut message: Vec<char> = "1289".chars().collect();
|
||||||
|
|
||||||
|
let delta = {
|
||||||
|
let (span, span_len) = delta_span("34567".chars());
|
||||||
|
Delta {
|
||||||
|
location: 2,
|
||||||
|
kind: DeltaKind::Insert{span, span_len},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("from: {:?}", message);
|
||||||
|
println!("delta: {:?}", delta);
|
||||||
|
delta.insert(&mut message);
|
||||||
|
|
||||||
|
assert_eq!(&message.into_iter().collect::<String>()[..], "123456789");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn insert_end_rev()
|
||||||
|
{
|
||||||
|
let mut message: Vec<char> = "1234569".chars().collect();
|
||||||
|
|
||||||
|
let delta = {
|
||||||
|
let (span, span_len) = delta_span("78".chars());
|
||||||
|
Delta {
|
||||||
|
location: 6,
|
||||||
|
kind: DeltaKind::Insert{span, span_len},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("from: {:?}", message);
|
||||||
|
println!("delta: {:?}", delta);
|
||||||
|
delta.insert(&mut message);
|
||||||
|
|
||||||
|
assert_eq!(&message.into_iter().collect::<String>()[..], "123456789");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn insert_begin_rev()
|
||||||
|
{
|
||||||
|
let mut message: Vec<char> = "1456789".chars().collect();
|
||||||
|
|
||||||
|
let delta = {
|
||||||
|
let (span, span_len) = delta_span("23".chars());
|
||||||
|
Delta {
|
||||||
|
location: 1,
|
||||||
|
kind: DeltaKind::Insert{span, span_len},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("from: {:?}", message);
|
||||||
|
println!("delta: {:?}", delta);
|
||||||
|
delta.insert(&mut message);
|
||||||
|
|
||||||
|
assert_eq!(&message.into_iter().collect::<String>()[..], "123456789");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn insert_begin()
|
||||||
|
{
|
||||||
|
let mut message: Vec<char> = "789".chars().collect();
|
||||||
|
|
||||||
|
let delta = {
|
||||||
|
let (span, span_len) = delta_span("123456".chars());
|
||||||
|
Delta {
|
||||||
|
location: 0,
|
||||||
|
kind: DeltaKind::Insert{span, span_len},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("from: {:?}", message);
|
||||||
|
println!("delta: {:?}", delta);
|
||||||
|
delta.insert(&mut message);
|
||||||
|
|
||||||
|
assert_eq!(&message.into_iter().collect::<String>()[..], "123456789");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn insert_end_f()
|
||||||
|
{
|
||||||
|
let mut message: Vec<char> = "123".chars().collect();
|
||||||
|
|
||||||
|
let delta = {
|
||||||
|
let (span, span_len) = delta_span("456789".chars());
|
||||||
|
Delta {
|
||||||
|
location: 3,
|
||||||
|
kind: DeltaKind::Insert{span, span_len},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("from: {:?}", message);
|
||||||
|
println!("delta: {:?}", delta);
|
||||||
|
delta.insert(&mut message);
|
||||||
|
|
||||||
|
assert_eq!(&message.into_iter().collect::<String>()[..], "123456789");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn insert_end_f_rev()
|
||||||
|
{
|
||||||
|
let mut message: Vec<char> = "1234567".chars().collect();
|
||||||
|
|
||||||
|
let delta = {
|
||||||
|
let (span, span_len) = delta_span("89".chars());
|
||||||
|
Delta {
|
||||||
|
location: 7,
|
||||||
|
kind: DeltaKind::Insert{span, span_len},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("from: {:?}", message);
|
||||||
|
println!("delta: {:?}", delta);
|
||||||
|
delta.insert(&mut message);
|
||||||
|
|
||||||
|
assert_eq!(&message.into_iter().collect::<String>()[..], "123456789");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
//! Local state change error
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
error,
|
||||||
|
fmt,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Local state updating errors
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Error {
|
||||||
|
BroadcastUpdate,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
impl error::Error for Error{}
|
||||||
|
impl fmt::Display for Error
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::BroadcastUpdate => write!(f, "failed to broadcast state body update"),
|
||||||
|
_ => write!(f, "unknown error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
|||||||
|
//! Mutating post body
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// A message that can be mutated by deltas.
|
||||||
|
pub type MessageSpan = Vec<char>;
|
||||||
|
|
||||||
|
/// Contains post deltas and an intermediate representation of the still-open post body.
|
||||||
|
/// Created and modified with `Kokoro` worker instances.
|
||||||
|
///
|
||||||
|
/// This should not be created by itself, instead `Kokoro` should create instances of this, so that it can retain the `watch::Sender` and other such things.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Karada
|
||||||
|
{
|
||||||
|
/// The post body so far as a vector of `char`s.
|
||||||
|
pub(super) scape: Arc<RwLock<MessageSpan>>,
|
||||||
|
/// All applied deltas so far. Last applied one is at the end.
|
||||||
|
pub(super) deltas: Arc<RwLock<Vec<Delta>>>,
|
||||||
|
|
||||||
|
/// the latest render of the whole body string. Updated whenever a delta(s) are applied atomically.
|
||||||
|
pub(super) current_body: watch::Receiver<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Karada
|
||||||
|
{
|
||||||
|
/// Clone the body string
|
||||||
|
pub fn body(&self) -> String
|
||||||
|
{
|
||||||
|
self.current_body.borrow().to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume this instance into a suspension
|
||||||
|
///
|
||||||
|
/// This will only acquire locks if needed, but since they might be needed, it must be awaited in case of `Kokoro` instances potentially owning the data.
|
||||||
|
pub async fn into_suspended(self) -> Suspension
|
||||||
|
{
|
||||||
|
let scape: String = {
|
||||||
|
let scape = self.scape;
|
||||||
|
match Arc::try_unwrap(scape) { //try to unwrap if possible, to avoid acquiring useless lock
|
||||||
|
Ok(scape) => scape.into_inner().into_iter().collect(),
|
||||||
|
Err(scape) => scape.read().await.iter().collect(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let deltas: Vec<Delta> = {
|
||||||
|
let deltas = self.deltas;
|
||||||
|
match Arc::try_unwrap(deltas) {
|
||||||
|
Ok(deltas) => deltas.into_inner(),
|
||||||
|
Err(deltas) => deltas.read().await.clone(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Suspension{scape,deltas}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_suspended(susp: Suspension, current_body: watch::Receiver<String>) -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
scape: Arc::new(RwLock::new(susp.scape.chars().collect())),
|
||||||
|
deltas: Arc::new(RwLock::new(susp.deltas)),
|
||||||
|
current_body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Suspension of [`Karada`](Karada).
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct Suspension
|
||||||
|
{
|
||||||
|
pub(super) scape: String,
|
||||||
|
pub(super) deltas: Vec<Delta>,
|
||||||
|
}
|
||||||
|
|
||||||
|
use suspend::{
|
||||||
|
Suspendable,
|
||||||
|
SuspendStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Suspendable for Suspension
|
||||||
|
{
|
||||||
|
async fn suspend<S: SuspendStream +Send+Sync + ?Sized>(self, into: &mut S) -> Result<(), suspend::Error>
|
||||||
|
{
|
||||||
|
let mut output = suspend::Object::new();
|
||||||
|
output.insert("post-dynamic", self);
|
||||||
|
into.set_object(output).await
|
||||||
|
}
|
||||||
|
async fn load<S: SuspendStream +Send+Sync+ ?Sized>(from: &mut S) -> Result<Self, suspend::Error>
|
||||||
|
{
|
||||||
|
let mut input = from.get_object().await?.ok_or(suspend::Error::BadObject)?;
|
||||||
|
input.try_get("post-dynamic").ok_or(suspend::Error::BadObject)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
//! Handles updating posts
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
sync::{
|
||||||
|
RwLock,
|
||||||
|
watch,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod karada;
|
||||||
|
pub use karada::*;
|
||||||
|
|
||||||
|
mod delta;
|
||||||
|
pub use delta::*;
|
||||||
|
|
||||||
|
mod work;
|
||||||
|
pub use work::*;
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
|
||||||
|
pub use error::Error as LocalError;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Worker
|
||||||
|
{
|
||||||
|
Attached(tokio::task::JoinHandle<()>),
|
||||||
|
/// Worker has not been attached yet
|
||||||
|
Suspended(Kokoro),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An open, as yet unfinied post
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Imouto
|
||||||
|
{
|
||||||
|
id: identity::PostID,
|
||||||
|
|
||||||
|
user: identity::User,
|
||||||
|
|
||||||
|
title: Option<String>,
|
||||||
|
|
||||||
|
karada: Karada,
|
||||||
|
worker: Worker,
|
||||||
|
|
||||||
|
timestamp: post::PostTimestamp, //Note that `closed` should always be `None` in `Imouto`. We use this for post lifetimes and such
|
||||||
|
}
|
||||||
|
|
||||||
|
use suspend::{
|
||||||
|
Suspendable,
|
||||||
|
SuspendStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Suspendable for Imouto
|
||||||
|
{
|
||||||
|
async fn suspend<S: SuspendStream +Send+Sync + ?Sized>(self, into: &mut S) -> Result<(), suspend::Error>
|
||||||
|
{
|
||||||
|
let mut output = suspend::Object::new();
|
||||||
|
output.insert("id", self.id);
|
||||||
|
output.insert("user", self.user);
|
||||||
|
output.insert("title", self.title);
|
||||||
|
output.insert("body", match self.worker {
|
||||||
|
Worker::Suspended(kokoro) => kokoro.into_suspended().await, // consume worker if possible
|
||||||
|
Worker::Attached(_) => self.karada.into_suspended().await, // consume body instead
|
||||||
|
});
|
||||||
|
//output.insert("hash", self.hash);
|
||||||
|
output.insert("timestamp", self.timestamp);
|
||||||
|
into.set_object(output).await
|
||||||
|
}
|
||||||
|
async fn load<S: SuspendStream +Send+Sync+ ?Sized>(from: &mut S) -> Result<Self, suspend::Error>
|
||||||
|
{
|
||||||
|
let mut input = from.get_object().await?.ok_or(suspend::Error::BadObject)?;
|
||||||
|
macro_rules! get {
|
||||||
|
($name:literal) => {
|
||||||
|
input.try_get($name).ok_or_else(|| suspend::Error::MissingObject(std::borrow::Cow::Borrowed($name)))?
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let id = get!("id");
|
||||||
|
let user = get!("user");
|
||||||
|
let title = get!("title");
|
||||||
|
let body = get!("body");
|
||||||
|
//let hash = get!("hash");
|
||||||
|
let timestamp = get!("timestamp");
|
||||||
|
|
||||||
|
let (karada, worker) = {
|
||||||
|
let mut kokoro = Kokoro::from_suspended(body);
|
||||||
|
(kokoro.spawn().unwrap(), Worker::Suspended(kokoro))
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
id,
|
||||||
|
user,
|
||||||
|
title,
|
||||||
|
karada,
|
||||||
|
worker,
|
||||||
|
//hash,
|
||||||
|
timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests
|
||||||
|
{
|
||||||
|
use super::*;
|
||||||
|
#[tokio::test]
|
||||||
|
async fn basic_ser()
|
||||||
|
{
|
||||||
|
let mut output = suspend::MemorySuspendStream::new();
|
||||||
|
let mut lolis = std::iter::repeat_with(|| {
|
||||||
|
let (karada, kokoro) = {
|
||||||
|
let mut kokoro = Kokoro::new();
|
||||||
|
(kokoro.spawn().unwrap(), kokoro)
|
||||||
|
};
|
||||||
|
Imouto {
|
||||||
|
id: identity::PostID::new(),
|
||||||
|
user: Default::default(),
|
||||||
|
title: None,
|
||||||
|
|
||||||
|
karada,
|
||||||
|
worker: Worker::Suspended(kokoro),
|
||||||
|
|
||||||
|
timestamp: post::PostTimestamp::new()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let imouto = lolis.next().unwrap();
|
||||||
|
imouto.suspend(&mut output).await.expect("Suspension failed");
|
||||||
|
|
||||||
|
let imouto2 = lolis.next().unwrap();
|
||||||
|
|
||||||
|
let imouto3 = Imouto::load(&mut output).await.expect("Load failed");
|
||||||
|
|
||||||
|
assert_eq!(imouto2.karada.body(), imouto3.karada.body());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
//! Worker that mutates `Kokoro`.
|
||||||
|
use super::*;
|
||||||
|
use tokio::{
|
||||||
|
sync::{
|
||||||
|
watch,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Handles working on `Karada` instances.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Kokoro
|
||||||
|
{
|
||||||
|
scape: Arc<RwLock<MessageSpan>>,
|
||||||
|
deltas: Arc<RwLock<Vec<Delta>>>,
|
||||||
|
update_body: watch::Sender<String>,
|
||||||
|
|
||||||
|
body_recv: Option<watch::Receiver<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Kokoro
|
||||||
|
{
|
||||||
|
/// Create a new instance. This instance can spawn a `Karada` instance.
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
let (tx, rx) = watch::channel(String::new());
|
||||||
|
Self {
|
||||||
|
scape: Arc::new(RwLock::new(MessageSpan::new())),
|
||||||
|
deltas: Arc::new(RwLock::new(Vec::new())),
|
||||||
|
|
||||||
|
update_body: tx,
|
||||||
|
body_recv: Some(rx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new worker instance from a suspension of `Karada`.
|
||||||
|
pub fn from_suspended(susp: Suspension) -> Self
|
||||||
|
{
|
||||||
|
let span = susp.scape.chars().collect();
|
||||||
|
let (tx, rx) = watch::channel(susp.scape);
|
||||||
|
Self {
|
||||||
|
scape: Arc::new(RwLock::new(span)),
|
||||||
|
deltas: Arc::new(RwLock::new(susp.deltas)),
|
||||||
|
|
||||||
|
update_body: tx,
|
||||||
|
body_recv: Some(rx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume this instance into a suspension
|
||||||
|
///
|
||||||
|
/// This will only acquire locks if needed, but since they might be needed, it must be awaited in case of `Kokoro` instances potentially owning the data.
|
||||||
|
pub async fn into_suspended(self) -> Suspension
|
||||||
|
{
|
||||||
|
|
||||||
|
let scape: String = {
|
||||||
|
let scape = self.scape;
|
||||||
|
match Arc::try_unwrap(scape) { //try to unwrap if possible, to avoid acquiring useless lock
|
||||||
|
Ok(scape) => scape.into_inner().into_iter().collect(),
|
||||||
|
Err(scape) => scape.read().await.iter().collect(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let deltas: Vec<Delta> = {
|
||||||
|
let deltas = self.deltas;
|
||||||
|
match Arc::try_unwrap(deltas) {
|
||||||
|
Ok(deltas) => deltas.into_inner(),
|
||||||
|
Err(deltas) => deltas.read().await.clone(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Suspension{scape,deltas}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawn one `Karada` instance. If this has already been called, it returns `None`.
|
||||||
|
pub fn spawn(&mut self) -> Option<Karada>
|
||||||
|
{
|
||||||
|
self.body_recv.take()
|
||||||
|
.map(|current_body| {
|
||||||
|
Karada {
|
||||||
|
scape: Arc::clone(&self.scape),
|
||||||
|
deltas: Arc::clone(&self.deltas),
|
||||||
|
|
||||||
|
current_body,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawn a clone `Karada` into a new instance. This function can be called many times to yield `Karada` instances that are all identical and controlled by this instance.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If `spawn` was previously called.
|
||||||
|
pub fn spawn_clone(&self) -> Karada
|
||||||
|
{
|
||||||
|
Karada {
|
||||||
|
scape: Arc::clone(&self.scape),
|
||||||
|
deltas: Arc::clone(&self.deltas),
|
||||||
|
current_body: self.body_recv.as_ref().unwrap().clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply a delta to this instance.
|
||||||
|
pub async fn apply(&mut self, delta: Delta) -> Result<(), error::Error>
|
||||||
|
{
|
||||||
|
let (mut scape, mut deltas) = tokio::join!{
|
||||||
|
self.scape.write(),
|
||||||
|
self.deltas.write(),
|
||||||
|
};
|
||||||
|
// Only start mutating now that both locks are writable. Is this needed, or can we do the mutation concurrently?
|
||||||
|
delta.insert(scape.as_mut());
|
||||||
|
deltas.push(delta);
|
||||||
|
|
||||||
|
self.update_body.broadcast(scape.iter().collect()).map_err(|_| error::Error::BroadcastUpdate)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue