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