mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-29 00:06:09 +08:00
feat(y-octo): import y-octo monorepo (#11750)
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
use std::io::{Error, Write};
|
||||
|
||||
use nom::bytes::complete::take;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn read_var_buffer(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let (tail, len) = read_var_u64(input)?;
|
||||
let (tail, val) = take(len as usize)(tail)?;
|
||||
Ok((tail, val))
|
||||
}
|
||||
|
||||
pub fn write_var_buffer<W: Write>(buffer: &mut W, data: &[u8]) -> Result<(), Error> {
|
||||
write_var_u64(buffer, data.len() as u64)?;
|
||||
buffer.write_all(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nom::{
|
||||
error::{Error, ErrorKind},
|
||||
AsBytes, Err,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_read_var_buffer() {
|
||||
// Test case 1: valid input, buffer length = 5
|
||||
let input = [0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
|
||||
let expected_output = [0x01, 0x02, 0x03, 0x04, 0x05];
|
||||
let result = read_var_buffer(&input);
|
||||
assert_eq!(result, Ok((&[][..], &expected_output[..])));
|
||||
|
||||
// Test case 2: truncated input, missing buffer
|
||||
let input = [0x05, 0x01, 0x02, 0x03];
|
||||
let result = read_var_buffer(&input);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(Err::Error(Error::new(&input[1..], ErrorKind::Eof)))
|
||||
);
|
||||
|
||||
// Test case 3: invalid input
|
||||
let input = [0xFF, 0x01, 0x02, 0x03];
|
||||
let result = read_var_buffer(&input);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(Err::Error(Error::new(&input[2..], ErrorKind::Eof)))
|
||||
);
|
||||
|
||||
// Test case 4: invalid var int encoding
|
||||
let input = [0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01];
|
||||
let result = read_var_buffer(&input);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(Err::Error(Error::new(&input[7..], ErrorKind::Eof)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_var_buf_codec() {
|
||||
test_var_buf_enc_dec(&[]);
|
||||
test_var_buf_enc_dec(&[0x01, 0x02, 0x03, 0x04, 0x05]);
|
||||
test_var_buf_enc_dec(b"test_var_buf_enc_dec");
|
||||
|
||||
#[cfg(not(miri))]
|
||||
{
|
||||
use rand::{rng, Rng};
|
||||
let mut rng = rng();
|
||||
for _ in 0..100 {
|
||||
test_var_buf_enc_dec(&{
|
||||
let mut bytes = vec![0u8; rng.random_range(0..u16::MAX as usize)];
|
||||
rng.fill(&mut bytes[..]);
|
||||
bytes
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_var_buf_enc_dec(data: &[u8]) {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
write_var_buffer(&mut buf, data).unwrap();
|
||||
let result = read_var_buffer(buf.as_bytes());
|
||||
assert_eq!(result, Ok((&[][..], data)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
use std::io::{Error, Write};
|
||||
|
||||
use byteorder::WriteBytesExt;
|
||||
use nom::Needed;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn read_var_u64(input: &[u8]) -> IResult<&[u8], u64> {
|
||||
// parse the first byte
|
||||
if let Some(next_byte) = input.first() {
|
||||
let mut shift = 7;
|
||||
let mut curr_byte = *next_byte;
|
||||
let mut rest = &input[1..];
|
||||
|
||||
// same logic in loop, but enable early exit when dealing with small numbers
|
||||
let mut num = (curr_byte & 0b0111_1111) as u64;
|
||||
|
||||
// if the sign bit is set, we need more bits
|
||||
while (curr_byte >> 7) & 0b1 != 0 {
|
||||
if let Some(next_byte) = rest.first() {
|
||||
curr_byte = *next_byte;
|
||||
// add the remaining 7 bits to the number
|
||||
num |= ((curr_byte & 0b0111_1111) as u64).wrapping_shl(shift);
|
||||
shift += 7;
|
||||
rest = &rest[1..];
|
||||
} else {
|
||||
return Err(nom::Err::Incomplete(Needed::new(input.len() + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok((rest, num))
|
||||
} else {
|
||||
Err(nom::Err::Incomplete(Needed::new(1)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_var_u64<W: Write>(buffer: &mut W, mut num: u64) -> Result<(), Error> {
|
||||
// bit or 0b1000_0000 pre 7 bit if has more bits
|
||||
while num >= 0b10000000 {
|
||||
buffer.write_u8(num as u8 & 0b0111_1111 | 0b10000000)?;
|
||||
num >>= 7;
|
||||
}
|
||||
|
||||
buffer.write_u8((num & 0b01111111) as u8)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_var_i32(input: &[u8]) -> IResult<&[u8], i32> {
|
||||
// parse the first byte
|
||||
if let Some(next_byte) = input.first() {
|
||||
let mut shift = 6;
|
||||
let mut curr_byte = *next_byte;
|
||||
let mut rest: &[u8] = &input[1..];
|
||||
|
||||
// get the sign bit and the first 6 bits of the number
|
||||
let sign_bit = (curr_byte >> 6) & 0b1;
|
||||
let mut num = (curr_byte & 0b0011_1111) as i64;
|
||||
|
||||
// if the sign bit is set, we need more bits
|
||||
while (curr_byte >> 7) & 0b1 != 0 {
|
||||
if let Some(next_byte) = rest.first() {
|
||||
curr_byte = *next_byte;
|
||||
// add the remaining 7 bits to the number
|
||||
num |= ((curr_byte & 0b0111_1111) as i64).wrapping_shl(shift);
|
||||
shift += 7;
|
||||
rest = &rest[1..];
|
||||
} else {
|
||||
return Err(nom::Err::Incomplete(Needed::new(input.len() + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
// negate the number if the sign bit is set
|
||||
if sign_bit == 1 {
|
||||
num = -num;
|
||||
}
|
||||
|
||||
Ok((rest, num as i32))
|
||||
} else {
|
||||
Err(nom::Err::Incomplete(Needed::new(1)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_var_i32<W: Write>(buffer: &mut W, num: i32) -> Result<(), Error> {
|
||||
let mut num = num as i64;
|
||||
let is_negative = num < 0;
|
||||
if is_negative {
|
||||
num = -num;
|
||||
}
|
||||
|
||||
buffer.write_u8(
|
||||
// bit or 0b1000_0000 if has more bits
|
||||
if num > 0b00111111 { 0b10000000 } else { 0 }
|
||||
// bit or 0b0100_0000 if negative
|
||||
| if is_negative { 0b0100_0000 } else { 0 }
|
||||
// store last 6 bits
|
||||
| num as u8 & 0b0011_1111,
|
||||
)?;
|
||||
num >>= 6;
|
||||
while num > 0 {
|
||||
buffer.write_u8(
|
||||
// bit or 0b1000_0000 pre 7 bit if has more bits
|
||||
if num > 0b01111111 { 0b10000000 } else { 0 }
|
||||
// store last 7 bits
|
||||
| num as u8 & 0b0111_1111,
|
||||
)?;
|
||||
num >>= 7;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
fn test_var_uint_enc_dec(num: u64) {
|
||||
let mut buf = Vec::new();
|
||||
write_var_u64(&mut buf, num).unwrap();
|
||||
|
||||
let (rest, decoded_num) = read_var_u64(&buf).unwrap();
|
||||
assert_eq!(num, decoded_num);
|
||||
assert_eq!(rest.len(), 0);
|
||||
}
|
||||
|
||||
fn test_var_int_enc_dec(num: i32) {
|
||||
{
|
||||
let mut buf = Vec::new();
|
||||
write_var_i32(&mut buf, num).unwrap();
|
||||
|
||||
let (rest, decoded_num) = read_var_i32(&buf).unwrap();
|
||||
assert_eq!(num, decoded_num);
|
||||
assert_eq!(rest.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_var_uint_codec() {
|
||||
test_var_uint_enc_dec(0);
|
||||
test_var_uint_enc_dec(1);
|
||||
test_var_uint_enc_dec(127);
|
||||
test_var_uint_enc_dec(0b1000_0000);
|
||||
test_var_uint_enc_dec(0b1_0000_0000);
|
||||
test_var_uint_enc_dec(0b1_1111_1111);
|
||||
test_var_uint_enc_dec(0b10_0000_0000);
|
||||
test_var_uint_enc_dec(0b11_1111_1111);
|
||||
test_var_uint_enc_dec(0x7fff_ffff_ffff_ffff);
|
||||
test_var_uint_enc_dec(u64::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_var_int() {
|
||||
test_var_int_enc_dec(0);
|
||||
test_var_int_enc_dec(1);
|
||||
test_var_int_enc_dec(-1);
|
||||
test_var_int_enc_dec(63);
|
||||
test_var_int_enc_dec(-63);
|
||||
test_var_int_enc_dec(64);
|
||||
test_var_int_enc_dec(-64);
|
||||
test_var_int_enc_dec(i32::MAX);
|
||||
test_var_int_enc_dec(i32::MIN);
|
||||
test_var_int_enc_dec(((1 << 20) - 1) * 8);
|
||||
test_var_int_enc_dec(-((1 << 20) - 1) * 8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
mod buffer;
|
||||
mod integer;
|
||||
mod string;
|
||||
|
||||
pub use buffer::{read_var_buffer, write_var_buffer};
|
||||
pub use integer::{read_var_i32, read_var_u64, write_var_i32, write_var_u64};
|
||||
pub use string::{read_var_string, write_var_string};
|
||||
|
||||
use super::*;
|
||||
@@ -0,0 +1,90 @@
|
||||
use std::io::{Error, Write};
|
||||
|
||||
use nom::{combinator::map_res, Parser};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn read_var_string(input: &[u8]) -> IResult<&[u8], String> {
|
||||
map_res(read_var_buffer, |s| String::from_utf8(s.to_vec())).parse(input)
|
||||
}
|
||||
|
||||
pub fn write_var_string<W: Write, S: AsRef<str>>(buffer: &mut W, input: S) -> Result<(), Error> {
|
||||
let bytes = input.as_ref().as_bytes();
|
||||
write_var_buffer(buffer, bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nom::{
|
||||
error::{Error, ErrorKind},
|
||||
AsBytes, Err,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_read_var_string() {
|
||||
// Test case 1: valid input, string length = 5
|
||||
let input = [0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F];
|
||||
let expected_output = "hello".to_string();
|
||||
let result = read_var_string(&input);
|
||||
assert_eq!(result, Ok((&[][..], expected_output)));
|
||||
|
||||
// Test case 2: missing string length
|
||||
let input = [0x68, 0x65, 0x6C, 0x6C, 0x6F];
|
||||
let result = read_var_string(&input);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(Err::Error(Error::new(&input[1..], ErrorKind::Eof)))
|
||||
);
|
||||
|
||||
// Test case 3: truncated input
|
||||
let input = [0x05, 0x68, 0x65, 0x6C, 0x6C];
|
||||
let result = read_var_string(&input);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(Err::Error(Error::new(&input[1..], ErrorKind::Eof)))
|
||||
);
|
||||
|
||||
// Test case 4: invalid input
|
||||
let input = [0xFF, 0x01, 0x02, 0x03, 0x04];
|
||||
let result = read_var_string(&input);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(Err::Error(Error::new(&input[2..], ErrorKind::Eof)))
|
||||
);
|
||||
|
||||
// Test case 5: invalid var int encoding
|
||||
let input = [0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01];
|
||||
let result = read_var_string(&input);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(Err::Error(Error::new(&input[7..], ErrorKind::Eof)))
|
||||
);
|
||||
|
||||
// Test case 6: invalid input, invalid UTF-8 encoding
|
||||
let input = [0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
|
||||
let result = read_var_string(&input);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(Err::Error(Error::new(&input[..], ErrorKind::MapRes)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_var_str_codec() {
|
||||
test_var_str_enc_dec("".to_string());
|
||||
test_var_str_enc_dec(" ".to_string());
|
||||
test_var_str_enc_dec("abcde".to_string());
|
||||
test_var_str_enc_dec("🃒🃓🃟☗🀥🀫∺∼≂≇⓵➎⓷➏".to_string());
|
||||
}
|
||||
|
||||
fn test_var_str_enc_dec(input: String) {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
write_var_string(&mut buf, input.clone()).unwrap();
|
||||
let (rest, decoded_str) = read_var_string(buf.as_bytes()).unwrap();
|
||||
assert_eq!(decoded_str, input);
|
||||
assert_eq!(rest.len(), 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
use std::{cmp::max, collections::hash_map::Entry};
|
||||
|
||||
use super::*;
|
||||
use crate::sync::Arc;
|
||||
|
||||
pub type AwarenessCallback = Arc<dyn Fn(&Awareness, AwarenessEvent) + Send + Sync + 'static>;
|
||||
|
||||
pub struct Awareness {
|
||||
awareness: AwarenessStates,
|
||||
callback: Option<AwarenessCallback>,
|
||||
local_id: u64,
|
||||
}
|
||||
|
||||
impl Awareness {
|
||||
pub fn new(local_id: u64) -> Self {
|
||||
Self {
|
||||
awareness: AwarenessStates::new(),
|
||||
callback: None,
|
||||
local_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_update(&mut self, f: impl Fn(&Awareness, AwarenessEvent) + Send + Sync + 'static) {
|
||||
self.callback = Some(Arc::new(f));
|
||||
}
|
||||
|
||||
pub fn get_states(&self) -> &AwarenessStates {
|
||||
&self.awareness
|
||||
}
|
||||
|
||||
pub fn get_local_state(&self) -> Option<String> {
|
||||
self
|
||||
.awareness
|
||||
.get(&self.local_id)
|
||||
.map(|state| state.content.clone())
|
||||
}
|
||||
|
||||
fn mut_local_state(&mut self) -> &mut AwarenessState {
|
||||
self.awareness.entry(self.local_id).or_default()
|
||||
}
|
||||
|
||||
pub fn set_local_state(&mut self, content: String) {
|
||||
self.mut_local_state().set_content(content);
|
||||
if let Some(cb) = self.callback.as_ref() {
|
||||
cb(
|
||||
self,
|
||||
AwarenessEventBuilder::new().update(self.local_id).build(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_local_state(&mut self) {
|
||||
self.mut_local_state().delete();
|
||||
if let Some(cb) = self.callback.as_ref() {
|
||||
cb(
|
||||
self,
|
||||
AwarenessEventBuilder::new().remove(self.local_id).build(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_update(&mut self, update: AwarenessStates) {
|
||||
let mut event = AwarenessEventBuilder::new();
|
||||
|
||||
for (client_id, state) in update {
|
||||
match self.awareness.entry(client_id) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
let prev_state = entry.get_mut();
|
||||
if client_id == self.local_id {
|
||||
// ignore remote update about local client and
|
||||
// add clock to overwrite remote data
|
||||
prev_state.set_clock(max(prev_state.clock, state.clock) + 1);
|
||||
event.update(client_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if prev_state.clock < state.clock {
|
||||
if state.is_deleted() {
|
||||
prev_state.delete();
|
||||
event.remove(client_id);
|
||||
} else {
|
||||
*prev_state = state;
|
||||
event.update(client_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(state);
|
||||
event.add(client_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cb) = self.callback.as_ref() {
|
||||
cb(self, event.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AwarenessEvent {
|
||||
added: Vec<u64>,
|
||||
updated: Vec<u64>,
|
||||
removed: Vec<u64>,
|
||||
}
|
||||
|
||||
impl AwarenessEvent {
|
||||
pub fn get_updated(&self, states: &AwarenessStates) -> AwarenessStates {
|
||||
states
|
||||
.iter()
|
||||
.filter(|(id, _)| {
|
||||
self.added.contains(id) || self.updated.contains(id) || self.removed.contains(id)
|
||||
})
|
||||
.map(|(id, state)| (*id, state.clone()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
struct AwarenessEventBuilder {
|
||||
added: Vec<u64>,
|
||||
updated: Vec<u64>,
|
||||
removed: Vec<u64>,
|
||||
}
|
||||
|
||||
impl AwarenessEventBuilder {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
added: Vec::new(),
|
||||
updated: Vec::new(),
|
||||
removed: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add(&mut self, client_id: u64) -> &mut Self {
|
||||
self.added.push(client_id);
|
||||
self
|
||||
}
|
||||
|
||||
fn update(&mut self, client_id: u64) -> &mut Self {
|
||||
self.updated.push(client_id);
|
||||
self
|
||||
}
|
||||
|
||||
fn remove(&mut self, client_id: u64) -> &mut Self {
|
||||
self.removed.push(client_id);
|
||||
self
|
||||
}
|
||||
|
||||
fn build(&mut self) -> AwarenessEvent {
|
||||
AwarenessEvent {
|
||||
added: self.added.clone(),
|
||||
updated: self.updated.clone(),
|
||||
removed: self.removed.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::sync::{Mutex, MutexGuard};
|
||||
|
||||
#[test]
|
||||
fn test_awareness() {
|
||||
loom_model!({
|
||||
let mut awareness = Awareness::new(0);
|
||||
|
||||
{
|
||||
// init state
|
||||
assert_eq!(awareness.local_id, 0);
|
||||
assert_eq!(awareness.awareness.len(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
// local state
|
||||
awareness.set_local_state("test".to_string());
|
||||
assert_eq!(awareness.get_local_state(), Some("test".to_string()));
|
||||
awareness.clear_local_state();
|
||||
assert_eq!(awareness.get_local_state(), Some("null".to_string()));
|
||||
}
|
||||
|
||||
{
|
||||
// apply remote update
|
||||
let mut states = AwarenessStates::new();
|
||||
states.insert(0, AwarenessState::new(2, "test0".to_string()));
|
||||
states.insert(1, AwarenessState::new(2, "test1".to_string()));
|
||||
awareness.apply_update(states);
|
||||
assert!(awareness.get_states().contains_key(&1));
|
||||
|
||||
// local state will not apply
|
||||
assert_eq!(
|
||||
awareness.get_states().get(&0).unwrap().content,
|
||||
"null".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
awareness.get_states().get(&1).unwrap().content,
|
||||
"test1".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// callback
|
||||
let values: Arc<Mutex<Vec<AwarenessEvent>>> = Arc::new(Mutex::new(Vec::new()));
|
||||
let callback_values = Arc::clone(&values);
|
||||
awareness.on_update(move |_, event| {
|
||||
let mut values = callback_values.lock().unwrap();
|
||||
values.push(event);
|
||||
});
|
||||
|
||||
let mut new_states = AwarenessStates::new();
|
||||
// exists in local awareness: update
|
||||
new_states.insert(1, AwarenessState::new(3, "test update".to_string()));
|
||||
// not exists in local awareness: add
|
||||
new_states.insert(2, AwarenessState::new(1, "test update".to_string()));
|
||||
// not exists in local awareness: add
|
||||
new_states.insert(3, AwarenessState::new(1, "null".to_string()));
|
||||
// not exists in local awareness: add
|
||||
new_states.insert(4, AwarenessState::new(1, "test update".to_string()));
|
||||
awareness.apply_update(new_states);
|
||||
|
||||
let mut new_states = AwarenessStates::new();
|
||||
// exists in local awareness: delete
|
||||
new_states.insert(4, AwarenessState::new(2, "null".to_string()));
|
||||
awareness.apply_update(new_states);
|
||||
|
||||
awareness.set_local_state("test".to_string());
|
||||
awareness.clear_local_state();
|
||||
|
||||
let values: MutexGuard<Vec<AwarenessEvent>> = values.lock().unwrap();
|
||||
assert_eq!(values.len(), 4);
|
||||
let event = values.first().unwrap();
|
||||
|
||||
let mut added = event.added.clone();
|
||||
added.sort();
|
||||
assert_eq!(added, [2, 3, 4]);
|
||||
assert_eq!(event.updated, [1]);
|
||||
|
||||
assert_eq!(
|
||||
event.get_updated(awareness.get_states()).get(&1).unwrap(),
|
||||
&AwarenessState::new(3, "test update".to_string())
|
||||
);
|
||||
|
||||
let event = values.get(1).unwrap();
|
||||
assert_eq!(event.removed, [4]);
|
||||
|
||||
let event = values.get(2).unwrap();
|
||||
assert_eq!(event.updated, [0]);
|
||||
|
||||
let event = values.get(3).unwrap();
|
||||
assert_eq!(event.removed, [0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,716 @@
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
ops::RangeInclusive,
|
||||
};
|
||||
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use super::*;
|
||||
|
||||
const MAX_JS_INT: i64 = 0x001F_FFFF_FFFF_FFFF;
|
||||
// The smallest int in js number.
|
||||
const MIN_JS_INT: i64 = -MAX_JS_INT;
|
||||
pub const JS_INT_RANGE: RangeInclusive<i64> = MIN_JS_INT..=MAX_JS_INT;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
|
||||
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
|
||||
pub enum Any {
|
||||
Undefined,
|
||||
Null,
|
||||
Integer(i32),
|
||||
Float32(OrderedFloat<f32>),
|
||||
Float64(OrderedFloat<f64>),
|
||||
BigInt64(i64),
|
||||
False,
|
||||
True,
|
||||
String(String),
|
||||
// FIXME: due to macro's overflow evaluating, we can't use proptest here
|
||||
#[cfg_attr(test, proptest(skip))]
|
||||
Object(HashMap<String, Any>),
|
||||
#[cfg_attr(test, proptest(skip))]
|
||||
Array(Vec<Any>),
|
||||
Binary(Vec<u8>),
|
||||
}
|
||||
|
||||
impl<R: CrdtReader> CrdtRead<R> for Any {
|
||||
fn read(reader: &mut R) -> JwstCodecResult<Self> {
|
||||
let index = reader.read_u8()?;
|
||||
match 127u8.overflowing_sub(index).0 {
|
||||
0 => Ok(Any::Undefined),
|
||||
1 => Ok(Any::Null),
|
||||
// in yjs implementation, flag 2 only save 32bit integer
|
||||
2 => Ok(Any::Integer(reader.read_var_i32()?)), // Integer
|
||||
3 => Ok(Any::Float32(reader.read_f32_be()?.into())), // Float32
|
||||
4 => Ok(Any::Float64(reader.read_f64_be()?.into())), // Float64
|
||||
5 => Ok(Any::BigInt64(reader.read_i64_be()?)), // BigInt64
|
||||
6 => Ok(Any::False),
|
||||
7 => Ok(Any::True),
|
||||
8 => Ok(Any::String(reader.read_var_string()?)), // String
|
||||
9 => {
|
||||
let len = reader.read_var_u64()?;
|
||||
let object = (0..len)
|
||||
.map(|_| Self::read_key_value(reader))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Any::Object(object.into_iter().collect()))
|
||||
} // Object
|
||||
10 => {
|
||||
let len = reader.read_var_u64()?;
|
||||
let any = (0..len)
|
||||
.map(|_| Self::read(reader))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Any::Array(any))
|
||||
} // Array
|
||||
11 => {
|
||||
let binary = reader.read_var_buffer()?;
|
||||
Ok(Any::Binary(binary.to_vec()))
|
||||
} // Binary
|
||||
_ => Ok(Any::Undefined),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: CrdtWriter> CrdtWrite<W> for Any {
|
||||
fn write(&self, writer: &mut W) -> JwstCodecResult {
|
||||
match self {
|
||||
Any::Undefined => writer.write_u8(127)?,
|
||||
Any::Null => writer.write_u8(127 - 1)?,
|
||||
Any::Integer(value) => {
|
||||
writer.write_u8(127 - 2)?;
|
||||
writer.write_var_i32(*value)?;
|
||||
}
|
||||
Any::Float32(value) => {
|
||||
writer.write_u8(127 - 3)?;
|
||||
writer.write_f32_be(value.into_inner())?;
|
||||
}
|
||||
Any::Float64(value) => {
|
||||
writer.write_u8(127 - 4)?;
|
||||
writer.write_f64_be(value.into_inner())?;
|
||||
}
|
||||
Any::BigInt64(value) => {
|
||||
writer.write_u8(127 - 5)?;
|
||||
writer.write_i64_be(*value)?;
|
||||
}
|
||||
Any::False => writer.write_u8(127 - 6)?,
|
||||
Any::True => writer.write_u8(127 - 7)?,
|
||||
Any::String(value) => {
|
||||
writer.write_u8(127 - 8)?;
|
||||
writer.write_var_string(value)?;
|
||||
}
|
||||
Any::Object(value) => {
|
||||
writer.write_u8(127 - 9)?;
|
||||
writer.write_var_u64(value.len() as u64)?;
|
||||
for (key, value) in value {
|
||||
Self::write_key_value(writer, key, value)?;
|
||||
}
|
||||
}
|
||||
Any::Array(values) => {
|
||||
writer.write_u8(127 - 10)?;
|
||||
writer.write_var_u64(values.len() as u64)?;
|
||||
for value in values {
|
||||
value.write(writer)?;
|
||||
}
|
||||
}
|
||||
Any::Binary(value) => {
|
||||
writer.write_u8(127 - 11)?;
|
||||
writer.write_var_buffer(value)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Any {
|
||||
fn read_key_value<R: CrdtReader>(reader: &mut R) -> JwstCodecResult<(String, Any)> {
|
||||
let key = reader.read_var_string()?;
|
||||
let value = Self::read(reader)?;
|
||||
|
||||
Ok((key, value))
|
||||
}
|
||||
|
||||
fn write_key_value<W: CrdtWriter>(writer: &mut W, key: &str, value: &Any) -> JwstCodecResult {
|
||||
writer.write_var_string(key)?;
|
||||
value.write(writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn read_multiple<R: CrdtReader>(reader: &mut R) -> JwstCodecResult<Vec<Any>> {
|
||||
let len = reader.read_var_u64()? as usize;
|
||||
let mut vec = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
vec.push(Any::read(reader)?);
|
||||
}
|
||||
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
pub(crate) fn write_multiple<W: CrdtWriter>(writer: &mut W, any: &[Any]) -> JwstCodecResult {
|
||||
writer.write_var_u64(any.len() as u64)?;
|
||||
for value in any {
|
||||
value.write(writer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_primitive_from {
|
||||
(unsigned, $($ty: ty),*) => {
|
||||
$(
|
||||
impl From<$ty> for Any {
|
||||
fn from(value: $ty) -> Self {
|
||||
// INFO: i64::MAX > value > u64::MAX will cut down
|
||||
// yjs binary does not consider the case that the int size exceeds i64
|
||||
let int: i64 = value as i64;
|
||||
// handle the behavior same as yjs
|
||||
if JS_INT_RANGE.contains(&int) {
|
||||
if int <= i32::MAX as i64 {
|
||||
Self::Integer(int as i32)
|
||||
} else if int as f32 as i64 == int {
|
||||
Self::Float32((int as f32).into())
|
||||
} else {
|
||||
Self::Float64((int as f64).into())
|
||||
}
|
||||
} else {
|
||||
Self::BigInt64(int)
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
(signed, $($ty: ty),*) => {
|
||||
$(
|
||||
impl From<$ty> for Any {
|
||||
fn from(value: $ty) -> Self {
|
||||
let int: i64 = value.into();
|
||||
// handle the behavior same as yjs
|
||||
if JS_INT_RANGE.contains(&int) {
|
||||
if int <= i32::MAX as i64 {
|
||||
Self::Integer(int as i32)
|
||||
} else if int as f32 as i64 == int {
|
||||
Self::Float32((int as f32).into())
|
||||
} else {
|
||||
Self::Float64((int as f64).into())
|
||||
}
|
||||
} else {
|
||||
Self::BigInt64(int)
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
(string, $($ty: ty),*) => {
|
||||
$(
|
||||
impl From<$ty> for Any {
|
||||
fn from(value: $ty) -> Self {
|
||||
Self::String(value.into())
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_primitive_from!(unsigned, u8, u16, u32, u64);
|
||||
impl_primitive_from!(signed, i8, i16, i32, i64);
|
||||
impl_primitive_from!(string, String, &str);
|
||||
|
||||
impl From<usize> for Any {
|
||||
fn from(value: usize) -> Self {
|
||||
(value as u64).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<isize> for Any {
|
||||
fn from(value: isize) -> Self {
|
||||
(value as i64).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Any {
|
||||
fn from(value: f32) -> Self {
|
||||
Self::Float32(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Any {
|
||||
fn from(value: f64) -> Self {
|
||||
if value.trunc() == value {
|
||||
(value as i64).into()
|
||||
} else if value as f32 as f64 == value {
|
||||
Self::Float32((value as f32).into())
|
||||
} else {
|
||||
Self::Float64(value.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Any {
|
||||
fn from(value: bool) -> Self {
|
||||
if value {
|
||||
Self::True
|
||||
} else {
|
||||
Self::False
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Any> for String {
|
||||
type Error = JwstCodecError;
|
||||
|
||||
fn try_from(value: Any) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Any::String(s) => Ok(s),
|
||||
_ => Err(JwstCodecError::UnexpectedType("String")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Any> for HashMap<String, Any> {
|
||||
type Error = JwstCodecError;
|
||||
|
||||
fn try_from(value: Any) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Any::Object(map) => Ok(map),
|
||||
_ => Err(JwstCodecError::UnexpectedType("Object")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Any> for Vec<Any> {
|
||||
type Error = JwstCodecError;
|
||||
|
||||
fn try_from(value: Any) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Any::Array(vec) => Ok(vec),
|
||||
_ => Err(JwstCodecError::UnexpectedType("Array")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Any> for bool {
|
||||
type Error = JwstCodecError;
|
||||
|
||||
fn try_from(value: Any) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Any::True => Ok(true),
|
||||
Any::False => Ok(false),
|
||||
_ => Err(JwstCodecError::UnexpectedType("Boolean")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Any> for Any {
|
||||
fn from_iter<I: IntoIterator<Item = Any>>(iter: I) -> Self {
|
||||
Self::Array(iter.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<&'a Any> for Any {
|
||||
fn from_iter<I: IntoIterator<Item = &'a Any>>(iter: I) -> Self {
|
||||
Self::Array(iter.into_iter().cloned().collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(String, Any)> for Any {
|
||||
fn from_iter<I: IntoIterator<Item = (String, Any)>>(iter: I) -> Self {
|
||||
let mut map = HashMap::new();
|
||||
map.extend(iter);
|
||||
Self::Object(map)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HashMap<String, Any>> for Any {
|
||||
fn from(value: HashMap<String, Any>) -> Self {
|
||||
Self::Object(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Any {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
Self::Binary(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for Any {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self::Binary(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: impl for Any::Undefined
|
||||
impl<T: Into<Any>> From<Option<T>> for Any {
|
||||
fn from(value: Option<T>) -> Self {
|
||||
if let Some(val) = value {
|
||||
val.into()
|
||||
} else {
|
||||
Any::Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde_json")]
|
||||
impl From<serde_json::Value> for Any {
|
||||
fn from(value: serde_json::Value) -> Self {
|
||||
match value {
|
||||
serde_json::Value::Null => Self::Null,
|
||||
serde_json::Value::Bool(b) => {
|
||||
if b {
|
||||
Self::True
|
||||
} else {
|
||||
Self::False
|
||||
}
|
||||
}
|
||||
serde_json::Value::Number(n) => {
|
||||
if n.is_f64() {
|
||||
Self::Float64(n.as_f64().unwrap().into())
|
||||
} else if n.is_i64() {
|
||||
Self::Integer(n.as_i64().unwrap() as i32)
|
||||
} else {
|
||||
Self::Integer(n.as_u64().unwrap() as i32)
|
||||
}
|
||||
}
|
||||
serde_json::Value::String(s) => Self::String(s),
|
||||
serde_json::Value::Array(vec) => {
|
||||
Self::Array(vec.into_iter().map(|v| v.into()).collect::<Vec<_>>())
|
||||
}
|
||||
serde_json::Value::Object(obj) => {
|
||||
Self::Object(obj.into_iter().map(|(k, v)| (k, v.into())).collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for Any {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::{Error, MapAccess, SeqAccess, Visitor};
|
||||
struct ValueVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ValueVisitor {
|
||||
type Value = Any;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("any valid JSON value")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_bool<E>(self, value: bool) -> Result<Any, E> {
|
||||
Ok(if value { Any::True } else { Any::False })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_i64<E>(self, value: i64) -> Result<Any, E> {
|
||||
Ok(Any::BigInt64(value))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_u64<E>(self, value: u64) -> Result<Any, E> {
|
||||
Ok((value as i64).into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_f64<E>(self, value: f64) -> Result<Any, E> {
|
||||
Ok(Any::Float64(OrderedFloat(value)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_str<E>(self, value: &str) -> Result<Any, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
self.visit_string(String::from(value))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_string<E>(self, value: String) -> Result<Any, E> {
|
||||
Ok(Any::String(value))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_none<E>(self) -> Result<Any, E> {
|
||||
Ok(Any::Null)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_some<D>(self, deserializer: D) -> Result<Any, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
serde::Deserialize::deserialize(deserializer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_unit<E>(self) -> Result<Any, E> {
|
||||
Ok(Any::Null)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_seq<V>(self, mut visitor: V) -> Result<Any, V::Error>
|
||||
where
|
||||
V: SeqAccess<'de>,
|
||||
{
|
||||
let mut vec = Vec::new();
|
||||
|
||||
while let Some(elem) = visitor.next_element()? {
|
||||
vec.push(elem);
|
||||
}
|
||||
|
||||
Ok(Any::Array(vec))
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut visitor: V) -> Result<Any, V::Error>
|
||||
where
|
||||
V: MapAccess<'de>,
|
||||
{
|
||||
match visitor.next_key::<String>()? {
|
||||
Some(k) => {
|
||||
let mut values = HashMap::new();
|
||||
|
||||
values.insert(k, visitor.next_value()?);
|
||||
while let Some((key, value)) = visitor.next_entry()? {
|
||||
values.insert(key, value);
|
||||
}
|
||||
|
||||
Ok(Any::Object(values))
|
||||
}
|
||||
None => Ok(Any::Object(HashMap::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(ValueVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for Any {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
use serde::ser::{SerializeMap, SerializeSeq};
|
||||
|
||||
match self {
|
||||
Any::Null => serializer.serialize_none(),
|
||||
Any::Undefined => serializer.serialize_none(),
|
||||
Any::True => serializer.serialize_bool(true),
|
||||
Any::False => serializer.serialize_bool(false),
|
||||
Any::Float32(value) => serializer.serialize_f32(value.0),
|
||||
Any::Float64(value) => serializer.serialize_f64(value.0),
|
||||
Any::Integer(value) => serializer.serialize_i32(*value),
|
||||
Any::BigInt64(value) => serializer.serialize_i64(*value),
|
||||
Any::String(value) => serializer.serialize_str(value.as_ref()),
|
||||
Any::Array(values) => {
|
||||
let mut seq = serializer.serialize_seq(Some(values.len()))?;
|
||||
for value in values.iter() {
|
||||
seq.serialize_element(value)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
Any::Object(entries) => {
|
||||
let mut map = serializer.serialize_map(Some(entries.len()))?;
|
||||
for (key, value) in entries.iter() {
|
||||
map.serialize_entry(key, value)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
Any::Binary(buf) => serializer.serialize_bytes(buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Any {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::True => write!(f, "true"),
|
||||
Self::False => write!(f, "false"),
|
||||
Self::String(s) => write!(f, "\"{}\"", s),
|
||||
Self::Integer(i) => write!(f, "{}", i),
|
||||
Self::Float32(v) => write!(f, "{}", v),
|
||||
Self::Float64(v) => write!(f, "{}", v),
|
||||
Self::BigInt64(v) => write!(f, "{}", v),
|
||||
Self::Object(map) => {
|
||||
write!(f, "{{")?;
|
||||
for (i, (key, value)) in map.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{}: {}", key, value)?;
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
Self::Array(vec) => {
|
||||
write!(f, "[")?;
|
||||
for (i, value) in vec.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{}", value)?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
Self::Binary(buf) => write!(f, "{:?}", buf),
|
||||
Self::Undefined => write!(f, "undefined"),
|
||||
Self::Null => write!(f, "null"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use proptest::{collection::vec, prelude::*};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_any_codec() {
|
||||
let any = Any::Object(
|
||||
vec![
|
||||
("name".to_string(), Any::String("Alice".to_string())),
|
||||
("age".to_string(), Any::Integer(25)),
|
||||
(
|
||||
"contacts".to_string(),
|
||||
Any::Array(vec![
|
||||
Any::Object(
|
||||
vec![
|
||||
("type".to_string(), Any::String("Mobile".to_string())),
|
||||
("number".to_string(), Any::String("1234567890".to_string())),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
),
|
||||
Any::Object(
|
||||
vec![
|
||||
("type".to_string(), Any::String("Email".to_string())),
|
||||
(
|
||||
"address".to_string(),
|
||||
Any::String("alice@example.com".to_string()),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
),
|
||||
Any::Undefined,
|
||||
]),
|
||||
),
|
||||
(
|
||||
"standard_data".to_string(),
|
||||
Any::Array(vec![
|
||||
Any::Undefined,
|
||||
Any::Null,
|
||||
Any::Integer(114514),
|
||||
Any::Float32(114.514.into()),
|
||||
Any::Float64(115.514.into()),
|
||||
Any::BigInt64(-1145141919810),
|
||||
Any::False,
|
||||
Any::True,
|
||||
Any::Object(
|
||||
vec![
|
||||
("name".to_string(), Any::String("tadokoro".to_string())),
|
||||
("age".to_string(), Any::String("24".to_string())),
|
||||
("profession".to_string(), Any::String("student".to_string())),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
),
|
||||
Any::Binary(vec![1, 2, 3, 4, 5]),
|
||||
]),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let mut encoder = RawEncoder::default();
|
||||
any.write(&mut encoder).unwrap();
|
||||
let encoded = encoder.into_inner();
|
||||
|
||||
let mut decoder = RawDecoder::new(&encoded);
|
||||
let decoded = Any::read(&mut decoder).unwrap();
|
||||
|
||||
assert_eq!(any, decoded);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_random_any(any in vec(any::<Any>(), 0..100)) {
|
||||
for any in &any {
|
||||
let mut encoder = RawEncoder::default();
|
||||
any.write(&mut encoder).unwrap();
|
||||
let encoded = encoder.into_inner();
|
||||
|
||||
let mut decoder = RawDecoder::new(&encoded);
|
||||
let decoded = Any::read(&mut decoder).unwrap();
|
||||
|
||||
assert_eq!(any, &decoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_to_any() {
|
||||
let any: Vec<Any> = vec![
|
||||
42u8.into(),
|
||||
42u16.into(),
|
||||
42u32.into(),
|
||||
42u64.into(),
|
||||
114.514f32.into(),
|
||||
1919.810f64.into(),
|
||||
(-42i8).into(),
|
||||
(-42i16).into(),
|
||||
(-42i32).into(),
|
||||
(-42i64).into(),
|
||||
false.into(),
|
||||
true.into(),
|
||||
"JWST".to_string().into(),
|
||||
"OctoBase".into(),
|
||||
vec![1u8, 9, 1, 9].into(),
|
||||
(&[8u8, 1, 0][..]).into(),
|
||||
[Any::True, 42u8.into()].iter().collect(),
|
||||
];
|
||||
assert_eq!(
|
||||
any,
|
||||
vec![
|
||||
Any::Integer(42),
|
||||
Any::Integer(42),
|
||||
Any::Integer(42),
|
||||
Any::Integer(42),
|
||||
Any::Float32(114.514.into()),
|
||||
Any::Float64(1919.810.into()),
|
||||
Any::Integer(-42),
|
||||
Any::Integer(-42),
|
||||
Any::Integer(-42),
|
||||
Any::Integer(-42),
|
||||
Any::False,
|
||||
Any::True,
|
||||
Any::String("JWST".to_string()),
|
||||
Any::String("OctoBase".to_string()),
|
||||
Any::Binary(vec![1, 9, 1, 9]),
|
||||
Any::Binary(vec![8, 1, 0]),
|
||||
Any::Array(vec![Any::True, Any::Integer(42)])
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
vec![("key".to_string(), 10u64.into())]
|
||||
.into_iter()
|
||||
.collect::<Any>(),
|
||||
Any::Object(HashMap::from_iter(vec![(
|
||||
"key".to_string(),
|
||||
Any::Integer(10)
|
||||
)]))
|
||||
);
|
||||
|
||||
let any: Any = 10u64.into();
|
||||
assert_eq!(
|
||||
[any].iter().collect::<Any>(),
|
||||
Any::Array(vec![Any::Integer(10)])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
|
||||
pub(crate) enum Content {
|
||||
Deleted(u64),
|
||||
Json(Vec<Option<String>>),
|
||||
Binary(Vec<u8>),
|
||||
String(String),
|
||||
#[cfg_attr(test, proptest(skip))]
|
||||
Embed(Any),
|
||||
#[cfg_attr(test, proptest(skip))]
|
||||
Format {
|
||||
key: String,
|
||||
value: Any,
|
||||
},
|
||||
#[cfg_attr(test, proptest(skip))]
|
||||
Type(YTypeRef),
|
||||
Any(Vec<Any>),
|
||||
Doc {
|
||||
guid: String,
|
||||
opts: Any,
|
||||
},
|
||||
}
|
||||
|
||||
unsafe impl Send for Content {}
|
||||
unsafe impl Sync for Content {}
|
||||
|
||||
impl From<Any> for Content {
|
||||
fn from(value: Any) -> Self {
|
||||
match value {
|
||||
Any::Undefined
|
||||
| Any::Null
|
||||
| Any::Integer(_)
|
||||
| Any::Float32(_)
|
||||
| Any::Float64(_)
|
||||
| Any::BigInt64(_)
|
||||
| Any::False
|
||||
| Any::True
|
||||
| Any::String(_)
|
||||
| Any::Object(_) => Content::Any(vec![value; 1]),
|
||||
Any::Array(v) => Content::Any(v),
|
||||
Any::Binary(b) => Content::Binary(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Content {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Deleted(len1), Self::Deleted(len2)) => len1 == len2,
|
||||
(Self::Json(vec1), Self::Json(vec2)) => vec1 == vec2,
|
||||
(Self::Binary(vec1), Self::Binary(vec2)) => vec1 == vec2,
|
||||
(Self::String(str1), Self::String(str2)) => str1 == str2,
|
||||
(Self::Embed(json1), Self::Embed(json2)) => json1 == json2,
|
||||
(
|
||||
Self::Format {
|
||||
key: key1,
|
||||
value: value1,
|
||||
},
|
||||
Self::Format {
|
||||
key: key2,
|
||||
value: value2,
|
||||
},
|
||||
) => key1 == key2 && value1 == value2,
|
||||
(Self::Any(any1), Self::Any(any2)) => any1 == any2,
|
||||
(Self::Doc { guid: guid1, .. }, Self::Doc { guid: guid2, .. }) => guid1 == guid2,
|
||||
(Self::Type(ty1), Self::Type(ty2)) => ty1 == ty2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Content {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Deleted(arg0) => f.debug_tuple("Deleted").field(arg0).finish(),
|
||||
Self::Json(arg0) => f
|
||||
.debug_tuple("JSON")
|
||||
.field(&format!("Vec [len: {}]", arg0.len()))
|
||||
.finish(),
|
||||
Self::Binary(arg0) => f
|
||||
.debug_tuple("Binary")
|
||||
.field(&format!("Binary [len: {}]", arg0.len()))
|
||||
.finish(),
|
||||
Self::String(arg0) => f.debug_tuple("String").field(arg0).finish(),
|
||||
Self::Embed(arg0) => f.debug_tuple("Embed").field(arg0).finish(),
|
||||
Self::Format { key, value } => f
|
||||
.debug_struct("Format")
|
||||
.field("key", key)
|
||||
.field("value", value)
|
||||
.finish(),
|
||||
Self::Type(arg0) => f
|
||||
.debug_tuple("Type")
|
||||
.field(&arg0.ty().unwrap().kind())
|
||||
.finish(),
|
||||
Self::Any(arg0) => f.debug_tuple("Any").field(arg0).finish(),
|
||||
Self::Doc { guid, opts } => f
|
||||
.debug_struct("Doc")
|
||||
.field("guid", guid)
|
||||
.field("opts", opts)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Content {
|
||||
pub(crate) fn read<R: CrdtReader>(decoder: &mut R, tag_type: u8) -> JwstCodecResult<Self> {
|
||||
match tag_type {
|
||||
1 => Ok(Self::Deleted(decoder.read_var_u64()?)), // Deleted
|
||||
2 => {
|
||||
let len = decoder.read_var_u64()?;
|
||||
let strings = (0..len)
|
||||
.map(|_| {
|
||||
decoder
|
||||
.read_var_string()
|
||||
.map(|s| (s != "undefined").then_some(s))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self::Json(strings))
|
||||
} // JSON
|
||||
3 => Ok(Self::Binary(decoder.read_var_buffer()?.to_vec())), // Binary
|
||||
4 => Ok(Self::String(decoder.read_var_string()?)), // String
|
||||
5 => {
|
||||
let string = decoder.read_var_string()?;
|
||||
let json =
|
||||
serde_json::from_str(&string).map_err(|_| JwstCodecError::DamagedDocumentJson)?;
|
||||
|
||||
Ok(Self::Embed(json))
|
||||
} // Embed
|
||||
6 => {
|
||||
let key = decoder.read_var_string()?;
|
||||
let value = decoder.read_var_string()?;
|
||||
let value =
|
||||
serde_json::from_str(&value).map_err(|_| JwstCodecError::DamagedDocumentJson)?;
|
||||
|
||||
Ok(Self::Format { key, value })
|
||||
} // Format
|
||||
7 => {
|
||||
let type_ref = decoder.read_var_u64()?;
|
||||
let kind = YTypeKind::from(type_ref);
|
||||
let tag_name = match kind {
|
||||
YTypeKind::XMLElement | YTypeKind::XMLHook => Some(decoder.read_var_string()?),
|
||||
YTypeKind::Unknown => {
|
||||
return Err(JwstCodecError::IncompleteDocument(format!(
|
||||
"Unknown y type: {type_ref}"
|
||||
)));
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Ok(Self::Type(YTypeRef::new(kind, tag_name)))
|
||||
} // YType
|
||||
8 => Ok(Self::Any(Any::read_multiple(decoder)?)), // Any
|
||||
9 => {
|
||||
let guid = decoder.read_var_string()?;
|
||||
let opts = Any::read(decoder)?;
|
||||
Ok(Self::Doc { guid, opts })
|
||||
} // Doc
|
||||
tag_type => Err(JwstCodecError::IncompleteDocument(format!(
|
||||
"Unknown content type: {tag_type}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_info(&self) -> u8 {
|
||||
match self {
|
||||
Self::Deleted(_) => 1,
|
||||
Self::Json(_) => 2,
|
||||
Self::Binary(_) => 3,
|
||||
Self::String(_) => 4,
|
||||
Self::Embed(_) => 5,
|
||||
Self::Format { .. } => 6,
|
||||
Self::Type(_) => 7,
|
||||
Self::Any(_) => 8,
|
||||
Self::Doc { .. } => 9,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write<W: CrdtWriter>(&self, encoder: &mut W) -> JwstCodecResult {
|
||||
match self {
|
||||
Self::Deleted(len) => {
|
||||
encoder.write_var_u64(*len)?;
|
||||
}
|
||||
Self::Json(strings) => {
|
||||
encoder.write_var_u64(strings.len() as u64)?;
|
||||
for string in strings {
|
||||
match string {
|
||||
Some(string) => encoder.write_var_string(string)?,
|
||||
None => encoder.write_var_string("undefined")?,
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Binary(buffer) => {
|
||||
encoder.write_var_buffer(buffer)?;
|
||||
}
|
||||
Self::String(string) => {
|
||||
encoder.write_var_string(string)?;
|
||||
}
|
||||
Self::Embed(val) => {
|
||||
encoder.write_var_string(
|
||||
serde_json::to_string(val).map_err(|_| JwstCodecError::DamagedDocumentJson)?,
|
||||
)?;
|
||||
}
|
||||
Self::Format { key, value } => {
|
||||
encoder.write_var_string(key)?;
|
||||
encoder.write_var_string(
|
||||
serde_json::to_string(value).map_err(|_| JwstCodecError::DamagedDocumentJson)?,
|
||||
)?;
|
||||
}
|
||||
Self::Type(ty) => {
|
||||
if let Some(ty) = ty.ty() {
|
||||
let type_ref = u64::from(ty.kind());
|
||||
encoder.write_var_u64(type_ref)?;
|
||||
|
||||
if matches!(ty.kind(), YTypeKind::XMLElement | YTypeKind::XMLHook) {
|
||||
encoder.write_var_string(ty.name.as_ref().unwrap())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Any(any) => {
|
||||
Any::write_multiple(encoder, any)?;
|
||||
}
|
||||
Self::Doc { guid, opts } => {
|
||||
encoder.write_var_string(guid)?;
|
||||
opts.write(encoder)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clock_len(&self) -> u64 {
|
||||
match self {
|
||||
Self::Deleted(len) => *len,
|
||||
Self::Json(strings) => strings.len() as u64,
|
||||
// TODO: need a custom wrapper with length cached, this cost too much
|
||||
Self::String(string) => string.chars().map(|c| c.len_utf16()).sum::<usize>() as u64,
|
||||
Self::Any(any) => any.len() as u64,
|
||||
Self::Binary(_) | Self::Embed(_) | Self::Format { .. } | Self::Type(_) | Self::Doc { .. } => {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn countable(&self) -> bool {
|
||||
!matches!(self, Content::Format { .. } | Content::Deleted(_))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn splittable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::String { .. } | Self::Any { .. } | Self::Json { .. }
|
||||
)
|
||||
}
|
||||
|
||||
pub fn split(&self, diff: u64) -> JwstCodecResult<(Self, Self)> {
|
||||
match self {
|
||||
Self::String(str) => {
|
||||
let (left, right) = Self::split_as_utf16_str(str.as_str(), diff);
|
||||
Ok((
|
||||
Self::String(left.to_string()),
|
||||
Self::String(right.to_string()),
|
||||
))
|
||||
}
|
||||
Self::Json(vec) => {
|
||||
let (left, right) = vec.split_at(diff as usize);
|
||||
Ok((Self::Json(left.to_owned()), Self::Json(right.to_owned())))
|
||||
}
|
||||
Self::Any(vec) => {
|
||||
let (left, right) = vec.split_at(diff as usize);
|
||||
Ok((Self::Any(left.to_owned()), Self::Any(right.to_owned())))
|
||||
}
|
||||
Self::Deleted(len) => {
|
||||
let (left, right) = (diff, *len - diff);
|
||||
|
||||
Ok((Self::Deleted(left), Self::Deleted(right)))
|
||||
}
|
||||
_ => Err(JwstCodecError::ContentSplitNotSupport(diff)),
|
||||
}
|
||||
}
|
||||
|
||||
/// consider `offset` as a utf-16 encoded string offset
|
||||
fn split_as_utf16_str(s: &str, offset: u64) -> (&str, &str) {
|
||||
let mut utf_16_offset = 0;
|
||||
let mut utf_8_offset = 0;
|
||||
for ch in s.chars() {
|
||||
utf_16_offset += ch.len_utf16();
|
||||
utf_8_offset += ch.len_utf8();
|
||||
if utf_16_offset as u64 >= offset {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
s.split_at(utf_8_offset)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use proptest::{collection::vec, prelude::*};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn content_round_trip(content: &Content) -> JwstCodecResult {
|
||||
let mut writer = RawEncoder::default();
|
||||
writer.write_u8(content.get_info())?;
|
||||
content.write(&mut writer)?;
|
||||
let update = writer.into_inner();
|
||||
|
||||
let mut reader = RawDecoder::new(&update);
|
||||
let tag_type = reader.read_u8()?;
|
||||
assert_eq!(Content::read(&mut reader, tag_type)?, *content);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content() {
|
||||
loom_model!({
|
||||
let contents = [
|
||||
Content::Deleted(42),
|
||||
Content::Json(vec![
|
||||
None,
|
||||
Some("test_1".to_string()),
|
||||
Some("test_2".to_string()),
|
||||
]),
|
||||
Content::Binary(vec![1, 2, 3]),
|
||||
Content::String("hello".to_string()),
|
||||
Content::Embed(Any::True),
|
||||
Content::Format {
|
||||
key: "key".to_string(),
|
||||
value: Any::Integer(42),
|
||||
},
|
||||
Content::Type(YTypeRef::new(YTypeKind::Array, None)),
|
||||
Content::Type(YTypeRef::new(YTypeKind::Map, None)),
|
||||
Content::Type(YTypeRef::new(YTypeKind::Text, None)),
|
||||
Content::Type(YTypeRef::new(
|
||||
YTypeKind::XMLElement,
|
||||
Some("test".to_string()),
|
||||
)),
|
||||
Content::Type(YTypeRef::new(YTypeKind::XMLFragment, None)),
|
||||
Content::Type(YTypeRef::new(YTypeKind::XMLHook, Some("test".to_string()))),
|
||||
Content::Type(YTypeRef::new(YTypeKind::XMLText, None)),
|
||||
Content::Any(vec![Any::BigInt64(42), Any::String("Test Any".to_string())]),
|
||||
Content::Doc {
|
||||
guid: "my_guid".to_string(),
|
||||
opts: Any::BigInt64(42),
|
||||
},
|
||||
];
|
||||
|
||||
for content in &contents {
|
||||
content_round_trip(content).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_split() {
|
||||
let contents = [
|
||||
Content::String("hello".to_string()),
|
||||
Content::Json(vec![
|
||||
None,
|
||||
Some("test_1".to_string()),
|
||||
Some("test_2".to_string()),
|
||||
]),
|
||||
Content::Any(vec![Any::BigInt64(42), Any::String("Test Any".to_string())]),
|
||||
Content::Binary(vec![]),
|
||||
];
|
||||
|
||||
{
|
||||
let (left, right) = contents[0].split(1).unwrap();
|
||||
assert!(contents[0].splittable());
|
||||
assert_eq!(left, Content::String("h".to_string()));
|
||||
assert_eq!(right, Content::String("ello".to_string()));
|
||||
}
|
||||
|
||||
{
|
||||
let (left, right) = contents[1].split(1).unwrap();
|
||||
assert!(contents[1].splittable());
|
||||
assert_eq!(left, Content::Json(vec![None]));
|
||||
assert_eq!(
|
||||
right,
|
||||
Content::Json(vec![Some("test_1".to_string()), Some("test_2".to_string())])
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let (left, right) = contents[2].split(1).unwrap();
|
||||
assert!(contents[2].splittable());
|
||||
assert_eq!(left, Content::Any(vec![Any::BigInt64(42)]));
|
||||
assert_eq!(
|
||||
right,
|
||||
Content::Any(vec![Any::String("Test Any".to_string())])
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
assert!(!contents[3].splittable());
|
||||
assert_eq!(
|
||||
contents[3].split(2),
|
||||
Err(JwstCodecError::ContentSplitNotSupport(2))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_random_content(contents in vec(any::<Content>(), 0..10)) {
|
||||
for content in &contents {
|
||||
content_round_trip(content).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
use std::{
|
||||
collections::{hash_map::Entry, VecDeque},
|
||||
ops::{Deref, DerefMut, Range},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::doc::OrderRange;
|
||||
|
||||
impl<R: CrdtReader> CrdtRead<R> for Range<u64> {
|
||||
fn read(decoder: &mut R) -> JwstCodecResult<Self> {
|
||||
let clock = decoder.read_var_u64()?;
|
||||
let len = decoder.read_var_u64()?;
|
||||
Ok(clock..clock + len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: CrdtWriter> CrdtWrite<W> for Range<u64> {
|
||||
fn write(&self, encoder: &mut W) -> JwstCodecResult {
|
||||
encoder.write_var_u64(self.start)?;
|
||||
encoder.write_var_u64(self.end - self.start)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: CrdtReader> CrdtRead<R> for OrderRange {
|
||||
fn read(decoder: &mut R) -> JwstCodecResult<Self> {
|
||||
let num_of_deletes = decoder.read_var_u64()? as usize;
|
||||
if num_of_deletes == 1 {
|
||||
Ok(OrderRange::Range(Range::<u64>::read(decoder)?))
|
||||
} else {
|
||||
let mut deletes = VecDeque::with_capacity(num_of_deletes);
|
||||
|
||||
for _ in 0..num_of_deletes {
|
||||
deletes.push_back(Range::<u64>::read(decoder)?);
|
||||
}
|
||||
|
||||
Ok(OrderRange::Fragment(deletes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: CrdtWriter> CrdtWrite<W> for OrderRange {
|
||||
fn write(&self, encoder: &mut W) -> JwstCodecResult {
|
||||
match self {
|
||||
OrderRange::Range(range) => {
|
||||
encoder.write_var_u64(1)?;
|
||||
range.write(encoder)?;
|
||||
}
|
||||
OrderRange::Fragment(ranges) => {
|
||||
encoder.write_var_u64(ranges.len() as u64)?;
|
||||
for range in ranges {
|
||||
range.write(encoder)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct DeleteSet(pub ClientMap<OrderRange>);
|
||||
|
||||
impl Deref for DeleteSet {
|
||||
type Target = ClientMap<OrderRange>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<[(Client, Vec<Range<u64>>); N]> for DeleteSet {
|
||||
fn from(value: [(Client, Vec<Range<u64>>); N]) -> Self {
|
||||
let mut map = ClientMap::with_capacity(N);
|
||||
for (client, ranges) in value {
|
||||
map.insert(client, ranges.into());
|
||||
}
|
||||
Self(map)
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for DeleteSet {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DeleteSet {
|
||||
pub fn add(&mut self, client: Client, from: Clock, len: Clock) {
|
||||
self.add_range(client, from..from + len);
|
||||
}
|
||||
|
||||
pub fn add_range(&mut self, client: Client, range: Range<u64>) {
|
||||
match self.0.entry(client) {
|
||||
Entry::Occupied(e) => {
|
||||
let r = e.into_mut();
|
||||
if r.is_empty() {
|
||||
*r = range.into();
|
||||
} else {
|
||||
r.push(range);
|
||||
}
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(range.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn batch_add_ranges(&mut self, client: Client, ranges: Vec<Range<u64>>) {
|
||||
match self.0.entry(client) {
|
||||
Entry::Occupied(e) => {
|
||||
e.into_mut().extend(ranges);
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(ranges.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, other: &Self) {
|
||||
for (client, range) in &other.0 {
|
||||
match self.0.entry(*client) {
|
||||
Entry::Occupied(e) => {
|
||||
e.into_mut().merge(range.clone());
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(range.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: CrdtReader> CrdtRead<R> for DeleteSet {
|
||||
fn read(decoder: &mut R) -> JwstCodecResult<Self> {
|
||||
let num_of_clients = decoder.read_var_u64()? as usize;
|
||||
// See: [HASHMAP_SAFE_CAPACITY]
|
||||
let mut map = ClientMap::with_capacity(num_of_clients.min(HASHMAP_SAFE_CAPACITY));
|
||||
|
||||
for _ in 0..num_of_clients {
|
||||
let client = decoder.read_var_u64()?;
|
||||
let deletes = OrderRange::read(decoder)?;
|
||||
map.insert(client, deletes);
|
||||
}
|
||||
|
||||
map.shrink_to_fit();
|
||||
Ok(DeleteSet(map))
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: CrdtWriter> CrdtWrite<W> for DeleteSet {
|
||||
fn write(&self, encoder: &mut W) -> JwstCodecResult {
|
||||
encoder.write_var_u64(self.len() as u64)?;
|
||||
let mut clients = self.keys().copied().collect::<Vec<_>>();
|
||||
|
||||
// Descending
|
||||
clients.sort_by(|a, b| b.cmp(a));
|
||||
|
||||
for client in clients {
|
||||
encoder.write_var_u64(client)?;
|
||||
self.get(&client).unwrap().write(encoder)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::single_range_in_vec_init)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_delete_set_add() {
|
||||
let delete_set = DeleteSet::from([
|
||||
(1, vec![0..10, 20..30]),
|
||||
(2, vec![0..5, 10..20]),
|
||||
(3, vec![15..20, 30..35]),
|
||||
(4, vec![0..10]),
|
||||
]);
|
||||
|
||||
{
|
||||
let mut delete_set = delete_set.clone();
|
||||
delete_set.add(1, 5, 25);
|
||||
assert_eq!(delete_set.get(&1), Some(&OrderRange::Range(0..30)));
|
||||
}
|
||||
|
||||
{
|
||||
let mut delete_set = delete_set;
|
||||
delete_set.add(1, 5, 10);
|
||||
assert_eq!(
|
||||
delete_set.get(&1),
|
||||
Some(&OrderRange::from(vec![0..15, 20..30]))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_set_batch_push() {
|
||||
let delete_set = DeleteSet::from([
|
||||
(1, vec![0..10, 20..30]),
|
||||
(2, vec![0..5, 10..20]),
|
||||
(3, vec![15..20, 30..35]),
|
||||
(4, vec![0..10]),
|
||||
]);
|
||||
|
||||
{
|
||||
let mut delete_set = delete_set.clone();
|
||||
delete_set.batch_add_ranges(1, vec![0..5, 10..20]);
|
||||
assert_eq!(delete_set.get(&1), Some(&OrderRange::Range(0..30)));
|
||||
}
|
||||
|
||||
{
|
||||
let mut delete_set = delete_set;
|
||||
delete_set.batch_add_ranges(1, vec![40..50, 10..20]);
|
||||
assert_eq!(
|
||||
delete_set.get(&1),
|
||||
Some(&OrderRange::from(vec![0..30, 40..50]))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_decode() {
|
||||
let delete_set = DeleteSet::from([(1, vec![0..10, 20..30]), (2, vec![0..5, 10..20])]);
|
||||
let mut encoder = RawEncoder::default();
|
||||
delete_set.write(&mut encoder).unwrap();
|
||||
let update = encoder.into_inner();
|
||||
let mut decoder = RawDecoder::new(&update);
|
||||
let decoded = DeleteSet::read(&mut decoder).unwrap();
|
||||
assert_eq!(delete_set, decoded);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
hash::Hash,
|
||||
ops::{Add, Sub},
|
||||
};
|
||||
|
||||
pub type Client = u64;
|
||||
pub type Clock = u64;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
|
||||
#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
|
||||
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
|
||||
pub struct Id {
|
||||
pub client: Client,
|
||||
pub clock: Clock,
|
||||
}
|
||||
|
||||
impl Id {
|
||||
pub fn new(client: Client, clock: Clock) -> Self {
|
||||
Self { client, clock }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Client, Clock)> for Id {
|
||||
fn from((client, clock): (Client, Clock)) -> Self {
|
||||
Id::new(client, clock)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Clock> for Id {
|
||||
type Output = Id;
|
||||
|
||||
fn sub(self, rhs: Clock) -> Self::Output {
|
||||
(self.client, self.clock - rhs).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Clock> for Id {
|
||||
type Output = Id;
|
||||
|
||||
fn add(self, rhs: Clock) -> Self::Output {
|
||||
(self.client, self.clock + rhs).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Id {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {})", self.client, self.clock)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn basic_id_operation() {
|
||||
let id_with_different_client_1 = Id::new(1, 1);
|
||||
let id_with_different_client_2 = Id::new(2, 1);
|
||||
|
||||
assert_ne!(id_with_different_client_1, id_with_different_client_2);
|
||||
assert_eq!(Id::new(1, 1), Id::new(1, 1));
|
||||
|
||||
let clock = 2;
|
||||
assert_eq!(Id::new(1, 1) + clock, (1, 3).into());
|
||||
assert_eq!(Id::new(1, 3) - clock, (1, 1).into());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[inline]
|
||||
pub fn read_with_cursor<T, F>(buffer: &mut Cursor<&[u8]>, f: F) -> JwstCodecResult<T>
|
||||
where
|
||||
F: FnOnce(&[u8]) -> IResult<&[u8], T>,
|
||||
{
|
||||
// TODO: use remaining_slice() instead after it is stabilized
|
||||
let input = buffer.get_ref();
|
||||
let rest_pos = buffer.position().min(input.len() as u64) as usize;
|
||||
let input = &input[rest_pos..];
|
||||
|
||||
let (tail, result) = f(input).map_err(|e| e.map_input(|u| u.len()))?;
|
||||
|
||||
buffer.set_position((rest_pos + input.len() - tail.len()) as u64);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// compatible with ydoc v1
|
||||
#[derive(Clone)]
|
||||
pub struct RawDecoder<'b> {
|
||||
pub(super) buffer: Cursor<&'b [u8]>,
|
||||
}
|
||||
|
||||
impl<'b> RawDecoder<'b> {
|
||||
pub fn new(buffer: &'b [u8]) -> Self {
|
||||
Self {
|
||||
buffer: Cursor::new(buffer),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rest_ref(&self) -> &[u8] {
|
||||
let pos = self.buffer.position();
|
||||
let buf = self.buffer.get_ref();
|
||||
|
||||
if pos == 0 {
|
||||
buf
|
||||
} else {
|
||||
&buf[(pos as usize).min(buf.len())..]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drain(self) -> &'b [u8] {
|
||||
let pos = self.buffer.position() as usize;
|
||||
let buf = self.buffer.into_inner();
|
||||
|
||||
if pos == 0 {
|
||||
buf
|
||||
} else {
|
||||
&buf[pos..]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CrdtReader for RawDecoder<'_> {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.buffer.position() >= self.buffer.get_ref().len() as u64
|
||||
}
|
||||
|
||||
fn len(&self) -> u64 {
|
||||
self.buffer.get_ref().len() as u64 - self.buffer.position()
|
||||
}
|
||||
|
||||
fn read_var_u64(&mut self) -> JwstCodecResult<u64> {
|
||||
read_with_cursor(&mut self.buffer, read_var_u64)
|
||||
}
|
||||
|
||||
fn read_var_i32(&mut self) -> JwstCodecResult<i32> {
|
||||
read_with_cursor(&mut self.buffer, read_var_i32)
|
||||
}
|
||||
|
||||
fn read_var_string(&mut self) -> JwstCodecResult<String> {
|
||||
read_with_cursor(&mut self.buffer, read_var_string)
|
||||
}
|
||||
|
||||
fn read_var_buffer(&mut self) -> JwstCodecResult<Vec<u8>> {
|
||||
read_with_cursor(&mut self.buffer, |i| {
|
||||
read_var_buffer(i).map(|(tail, val)| (tail, val.to_vec()))
|
||||
})
|
||||
}
|
||||
|
||||
fn read_u8(&mut self) -> JwstCodecResult<u8> {
|
||||
self.buffer.read_u8().map_err(reader::map_read_error)
|
||||
}
|
||||
|
||||
fn read_f32_be(&mut self) -> JwstCodecResult<f32> {
|
||||
self
|
||||
.buffer
|
||||
.read_f32::<BigEndian>()
|
||||
.map_err(reader::map_read_error)
|
||||
}
|
||||
|
||||
fn read_f64_be(&mut self) -> JwstCodecResult<f64> {
|
||||
self
|
||||
.buffer
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(reader::map_read_error)
|
||||
}
|
||||
|
||||
fn read_i64_be(&mut self) -> JwstCodecResult<i64> {
|
||||
self
|
||||
.buffer
|
||||
.read_i64::<BigEndian>()
|
||||
.map_err(reader::map_read_error)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_info(&mut self) -> JwstCodecResult<u8> {
|
||||
self.read_u8()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_item_id(&mut self) -> JwstCodecResult<Id> {
|
||||
let client = self.read_var_u64()?;
|
||||
let clock = self.read_var_u64()?;
|
||||
Ok(Id::new(client, clock))
|
||||
}
|
||||
}
|
||||
|
||||
// compatible with ydoc v1
|
||||
#[derive(Default)]
|
||||
pub struct RawEncoder {
|
||||
buffer: Cursor<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl RawEncoder {
|
||||
pub fn into_inner(self) -> Vec<u8> {
|
||||
self.buffer.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl CrdtWriter for RawEncoder {
|
||||
fn write_var_u64(&mut self, num: u64) -> JwstCodecResult {
|
||||
write_var_u64(&mut self.buffer, num).map_err(writer::map_write_error)
|
||||
}
|
||||
fn write_var_i32(&mut self, num: i32) -> JwstCodecResult {
|
||||
write_var_i32(&mut self.buffer, num).map_err(writer::map_write_error)
|
||||
}
|
||||
fn write_var_string<S: AsRef<str>>(&mut self, s: S) -> JwstCodecResult {
|
||||
write_var_string(&mut self.buffer, s).map_err(writer::map_write_error)
|
||||
}
|
||||
fn write_var_buffer(&mut self, buf: &[u8]) -> JwstCodecResult {
|
||||
write_var_buffer(&mut self.buffer, buf).map_err(writer::map_write_error)
|
||||
}
|
||||
fn write_u8(&mut self, num: u8) -> JwstCodecResult {
|
||||
self.buffer.write_u8(num).map_err(writer::map_write_error)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_f32_be(&mut self, num: f32) -> JwstCodecResult {
|
||||
self
|
||||
.buffer
|
||||
.write_f32::<BigEndian>(num)
|
||||
.map_err(writer::map_write_error)
|
||||
}
|
||||
fn write_f64_be(&mut self, num: f64) -> JwstCodecResult {
|
||||
self
|
||||
.buffer
|
||||
.write_f64::<BigEndian>(num)
|
||||
.map_err(writer::map_write_error)
|
||||
}
|
||||
fn write_i64_be(&mut self, num: i64) -> JwstCodecResult {
|
||||
self
|
||||
.buffer
|
||||
.write_i64::<BigEndian>(num)
|
||||
.map_err(writer::map_write_error)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn write_info(&mut self, num: u8) -> JwstCodecResult {
|
||||
self.write_u8(num)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn write_item_id(&mut self, id: &Id) -> JwstCodecResult {
|
||||
self.write_var_u64(id.client)?;
|
||||
self.write_var_u64(id.clock)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::approx_constant)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_crdt_reader() {
|
||||
{
|
||||
let mut reader = RawDecoder::new(&[0xf2, 0x5]);
|
||||
assert_eq!(reader.read_var_u64().unwrap(), 754);
|
||||
}
|
||||
{
|
||||
let mut reader = RawDecoder::new(&[0x5, b'h', b'e', b'l', b'l', b'o']);
|
||||
|
||||
assert_eq!(reader.clone().read_var_string().unwrap(), "hello");
|
||||
assert_eq!(
|
||||
reader.clone().read_var_buffer().unwrap().as_slice(),
|
||||
b"hello"
|
||||
);
|
||||
|
||||
assert_eq!(reader.read_u8().unwrap(), 5);
|
||||
assert_eq!(reader.read_u8().unwrap(), b'h');
|
||||
assert_eq!(reader.read_u8().unwrap(), b'e');
|
||||
assert_eq!(reader.read_u8().unwrap(), b'l');
|
||||
assert_eq!(reader.read_u8().unwrap(), b'l');
|
||||
assert_eq!(reader.read_u8().unwrap(), b'o');
|
||||
}
|
||||
{
|
||||
let mut reader = RawDecoder::new(&[0x40, 0x49, 0x0f, 0xdb]);
|
||||
assert_eq!(reader.read_f32_be().unwrap(), 3.1415927);
|
||||
}
|
||||
{
|
||||
let mut reader = RawDecoder::new(&[0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18]);
|
||||
assert_eq!(reader.read_f64_be().unwrap(), 3.141592653589793);
|
||||
}
|
||||
{
|
||||
let mut reader = RawDecoder::new(&[0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
|
||||
assert_eq!(reader.read_i64_be().unwrap(), i64::MAX);
|
||||
}
|
||||
{
|
||||
let mut reader = RawDecoder::new(&[0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
||||
assert_eq!(reader.read_i64_be().unwrap(), i64::MIN);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_crdt_writer() {
|
||||
{
|
||||
let mut writer = RawEncoder::default();
|
||||
writer.write_var_u64(754).unwrap();
|
||||
assert_eq!(writer.into_inner(), vec![0xf2, 0x5]);
|
||||
}
|
||||
{
|
||||
let ret = vec![0x5, b'h', b'e', b'l', b'l', b'o'];
|
||||
let mut writer = RawEncoder::default();
|
||||
writer.write_var_string("hello").unwrap();
|
||||
assert_eq!(writer.into_inner(), ret);
|
||||
|
||||
let mut writer = RawEncoder::default();
|
||||
writer.write_var_buffer(b"hello").unwrap();
|
||||
assert_eq!(writer.into_inner(), ret);
|
||||
|
||||
let mut writer = RawEncoder::default();
|
||||
writer.write_u8(5).unwrap();
|
||||
writer.write_u8(b'h').unwrap();
|
||||
writer.write_u8(b'e').unwrap();
|
||||
writer.write_u8(b'l').unwrap();
|
||||
writer.write_u8(b'l').unwrap();
|
||||
writer.write_u8(b'o').unwrap();
|
||||
assert_eq!(writer.into_inner(), ret);
|
||||
}
|
||||
{
|
||||
let mut writer = RawEncoder::default();
|
||||
writer.write_f32_be(3.1415927).unwrap();
|
||||
assert_eq!(writer.into_inner(), vec![0x40, 0x49, 0x0f, 0xdb]);
|
||||
}
|
||||
{
|
||||
let mut writer = RawEncoder::default();
|
||||
writer.write_f64_be(3.141592653589793).unwrap();
|
||||
assert_eq!(
|
||||
writer.into_inner(),
|
||||
vec![0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18]
|
||||
);
|
||||
}
|
||||
{
|
||||
let mut writer = RawEncoder::default();
|
||||
writer.write_i64_be(i64::MAX).unwrap();
|
||||
assert_eq!(
|
||||
writer.into_inner(),
|
||||
vec![0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
|
||||
);
|
||||
}
|
||||
{
|
||||
let mut writer = RawEncoder::default();
|
||||
writer.write_i64_be(i64::MIN).unwrap();
|
||||
assert_eq!(
|
||||
writer.into_inner(),
|
||||
vec![0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
);
|
||||
}
|
||||
{
|
||||
let mut writer = RawEncoder::default();
|
||||
writer.write_info(0x80).unwrap();
|
||||
assert_eq!(writer.into_inner(), vec![0x80]);
|
||||
}
|
||||
{
|
||||
let mut writer = RawEncoder::default();
|
||||
writer.write_item_id(&Id::new(1, 2)).unwrap();
|
||||
assert_eq!(writer.into_inner(), vec![0x1, 0x2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
mod codec_v1;
|
||||
mod reader;
|
||||
mod writer;
|
||||
|
||||
pub use codec_v1::{RawDecoder, RawEncoder};
|
||||
pub use reader::{CrdtRead, CrdtReader};
|
||||
pub use writer::{CrdtWrite, CrdtWriter};
|
||||
|
||||
use super::*;
|
||||
@@ -0,0 +1,30 @@
|
||||
use std::io::Error;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[inline]
|
||||
pub fn map_read_error(e: Error) -> JwstCodecError {
|
||||
JwstCodecError::IncompleteDocument(e.to_string())
|
||||
}
|
||||
|
||||
pub trait CrdtReader {
|
||||
fn is_empty(&self) -> bool;
|
||||
fn len(&self) -> u64;
|
||||
fn read_var_u64(&mut self) -> JwstCodecResult<u64>;
|
||||
fn read_var_i32(&mut self) -> JwstCodecResult<i32>;
|
||||
fn read_var_string(&mut self) -> JwstCodecResult<String>;
|
||||
fn read_var_buffer(&mut self) -> JwstCodecResult<Vec<u8>>;
|
||||
fn read_u8(&mut self) -> JwstCodecResult<u8>;
|
||||
fn read_f32_be(&mut self) -> JwstCodecResult<f32>;
|
||||
fn read_f64_be(&mut self) -> JwstCodecResult<f64>;
|
||||
fn read_i64_be(&mut self) -> JwstCodecResult<i64>;
|
||||
|
||||
fn read_info(&mut self) -> JwstCodecResult<u8>;
|
||||
fn read_item_id(&mut self) -> JwstCodecResult<Id>;
|
||||
}
|
||||
|
||||
pub trait CrdtRead<R: CrdtReader> {
|
||||
fn read(reader: &mut R) -> JwstCodecResult<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use std::io::Error;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[inline]
|
||||
pub fn map_write_error(e: Error) -> JwstCodecError {
|
||||
JwstCodecError::InvalidWriteBuffer(e.to_string())
|
||||
}
|
||||
|
||||
pub trait CrdtWriter {
|
||||
fn write_var_u64(&mut self, num: u64) -> JwstCodecResult;
|
||||
fn write_var_i32(&mut self, num: i32) -> JwstCodecResult;
|
||||
fn write_var_string<S: AsRef<str>>(&mut self, s: S) -> JwstCodecResult;
|
||||
fn write_var_buffer(&mut self, buf: &[u8]) -> JwstCodecResult;
|
||||
fn write_u8(&mut self, num: u8) -> JwstCodecResult;
|
||||
fn write_f32_be(&mut self, num: f32) -> JwstCodecResult;
|
||||
fn write_f64_be(&mut self, num: f64) -> JwstCodecResult;
|
||||
fn write_i64_be(&mut self, num: i64) -> JwstCodecResult;
|
||||
|
||||
fn write_info(&mut self, num: u8) -> JwstCodecResult;
|
||||
fn write_item_id(&mut self, id: &Id) -> JwstCodecResult;
|
||||
}
|
||||
|
||||
pub trait CrdtWrite<W: CrdtWriter> {
|
||||
fn write(&self, writer: &mut W) -> JwstCodecResult
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
|
||||
pub(crate) enum Parent {
|
||||
#[cfg_attr(test, proptest(skip))]
|
||||
Type(YTypeRef),
|
||||
#[cfg_attr(test, proptest(value = "Parent::String(SmolStr::default())"))]
|
||||
String(SmolStr),
|
||||
Id(Id),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(all(test, not(loom)), derive(proptest_derive::Arbitrary))]
|
||||
pub(crate) struct Item {
|
||||
pub id: Id,
|
||||
pub origin_left_id: Option<Id>,
|
||||
pub origin_right_id: Option<Id>,
|
||||
#[cfg_attr(all(test, not(loom)), proptest(value = "Somr::none()"))]
|
||||
pub left: ItemRef,
|
||||
#[cfg_attr(all(test, not(loom)), proptest(value = "Somr::none()"))]
|
||||
pub right: ItemRef,
|
||||
pub parent: Option<Parent>,
|
||||
#[cfg_attr(all(test, not(loom)), proptest(value = "Option::<SmolStr>::None"))]
|
||||
pub parent_sub: Option<SmolStr>,
|
||||
pub content: Content,
|
||||
#[cfg_attr(all(test, not(loom)), proptest(value = "ItemFlag::default()"))]
|
||||
pub flags: ItemFlag,
|
||||
}
|
||||
|
||||
// make all Item readonly
|
||||
pub(crate) type ItemRef = Somr<Item>;
|
||||
|
||||
impl PartialEq for Item {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Item {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut dbg = f.debug_struct("Item");
|
||||
dbg
|
||||
.field("id", &self.id)
|
||||
.field("origin_left_id", &self.origin_left_id)
|
||||
.field("origin_right_id", &self.origin_right_id);
|
||||
|
||||
if let Some(left) = self.left.get() {
|
||||
dbg.field("left", &left.id);
|
||||
}
|
||||
|
||||
if let Some(right) = self.right.get() {
|
||||
dbg.field("right", &right.id);
|
||||
}
|
||||
|
||||
dbg
|
||||
.field(
|
||||
"parent",
|
||||
&self.parent.as_ref().map(|p| match p {
|
||||
Parent::Type(_) => "[Type]".to_string(),
|
||||
Parent::String(name) => format!("Parent({name})"),
|
||||
Parent::Id(id) => format!("({}, {})", id.client, id.clock),
|
||||
}),
|
||||
)
|
||||
.field("parent_sub", &self.parent_sub)
|
||||
.field("content", &self.content)
|
||||
.field("flags", &self.flags)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Item {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Item{}: [{:?}]", self.id, self.content)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Item {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: Id::default(),
|
||||
origin_left_id: None,
|
||||
origin_right_id: None,
|
||||
left: Somr::none(),
|
||||
right: Somr::none(),
|
||||
parent: None,
|
||||
parent_sub: None,
|
||||
content: Content::Deleted(0),
|
||||
flags: ItemFlag::from(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn new(
|
||||
id: Id,
|
||||
content: Content,
|
||||
left: Somr<Item>,
|
||||
right: Somr<Item>,
|
||||
parent: Option<Parent>,
|
||||
parent_sub: Option<SmolStr>,
|
||||
) -> Self {
|
||||
let flags = ItemFlag::from(if content.countable() {
|
||||
item_flags::ITEM_COUNTABLE
|
||||
} else {
|
||||
0
|
||||
});
|
||||
|
||||
Self {
|
||||
id,
|
||||
origin_left_id: left.get().map(|left| left.last_id()),
|
||||
left,
|
||||
origin_right_id: right.get().map(|right| right.id),
|
||||
right,
|
||||
parent,
|
||||
parent_sub,
|
||||
content,
|
||||
flags,
|
||||
}
|
||||
}
|
||||
|
||||
// find a note that has parent info
|
||||
// in crdt tree, not all node has parent info
|
||||
// so we need to check left and right node if they have parent info
|
||||
pub fn find_node_with_parent_info(&self) -> Option<Item> {
|
||||
if self.parent.is_some() {
|
||||
return Some(self.clone());
|
||||
} else if let Some(item) = self.left.get() {
|
||||
if item.parent.is_none() {
|
||||
if let Some(item) = item.right.get() {
|
||||
return Some(item.clone());
|
||||
}
|
||||
} else {
|
||||
return Some(item.clone());
|
||||
}
|
||||
} else if let Some(item) = self.right.get() {
|
||||
return Some(item.clone());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn len(&self) -> u64 {
|
||||
self.content.clock_len()
|
||||
}
|
||||
|
||||
pub fn deleted(&self) -> bool {
|
||||
self.flags.deleted()
|
||||
}
|
||||
|
||||
pub fn delete(&self) -> bool {
|
||||
if self.deleted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.flags.set_deleted();
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn countable(&self) -> bool {
|
||||
self.flags.countable()
|
||||
}
|
||||
|
||||
pub fn keep(&self) -> bool {
|
||||
self.flags.keep()
|
||||
}
|
||||
|
||||
pub fn indexable(&self) -> bool {
|
||||
self.countable() && !self.deleted()
|
||||
}
|
||||
|
||||
pub fn last_id(&self) -> Id {
|
||||
let Id { client, clock } = self.id;
|
||||
|
||||
Id::new(client, clock + self.len() - 1)
|
||||
}
|
||||
|
||||
pub fn split_at(&self, offset: u64) -> JwstCodecResult<(Self, Self)> {
|
||||
debug_assert!(offset > 0 && self.len() > 1 && offset < self.len());
|
||||
let id = self.id;
|
||||
let right_id = Id::new(id.client, id.clock + offset);
|
||||
let (left_content, right_content) = self.content.split(offset)?;
|
||||
|
||||
let left_item = Item::new(
|
||||
id,
|
||||
left_content,
|
||||
// let caller connect left <-> node <-> right
|
||||
Somr::none(),
|
||||
Somr::none(),
|
||||
self.parent.clone(),
|
||||
self.parent_sub.clone(),
|
||||
);
|
||||
|
||||
let right_item = Item::new(
|
||||
right_id,
|
||||
right_content,
|
||||
// let caller connect left <-> node <-> right
|
||||
Somr::none(),
|
||||
Somr::none(),
|
||||
self.parent.clone(),
|
||||
self.parent_sub.clone(),
|
||||
);
|
||||
|
||||
if left_item.deleted() {
|
||||
left_item.flags.set_deleted();
|
||||
}
|
||||
if left_item.keep() {
|
||||
left_item.flags.set_keep();
|
||||
}
|
||||
|
||||
Ok((left_item, right_item))
|
||||
}
|
||||
|
||||
fn get_info(&self) -> u8 {
|
||||
let mut info = self.content.get_info();
|
||||
|
||||
if self.origin_left_id.is_some() {
|
||||
info |= item_flags::ITEM_HAS_LEFT_ID;
|
||||
}
|
||||
if self.origin_right_id.is_some() {
|
||||
info |= item_flags::ITEM_HAS_RIGHT_ID;
|
||||
}
|
||||
if self.parent_sub.is_some() {
|
||||
info |= item_flags::ITEM_HAS_PARENT_SUB;
|
||||
}
|
||||
|
||||
info
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
let has_id = self.origin_left_id.is_some() || self.origin_right_id.is_some();
|
||||
!has_id && self.parent.is_some() || has_id && self.parent.is_none() && self.parent_sub.is_none()
|
||||
}
|
||||
|
||||
pub fn read<R: CrdtReader>(
|
||||
decoder: &mut R,
|
||||
id: Id,
|
||||
info: u8,
|
||||
first_5_bit: u8,
|
||||
) -> JwstCodecResult<Self> {
|
||||
let flags: ItemFlag = info.into();
|
||||
let has_left_id = flags.check(item_flags::ITEM_HAS_LEFT_ID);
|
||||
let has_right_id = flags.check(item_flags::ITEM_HAS_RIGHT_ID);
|
||||
let has_parent_sub = flags.check(item_flags::ITEM_HAS_PARENT_SUB);
|
||||
let has_not_sibling = flags.not(item_flags::ITEM_HAS_SIBLING);
|
||||
|
||||
// NOTE: read order must keep the same as the order in yjs
|
||||
// TODO: this data structure design will break the cpu OOE, need to be optimized
|
||||
let item = Self {
|
||||
id,
|
||||
origin_left_id: if has_left_id {
|
||||
Some(decoder.read_item_id()?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
origin_right_id: if has_right_id {
|
||||
Some(decoder.read_item_id()?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
parent: {
|
||||
if has_not_sibling {
|
||||
let has_parent = decoder.read_var_u64()? == 1;
|
||||
Some(if has_parent {
|
||||
Parent::String(SmolStr::new(decoder.read_var_string()?))
|
||||
} else {
|
||||
Parent::Id(decoder.read_item_id()?)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
parent_sub: if has_not_sibling && has_parent_sub {
|
||||
Some(SmolStr::new(decoder.read_var_string()?))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
content: {
|
||||
// tag must not GC or Skip, this must process in parse_struct
|
||||
debug_assert_ne!(first_5_bit, 0);
|
||||
debug_assert_ne!(first_5_bit, 10);
|
||||
Content::read(decoder, first_5_bit)?
|
||||
},
|
||||
left: Somr::none(),
|
||||
right: Somr::none(),
|
||||
flags: ItemFlag::from(0),
|
||||
};
|
||||
|
||||
if item.content.countable() {
|
||||
item.flags.set_countable();
|
||||
}
|
||||
|
||||
if matches!(item.content, Content::Deleted(_)) {
|
||||
item.flags.set_deleted();
|
||||
}
|
||||
|
||||
debug_assert!(item.is_valid());
|
||||
|
||||
Ok(item)
|
||||
}
|
||||
|
||||
pub fn write<W: CrdtWriter>(&self, encoder: &mut W) -> JwstCodecResult {
|
||||
let info = self.get_info();
|
||||
let has_not_sibling = info & item_flags::ITEM_HAS_SIBLING == 0;
|
||||
|
||||
encoder.write_info(info)?;
|
||||
|
||||
if let Some(left_id) = self.origin_left_id {
|
||||
encoder.write_item_id(&left_id)?;
|
||||
}
|
||||
if let Some(right_id) = self.origin_right_id {
|
||||
encoder.write_item_id(&right_id)?;
|
||||
}
|
||||
|
||||
if has_not_sibling {
|
||||
if let Some(parent) = &self.parent {
|
||||
match parent {
|
||||
Parent::String(s) => {
|
||||
encoder.write_var_u64(1)?;
|
||||
encoder.write_var_string(s)?;
|
||||
}
|
||||
Parent::Id(id) => {
|
||||
encoder.write_var_u64(0)?;
|
||||
encoder.write_item_id(id)?;
|
||||
}
|
||||
Parent::Type(ty) => {
|
||||
if let Some(ty) = ty.ty() {
|
||||
if let Some(item) = ty.item.get() {
|
||||
encoder.write_var_u64(0)?;
|
||||
encoder.write_item_id(&item.id)?;
|
||||
} else if let Some(name) = &ty.root_name {
|
||||
encoder.write_var_u64(1)?;
|
||||
encoder.write_var_string(name)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if item delete, it must not exists in crdt state tree
|
||||
debug_assert!(!self.deleted());
|
||||
return Err(JwstCodecError::ParentNotFound);
|
||||
}
|
||||
|
||||
if let Some(parent_sub) = &self.parent_sub {
|
||||
encoder.write_var_string(parent_sub)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.content.write(encoder)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(any(debug, test))]
|
||||
impl Item {
|
||||
pub fn print_left(&self) {
|
||||
let mut ret = vec![format!("Self{}: [{:?}]", self.id, self.content)];
|
||||
let mut left: Somr<Item> = self.left.clone();
|
||||
|
||||
while let Some(item) = left.get() {
|
||||
ret.push(format!("{item}"));
|
||||
left = item.left.clone();
|
||||
}
|
||||
ret.reverse();
|
||||
|
||||
println!("{}", ret.join(" <- "));
|
||||
}
|
||||
|
||||
pub fn print_right(&self) {
|
||||
let mut ret = vec![format!("Self{}: [{:?}]", self.id, self.content)];
|
||||
let mut right = self.right.clone();
|
||||
|
||||
while let Some(item) = right.get() {
|
||||
ret.push(format!("{item}"));
|
||||
right = item.right.clone();
|
||||
}
|
||||
|
||||
println!("{}", ret.join(" -> "));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[cfg(not(loom))]
|
||||
use proptest::{collection::vec, prelude::*};
|
||||
|
||||
#[cfg(not(loom))]
|
||||
use super::*;
|
||||
|
||||
#[cfg(not(loom))]
|
||||
fn item_round_trip(item: &mut Item) -> JwstCodecResult {
|
||||
if !item.is_valid() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if item.content.countable() {
|
||||
item.flags.set_countable();
|
||||
}
|
||||
|
||||
let mut encoder = RawEncoder::default();
|
||||
item.write(&mut encoder)?;
|
||||
|
||||
let update = encoder.into_inner();
|
||||
let mut decoder = RawDecoder::new(&update);
|
||||
|
||||
let info = decoder.read_info()?;
|
||||
let first_5_bit = info & 0b11111;
|
||||
let decoded_item = Item::read(&mut decoder, item.id, info, first_5_bit)?;
|
||||
|
||||
assert_eq!(item, &decoded_item);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(loom))]
|
||||
proptest! {
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_random_content(mut items in vec(any::<Item>(), 0..10)) {
|
||||
for item in &mut items {
|
||||
item_round_trip(item).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[allow(dead_code)]
|
||||
pub mod item_flags {
|
||||
pub const ITEM_KEEP : u8 = 0b0000_0001;
|
||||
pub const ITEM_COUNTABLE : u8 = 0b0000_0010;
|
||||
pub const ITEM_DELETED : u8 = 0b0000_0100;
|
||||
pub const ITEM_MARKED : u8 = 0b0000_1000;
|
||||
pub const ITEM_HAS_PARENT_SUB : u8 = 0b0010_0000;
|
||||
pub const ITEM_HAS_RIGHT_ID : u8 = 0b0100_0000;
|
||||
pub const ITEM_HAS_LEFT_ID : u8 = 0b1000_0000;
|
||||
pub const ITEM_HAS_SIBLING : u8 = 0b1100_0000;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ItemFlag(pub(self) AtomicU8);
|
||||
|
||||
impl Default for ItemFlag {
|
||||
fn default() -> Self {
|
||||
Self(AtomicU8::new(0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ItemFlag {
|
||||
fn clone(&self) -> Self {
|
||||
Self(AtomicU8::new(self.0.load(Ordering::Acquire)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for ItemFlag {
|
||||
fn from(flags: u8) -> Self {
|
||||
Self(AtomicU8::new(flags))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl ItemFlag {
|
||||
#[inline(always)]
|
||||
pub fn set(&self, flag: u8) {
|
||||
self.0.fetch_or(flag, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn clear(&self, flag: u8) {
|
||||
self.0.fetch_and(!flag, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn check(&self, flag: u8) -> bool {
|
||||
self.0.load(Ordering::Acquire) & flag == flag
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn not(&self, flag: u8) -> bool {
|
||||
self.0.load(Ordering::Acquire) & flag == 0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn keep(&self) -> bool {
|
||||
self.check(item_flags::ITEM_KEEP)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_keep(&self) {
|
||||
self.set(item_flags::ITEM_KEEP);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn clear_keep(&self) {
|
||||
self.clear(item_flags::ITEM_KEEP);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn countable(&self) -> bool {
|
||||
self.check(item_flags::ITEM_COUNTABLE)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_countable(&self) {
|
||||
self.set(item_flags::ITEM_COUNTABLE);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn clear_countable(&self) {
|
||||
self.clear(item_flags::ITEM_COUNTABLE);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn deleted(&self) -> bool {
|
||||
self.check(item_flags::ITEM_DELETED)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_deleted(&self) {
|
||||
self.set(item_flags::ITEM_DELETED);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn clear_deleted(&self) {
|
||||
self.clear(item_flags::ITEM_DELETED);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_flag_set_and_clear() {
|
||||
{
|
||||
let flag = super::ItemFlag::default();
|
||||
assert!(!flag.keep());
|
||||
flag.set_keep();
|
||||
assert!(flag.keep());
|
||||
flag.clear_keep();
|
||||
assert!(!flag.keep());
|
||||
assert_eq!(
|
||||
flag.0.load(Ordering::SeqCst),
|
||||
ItemFlag::default().0.load(Ordering::SeqCst)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let flag = super::ItemFlag::default();
|
||||
assert!(!flag.countable());
|
||||
flag.set_countable();
|
||||
assert!(flag.countable());
|
||||
flag.clear_countable();
|
||||
assert!(!flag.countable());
|
||||
assert_eq!(
|
||||
flag.0.load(Ordering::SeqCst),
|
||||
ItemFlag::default().0.load(Ordering::SeqCst)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let flag = super::ItemFlag::default();
|
||||
assert!(!flag.deleted());
|
||||
flag.set_deleted();
|
||||
assert!(flag.deleted());
|
||||
flag.clear_deleted();
|
||||
assert!(!flag.deleted());
|
||||
assert_eq!(
|
||||
flag.0.load(Ordering::SeqCst),
|
||||
ItemFlag::default().0.load(Ordering::SeqCst)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let flag = super::ItemFlag::default();
|
||||
flag.set_keep();
|
||||
flag.set_countable();
|
||||
flag.set_deleted();
|
||||
assert!(flag.keep());
|
||||
assert!(flag.countable());
|
||||
assert!(flag.deleted());
|
||||
flag.clear_keep();
|
||||
flag.clear_countable();
|
||||
flag.clear_deleted();
|
||||
assert!(!flag.keep());
|
||||
assert!(!flag.countable());
|
||||
assert!(!flag.deleted());
|
||||
assert_eq!(
|
||||
flag.0.load(Ordering::SeqCst),
|
||||
ItemFlag::default().0.load(Ordering::SeqCst)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
mod any;
|
||||
mod content;
|
||||
mod delete_set;
|
||||
mod id;
|
||||
mod io;
|
||||
mod item;
|
||||
mod item_flag;
|
||||
mod refs;
|
||||
mod update;
|
||||
#[cfg(test)]
|
||||
mod utils;
|
||||
|
||||
pub use any::Any;
|
||||
pub(crate) use content::Content;
|
||||
pub use delete_set::DeleteSet;
|
||||
pub use id::{Client, Clock, Id};
|
||||
pub use io::{CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, RawDecoder, RawEncoder};
|
||||
pub(crate) use item::{Item, ItemRef, Parent};
|
||||
pub(crate) use item_flag::{item_flags, ItemFlag};
|
||||
pub(crate) use refs::Node;
|
||||
pub use update::Update;
|
||||
#[cfg(test)]
|
||||
pub(crate) use utils::*;
|
||||
|
||||
use super::*;
|
||||
@@ -0,0 +1,480 @@
|
||||
use super::*;
|
||||
|
||||
// make fields Copy + Clone without much effort
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(all(test, not(loom)), derive(proptest_derive::Arbitrary))]
|
||||
pub(crate) enum Node {
|
||||
GC(Box<NodeLen>),
|
||||
Skip(Box<NodeLen>),
|
||||
Item(ItemRef),
|
||||
}
|
||||
|
||||
/// Simple representation of id and len struct used by GC and Skip node.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(all(test, not(loom)), derive(proptest_derive::Arbitrary))]
|
||||
pub(crate) struct NodeLen {
|
||||
pub id: Id,
|
||||
pub len: u64,
|
||||
}
|
||||
|
||||
impl<W: CrdtWriter> CrdtWrite<W> for Node {
|
||||
fn write(&self, writer: &mut W) -> JwstCodecResult {
|
||||
match self {
|
||||
Node::GC(item) => {
|
||||
writer.write_info(0)?;
|
||||
writer.write_var_u64(item.len)
|
||||
}
|
||||
Node::Skip(item) => {
|
||||
writer.write_info(10)?;
|
||||
writer.write_var_u64(item.len)
|
||||
}
|
||||
Node::Item(item) => item.get().unwrap().write(writer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Node {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Node::GC(left), Node::GC(right)) => left.id == right.id,
|
||||
(Node::Skip(left), Node::Skip(right)) => left.id == right.id,
|
||||
(Node::Item(item1), Node::Item(item2)) => item1.get() == item2.get(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Node {
|
||||
fn assert_receiver_is_total_eq(&self) {}
|
||||
}
|
||||
|
||||
impl From<Item> for Node {
|
||||
fn from(value: Item) -> Self {
|
||||
Self::Item(Somr::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn new_skip(id: Id, len: u64) -> Self {
|
||||
Self::Skip(Box::new(NodeLen { id, len }))
|
||||
}
|
||||
|
||||
pub fn new_gc(id: Id, len: u64) -> Self {
|
||||
Self::GC(Box::new(NodeLen { id, len }))
|
||||
}
|
||||
|
||||
pub fn read<R: CrdtReader>(decoder: &mut R, id: Id) -> JwstCodecResult<Self> {
|
||||
let info = decoder.read_info()?;
|
||||
let first_5_bit = info & 0b11111;
|
||||
|
||||
match first_5_bit {
|
||||
0 => {
|
||||
let len = decoder.read_var_u64()?;
|
||||
Ok(Node::new_gc(id, len))
|
||||
}
|
||||
10 => {
|
||||
let len = decoder.read_var_u64()?;
|
||||
Ok(Node::new_skip(id, len))
|
||||
}
|
||||
_ => {
|
||||
let item = Somr::new(Item::read(decoder, id, info, first_5_bit)?);
|
||||
|
||||
if let Content::Type(ty) = &item.get().unwrap().content {
|
||||
if let Some(mut ty) = ty.ty_mut() {
|
||||
ty.item = item.clone();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Node::Item(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Id {
|
||||
match self {
|
||||
Node::GC(item) => item.id,
|
||||
Node::Skip(item) => item.id,
|
||||
Node::Item(item) => unsafe { item.get_unchecked() }.id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client(&self) -> Client {
|
||||
self.id().client
|
||||
}
|
||||
|
||||
pub fn clock(&self) -> Clock {
|
||||
self.id().clock
|
||||
}
|
||||
|
||||
pub fn len(&self) -> u64 {
|
||||
match self {
|
||||
Self::GC(item) => item.len,
|
||||
Self::Skip(item) => item.len,
|
||||
Self::Item(item) => unsafe { item.get_unchecked() }.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_gc(&self) -> bool {
|
||||
matches!(self, Self::GC { .. })
|
||||
}
|
||||
|
||||
pub fn is_skip(&self) -> bool {
|
||||
matches!(self, Self::Skip { .. })
|
||||
}
|
||||
|
||||
pub fn is_item(&self) -> bool {
|
||||
matches!(self, Self::Item(_))
|
||||
}
|
||||
|
||||
pub fn as_item(&self) -> Somr<Item> {
|
||||
if let Self::Item(item) = self {
|
||||
item.clone()
|
||||
} else {
|
||||
Somr::none()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn left(&self) -> Option<Self> {
|
||||
if let Node::Item(item) = self {
|
||||
item.get().map(|item| Node::Item(item.left.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn right(&self) -> Option<Self> {
|
||||
if let Node::Item(item) = self {
|
||||
item.get().map(|item| Node::Item(item.right.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn head(&self) -> Self {
|
||||
let mut cur = self.clone();
|
||||
|
||||
while let Some(left) = cur.left() {
|
||||
if left.is_item() {
|
||||
cur = left
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cur
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn tail(&self) -> Self {
|
||||
let mut cur = self.clone();
|
||||
|
||||
while let Some(right) = cur.right() {
|
||||
if right.is_item() {
|
||||
cur = right
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cur
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> ItemFlag {
|
||||
if let Node::Item(item) = self {
|
||||
item.get().unwrap().flags.clone()
|
||||
} else {
|
||||
// deleted
|
||||
ItemFlag::from(4)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_id(&self) -> Option<Id> {
|
||||
if let Node::Item(item) = self {
|
||||
item.get().map(|item| item.last_id())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split_at(&self, offset: u64) -> JwstCodecResult<(Self, Self)> {
|
||||
if let Self::Item(item) = self {
|
||||
let item = item.get().unwrap();
|
||||
debug_assert!(offset > 0 && item.len() > 1 && offset < item.len());
|
||||
let id = item.id;
|
||||
let right_id = Id::new(id.client, id.clock + offset);
|
||||
let (left_content, right_content) = item.content.split(offset)?;
|
||||
|
||||
let left_item = Somr::new(Item::new(
|
||||
id,
|
||||
left_content,
|
||||
// let caller connect left <-> node <-> right
|
||||
Somr::none(),
|
||||
Somr::none(),
|
||||
item.parent.clone(),
|
||||
item.parent_sub.clone(),
|
||||
));
|
||||
|
||||
let right_item = Somr::new(Item::new(
|
||||
right_id,
|
||||
right_content,
|
||||
// let caller connect left <-> node <-> right
|
||||
Somr::none(),
|
||||
Somr::none(),
|
||||
item.parent.clone(),
|
||||
item.parent_sub.clone(),
|
||||
));
|
||||
|
||||
Ok((Self::Item(left_item), Self::Item(right_item)))
|
||||
} else {
|
||||
Err(JwstCodecError::ItemSplitNotSupport)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub fn countable(&self) -> bool {
|
||||
self.flags().countable()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn deleted(&self) -> bool {
|
||||
self.flags().deleted()
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, right: Self) -> bool {
|
||||
match (self, right) {
|
||||
(Node::GC(left), Node::GC(right)) => {
|
||||
left.len += right.len;
|
||||
}
|
||||
(Node::Skip(left), Node::Skip(right)) => {
|
||||
left.len += right.len;
|
||||
}
|
||||
(Node::Item(lref), Node::Item(rref)) => {
|
||||
let mut litem = unsafe { lref.get_mut_unchecked() };
|
||||
let mut ritem = unsafe { rref.get_mut_unchecked() };
|
||||
let llen = litem.len();
|
||||
|
||||
if litem.id.client != ritem.id.client
|
||||
// not same delete status
|
||||
|| litem.deleted() != ritem.deleted()
|
||||
// not clock continuous
|
||||
|| litem.id.clock + litem.len() != ritem.id.clock
|
||||
// not insertion continuous
|
||||
|| Some(litem.last_id()) != ritem.origin_left_id
|
||||
// not insertion continuous
|
||||
|| litem.origin_right_id != ritem.origin_right_id
|
||||
// not runtime continuous
|
||||
|| litem.right != rref
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
match (&mut litem.content, &mut ritem.content) {
|
||||
(Content::Deleted(l), Content::Deleted(r)) => {
|
||||
*l += *r;
|
||||
}
|
||||
(Content::Json(l), Content::Json(r)) => {
|
||||
l.extend(r.drain(0..));
|
||||
}
|
||||
(Content::String(l), Content::String(r)) => {
|
||||
*l += r;
|
||||
}
|
||||
(Content::Any(l), Content::Any(r)) => {
|
||||
l.extend(r.drain(0..));
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Parent::Type(p)) = &litem.parent {
|
||||
if let Some(parent) = p.ty_mut() {
|
||||
if let Some(markers) = &parent.markers {
|
||||
markers.replace_marker(rref.clone(), lref.clone(), -(llen as i64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ritem.keep() {
|
||||
litem.flags.set_keep()
|
||||
}
|
||||
|
||||
litem.right = ritem.right.clone();
|
||||
unsafe {
|
||||
if litem.right.is_some() {
|
||||
litem.right.get_mut_unchecked().left = lref.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<Node>> for Somr<Item> {
|
||||
fn from(value: Option<Node>) -> Self {
|
||||
match value {
|
||||
Some(n) => n.as_item(),
|
||||
None => Somr::none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Option<Node>> for Somr<Item> {
|
||||
fn from(value: &Option<Node>) -> Self {
|
||||
match value {
|
||||
Some(n) => n.as_item(),
|
||||
None => Somr::none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<&Node>> for Somr<Item> {
|
||||
fn from(value: Option<&Node>) -> Self {
|
||||
match value {
|
||||
Some(n) => n.as_item(),
|
||||
None => Somr::none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[cfg(not(loom))]
|
||||
use proptest::{collection::vec, prelude::*};
|
||||
|
||||
use super::{utils::ItemBuilder, *};
|
||||
|
||||
#[test]
|
||||
fn test_struct_info() {
|
||||
loom_model!({
|
||||
{
|
||||
let struct_info = Node::new_gc(Id::new(1, 0), 10);
|
||||
assert_eq!(struct_info.len(), 10);
|
||||
assert_eq!(struct_info.client(), 1);
|
||||
assert_eq!(struct_info.clock(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
let struct_info = Node::new_skip(Id::new(2, 0), 20);
|
||||
assert_eq!(struct_info.len(), 20);
|
||||
assert_eq!(struct_info.client(), 2);
|
||||
assert_eq!(struct_info.clock(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
let item = ItemBuilder::new()
|
||||
.id((3, 0).into())
|
||||
.left_id(None)
|
||||
.right_id(None)
|
||||
.parent(Some(Parent::String(SmolStr::new_inline("parent"))))
|
||||
.parent_sub(None)
|
||||
.content(Content::String(String::from("content")))
|
||||
.build();
|
||||
let struct_info = Node::Item(Somr::new(item));
|
||||
|
||||
assert_eq!(struct_info.len(), 7);
|
||||
assert_eq!(struct_info.client(), 3);
|
||||
assert_eq!(struct_info.clock(), 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_write_struct_info() {
|
||||
loom_model!({
|
||||
let has_not_parent_id_and_has_parent = Node::Item(Somr::new(
|
||||
ItemBuilder::new()
|
||||
.id((0, 0).into())
|
||||
.left_id(None)
|
||||
.right_id(None)
|
||||
.parent(Some(Parent::String(SmolStr::new_inline("parent"))))
|
||||
.parent_sub(None)
|
||||
.content(Content::String(String::from("content")))
|
||||
.build(),
|
||||
));
|
||||
|
||||
let has_not_parent_id_and_has_parent_with_key = Node::Item(Somr::new(
|
||||
ItemBuilder::new()
|
||||
.id((0, 0).into())
|
||||
.left_id(None)
|
||||
.right_id(None)
|
||||
.parent(Some(Parent::String(SmolStr::new_inline("parent"))))
|
||||
.parent_sub(Some(SmolStr::new_inline("parent_sub")))
|
||||
.content(Content::String(String::from("content")))
|
||||
.build(),
|
||||
));
|
||||
|
||||
let has_parent_id = Node::Item(Somr::new(
|
||||
ItemBuilder::new()
|
||||
.id((0, 0).into())
|
||||
.left_id(Some((1, 2).into()))
|
||||
.right_id(Some((2, 5).into()))
|
||||
.parent(None)
|
||||
.parent_sub(None)
|
||||
.content(Content::String(String::from("content")))
|
||||
.build(),
|
||||
));
|
||||
|
||||
let struct_infos = vec![
|
||||
Node::new_gc((0, 0).into(), 42),
|
||||
Node::new_skip((0, 0).into(), 314),
|
||||
has_not_parent_id_and_has_parent,
|
||||
has_not_parent_id_and_has_parent_with_key,
|
||||
has_parent_id,
|
||||
];
|
||||
|
||||
for info in struct_infos {
|
||||
let mut encoder = RawEncoder::default();
|
||||
info.write(&mut encoder).unwrap();
|
||||
|
||||
let update = encoder.into_inner();
|
||||
let mut decoder = RawDecoder::new(&update);
|
||||
let decoded = Node::read(&mut decoder, info.id()).unwrap();
|
||||
|
||||
assert_eq!(info, decoded);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(loom))]
|
||||
fn struct_info_round_trip(info: &mut Node) -> JwstCodecResult {
|
||||
if let Node::Item(item) = info {
|
||||
if let Some(item) = item.get_mut() {
|
||||
if !item.is_valid() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if item.content.countable() {
|
||||
item.flags.set_countable();
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut encoder = RawEncoder::default();
|
||||
info.write(&mut encoder)?;
|
||||
|
||||
let ret = encoder.into_inner();
|
||||
let mut decoder = RawDecoder::new(&ret);
|
||||
|
||||
let decoded = Node::read(&mut decoder, info.id())?;
|
||||
|
||||
assert_eq!(info, &decoded);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(loom))]
|
||||
proptest! {
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_random_struct_info(mut infos in vec(any::<Node>(), 0..10)) {
|
||||
for info in &mut infos {
|
||||
struct_info_round_trip(info).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,721 @@
|
||||
use std::{collections::VecDeque, ops::Range};
|
||||
|
||||
use super::*;
|
||||
use crate::doc::StateVector;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Update {
|
||||
pub(crate) structs: ClientMap<VecDeque<Node>>,
|
||||
pub(crate) delete_set: DeleteSet,
|
||||
|
||||
/// all unapplicable items that we can't integrate into doc
|
||||
/// any item with inconsistent id clock or missing dependency will be put
|
||||
/// here
|
||||
pub(crate) pending_structs: ClientMap<VecDeque<Node>>,
|
||||
/// missing state vector after applying updates
|
||||
pub(crate) missing_state: StateVector,
|
||||
/// all unapplicable delete set
|
||||
pub(crate) pending_delete_set: DeleteSet,
|
||||
}
|
||||
|
||||
impl<R: CrdtReader> CrdtRead<R> for Update {
|
||||
fn read(decoder: &mut R) -> JwstCodecResult<Self> {
|
||||
let num_of_clients = decoder.read_var_u64()? as usize;
|
||||
|
||||
// See: [HASHMAP_SAFE_CAPACITY]
|
||||
let mut map = ClientMap::with_capacity(num_of_clients.min(HASHMAP_SAFE_CAPACITY));
|
||||
for _ in 0..num_of_clients {
|
||||
let num_of_structs = decoder.read_var_u64()? as usize;
|
||||
let client = decoder.read_var_u64()?;
|
||||
let mut clock = decoder.read_var_u64()?;
|
||||
|
||||
// same reason as above
|
||||
let mut structs = VecDeque::with_capacity(num_of_structs.min(HASHMAP_SAFE_CAPACITY));
|
||||
|
||||
for _ in 0..num_of_structs {
|
||||
let struct_info = Node::read(decoder, Id::new(client, clock))?;
|
||||
clock += struct_info.len();
|
||||
structs.push_back(struct_info);
|
||||
}
|
||||
|
||||
structs.shrink_to_fit();
|
||||
map.insert(client, structs);
|
||||
}
|
||||
|
||||
map.shrink_to_fit();
|
||||
|
||||
let delete_set = DeleteSet::read(decoder)?;
|
||||
|
||||
if !decoder.is_empty() {
|
||||
return Err(JwstCodecError::UpdateNotFullyConsumed(
|
||||
decoder.len() as usize
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Update {
|
||||
structs: map,
|
||||
delete_set,
|
||||
..Update::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: CrdtWriter> CrdtWrite<W> for Update {
|
||||
fn write(&self, encoder: &mut W) -> JwstCodecResult {
|
||||
encoder.write_var_u64(self.structs.len() as u64)?;
|
||||
|
||||
let mut clients = self.structs.keys().copied().collect::<Vec<_>>();
|
||||
|
||||
// Descending
|
||||
clients.sort_by(|a, b| b.cmp(a));
|
||||
|
||||
for client in clients {
|
||||
let structs = self.structs.get(&client).unwrap();
|
||||
|
||||
encoder.write_var_u64(structs.len() as u64)?;
|
||||
encoder.write_var_u64(client)?;
|
||||
encoder.write_var_u64(structs.front().map(|s| s.clock()).unwrap_or(0))?;
|
||||
|
||||
for struct_info in structs {
|
||||
struct_info.write(encoder)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.delete_set.write(encoder)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Update {
|
||||
// decode from ydoc v1
|
||||
pub fn decode_v1<T: AsRef<[u8]>>(buffer: T) -> JwstCodecResult<Update> {
|
||||
Update::read(&mut RawDecoder::new(buffer.as_ref()))
|
||||
}
|
||||
|
||||
pub fn encode_v1(&self) -> JwstCodecResult<Vec<u8>> {
|
||||
let mut encoder = RawEncoder::default();
|
||||
self.write(&mut encoder)?;
|
||||
Ok(encoder.into_inner())
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&mut self, state: StateVector) -> UpdateIterator {
|
||||
UpdateIterator::new(self, state)
|
||||
}
|
||||
|
||||
pub fn delete_set_iter(&mut self, state: StateVector) -> DeleteSetIterator {
|
||||
DeleteSetIterator::new(self, state)
|
||||
}
|
||||
|
||||
// take all pending structs and delete set to [self] update struct
|
||||
pub fn drain_pending_state(&mut self) {
|
||||
debug_assert!(self.is_empty());
|
||||
|
||||
std::mem::swap(&mut self.pending_structs, &mut self.structs);
|
||||
std::mem::swap(&mut self.pending_delete_set, &mut self.delete_set);
|
||||
}
|
||||
|
||||
pub fn merge<I: IntoIterator<Item = Update>>(updates: I) -> Update {
|
||||
let mut merged = Update::default();
|
||||
|
||||
Self::merge_into(&mut merged, updates);
|
||||
|
||||
merged
|
||||
}
|
||||
|
||||
pub fn merge_into<I: IntoIterator<Item = Update>>(target: &mut Update, updates: I) {
|
||||
for update in updates {
|
||||
target.delete_set.merge(&update.delete_set);
|
||||
|
||||
for (client, structs) in update.structs {
|
||||
let iter = structs.into_iter().filter(|p| !p.is_skip());
|
||||
if let Some(merged_structs) = target.structs.get_mut(&client) {
|
||||
merged_structs.extend(iter);
|
||||
} else {
|
||||
target.structs.insert(client, iter.collect());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for structs in target.structs.values_mut() {
|
||||
structs.make_contiguous().sort_by_key(|s| s.id().clock);
|
||||
|
||||
// insert [Node::Skip] if structs[index].id().clock + structs[index].len() <
|
||||
// structs[index + 1].id().clock
|
||||
let mut index = 0;
|
||||
let mut merged_index = vec![];
|
||||
while index < structs.len() - 1 {
|
||||
let cur = &structs[index];
|
||||
let next = &structs[index + 1];
|
||||
|
||||
let clock_end = cur.id().clock + cur.len();
|
||||
let next_clock = next.id().clock;
|
||||
|
||||
if next_clock > clock_end {
|
||||
structs.insert(
|
||||
index + 1,
|
||||
Node::new_skip((cur.id().client, clock_end).into(), next_clock - clock_end),
|
||||
);
|
||||
index += 1;
|
||||
} else if cur.id().clock == next_clock {
|
||||
if cur.deleted() == next.deleted()
|
||||
&& cur.last_id() == next.last_id()
|
||||
&& cur.left() == next.left()
|
||||
&& cur.right() == next.right()
|
||||
{
|
||||
// merge two nodes, mark the index
|
||||
merged_index.push(index + 1);
|
||||
} else {
|
||||
debug!("merge failed: {:?} {:?}", cur, next)
|
||||
}
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
|
||||
{
|
||||
// prune the merged nodes
|
||||
let mut new_structs = VecDeque::with_capacity(structs.len() - merged_index.len());
|
||||
let mut next_remove_idx = 0;
|
||||
for (idx, val) in structs.drain(..).enumerate() {
|
||||
if next_remove_idx < merged_index.len() && idx == merged_index[next_remove_idx] {
|
||||
next_remove_idx += 1;
|
||||
} else {
|
||||
new_structs.push_back(val);
|
||||
}
|
||||
}
|
||||
structs.extend(new_structs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_content_empty(&self) -> bool {
|
||||
self.structs.is_empty()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.structs.is_empty() && self.delete_set.is_empty()
|
||||
}
|
||||
|
||||
pub fn is_pending_empty(&self) -> bool {
|
||||
self.pending_structs.is_empty() && self.pending_delete_set.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct UpdateIterator<'a> {
|
||||
update: &'a mut Update,
|
||||
|
||||
// --- local iterator state ---
|
||||
/// current state vector from store
|
||||
state: StateVector,
|
||||
/// all client ids sorted ascending
|
||||
client_ids: Vec<Client>,
|
||||
/// current id of client of the updates we're processing
|
||||
cur_client_id: Option<Client>,
|
||||
/// stack of previous iterating item with higher priority than updates in
|
||||
/// next iteration
|
||||
stack: Vec<Node>,
|
||||
}
|
||||
|
||||
impl<'a> UpdateIterator<'a> {
|
||||
pub fn new(update: &'a mut Update, state: StateVector) -> Self {
|
||||
let mut client_ids = update.structs.keys().cloned().collect::<Vec<_>>();
|
||||
client_ids.sort();
|
||||
let cur_client_id = client_ids.pop();
|
||||
|
||||
UpdateIterator {
|
||||
update,
|
||||
state,
|
||||
client_ids,
|
||||
cur_client_id,
|
||||
stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// iterate the client ids until we find the next client with left updates
|
||||
/// that can be consumed
|
||||
///
|
||||
/// note:
|
||||
/// firstly we will check current client id as well to ensure current
|
||||
/// updates queue is not empty yet
|
||||
fn next_client(&mut self) -> Option<Client> {
|
||||
while let Some(client_id) = self.cur_client_id {
|
||||
match self.update.structs.get(&client_id) {
|
||||
Some(refs) if !refs.is_empty() => {
|
||||
self.cur_client_id.replace(client_id);
|
||||
return self.cur_client_id;
|
||||
}
|
||||
_ => {
|
||||
self.update.structs.remove(&client_id);
|
||||
self.cur_client_id = self.client_ids.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// update the missing state vector
|
||||
/// tell it the smallest clock that missed.
|
||||
fn update_missing_state(&mut self, client: Client, clock: Clock) {
|
||||
self.update.missing_state.set_min(client, clock);
|
||||
}
|
||||
|
||||
/// any time we can't apply an update during the iteration,
|
||||
/// we should put all items in pending stack to rest structs
|
||||
fn add_stack_to_rest(&mut self) {
|
||||
for s in self.stack.drain(..) {
|
||||
let client = s.id().client;
|
||||
let unapplicable_items = self.update.structs.remove(&client);
|
||||
if let Some(mut items) = unapplicable_items {
|
||||
items.push_front(s);
|
||||
self.update.pending_structs.insert(client, items);
|
||||
} else {
|
||||
self.update.pending_structs.insert(client, [s].into());
|
||||
}
|
||||
self.client_ids.retain(|&c| c != client);
|
||||
}
|
||||
}
|
||||
|
||||
/// tell if current update's dependencies(left, right, parent) has already
|
||||
/// been consumed and recorded and return the client of them if not.
|
||||
fn get_missing_dep(&self, struct_info: &Node) -> Option<Client> {
|
||||
if let Some(item) = struct_info.as_item().get() {
|
||||
let id = item.id;
|
||||
if let Some(left) = &item.origin_left_id {
|
||||
if left.client != id.client && left.clock >= self.state.get(&left.client) {
|
||||
return Some(left.client);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(right) = &item.origin_right_id {
|
||||
if right.client != id.client && right.clock >= self.state.get(&right.client) {
|
||||
return Some(right.client);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(parent) = &item.parent {
|
||||
match parent {
|
||||
Parent::Id(parent_id)
|
||||
if parent_id.client != id.client
|
||||
&& parent_id.clock >= self.state.get(&parent_id.client) =>
|
||||
{
|
||||
return Some(parent_id.client);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn next_candidate(&mut self) -> Option<Node> {
|
||||
let mut cur = None;
|
||||
|
||||
if !self.stack.is_empty() {
|
||||
cur.replace(self.stack.pop().unwrap());
|
||||
} else if let Some(client) = self.next_client() {
|
||||
// Safety:
|
||||
// client index of updates and update length are both checked in next_client
|
||||
// safe to use unwrap
|
||||
cur.replace(
|
||||
self
|
||||
.update
|
||||
.structs
|
||||
.get_mut(&client)
|
||||
.unwrap()
|
||||
.pop_front()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
cur
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for UpdateIterator<'_> {
|
||||
type Item = (Node, u64);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// fetch the first candidate from stack or updates
|
||||
let mut cur = self.next_candidate();
|
||||
|
||||
while let Some(cur_update) = cur.take() {
|
||||
let id = cur_update.id();
|
||||
if cur_update.is_skip() {
|
||||
cur = self.next_candidate();
|
||||
continue;
|
||||
} else if !self.state.contains(&id) {
|
||||
// missing local state of same client
|
||||
// can't apply the continuous updates from same client
|
||||
// push into the stack and put tell all the items in stack are unapplicable
|
||||
self.stack.push(cur_update);
|
||||
self.update_missing_state(id.client, id.clock - 1);
|
||||
self.add_stack_to_rest();
|
||||
} else {
|
||||
let id = cur_update.id();
|
||||
let dep = self.get_missing_dep(&cur_update);
|
||||
// some dependency is missing, we need to turn to iterate the dependency first.
|
||||
if let Some(dep) = dep {
|
||||
self.stack.push(cur_update);
|
||||
|
||||
match self.update.structs.get_mut(&dep) {
|
||||
Some(updates) if !updates.is_empty() => {
|
||||
// iterate the dependency client first
|
||||
cur.replace(updates.pop_front().unwrap());
|
||||
continue;
|
||||
}
|
||||
// but the dependency update is drained
|
||||
// need to move all stack item to unapplicable store
|
||||
_ => {
|
||||
self.update_missing_state(dep, self.state.get(&dep));
|
||||
self.add_stack_to_rest();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we finally find the first applicable update
|
||||
let local_state = self.state.get(&id.client);
|
||||
// we've already check the local state is greater or equal to current update's
|
||||
// clock so offset here will never be negative
|
||||
let offset = local_state - id.clock;
|
||||
if offset == 0 || offset < cur_update.len() {
|
||||
self.state.set_max(id.client, id.clock + cur_update.len());
|
||||
return Some((cur_update, offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cur = self.next_candidate();
|
||||
}
|
||||
|
||||
// we all done
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeleteSetIterator<'a> {
|
||||
update: &'a mut Update,
|
||||
/// current state vector from store
|
||||
state: StateVector,
|
||||
}
|
||||
|
||||
impl<'a> DeleteSetIterator<'a> {
|
||||
pub fn new(update: &'a mut Update, state: StateVector) -> Self {
|
||||
DeleteSetIterator { update, state }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for DeleteSetIterator<'_> {
|
||||
type Item = (Client, Range<u64>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(client) = self.update.delete_set.keys().next().cloned() {
|
||||
let deletes = self.update.delete_set.get_mut(&client).unwrap();
|
||||
let local_state = self.state.get(&client);
|
||||
|
||||
while let Some(range) = deletes.pop() {
|
||||
let start = range.start;
|
||||
let end = range.end;
|
||||
|
||||
if start < local_state {
|
||||
if local_state < end {
|
||||
// partially state missing
|
||||
// [start..end)
|
||||
// ^ local_state in between
|
||||
// // split
|
||||
// [start..local_state) [local_state..end)
|
||||
// ^^^^^ unapplicable
|
||||
self
|
||||
.update
|
||||
.pending_delete_set
|
||||
.add(client, local_state, end - local_state);
|
||||
|
||||
return Some((client, start..local_state));
|
||||
}
|
||||
|
||||
return Some((client, range));
|
||||
} else {
|
||||
// all state missing
|
||||
self
|
||||
.update
|
||||
.pending_delete_set
|
||||
.add(client, start, end - start);
|
||||
}
|
||||
}
|
||||
|
||||
self.update.delete_set.remove(&client);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{num::ParseIntError, path::PathBuf};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::*;
|
||||
use crate::doc::common::OrderRange;
|
||||
|
||||
fn struct_item(id: (Client, Clock), len: usize) -> Node {
|
||||
Node::Item(Somr::new(
|
||||
ItemBuilder::new()
|
||||
.id(id.into())
|
||||
.content(Content::String("c".repeat(len)))
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_doc_update(input: Vec<u8>) -> JwstCodecResult<Update> {
|
||||
Update::decode_v1(input)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(any(miri, loom), ignore)]
|
||||
fn test_parse_doc() {
|
||||
let docs = [
|
||||
(include_bytes!("../../fixtures/basic.bin").to_vec(), 1, 188),
|
||||
(
|
||||
include_bytes!("../../fixtures/database.bin").to_vec(),
|
||||
1,
|
||||
149,
|
||||
),
|
||||
(include_bytes!("../../fixtures/large.bin").to_vec(), 1, 9036),
|
||||
(
|
||||
include_bytes!("../../fixtures/with-subdoc.bin").to_vec(),
|
||||
2,
|
||||
30,
|
||||
),
|
||||
(
|
||||
include_bytes!("../../fixtures/edge-case-left-right-same-node.bin").to_vec(),
|
||||
2,
|
||||
243,
|
||||
),
|
||||
];
|
||||
|
||||
for (doc, clients, structs) in docs {
|
||||
let update = parse_doc_update(doc).unwrap();
|
||||
|
||||
assert_eq!(update.structs.len(), clients);
|
||||
assert_eq!(
|
||||
update.structs.iter().map(|s| s.1.len()).sum::<usize>(),
|
||||
structs
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_hex(s: &str) -> Result<Vec<u8>, ParseIntError> {
|
||||
(0..s.len())
|
||||
.step_by(2)
|
||||
.map(|i| u8::from_str_radix(&s[i..i + 2], 16))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Data {
|
||||
id: u64,
|
||||
workspace: String,
|
||||
timestamp: String,
|
||||
blob: String,
|
||||
}
|
||||
|
||||
#[ignore = "just for local data test"]
|
||||
#[test]
|
||||
fn test_parse_local_doc() {
|
||||
let json =
|
||||
serde_json::from_slice::<Vec<Data>>(include_bytes!("../../fixtures/local_docs.json"))
|
||||
.unwrap();
|
||||
|
||||
for ws in json {
|
||||
let data = &ws.blob[5..=(ws.blob.len() - 2)];
|
||||
if let Ok(data) = decode_hex(data) {
|
||||
match parse_doc_update(data.clone()) {
|
||||
Ok(update) => {
|
||||
println!(
|
||||
"workspace: {}, global structs: {}, total structs: {}",
|
||||
ws.workspace,
|
||||
update.structs.len(),
|
||||
update.structs.iter().map(|s| s.1.len()).sum::<usize>()
|
||||
);
|
||||
}
|
||||
Err(_e) => {
|
||||
std::fs::write(
|
||||
PathBuf::from("./src/fixtures/invalid").join(format!("{}.ydoc", ws.workspace)),
|
||||
data,
|
||||
)
|
||||
.unwrap();
|
||||
println!("doc error: {}", ws.workspace);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("error origin data: {}", ws.workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_iterator() {
|
||||
loom_model!({
|
||||
let mut update = Update {
|
||||
structs: ClientMap::from_iter([
|
||||
(
|
||||
0,
|
||||
VecDeque::from([
|
||||
struct_item((0, 0), 1),
|
||||
struct_item((0, 1), 1),
|
||||
Node::new_skip((0, 2).into(), 1),
|
||||
]),
|
||||
),
|
||||
(
|
||||
1,
|
||||
VecDeque::from([
|
||||
struct_item((1, 0), 1),
|
||||
Node::Item(Somr::new(
|
||||
ItemBuilder::new()
|
||||
.id((1, 1).into())
|
||||
.left_id(Some((0, 1).into()))
|
||||
.content(Content::String("c".repeat(2)))
|
||||
.build(),
|
||||
)),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
..Update::default()
|
||||
};
|
||||
|
||||
let mut iter = update.iter(StateVector::default());
|
||||
assert_eq!(iter.next().unwrap().0.id(), (1, 0).into());
|
||||
assert_eq!(iter.next().unwrap().0.id(), (0, 0).into());
|
||||
assert_eq!(iter.next().unwrap().0.id(), (0, 1).into());
|
||||
assert_eq!(iter.next().unwrap().0.id(), (1, 1).into());
|
||||
assert_eq!(iter.next(), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_iterator_with_missing_state() {
|
||||
loom_model!({
|
||||
let mut update = Update {
|
||||
// an item with higher sequence id than local state
|
||||
structs: ClientMap::from_iter([(0, VecDeque::from([struct_item((0, 4), 1)]))]),
|
||||
..Update::default()
|
||||
};
|
||||
|
||||
let mut iter = update.iter(StateVector::from([(0, 3)]));
|
||||
assert_eq!(iter.next(), None);
|
||||
assert!(!update.pending_structs.is_empty());
|
||||
assert_eq!(
|
||||
update
|
||||
.pending_structs
|
||||
.get_mut(&0)
|
||||
.unwrap()
|
||||
.pop_front()
|
||||
.unwrap()
|
||||
.id(),
|
||||
(0, 4).into()
|
||||
);
|
||||
assert!(!update.missing_state.is_empty());
|
||||
assert_eq!(update.missing_state.get(&0), 3);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_set_iterator() {
|
||||
let mut update = Update {
|
||||
delete_set: DeleteSet::from([(0, vec![(0..2), (3..5)])]),
|
||||
..Update::default()
|
||||
};
|
||||
|
||||
let mut iter = update.delete_set_iter(StateVector::from([(0, 10)]));
|
||||
assert_eq!(iter.next().unwrap(), (0, 0..2));
|
||||
assert_eq!(iter.next().unwrap(), (0, 3..5));
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_set_with_missing_state() {
|
||||
let mut update = Update {
|
||||
delete_set: DeleteSet::from([(0, vec![(3..5), (7..12), (13..15)])]),
|
||||
..Update::default()
|
||||
};
|
||||
|
||||
let mut iter = update.delete_set_iter(StateVector::from([(0, 10)]));
|
||||
assert_eq!(iter.next().unwrap(), (0, 3..5));
|
||||
assert_eq!(iter.next().unwrap(), (0, 7..10));
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
assert!(!update.pending_delete_set.is_empty());
|
||||
assert_eq!(
|
||||
update.pending_delete_set.get(&0).unwrap(),
|
||||
&OrderRange::from(vec![(10..12), (13..15)])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_add_skip_when_clock_not_continuous() {
|
||||
loom_model!({
|
||||
let update = Update {
|
||||
structs: ClientMap::from_iter([(
|
||||
0,
|
||||
VecDeque::from([
|
||||
struct_item((0, 0), 1),
|
||||
struct_item((0, 1), 1),
|
||||
struct_item((0, 10), 1),
|
||||
Node::new_gc((0, 20).into(), 10),
|
||||
]),
|
||||
)]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let merged = Update::merge([update]);
|
||||
|
||||
assert_eq!(
|
||||
merged.structs.get(&0).unwrap(),
|
||||
&VecDeque::from([
|
||||
struct_item((0, 0), 1),
|
||||
struct_item((0, 1), 1),
|
||||
Node::new_skip((0, 2).into(), 8),
|
||||
struct_item((0, 10), 1),
|
||||
Node::new_skip((0, 11).into(), 9),
|
||||
Node::new_gc((0, 20).into(), 10),
|
||||
])
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merged_update_should_not_be_released_in_next_turn() {
|
||||
loom_model!({
|
||||
let update = Update {
|
||||
structs: ClientMap::from_iter([(
|
||||
0,
|
||||
VecDeque::from([
|
||||
struct_item((0, 0), 1),
|
||||
struct_item((0, 1), 1),
|
||||
struct_item((0, 10), 1),
|
||||
Node::new_gc((0, 20).into(), 10),
|
||||
]),
|
||||
)]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let merged = Update::merge([update]);
|
||||
|
||||
let update2 = Update {
|
||||
structs: ClientMap::from_iter([(
|
||||
0,
|
||||
VecDeque::from([struct_item((0, 30), 1), Node::new_gc((0, 32).into(), 1)]),
|
||||
)]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let merged2 = Update::merge([update2, merged]);
|
||||
|
||||
assert_eq!(merged2.structs.get(&0).unwrap().len(), 9);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
use super::*;
|
||||
|
||||
pub(crate) struct ItemBuilder {
|
||||
item: Item,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl ItemBuilder {
|
||||
pub fn new() -> ItemBuilder {
|
||||
Self {
|
||||
item: Item::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(mut self, id: Id) -> ItemBuilder {
|
||||
self.item.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left(mut self, left: Somr<Item>) -> ItemBuilder {
|
||||
if let Some(l) = left.get() {
|
||||
self.item.origin_left_id = Some(l.last_id());
|
||||
self.item.left = left;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn right(mut self, right: Somr<Item>) -> ItemBuilder {
|
||||
if let Some(r) = right.get() {
|
||||
self.item.origin_right_id = Some(r.id);
|
||||
self.item.right = right;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_id(mut self, left_id: Option<Id>) -> ItemBuilder {
|
||||
self.item.origin_left_id = left_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn right_id(mut self, right_id: Option<Id>) -> ItemBuilder {
|
||||
self.item.origin_right_id = right_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn parent(mut self, parent: Option<Parent>) -> ItemBuilder {
|
||||
self.item.parent = parent;
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn parent_sub(mut self, parent_sub: Option<SmolStr>) -> ItemBuilder {
|
||||
self.item.parent_sub = parent_sub;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn content(mut self, content: Content) -> ItemBuilder {
|
||||
self.item.content = content;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn flags(mut self, flags: ItemFlag) -> ItemBuilder {
|
||||
self.item.flags = flags;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Item {
|
||||
if self.item.content.countable() {
|
||||
self.item.flags.set(item_flags::ITEM_COUNTABLE);
|
||||
}
|
||||
|
||||
self.item
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_item_builder() {
|
||||
loom_model!({
|
||||
let item = ItemBuilder::new()
|
||||
.id(Id::new(0, 1))
|
||||
.left_id(Some(Id::new(2, 3)))
|
||||
.right_id(Some(Id::new(4, 5)))
|
||||
.parent(Some(Parent::String("test".into())))
|
||||
.content(Content::Any(vec![Any::String("Hello".into())]))
|
||||
.build();
|
||||
|
||||
assert_eq!(item.id, Id::new(0, 1));
|
||||
assert_eq!(item.origin_left_id, Some(Id::new(2, 3)));
|
||||
assert_eq!(item.origin_right_id, Some(Id::new(4, 5)));
|
||||
assert!(matches!(item.parent, Some(Parent::String(text)) if text == "test"));
|
||||
assert_eq!(item.parent_sub, None);
|
||||
assert_eq!(
|
||||
item.content,
|
||||
Content::Any(vec![Any::String("Hello".into())])
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod items;
|
||||
|
||||
pub(crate) use items::*;
|
||||
|
||||
use super::*;
|
||||
@@ -0,0 +1,9 @@
|
||||
mod range;
|
||||
mod somr;
|
||||
mod state;
|
||||
|
||||
pub use range::*;
|
||||
pub use somr::*;
|
||||
pub use state::*;
|
||||
|
||||
use super::*;
|
||||
@@ -0,0 +1,481 @@
|
||||
use std::{collections::VecDeque, mem, ops::Range};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum OrderRange {
|
||||
Range(Range<u64>),
|
||||
Fragment(VecDeque<Range<u64>>),
|
||||
}
|
||||
|
||||
impl Default for OrderRange {
|
||||
fn default() -> Self {
|
||||
Self::Range(0..0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range<u64>> for OrderRange {
|
||||
fn from(range: Range<u64>) -> Self {
|
||||
Self::Range(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Range<u64>>> for OrderRange {
|
||||
fn from(value: Vec<Range<u64>>) -> Self {
|
||||
Self::Fragment(value.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VecDeque<Range<u64>>> for OrderRange {
|
||||
fn from(value: VecDeque<Range<u64>>) -> Self {
|
||||
Self::Fragment(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_continuous_range(lhs: &Range<u64>, rhs: &Range<u64>) -> bool {
|
||||
lhs.end >= rhs.start && lhs.start <= rhs.end
|
||||
}
|
||||
|
||||
impl OrderRange {
|
||||
pub fn ranges_len(&self) -> usize {
|
||||
match self {
|
||||
OrderRange::Range(_) => 1,
|
||||
OrderRange::Fragment(ranges) => ranges.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
OrderRange::Range(range) => range.is_empty(),
|
||||
OrderRange::Fragment(vec) => vec.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, clock: u64) -> bool {
|
||||
match self {
|
||||
OrderRange::Range(range) => range.contains(&clock),
|
||||
OrderRange::Fragment(ranges) => ranges.iter().any(|r| r.contains(&clock)),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_range_covered(old_vec: &[Range<u64>], new_vec: &[Range<u64>]) -> bool {
|
||||
let mut old_iter = old_vec.iter();
|
||||
let mut next_old = old_iter.next();
|
||||
let mut new_iter = new_vec.iter().peekable();
|
||||
let mut next_new = new_iter.next();
|
||||
'new_loop: while let Some(new_range) = next_new {
|
||||
while let Some(old_range) = next_old {
|
||||
if old_range.start < new_range.start || old_range.end > new_range.end {
|
||||
if new_iter.peek().is_some() {
|
||||
next_new = new_iter.next();
|
||||
continue 'new_loop;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
next_old = old_iter.next();
|
||||
if let Some(next_old) = &next_old {
|
||||
if next_old.start > new_range.end {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
next_new = new_iter.next();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// diff_range returns the difference between the old range and the new
|
||||
/// range. current range must be covered by the new range
|
||||
pub fn diff_range(&self, new_range: &OrderRange) -> Vec<Range<u64>> {
|
||||
let old_vec = self.clone().into_iter().collect::<Vec<_>>();
|
||||
let new_vec = new_range.clone().into_iter().collect::<Vec<_>>();
|
||||
|
||||
if !Self::check_range_covered(&old_vec, &new_vec) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut diffs = Vec::new();
|
||||
let mut old_idx = 0;
|
||||
|
||||
for new_range in &new_vec {
|
||||
let mut overlap_ranges = Vec::new();
|
||||
while old_idx < old_vec.len() && old_vec[old_idx].start <= new_range.end {
|
||||
overlap_ranges.push(old_vec[old_idx].clone());
|
||||
old_idx += 1;
|
||||
}
|
||||
|
||||
if overlap_ranges.is_empty() {
|
||||
diffs.push(new_range.clone());
|
||||
} else {
|
||||
let mut last_end = overlap_ranges[0].start;
|
||||
if last_end > new_range.start {
|
||||
diffs.push(new_range.start..last_end);
|
||||
}
|
||||
|
||||
for overlap in &overlap_ranges {
|
||||
if overlap.start > last_end {
|
||||
diffs.push(last_end..overlap.start);
|
||||
}
|
||||
last_end = overlap.end;
|
||||
}
|
||||
|
||||
if new_range.end > last_end {
|
||||
diffs.push(last_end..new_range.end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diffs
|
||||
}
|
||||
|
||||
/// Push new range to current one.
|
||||
/// Range will be merged if overlap exists or turned into fragment if it's
|
||||
/// not continuous.
|
||||
pub fn push(&mut self, range: Range<u64>) {
|
||||
match self {
|
||||
OrderRange::Range(r) => {
|
||||
if r.start == r.end {
|
||||
*self = range.into();
|
||||
} else if is_continuous_range(r, &range) {
|
||||
r.end = r.end.max(range.end);
|
||||
r.start = r.start.min(range.start);
|
||||
} else {
|
||||
*self = OrderRange::Fragment(if r.start < range.start {
|
||||
VecDeque::from([r.clone(), range])
|
||||
} else {
|
||||
VecDeque::from([range, r.clone()])
|
||||
});
|
||||
}
|
||||
}
|
||||
OrderRange::Fragment(ranges) => {
|
||||
if ranges.is_empty() {
|
||||
*self = OrderRange::Range(range);
|
||||
} else {
|
||||
OrderRange::push_inner(ranges, range);
|
||||
self.make_single();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<Range<u64>> {
|
||||
if self.is_empty() {
|
||||
None
|
||||
} else {
|
||||
match self {
|
||||
OrderRange::Range(range) => Some(mem::replace(range, 0..0)),
|
||||
OrderRange::Fragment(list) => list.pop_front(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, other: Self) {
|
||||
self.extend(&other);
|
||||
}
|
||||
|
||||
fn make_fragment(&mut self) {
|
||||
if let OrderRange::Range(range) = self {
|
||||
*self = OrderRange::Fragment(if range.is_empty() {
|
||||
VecDeque::new()
|
||||
} else {
|
||||
VecDeque::from([range.clone()])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn make_single(&mut self) {
|
||||
if let OrderRange::Fragment(ranges) = self {
|
||||
if ranges.len() == 1 {
|
||||
*self = OrderRange::Range(ranges[0].clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge all available ranges list into one.
|
||||
pub fn squash(&mut self) {
|
||||
// merge all available ranges
|
||||
if let OrderRange::Fragment(ranges) = self {
|
||||
if ranges.is_empty() {
|
||||
*self = OrderRange::Range(0..0);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut changed = false;
|
||||
let mut merged = VecDeque::with_capacity(ranges.len());
|
||||
let mut cur = ranges[0].clone();
|
||||
|
||||
for next in ranges.iter().skip(1) {
|
||||
if is_continuous_range(&cur, next) {
|
||||
cur.start = cur.start.min(next.start);
|
||||
cur.end = cur.end.max(next.end);
|
||||
changed = true;
|
||||
} else {
|
||||
merged.push_back(cur);
|
||||
cur = next.clone();
|
||||
}
|
||||
}
|
||||
merged.push_back(cur);
|
||||
|
||||
if merged.len() == 1 {
|
||||
*self = OrderRange::Range(merged[0].clone());
|
||||
} else if changed {
|
||||
mem::swap(ranges, &mut merged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_inner(list: &mut VecDeque<Range<u64>>, range: Range<u64>) {
|
||||
if list.is_empty() {
|
||||
list.push_back(range);
|
||||
} else {
|
||||
let search_result = list.binary_search_by(|r| {
|
||||
if is_continuous_range(r, &range) {
|
||||
std::cmp::Ordering::Equal
|
||||
} else if r.end < range.start {
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Greater
|
||||
}
|
||||
});
|
||||
|
||||
match search_result {
|
||||
Ok(idx) => {
|
||||
let old = &mut list[idx];
|
||||
list[idx] = old.start.min(range.start)..old.end.max(range.end);
|
||||
Self::squash_around(list, idx);
|
||||
}
|
||||
Err(idx) => {
|
||||
list.insert(idx, range);
|
||||
Self::squash_around(list, idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn squash_around(list: &mut VecDeque<Range<u64>>, idx: usize) {
|
||||
if idx > 0 {
|
||||
let prev = &list[idx - 1];
|
||||
let cur = &list[idx];
|
||||
if is_continuous_range(prev, cur) {
|
||||
list[idx - 1] = prev.start.min(cur.start)..prev.end.max(cur.end);
|
||||
list.remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if idx < list.len() - 1 {
|
||||
let next = &list[idx + 1];
|
||||
let cur = &list[idx];
|
||||
if is_continuous_range(cur, next) {
|
||||
list[idx] = cur.start.min(next.start)..cur.end.max(next.end);
|
||||
list.remove(idx + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a OrderRange {
|
||||
type Item = Range<u64>;
|
||||
type IntoIter = OrderRangeIter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
OrderRangeIter {
|
||||
range: self,
|
||||
idx: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<Range<u64>> for OrderRange {
|
||||
fn extend<T: IntoIterator<Item = Range<u64>>>(&mut self, other: T) {
|
||||
self.make_fragment();
|
||||
match self {
|
||||
OrderRange::Fragment(ranges) => {
|
||||
for range in other {
|
||||
OrderRange::push_inner(ranges, range);
|
||||
}
|
||||
|
||||
self.make_single();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OrderRangeIter<'a> {
|
||||
range: &'a OrderRange,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl Iterator for OrderRangeIter<'_> {
|
||||
type Item = Range<u64>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.range {
|
||||
OrderRange::Range(range) => {
|
||||
if self.idx == 0 {
|
||||
self.idx += 1;
|
||||
Some(range.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
OrderRange::Fragment(ranges) => {
|
||||
if self.idx < ranges.len() {
|
||||
let range = ranges[self.idx].clone();
|
||||
self.idx += 1;
|
||||
Some(range)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::single_range_in_vec_init)]
|
||||
mod tests {
|
||||
use super::OrderRange;
|
||||
#[test]
|
||||
fn test_range_push() {
|
||||
let mut range: OrderRange = (0..10).into();
|
||||
|
||||
range.push(5..15);
|
||||
assert_eq!(range, OrderRange::Range(0..15));
|
||||
|
||||
// turn to fragment
|
||||
range.push(20..30);
|
||||
assert_eq!(range, OrderRange::from(vec![(0..15), (20..30)]));
|
||||
|
||||
// auto merge
|
||||
range.push(15..16);
|
||||
assert_eq!(range, OrderRange::from(vec![(0..16), (20..30)]));
|
||||
|
||||
// squash
|
||||
range.push(16..20);
|
||||
assert_eq!(range, OrderRange::Range(0..30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_pop() {
|
||||
let mut range: OrderRange = vec![(0..10), (20..30)].into();
|
||||
assert_eq!(range.pop(), Some(0..10));
|
||||
|
||||
let mut range: OrderRange = (0..10).into();
|
||||
assert_eq!(range.pop(), Some(0..10));
|
||||
assert!(range.is_empty());
|
||||
assert_eq!(range.pop(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ranges_squash() {
|
||||
let mut range = OrderRange::from(vec![(0..10), (20..30)]);
|
||||
|
||||
// do nothing
|
||||
range.squash();
|
||||
assert_eq!(range, OrderRange::from(vec![(0..10), (20..30)]));
|
||||
|
||||
// merged into list
|
||||
range = OrderRange::from(vec![(0..10), (10..20), (30..40)]);
|
||||
range.squash();
|
||||
assert_eq!(range, OrderRange::from(vec![(0..20), (30..40)]));
|
||||
|
||||
// turn to range
|
||||
range = OrderRange::from(vec![(0..10), (10..20), (20..30)]);
|
||||
range.squash();
|
||||
assert_eq!(range, OrderRange::Range(0..30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_covered() {
|
||||
assert!(!OrderRange::check_range_covered(&[0..1], &[2..3]));
|
||||
assert!(OrderRange::check_range_covered(&[0..1], &[0..3]));
|
||||
assert!(!OrderRange::check_range_covered(&[0..1], &[1..3]));
|
||||
assert!(OrderRange::check_range_covered(&[0..1], &[0..3]));
|
||||
assert!(OrderRange::check_range_covered(&[1..2], &[0..3]));
|
||||
assert!(OrderRange::check_range_covered(&[1..2, 2..3], &[0..3]));
|
||||
assert!(!OrderRange::check_range_covered(
|
||||
&[1..2, 2..3, 3..4],
|
||||
&[0..3]
|
||||
));
|
||||
assert!(OrderRange::check_range_covered(
|
||||
&[0..1, 2..3],
|
||||
&[0..2, 2..4]
|
||||
));
|
||||
assert!(OrderRange::check_range_covered(
|
||||
&[0..1, 2..3, 3..4],
|
||||
&[0..2, 2..4]
|
||||
),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_diff() {
|
||||
{
|
||||
let old = OrderRange::Range(0..1);
|
||||
let new = OrderRange::Range(2..3);
|
||||
let ranges = old.diff_range(&new);
|
||||
assert_eq!(ranges, vec![]);
|
||||
}
|
||||
|
||||
{
|
||||
let old = OrderRange::Range(0..10);
|
||||
let new = OrderRange::Range(0..11);
|
||||
let ranges = old.diff_range(&new);
|
||||
assert_eq!(ranges, vec![(10..11)]);
|
||||
}
|
||||
|
||||
{
|
||||
let old: OrderRange = vec![(0..10), (20..30)].into();
|
||||
let new: OrderRange = vec![(0..15), (20..30)].into();
|
||||
let ranges = old.diff_range(&new);
|
||||
assert_eq!(ranges, vec![(10..15)]);
|
||||
}
|
||||
|
||||
{
|
||||
let old: OrderRange = vec![(0..3), (5..7), (8..10), (16..18), (21..23)].into();
|
||||
let new: OrderRange = vec![(0..12), (15..23)].into();
|
||||
let ranges = old.diff_range(&new);
|
||||
assert_eq!(ranges, vec![(3..5), (7..8), (10..12), (15..16), (18..21)]);
|
||||
}
|
||||
|
||||
{
|
||||
let old: OrderRange = vec![(1..6), (8..12)].into();
|
||||
let new: OrderRange = vec![(0..12), (15..23), (24..28)].into();
|
||||
let ranges = old.diff_range(&new);
|
||||
assert_eq!(ranges, vec![(0..1), (6..8), (15..23), (24..28)]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_extend() {
|
||||
let mut range: OrderRange = (0..10).into();
|
||||
range.merge((20..30).into());
|
||||
assert_eq!(range, OrderRange::from(vec![(0..10), (20..30)]));
|
||||
|
||||
let mut range: OrderRange = (0..10).into();
|
||||
range.merge(vec![(10..15), (20..30)].into());
|
||||
assert_eq!(range, OrderRange::from(vec![(0..15), (20..30)]));
|
||||
|
||||
let mut range: OrderRange = vec![(0..10), (20..30)].into();
|
||||
range.merge((10..20).into());
|
||||
assert_eq!(range, OrderRange::Range(0..30));
|
||||
|
||||
let mut range: OrderRange = vec![(0..10), (20..30)].into();
|
||||
range.merge(vec![(10..20), (30..40)].into());
|
||||
assert_eq!(range, OrderRange::Range(0..40));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter() {
|
||||
let range: OrderRange = vec![(0..10), (20..30)].into();
|
||||
|
||||
assert_eq!(
|
||||
range.into_iter().collect::<Vec<_>>(),
|
||||
vec![(0..10), (20..30)]
|
||||
);
|
||||
|
||||
let range: OrderRange = OrderRange::Range(0..10);
|
||||
|
||||
assert_eq!(range.into_iter().collect::<Vec<_>>(), vec![(0..10)]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,525 @@
|
||||
use std::{
|
||||
cell::UnsafeCell,
|
||||
fmt::{self, Write},
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
use crate::sync::Ordering;
|
||||
const DANGLING_PTR: usize = usize::MAX;
|
||||
#[inline]
|
||||
fn is_dangling<T>(ptr: NonNull<T>) -> bool {
|
||||
ptr.as_ptr() as usize == DANGLING_PTR
|
||||
}
|
||||
|
||||
/// Heap data with single owner but multiple refs with dangling checking at
|
||||
/// runtime.
|
||||
pub(crate) enum Somr<T> {
|
||||
Owned(Owned<T>),
|
||||
Ref(Ref<T>),
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct Owned<T>(NonNull<SomrInner<T>>);
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct Ref<T>(NonNull<SomrInner<T>>);
|
||||
|
||||
#[cfg(feature = "large_refs")]
|
||||
type RefAtomicType = crate::sync::AtomicU32;
|
||||
#[cfg(feature = "large_refs")]
|
||||
type RefPrimitiveType = u32;
|
||||
|
||||
#[cfg(not(feature = "large_refs"))]
|
||||
type RefAtomicType = crate::sync::AtomicU16;
|
||||
#[cfg(not(feature = "large_refs"))]
|
||||
type RefPrimitiveType = u16;
|
||||
|
||||
pub(crate) struct SomrInner<T> {
|
||||
data: Option<UnsafeCell<T>>,
|
||||
/// increase the size when we really meet the the scenario with refs more
|
||||
/// then u16::MAX(65535) times
|
||||
refs: RefAtomicType,
|
||||
_marker: PhantomData<Option<T>>,
|
||||
}
|
||||
|
||||
pub(crate) struct InnerRefMut<'a, T> {
|
||||
inner: NonNull<T>,
|
||||
_marker: PhantomData<&'a mut T>,
|
||||
}
|
||||
|
||||
impl<T> Deref for InnerRefMut<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*self.inner.as_ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for InnerRefMut<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe { &mut *self.inner.as_ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send for Somr<T> {}
|
||||
unsafe impl<T: Sync> Sync for Somr<T> {}
|
||||
|
||||
impl<T> Default for Somr<T> {
|
||||
fn default() -> Self {
|
||||
Self::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Somr<T> {
|
||||
pub fn new(data: T) -> Self {
|
||||
let inner = Box::new(SomrInner {
|
||||
data: Some(UnsafeCell::new(data)),
|
||||
refs: RefAtomicType::new(1),
|
||||
_marker: PhantomData,
|
||||
});
|
||||
|
||||
Self::Owned(Owned(Box::leak(inner).into()))
|
||||
}
|
||||
|
||||
pub fn none() -> Self {
|
||||
Self::Ref(Ref(NonNull::new(DANGLING_PTR as *mut _).unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SomrInner<T> {
|
||||
fn data_ref(&self) -> Option<&T> {
|
||||
self.data.as_ref().map(|x| unsafe { &*x.get() })
|
||||
}
|
||||
|
||||
fn data_mut(&self) -> Option<InnerRefMut<T>> {
|
||||
self.data.as_ref().map(|x| InnerRefMut {
|
||||
inner: unsafe { NonNull::new_unchecked(x.get()) },
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Somr<T> {
|
||||
#[inline]
|
||||
pub fn is_none(&self) -> bool {
|
||||
self.dangling() || self.inner().data_ref().is_none()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_some(&self) -> bool {
|
||||
!self.dangling() && self.inner().data_ref().is_some()
|
||||
}
|
||||
|
||||
pub fn get(&self) -> Option<&T> {
|
||||
if self.dangling() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.inner().data_ref()
|
||||
}
|
||||
|
||||
pub unsafe fn get_unchecked(&self) -> &T {
|
||||
if self.dangling() {
|
||||
panic!("Try to visit Somr data that has already been dropped.")
|
||||
}
|
||||
|
||||
match &self.inner().data_ref() {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
panic!("Try to unwrap on None")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn get_mut(&mut self) -> Option<&mut T> {
|
||||
if !self.is_owned() || self.dangling() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let inner = self.inner_mut();
|
||||
inner.data.as_mut().map(|x| x.get_mut())
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub unsafe fn get_mut_from_ref(&self) -> Option<InnerRefMut<T>> {
|
||||
if !self.is_owned() || self.dangling() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let inner = self.inner_mut();
|
||||
inner.data_mut()
|
||||
}
|
||||
|
||||
pub unsafe fn get_mut_unchecked(&self) -> InnerRefMut<'_, T> {
|
||||
if self.dangling() {
|
||||
panic!("Try to visit Somr data that has already been dropped.")
|
||||
}
|
||||
|
||||
match self.inner_mut().data_mut() {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
panic!("Try to unwrap on None")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_owned(&self) -> bool {
|
||||
matches!(self, Self::Owned(_))
|
||||
}
|
||||
|
||||
pub fn swap_take(&mut self) -> Self {
|
||||
debug_assert!(self.is_owned());
|
||||
|
||||
let mut r = self.clone();
|
||||
|
||||
mem::swap(self, &mut r);
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inner(&self) -> &SomrInner<T> {
|
||||
debug_assert!(!self.dangling());
|
||||
unsafe { self.ptr().as_ref() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
fn inner_mut(&self) -> &mut SomrInner<T> {
|
||||
debug_assert!(!self.dangling());
|
||||
unsafe { self.ptr().as_mut() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ptr(&self) -> NonNull<SomrInner<T>> {
|
||||
match self {
|
||||
Somr::Owned(ptr) => ptr.0,
|
||||
Somr::Ref(ptr) => ptr.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ptr_eq(&self, other: &Self) -> bool {
|
||||
self.ptr().as_ptr() as usize == other.ptr().as_ptr() as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dangling(&self) -> bool {
|
||||
is_dangling(self.ptr())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Somr<T> {
|
||||
fn clone(&self) -> Self {
|
||||
if self.dangling() {
|
||||
return Self::none();
|
||||
}
|
||||
|
||||
let inner = unsafe { &*self.ptr().as_ptr() };
|
||||
|
||||
let old_size = inner.refs.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
if old_size == RefPrimitiveType::MAX {
|
||||
panic!("Too many refs on Somr, maybe we need to increase the limitation now.")
|
||||
}
|
||||
|
||||
Self::Ref(Ref(self.ptr()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Owned<T> {
|
||||
fn drop(&mut self) {
|
||||
let inner = unsafe { &mut *self.0.as_ptr() };
|
||||
|
||||
// ensure all reads are finished
|
||||
// See [Arc::Drop]
|
||||
inner.refs.load(Ordering::Acquire);
|
||||
|
||||
inner.data.take();
|
||||
drop(Ref(self.0));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Ref<T> {
|
||||
fn drop(&mut self) {
|
||||
if is_dangling(self.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let rc = unsafe { &(*self.0.as_ptr()).refs };
|
||||
|
||||
// no other refs
|
||||
if rc.fetch_sub(1, Ordering::Release) == 1 {
|
||||
// ensure all reads are finished
|
||||
// See [Arc::Drop]
|
||||
rc.load(Ordering::Acquire);
|
||||
|
||||
drop(unsafe { Box::from_raw(self.0.as_ptr()) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Somr<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Somr::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<Somr<T>>> for Somr<T> {
|
||||
fn from(value: Option<Somr<T>>) -> Self {
|
||||
match value {
|
||||
Some(somr) => somr,
|
||||
None => Somr::none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FlattenGet<T> {
|
||||
#[allow(dead_code)]
|
||||
fn flatten_get(&self) -> Option<&T>;
|
||||
}
|
||||
|
||||
impl<T> FlattenGet<T> for Option<Somr<T>> {
|
||||
fn flatten_get(&self) -> Option<&T> {
|
||||
self.as_ref().and_then(|data| data.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for Somr<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.ptr() == other.ptr()
|
||||
|| !self.dangling() && !other.dangling() && self.inner() == other.inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for SomrInner<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.data_ref() == other.data_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> Eq for Somr<T> {
|
||||
fn assert_receiver_is_total_eq(&self) {}
|
||||
}
|
||||
|
||||
impl<T: PartialOrd> PartialOrd for Somr<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
match (self.get(), other.get()) {
|
||||
(Some(a), Some(b)) => a.partial_cmp(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Hash for Somr<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.ptr().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for Somr<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.is_owned() {
|
||||
f.write_str("Owned(")?;
|
||||
} else {
|
||||
f.write_str("Ref(")?;
|
||||
}
|
||||
|
||||
if let Some(value) = self.get() {
|
||||
fmt::Debug::fmt(value, f)?;
|
||||
} else {
|
||||
f.write_str("None")?;
|
||||
}
|
||||
|
||||
f.write_char(')')
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display> fmt::Display for Somr<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.is_owned() {
|
||||
f.write_str("Owned(")?;
|
||||
} else {
|
||||
f.write_str("Ref(")?;
|
||||
}
|
||||
|
||||
if let Some(value) = self.get() {
|
||||
fmt::Display::fmt(value, f)?;
|
||||
} else {
|
||||
f.write_str("None")?;
|
||||
}
|
||||
|
||||
f.write_char(')')
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sized> fmt::Pointer for Somr<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Pointer::fmt(&(self.get().unwrap() as *const T), f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(loom)))]
|
||||
impl<T: proptest::arbitrary::Arbitrary> proptest::arbitrary::Arbitrary for Somr<T> {
|
||||
type Parameters = T::Parameters;
|
||||
type Strategy = proptest::strategy::MapInto<T::Strategy, Self>;
|
||||
|
||||
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
|
||||
proptest::strategy::Strategy::prop_map_into(proptest::arbitrary::any_with::<T>(args))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::loom_model;
|
||||
|
||||
#[test]
|
||||
fn basic_example() {
|
||||
loom_model!({
|
||||
let five = Somr::new(5);
|
||||
assert_eq!(five.get(), Some(&5));
|
||||
|
||||
let five_ref = five.clone();
|
||||
assert!(!five_ref.is_owned());
|
||||
assert_eq!(five_ref.get(), Some(&5));
|
||||
assert_eq!(
|
||||
five_ref.ptr().as_ptr() as usize,
|
||||
five.ptr().as_ptr() as usize
|
||||
);
|
||||
|
||||
drop(five);
|
||||
// owner released
|
||||
assert_eq!(five_ref.get(), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex_struct() {
|
||||
loom_model!({
|
||||
struct T {
|
||||
a: usize,
|
||||
b: String,
|
||||
}
|
||||
|
||||
let t1 = Somr::new(T {
|
||||
a: 1,
|
||||
b: "hello".to_owned(),
|
||||
});
|
||||
|
||||
assert_eq!(t1.get().unwrap().a, 1);
|
||||
assert_eq!(t1.get().unwrap().b.as_str(), "hello");
|
||||
|
||||
let t2 = t1.clone();
|
||||
assert!(!t2.is_owned());
|
||||
assert_eq!(t2.ptr().as_ptr() as usize, t1.ptr().as_ptr() as usize);
|
||||
assert_eq!(t2.get().unwrap().a, 1);
|
||||
assert_eq!(t2.get().unwrap().b.as_str(), "hello");
|
||||
|
||||
drop(t1);
|
||||
|
||||
assert!(t2.get().is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn acquire_mut_ref() {
|
||||
loom_model!({
|
||||
let mut five = Somr::new(5);
|
||||
|
||||
*five.get_mut().unwrap() += 1;
|
||||
assert_eq!(five.get(), Some(&6));
|
||||
|
||||
let five_ref = five.clone();
|
||||
|
||||
// only owner can mut ref
|
||||
assert!(five_ref.get().is_some());
|
||||
assert!(unsafe { five_ref.get_mut_from_ref() }.is_none());
|
||||
|
||||
drop(five);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comparison() {
|
||||
loom_model!({
|
||||
let five = Somr::new(5);
|
||||
let five_ref = five.clone();
|
||||
let another_five = Somr::new(5);
|
||||
let six = Somr::new(6);
|
||||
|
||||
assert_eq!(five, five_ref);
|
||||
assert_eq!(five, another_five);
|
||||
assert_eq!(five.ptr().as_ptr(), five_ref.ptr().as_ptr());
|
||||
assert_ne!(five.ptr().as_ptr(), another_five.ptr().as_ptr());
|
||||
|
||||
assert!(six > five);
|
||||
assert!(six > five_ref);
|
||||
|
||||
assert_eq!(five_ref.partial_cmp(&six), Some(std::cmp::Ordering::Less));
|
||||
drop(five);
|
||||
assert_eq!(five_ref.partial_cmp(&six), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn represent_none() {
|
||||
loom_model!({
|
||||
let none = Somr::<u32>::none();
|
||||
|
||||
assert!(!none.is_owned());
|
||||
assert!(none.is_none());
|
||||
assert!(none.get().is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_ref_without_affecting_owner() {
|
||||
loom_model!({
|
||||
let five = Somr::new(5);
|
||||
let five_ref = five.clone();
|
||||
|
||||
assert_eq!(five.get().unwrap(), &5);
|
||||
assert_eq!(five_ref.get().unwrap(), &5);
|
||||
|
||||
drop(five_ref);
|
||||
|
||||
assert_eq!(five.get().unwrap(), &5);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_take() {
|
||||
loom_model!({
|
||||
let mut five = Somr::new(5);
|
||||
let owned = five.swap_take();
|
||||
|
||||
assert_eq!(owned.get().unwrap(), &5);
|
||||
assert_eq!(five.get().unwrap(), &5);
|
||||
|
||||
assert!(owned.is_owned());
|
||||
assert!(!five.is_owned());
|
||||
});
|
||||
}
|
||||
|
||||
// This is UB if we didn't use `UnsafeCell` in `Somr`
|
||||
#[test]
|
||||
fn test_inner_mut() {
|
||||
loom_model!({
|
||||
let five = Somr::new(5);
|
||||
fn add(a: &Somr<i32>, b: &Somr<i32>) {
|
||||
unsafe { a.get_mut_from_ref() }
|
||||
.map(|mut x| *x += *b.get().unwrap())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
add(&five, &five);
|
||||
assert_eq!(five.get().copied().unwrap(), 10);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use super::{
|
||||
Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, HashMapExt, Id,
|
||||
JwstCodecResult, HASHMAP_SAFE_CAPACITY,
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Clone)]
|
||||
pub struct StateVector(ClientMap<Clock>);
|
||||
|
||||
impl StateVector {
|
||||
pub fn set_max(&mut self, client: Client, clock: Clock) {
|
||||
self
|
||||
.entry(client)
|
||||
.and_modify(|m_clock| {
|
||||
if *m_clock < clock {
|
||||
*m_clock = clock;
|
||||
}
|
||||
})
|
||||
.or_insert(clock);
|
||||
}
|
||||
|
||||
pub fn get(&self, client: &Client) -> Clock {
|
||||
*self.0.get(client).unwrap_or(&0)
|
||||
}
|
||||
|
||||
pub fn contains(&self, id: &Id) -> bool {
|
||||
id.clock <= self.get(&id.client)
|
||||
}
|
||||
|
||||
pub fn set_min(&mut self, client: Client, clock: Clock) {
|
||||
self
|
||||
.entry(client)
|
||||
.and_modify(|m_clock| {
|
||||
if *m_clock > clock {
|
||||
*m_clock = clock;
|
||||
}
|
||||
})
|
||||
.or_insert(clock);
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&Client, &Clock)> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
pub fn merge_with(&mut self, other: &Self) {
|
||||
for (client, clock) in other.iter() {
|
||||
self.set_min(*client, *clock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for StateVector {
|
||||
type Target = ClientMap<Clock>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for StateVector {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<[(Client, Clock); N]> for StateVector {
|
||||
fn from(value: [(Client, Clock); N]) -> Self {
|
||||
let mut map = ClientMap::with_capacity(N);
|
||||
|
||||
for (client, clock) in value {
|
||||
map.insert(client, clock);
|
||||
}
|
||||
|
||||
Self(map)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: CrdtReader> CrdtRead<R> for StateVector {
|
||||
fn read(decoder: &mut R) -> JwstCodecResult<Self> {
|
||||
let len = decoder.read_var_u64()? as usize;
|
||||
|
||||
// See: [HASHMAP_SAFE_CAPACITY]
|
||||
let mut map = ClientMap::with_capacity(len.min(HASHMAP_SAFE_CAPACITY));
|
||||
for _ in 0..len {
|
||||
let client = decoder.read_var_u64()?;
|
||||
let clock = decoder.read_var_u64()?;
|
||||
map.insert(client, clock);
|
||||
}
|
||||
|
||||
map.shrink_to_fit();
|
||||
Ok(Self(map))
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: CrdtWriter> CrdtWrite<W> for StateVector {
|
||||
fn write(&self, encoder: &mut W) -> JwstCodecResult {
|
||||
encoder.write_var_u64(self.len() as u64)?;
|
||||
|
||||
for (client, clock) in self.iter() {
|
||||
encoder.write_var_u64(*client)?;
|
||||
encoder.write_var_u64(*clock)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_state_vector_basic() {
|
||||
let mut state_vector = StateVector::from([(1, 1), (2, 2), (3, 3)]);
|
||||
assert_eq!(state_vector.len(), 3);
|
||||
assert_eq!(state_vector.get(&1), 1);
|
||||
|
||||
state_vector.set_min(1, 0);
|
||||
assert_eq!(state_vector.get(&1), 0);
|
||||
|
||||
state_vector.set_max(1, 4);
|
||||
assert_eq!(state_vector.get(&1), 4);
|
||||
|
||||
// set inexistent client
|
||||
state_vector.set_max(4, 1);
|
||||
assert_eq!(state_vector.get(&4), 1);
|
||||
|
||||
// same client with larger clock
|
||||
assert!(!state_vector.contains(&(1, 5).into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_state_vector_merge() {
|
||||
let mut state_vector = StateVector::from([(1, 1), (2, 2), (3, 3)]);
|
||||
let other_state_vector = StateVector::from([(1, 5), (2, 6), (3, 7)]);
|
||||
state_vector.merge_with(&other_state_vector);
|
||||
assert_eq!(state_vector, StateVector::from([(3, 3), (1, 1), (2, 2)]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,656 @@
|
||||
use super::{history::StoreHistory, publisher::DocPublisher, store::StoreRef, *};
|
||||
use crate::sync::{Arc, RwLock};
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DocStoreStatus {
|
||||
pub nodes: usize,
|
||||
pub delete_sets: usize,
|
||||
pub types: usize,
|
||||
pub dangling_types: usize,
|
||||
pub pending_nodes: usize,
|
||||
}
|
||||
|
||||
/// [DocOptions] used to create a new [Doc]
|
||||
///
|
||||
/// ```
|
||||
/// use y_octo::DocOptions;
|
||||
///
|
||||
/// let doc = DocOptions::new()
|
||||
/// .with_client_id(1)
|
||||
/// .with_guid("guid".into())
|
||||
/// .auto_gc(true)
|
||||
/// .build();
|
||||
///
|
||||
/// assert_eq!(doc.guid(), "guid")
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DocOptions {
|
||||
pub guid: String,
|
||||
pub client_id: u64,
|
||||
pub gc: bool,
|
||||
}
|
||||
|
||||
impl Default for DocOptions {
|
||||
fn default() -> Self {
|
||||
if cfg!(any(test, feature = "bench")) {
|
||||
Self {
|
||||
client_id: 1,
|
||||
guid: "test".into(),
|
||||
gc: true,
|
||||
}
|
||||
} else {
|
||||
/// It tends to generate small numbers.
|
||||
/// Since the client id will be included in all crdt items, the
|
||||
/// small client helps to reduce the binary size.
|
||||
///
|
||||
/// NOTE: The probability of 36% of the random number generated by
|
||||
/// this function is greater than [u32::MAX]
|
||||
fn prefer_small_random() -> u64 {
|
||||
use rand::{distr::Distribution, rng};
|
||||
use rand_distr::Exp;
|
||||
|
||||
let scale_factor = u16::MAX as f64;
|
||||
let v: f64 = Exp::new(1.0 / scale_factor)
|
||||
.map(|exp| exp.sample(&mut rng()))
|
||||
.unwrap_or_else(|_| rand::random());
|
||||
|
||||
(v * scale_factor) as u64
|
||||
}
|
||||
|
||||
Self {
|
||||
client_id: prefer_small_random(),
|
||||
guid: nanoid::nanoid!(),
|
||||
gc: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DocOptions {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_client_id(mut self, client_id: u64) -> Self {
|
||||
self.client_id = client_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_guid(mut self, guid: String) -> Self {
|
||||
self.guid = guid;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn auto_gc(mut self, gc: bool) -> Self {
|
||||
self.gc = gc;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Doc {
|
||||
Doc::with_options(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DocOptions> for Any {
|
||||
fn from(value: DocOptions) -> Self {
|
||||
Any::Object(HashMap::from_iter([
|
||||
("gc".into(), value.gc.into()),
|
||||
("guid".into(), value.guid.into()),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Any> for DocOptions {
|
||||
type Error = JwstCodecError;
|
||||
|
||||
fn try_from(value: Any) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Any::Object(map) => {
|
||||
let mut options = DocOptions::default();
|
||||
for (key, value) in map {
|
||||
match key.as_str() {
|
||||
"gc" => {
|
||||
options.gc = bool::try_from(value)?;
|
||||
}
|
||||
"guid" => {
|
||||
options.guid = String::try_from(value)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
_ => Err(JwstCodecError::UnexpectedType("Object")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Doc {
|
||||
client_id: u64,
|
||||
opts: DocOptions,
|
||||
|
||||
pub(crate) store: StoreRef,
|
||||
pub publisher: Arc<DocPublisher>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Doc {}
|
||||
unsafe impl Sync for Doc {}
|
||||
|
||||
impl Default for Doc {
|
||||
fn default() -> Self {
|
||||
Doc::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Doc {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.client_id == other.client_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Doc {
|
||||
pub fn new() -> Self {
|
||||
Self::with_options(DocOptions::default())
|
||||
}
|
||||
|
||||
pub fn with_options(options: DocOptions) -> Self {
|
||||
let store = Arc::new(RwLock::new(DocStore::with_client(options.client_id)));
|
||||
let publisher = Arc::new(DocPublisher::new(store.clone()));
|
||||
|
||||
Self {
|
||||
client_id: options.client_id,
|
||||
opts: options,
|
||||
store,
|
||||
publisher,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_client(client_id: u64) -> Self {
|
||||
DocOptions::new().with_client_id(client_id).build()
|
||||
}
|
||||
|
||||
pub fn client(&self) -> Client {
|
||||
self.client_id
|
||||
}
|
||||
|
||||
pub fn clients(&self) -> Vec<u64> {
|
||||
self.store.read().unwrap().clients()
|
||||
}
|
||||
|
||||
pub fn history(&self) -> StoreHistory {
|
||||
let history = StoreHistory::new(&self.store);
|
||||
history.resolve();
|
||||
history
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub fn store_status(&self) -> DocStoreStatus {
|
||||
let store = self.store.read().unwrap();
|
||||
|
||||
DocStoreStatus {
|
||||
nodes: store.total_nodes(),
|
||||
delete_sets: store.total_delete_sets(),
|
||||
types: store.total_types(),
|
||||
dangling_types: store.total_dangling_types(),
|
||||
pending_nodes: store.total_pending_nodes(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn options(&self) -> &DocOptions {
|
||||
&self.opts
|
||||
}
|
||||
|
||||
pub fn guid(&self) -> &str {
|
||||
self.opts.guid.as_str()
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// provide a better way instead of `_v1` methods
|
||||
// when implementing `v2` binary format
|
||||
pub fn try_from_binary_v1<T: AsRef<[u8]>>(binary: T) -> JwstCodecResult<Self> {
|
||||
Self::try_from_binary_v1_with_options(binary, DocOptions::default())
|
||||
}
|
||||
|
||||
pub fn try_from_binary_v1_with_options<T: AsRef<[u8]>>(
|
||||
binary: T,
|
||||
options: DocOptions,
|
||||
) -> JwstCodecResult<Self> {
|
||||
let mut doc = Doc::with_options(options);
|
||||
doc.apply_update_from_binary_v1(binary)?;
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
pub fn apply_update_from_binary_v1<T: AsRef<[u8]>>(&mut self, binary: T) -> JwstCodecResult {
|
||||
let mut decoder = RawDecoder::new(binary.as_ref());
|
||||
let update = Update::read(&mut decoder)?;
|
||||
self.apply_update(update)
|
||||
}
|
||||
|
||||
pub fn apply_update(&mut self, mut update: Update) -> JwstCodecResult {
|
||||
let mut store = self.store.write().unwrap();
|
||||
let mut retry = false;
|
||||
loop {
|
||||
for (mut s, offset) in update.iter(store.get_state_vector()) {
|
||||
if let Node::Item(item) = &mut s {
|
||||
debug_assert!(item.is_owned());
|
||||
let mut item = unsafe { item.get_mut_unchecked() };
|
||||
store.repair(&mut item, self.store.clone())?;
|
||||
}
|
||||
store.integrate(s, offset, None)?;
|
||||
}
|
||||
|
||||
for (client, range) in update.delete_set_iter(store.get_state_vector()) {
|
||||
store.delete_range(client, range)?;
|
||||
}
|
||||
|
||||
if let Some(mut pending_update) = store.pending.take() {
|
||||
if pending_update
|
||||
.missing_state
|
||||
.iter()
|
||||
.any(|(client, clock)| *clock < store.get_state(*client))
|
||||
{
|
||||
// new update has been applied to the doc, need to re-integrate
|
||||
retry = true;
|
||||
}
|
||||
|
||||
for (client, range) in pending_update.delete_set_iter(store.get_state_vector()) {
|
||||
store.delete_range(client, range)?;
|
||||
}
|
||||
|
||||
if update.is_pending_empty() {
|
||||
update = pending_update;
|
||||
} else {
|
||||
// drain all pending state to pending update for later iteration
|
||||
update.drain_pending_state();
|
||||
Update::merge_into(&mut update, [pending_update]);
|
||||
}
|
||||
} else {
|
||||
// no pending update at store
|
||||
|
||||
// no pending update in current iteration
|
||||
// thank god, all clean
|
||||
if update.is_pending_empty() {
|
||||
break;
|
||||
} else {
|
||||
// need to turn all pending state into update for later iteration
|
||||
update.drain_pending_state();
|
||||
retry = false;
|
||||
};
|
||||
}
|
||||
|
||||
// can't integrate any more, save the pending update
|
||||
if !retry {
|
||||
if !update.is_empty() {
|
||||
store.pending.replace(update);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> Vec<String> {
|
||||
let store = self.store.read().unwrap();
|
||||
store.types.keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn get_or_create_text<S: AsRef<str>>(&self, name: S) -> JwstCodecResult<Text> {
|
||||
YTypeBuilder::new(self.store.clone())
|
||||
.with_kind(YTypeKind::Text)
|
||||
.set_name(name.as_ref().to_string())
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn create_text(&self) -> JwstCodecResult<Text> {
|
||||
YTypeBuilder::new(self.store.clone())
|
||||
.with_kind(YTypeKind::Text)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn get_or_create_array<S: AsRef<str>>(&self, str: S) -> JwstCodecResult<Array> {
|
||||
YTypeBuilder::new(self.store.clone())
|
||||
.with_kind(YTypeKind::Array)
|
||||
.set_name(str.as_ref().to_string())
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn create_array(&self) -> JwstCodecResult<Array> {
|
||||
YTypeBuilder::new(self.store.clone())
|
||||
.with_kind(YTypeKind::Array)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn get_or_create_map<S: AsRef<str>>(&self, str: S) -> JwstCodecResult<Map> {
|
||||
YTypeBuilder::new(self.store.clone())
|
||||
.with_kind(YTypeKind::Map)
|
||||
.set_name(str.as_ref().to_string())
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn create_map(&self) -> JwstCodecResult<Map> {
|
||||
YTypeBuilder::new(self.store.clone())
|
||||
.with_kind(YTypeKind::Map)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn get_map(&self, str: &str) -> JwstCodecResult<Map> {
|
||||
YTypeBuilder::new(self.store.clone())
|
||||
.with_kind(YTypeKind::Map)
|
||||
.set_name(str.to_string())
|
||||
.build_exists()
|
||||
}
|
||||
|
||||
pub fn encode_update_v1(&self) -> JwstCodecResult<Vec<u8>> {
|
||||
self.encode_state_as_update_v1(&StateVector::default())
|
||||
}
|
||||
|
||||
pub fn encode_state_as_update_v1(&self, sv: &StateVector) -> JwstCodecResult<Vec<u8>> {
|
||||
let update = self.encode_state_as_update(sv)?;
|
||||
|
||||
let mut encoder = RawEncoder::default();
|
||||
update.write(&mut encoder)?;
|
||||
Ok(encoder.into_inner())
|
||||
}
|
||||
|
||||
pub fn encode_update(&self) -> JwstCodecResult<Update> {
|
||||
self.encode_state_as_update(&StateVector::default())
|
||||
}
|
||||
|
||||
pub fn encode_state_as_update(&self, sv: &StateVector) -> JwstCodecResult<Update> {
|
||||
self.store.read().unwrap().diff_state_vector(sv, true)
|
||||
}
|
||||
|
||||
pub fn get_state_vector(&self) -> StateVector {
|
||||
self.store.read().unwrap().get_state_vector()
|
||||
}
|
||||
|
||||
pub fn subscribe(&self, cb: impl Fn(&[u8], &[History]) + Sync + Send + 'static) {
|
||||
self.publisher.subscribe(cb);
|
||||
}
|
||||
|
||||
pub fn unsubscribe_all(&self) {
|
||||
self.publisher.unsubscribe_all();
|
||||
}
|
||||
|
||||
pub fn subscribe_count(&self) -> usize {
|
||||
self.publisher.count()
|
||||
}
|
||||
|
||||
pub fn gc(&self) -> JwstCodecResult<()> {
|
||||
self.store.write().unwrap().optimize()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use yrs::{types::ToJson, updates::decoder::Decode, Array, Map, Options, Transact};
|
||||
|
||||
use super::*;
|
||||
use crate::sync::{AtomicU8, Ordering};
|
||||
|
||||
#[test]
|
||||
fn test_encode_state_as_update() {
|
||||
let yrs_options_left = Options::default();
|
||||
let yrs_options_right = Options::default();
|
||||
|
||||
loom_model!({
|
||||
let (binary, binary_new) = if cfg!(miri) {
|
||||
let doc = Doc::new();
|
||||
|
||||
let mut map = doc.get_or_create_map("abc").unwrap();
|
||||
map.insert("a".to_string(), 1).unwrap();
|
||||
let binary = doc.encode_update_v1().unwrap();
|
||||
|
||||
let doc_new = Doc::new();
|
||||
let mut array = doc_new.get_or_create_array("array").unwrap();
|
||||
array.insert(0, "array_value").unwrap();
|
||||
let binary_new = doc.encode_update_v1().unwrap();
|
||||
|
||||
(binary, binary_new)
|
||||
} else {
|
||||
let yrs_doc = yrs::Doc::with_options(yrs_options_left.clone());
|
||||
|
||||
let map = yrs_doc.get_or_insert_map("abc");
|
||||
let mut trx = yrs_doc.transact_mut();
|
||||
map.insert(&mut trx, "a", 1);
|
||||
let binary = trx.encode_update_v1();
|
||||
|
||||
let yrs_doc_new = yrs::Doc::with_options(yrs_options_right.clone());
|
||||
let array = yrs_doc_new.get_or_insert_array("array");
|
||||
let mut trx = yrs_doc_new.transact_mut();
|
||||
array.insert(&mut trx, 0, "array_value");
|
||||
let binary_new = trx.encode_update_v1();
|
||||
|
||||
(binary, binary_new)
|
||||
};
|
||||
|
||||
let mut doc = Doc::try_from_binary_v1(binary).unwrap();
|
||||
let mut doc_new = Doc::try_from_binary_v1(binary_new).unwrap();
|
||||
|
||||
let diff_update = doc_new
|
||||
.encode_state_as_update_v1(&doc.get_state_vector())
|
||||
.unwrap();
|
||||
|
||||
let diff_update_reverse = doc
|
||||
.encode_state_as_update_v1(&doc_new.get_state_vector())
|
||||
.unwrap();
|
||||
|
||||
doc.apply_update_from_binary_v1(diff_update).unwrap();
|
||||
doc_new
|
||||
.apply_update_from_binary_v1(diff_update_reverse)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
doc.encode_update_v1().unwrap(),
|
||||
doc_new.encode_update_v1().unwrap()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(any(miri, loom), ignore)]
|
||||
fn test_array_create() {
|
||||
let yrs_options = yrs::Options::default();
|
||||
|
||||
let json = serde_json::json!([42.0, -42.0, true, false, "hello", "world", [1.0]]);
|
||||
|
||||
{
|
||||
let doc = yrs::Doc::with_options(yrs_options.clone());
|
||||
let array = doc.get_or_insert_array("abc");
|
||||
let mut trx = doc.transact_mut();
|
||||
array.insert(&mut trx, 0, 42);
|
||||
array.insert(&mut trx, 1, -42);
|
||||
array.insert(&mut trx, 2, true);
|
||||
array.insert(&mut trx, 3, false);
|
||||
array.insert(&mut trx, 4, "hello");
|
||||
array.insert(&mut trx, 5, "world");
|
||||
|
||||
let sub_array = yrs::ArrayPrelim::default();
|
||||
let sub_array = array.insert(&mut trx, 6, sub_array);
|
||||
sub_array.insert(&mut trx, 0, 1);
|
||||
|
||||
drop(trx);
|
||||
let config = assert_json_diff::Config::new(assert_json_diff::CompareMode::Strict)
|
||||
.numeric_mode(assert_json_diff::NumericMode::AssumeFloat);
|
||||
assert_json_diff::assert_json_matches!(array.to_json(&doc.transact()), json, config);
|
||||
};
|
||||
|
||||
let binary = {
|
||||
let doc = Doc::new();
|
||||
let mut array = doc.get_or_create_array("abc").unwrap();
|
||||
array.insert(0, 42).unwrap();
|
||||
array.insert(1, -42).unwrap();
|
||||
array.insert(2, true).unwrap();
|
||||
array.insert(3, false).unwrap();
|
||||
array.insert(4, "hello").unwrap();
|
||||
array.insert(5, "world").unwrap();
|
||||
|
||||
let mut sub_array = doc.create_array().unwrap();
|
||||
array.insert(6, sub_array.clone()).unwrap();
|
||||
// FIXME: array need insert first to compatible with yrs
|
||||
sub_array.insert(0, 1).unwrap();
|
||||
|
||||
doc.encode_update_v1().unwrap()
|
||||
};
|
||||
|
||||
let ydoc = yrs::Doc::with_options(yrs_options);
|
||||
let array = ydoc.get_or_insert_array("abc");
|
||||
let mut trx = ydoc.transact_mut();
|
||||
trx
|
||||
.apply_update(yrs::Update::decode_v1(&binary).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let config = assert_json_diff::Config::new(assert_json_diff::CompareMode::Strict)
|
||||
.numeric_mode(assert_json_diff::NumericMode::AssumeFloat);
|
||||
assert_json_diff::assert_json_matches!(array.to_json(&trx), json, config);
|
||||
|
||||
let mut doc = Doc::new();
|
||||
let array = doc.get_or_create_array("abc").unwrap();
|
||||
doc.apply_update_from_binary_v1(binary).unwrap();
|
||||
|
||||
let list = array.iter().collect::<Vec<_>>();
|
||||
|
||||
assert!(list.len() == 7);
|
||||
assert!(matches!(list[6], Value::Array(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "inaccurate timing on ci, need for more accurate timing testing"]
|
||||
fn test_subscribe() {
|
||||
loom_model!({
|
||||
let doc = Doc::default();
|
||||
let doc_clone = doc.clone();
|
||||
|
||||
let count = Arc::new(AtomicU8::new(0));
|
||||
let count_clone1 = count.clone();
|
||||
let count_clone2 = count.clone();
|
||||
doc.subscribe(move |_, _| {
|
||||
count_clone1.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
doc_clone.subscribe(move |_, _| {
|
||||
count_clone2.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
doc_clone
|
||||
.get_or_create_array("abc")
|
||||
.unwrap()
|
||||
.insert(0, 42)
|
||||
.unwrap();
|
||||
|
||||
// wait observer, cycle once every 100mm
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
|
||||
assert_eq!(count.load(Ordering::SeqCst), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repeated_applied_pending_update() {
|
||||
// generate a pending update
|
||||
// update: [1, 1, 1, 0, 39, 1, 4, 116, 101, 115, 116, 3, 109, 97, 112, 1, 0]
|
||||
// update: [1, 1, 1, 1, 40, 0, 1, 0, 11, 115, 117, 98, 95, 109, 97, 112, 95,
|
||||
// 107, 101, 121, 1, 119, 13, 115, 117, 98, 95, 109, 97, 112, 95, 118, 97, 108,
|
||||
// 117, 101, 0]
|
||||
// {
|
||||
// let doc1 = Doc::default();
|
||||
|
||||
// doc1.subscribe(|update| {
|
||||
// println!("update: {:?}", update);
|
||||
// });
|
||||
|
||||
// let mut map = doc1.get_or_create_map("test").unwrap();
|
||||
// std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
|
||||
// let mut sub_map = doc1.create_map().unwrap();
|
||||
// map.insert("map", sub_map.clone()).unwrap();
|
||||
// std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
|
||||
// sub_map.insert("sub_map_key", "sub_map_value").unwrap();
|
||||
// std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
// }
|
||||
|
||||
loom_model!({
|
||||
let mut doc = Doc::default();
|
||||
|
||||
doc
|
||||
.apply_update_from_binary_v1(vec![
|
||||
1, 1, 1, 1, 40, 0, 1, 0, 11, 115, 117, 98, 95, 109, 97, 112, 95, 107, 101, 121, 1, 119,
|
||||
13, 115, 117, 98, 95, 109, 97, 112, 95, 118, 97, 108, 117, 101, 0,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let pending_size = doc
|
||||
.store
|
||||
.read()
|
||||
.unwrap()
|
||||
.pending
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.structs
|
||||
.iter()
|
||||
.map(|s| s.1.len())
|
||||
.sum::<usize>();
|
||||
doc
|
||||
.apply_update_from_binary_v1(vec![
|
||||
1, 1, 1, 1, 40, 0, 1, 0, 11, 115, 117, 98, 95, 109, 97, 112, 95, 107, 101, 121, 1, 119,
|
||||
13, 115, 117, 98, 95, 109, 97, 112, 95, 118, 97, 108, 117, 101, 0,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
// pending nodes should not grow up after apply same pending update
|
||||
assert_eq!(
|
||||
pending_size,
|
||||
doc
|
||||
.store
|
||||
.read()
|
||||
.unwrap()
|
||||
.pending
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.structs
|
||||
.iter()
|
||||
.map(|s| s.1.len())
|
||||
.sum::<usize>()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_from_vec_ref() {
|
||||
loom_model!({
|
||||
let doc = Doc::new();
|
||||
|
||||
let mut text = doc.get_or_create_text("text").unwrap();
|
||||
text.insert(0, "hello world").unwrap();
|
||||
|
||||
let update = doc.encode_update_v1().unwrap();
|
||||
|
||||
let doc = Doc::try_from_binary_v1(update).unwrap();
|
||||
let text = doc.get_or_create_text("text").unwrap();
|
||||
|
||||
assert_eq!(&text.to_string(), "hello world");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(any(miri, loom), ignore)]
|
||||
fn test_apply_update() {
|
||||
let updates = [
|
||||
include_bytes!("../fixtures/basic.bin").to_vec(),
|
||||
include_bytes!("../fixtures/database.bin").to_vec(),
|
||||
include_bytes!("../fixtures/large.bin").to_vec(),
|
||||
include_bytes!("../fixtures/with-subdoc.bin").to_vec(),
|
||||
include_bytes!("../fixtures/edge-case-left-right-same-node.bin").to_vec(),
|
||||
];
|
||||
|
||||
for update in updates {
|
||||
let mut doc = Doc::new();
|
||||
doc.apply_update_from_binary_v1(&update).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
hash::{BuildHasher, Hasher},
|
||||
};
|
||||
|
||||
use super::Client;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ClientHasher(Client);
|
||||
|
||||
impl Hasher for ClientHasher {
|
||||
fn finish(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn write(&mut self, _: &[u8]) {}
|
||||
|
||||
fn write_u64(&mut self, i: u64) {
|
||||
self.0 = i
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ClientHasherBuilder;
|
||||
|
||||
impl BuildHasher for ClientHasherBuilder {
|
||||
type Hasher = ClientHasher;
|
||||
|
||||
fn build_hasher(&self) -> Self::Hasher {
|
||||
ClientHasher::default()
|
||||
}
|
||||
}
|
||||
|
||||
// use ClientID as key
|
||||
pub type ClientMap<V> = HashMap<Client, V, ClientHasherBuilder>;
|
||||
@@ -0,0 +1,327 @@
|
||||
use std::{collections::VecDeque, sync::Arc};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{store::StoreRef, *};
|
||||
use crate::sync::RwLock;
|
||||
|
||||
enum ParentNode {
|
||||
Root(String),
|
||||
Node(Somr<Item>),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct HistoryOptions {
|
||||
pub client: Option<u64>,
|
||||
/// Only available when client is set
|
||||
pub skip: Option<usize>,
|
||||
/// Only available when client is set
|
||||
pub limit: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct StoreHistory {
|
||||
store: StoreRef,
|
||||
parents: Arc<RwLock<HashMap<Id, Somr<Item>>>>,
|
||||
}
|
||||
|
||||
impl StoreHistory {
|
||||
pub(crate) fn new(store: &StoreRef) -> Self {
|
||||
Self {
|
||||
store: store.clone(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve(&self) {
|
||||
let store = self.store.read().unwrap();
|
||||
self.resolve_with_store(&store);
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_with_store(&self, store: &DocStore) {
|
||||
let mut parents = self.parents.write().unwrap();
|
||||
|
||||
for node in store.items.values().flat_map(|items| items.iter()) {
|
||||
let node = node.as_item();
|
||||
if let Some(item) = node.get() {
|
||||
parents
|
||||
.entry(item.id)
|
||||
.and_modify(|e| {
|
||||
if *e != node {
|
||||
*e = node.clone();
|
||||
}
|
||||
})
|
||||
.or_insert(node.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_update(&self, update: &Update) -> Vec<History> {
|
||||
let store_items = SortedNodes::new(update.structs.iter().collect::<Vec<_>>())
|
||||
.filter_map(|n| n.as_item().get().cloned())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// make items as reference
|
||||
let mut store_items = store_items.iter().collect::<Vec<_>>();
|
||||
store_items.sort_by(|a, b| a.id.clock.cmp(&b.id.clock));
|
||||
|
||||
self.parse_items(store_items)
|
||||
}
|
||||
|
||||
pub fn parse_delete_sets(
|
||||
&self,
|
||||
old_sets: &ClientMap<OrderRange>,
|
||||
new_sets: &ClientMap<OrderRange>,
|
||||
) -> Vec<History> {
|
||||
let store = self.store.read().unwrap();
|
||||
let deleted_items = new_sets
|
||||
.iter()
|
||||
.filter_map(|(id, new_range)| {
|
||||
// diff range if old range exists, or use new range
|
||||
let range = old_sets
|
||||
.get(id)
|
||||
.map(|r| r.diff_range(new_range).into())
|
||||
.unwrap_or(new_range.clone());
|
||||
(!range.is_empty()).then_some((id, range))
|
||||
})
|
||||
.filter_map(|(client, range)| {
|
||||
// check items contains in deleted range
|
||||
store.items.get(client).map(move |items| {
|
||||
items
|
||||
.iter()
|
||||
.filter(move |i| range.contains(i.clock()))
|
||||
.filter_map(|i| i.as_item().get().cloned())
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
self.parse_deleted_items(deleted_items)
|
||||
}
|
||||
|
||||
pub fn parse_store(&self, options: HistoryOptions) -> Vec<History> {
|
||||
let store_items = {
|
||||
let client = options
|
||||
.client
|
||||
.as_ref()
|
||||
.and_then(|client| client.ne(&0).then_some(client));
|
||||
let store = self.store.read().unwrap();
|
||||
let mut sort_iter: Box<dyn Iterator<Item = Item>> = Box::new(
|
||||
SortedNodes::new(if let Some(client) = client {
|
||||
store
|
||||
.items
|
||||
.get(client)
|
||||
.map(|i| vec![(client, i)])
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
store.items.iter().collect::<Vec<_>>()
|
||||
})
|
||||
.filter_map(|n| n.as_item().get().cloned()),
|
||||
);
|
||||
if client.is_some() {
|
||||
// skip and limit only available when client is set
|
||||
if let Some(skip) = options.skip {
|
||||
sort_iter = Box::new(sort_iter.skip(skip));
|
||||
}
|
||||
if let Some(limit) = options.limit {
|
||||
sort_iter = Box::new(sort_iter.take(limit));
|
||||
}
|
||||
}
|
||||
|
||||
sort_iter.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
// make items as reference
|
||||
let mut store_items = store_items.iter().collect::<Vec<_>>();
|
||||
store_items.sort_by(|a, b| a.id.clock.cmp(&b.id.clock));
|
||||
|
||||
self.parse_items(store_items)
|
||||
}
|
||||
|
||||
fn parse_items(&self, store_items: Vec<&Item>) -> Vec<History> {
|
||||
let parents = self.parents.read().unwrap();
|
||||
let mut histories = vec![];
|
||||
|
||||
for item in store_items {
|
||||
if item.deleted() {
|
||||
continue;
|
||||
}
|
||||
|
||||
histories.push(History {
|
||||
id: item.id.to_string(),
|
||||
parent: Self::parse_path(item, &parents),
|
||||
content: Value::from(&item.content).to_string(),
|
||||
action: HistoryAction::Update,
|
||||
})
|
||||
}
|
||||
|
||||
histories
|
||||
}
|
||||
|
||||
fn parse_deleted_items(&self, deleted_items: Vec<Item>) -> Vec<History> {
|
||||
let parents = self.parents.read().unwrap();
|
||||
let mut histories = vec![];
|
||||
|
||||
for item in deleted_items {
|
||||
histories.push(History {
|
||||
id: item.id.to_string(),
|
||||
parent: Self::parse_path(&item, &parents),
|
||||
content: Value::from(&item.content).to_string(),
|
||||
action: HistoryAction::Delete,
|
||||
})
|
||||
}
|
||||
|
||||
histories
|
||||
}
|
||||
|
||||
fn parse_path(item: &Item, parents: &HashMap<Id, Somr<Item>>) -> Vec<String> {
|
||||
let mut path = Vec::new();
|
||||
let mut cur = item.clone();
|
||||
|
||||
while let Some(node) = cur.find_node_with_parent_info() {
|
||||
path.push(Self::get_node_name(&node));
|
||||
|
||||
match Self::get_parent(parents, &node.parent) {
|
||||
ParentNode::Root(name) => {
|
||||
path.push(name);
|
||||
break;
|
||||
}
|
||||
ParentNode::Node(parent) => {
|
||||
if let Some(parent) = parent.get() {
|
||||
cur = parent.clone();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ParentNode::Unknown => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path.reverse();
|
||||
path
|
||||
}
|
||||
|
||||
fn get_node_name(item: &Item) -> String {
|
||||
if let Some(name) = item.parent_sub.clone() {
|
||||
name.to_string()
|
||||
} else {
|
||||
let mut curr = item.clone();
|
||||
let mut idx = 0;
|
||||
|
||||
while let Some(item) = curr.left.get() {
|
||||
curr = item.clone();
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
idx.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_parent(parents: &HashMap<Id, Somr<Item>>, parent: &Option<Parent>) -> ParentNode {
|
||||
match parent {
|
||||
None => ParentNode::Unknown,
|
||||
Some(Parent::Type(ptr)) => ptr
|
||||
.ty()
|
||||
.and_then(|ty| {
|
||||
ty.item
|
||||
.get()
|
||||
.and_then(|i| parents.get(&i.id).map(|p| ParentNode::Node(p.clone())))
|
||||
.or(ty.root_name.clone().map(ParentNode::Root))
|
||||
})
|
||||
.unwrap_or(ParentNode::Unknown),
|
||||
Some(Parent::String(name)) => ParentNode::Root(name.to_string()),
|
||||
Some(Parent::Id(id)) => parents
|
||||
.get(id)
|
||||
.map(|p| ParentNode::Node(p.clone()))
|
||||
.unwrap_or(ParentNode::Unknown),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum HistoryAction {
|
||||
Insert,
|
||||
Update,
|
||||
Delete,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct History {
|
||||
pub id: String,
|
||||
pub parent: Vec<String>,
|
||||
pub content: String,
|
||||
pub action: HistoryAction,
|
||||
}
|
||||
|
||||
pub(crate) struct SortedNodes<'a> {
|
||||
nodes: Vec<(&'a Client, &'a VecDeque<Node>)>,
|
||||
current: Option<VecDeque<Node>>,
|
||||
}
|
||||
|
||||
impl<'a> SortedNodes<'a> {
|
||||
pub fn new(mut nodes: Vec<(&'a Client, &'a VecDeque<Node>)>) -> Self {
|
||||
nodes.sort_by(|a, b| b.0.cmp(a.0));
|
||||
let current = nodes.pop().map(|(_, v)| v.clone());
|
||||
Self { nodes, current }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SortedNodes<'_> {
|
||||
type Item = Node;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(current) = self.current.as_mut() {
|
||||
if let Some(node) = current.pop_back() {
|
||||
return Some(node);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((_, nodes)) = self.nodes.pop() {
|
||||
self.current = Some(nodes.clone());
|
||||
self.next()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_history_client_test() {
|
||||
loom_model!({
|
||||
let doc = Doc::default();
|
||||
let mut map = doc.get_or_create_map("map").unwrap();
|
||||
let mut sub_map = doc.create_map().unwrap();
|
||||
map.insert("sub_map".to_string(), sub_map.clone()).unwrap();
|
||||
sub_map.insert("key".to_string(), "value").unwrap();
|
||||
|
||||
assert_eq!(doc.clients()[0], doc.client());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_history_test() {
|
||||
loom_model!({
|
||||
let doc = Doc::default();
|
||||
let mut map = doc.get_or_create_map("map").unwrap();
|
||||
let mut sub_map = doc.create_map().unwrap();
|
||||
map.insert("sub_map".to_string(), sub_map.clone()).unwrap();
|
||||
sub_map.insert("key".to_string(), "value").unwrap();
|
||||
|
||||
let history = StoreHistory::new(&doc.store);
|
||||
|
||||
let update = doc.encode_update().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
history.parse_store(Default::default()),
|
||||
history.parse_update(&update,)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
mod awareness;
|
||||
mod codec;
|
||||
mod common;
|
||||
mod document;
|
||||
mod hasher;
|
||||
mod history;
|
||||
mod publisher;
|
||||
mod store;
|
||||
mod types;
|
||||
mod utils;
|
||||
|
||||
pub use ahash::{HashMap, HashMapExt, HashSet, HashSetExt};
|
||||
pub use awareness::{Awareness, AwarenessEvent};
|
||||
pub use codec::*;
|
||||
pub use common::*;
|
||||
pub use document::{Doc, DocOptions};
|
||||
pub use hasher::ClientMap;
|
||||
pub use history::{History, HistoryOptions, StoreHistory};
|
||||
use smol_str::SmolStr;
|
||||
pub(crate) use store::DocStore;
|
||||
pub use types::*;
|
||||
pub use utils::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// NOTE:
|
||||
/// - We do not use [HashMap::with_capacity(num_of_clients)] directly here
|
||||
/// because we don't trust the input data.
|
||||
/// - For instance, what if the first u64 was somehow set a very big value?
|
||||
/// - A pre-allocated HashMap with a big capacity may cause OOM.
|
||||
/// - A kinda safer approach is give it a max capacity of 1024 at first
|
||||
/// allocation, and then let std makes the growth as need.
|
||||
pub const HASHMAP_SAFE_CAPACITY: usize = 1 << 10;
|
||||
@@ -0,0 +1,244 @@
|
||||
use std::{
|
||||
thread::{current, sleep, spawn},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use log::{debug, trace};
|
||||
|
||||
use super::{history::StoreHistory, store::StoreRef, *};
|
||||
use crate::sync::{Arc, AtomicBool, Mutex, Ordering, RwLock};
|
||||
|
||||
pub type DocSubscriber = Box<dyn Fn(&[u8], &[History]) + Sync + Send + 'static>;
|
||||
|
||||
const OBSERVE_INTERVAL: u64 = 100;
|
||||
|
||||
pub struct DocPublisher {
|
||||
store: StoreRef,
|
||||
history: StoreHistory,
|
||||
subscribers: Arc<RwLock<Vec<DocSubscriber>>>,
|
||||
observer: Arc<Mutex<Option<std::thread::JoinHandle<()>>>>,
|
||||
observing: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl DocPublisher {
|
||||
pub(crate) fn new(store: StoreRef) -> Self {
|
||||
let subscribers = Arc::new(RwLock::new(Vec::<DocSubscriber>::new()));
|
||||
let history = StoreHistory::new(&store);
|
||||
history.resolve();
|
||||
|
||||
let publisher = Self {
|
||||
store,
|
||||
history,
|
||||
subscribers,
|
||||
observer: Arc::default(),
|
||||
observing: Arc::new(AtomicBool::new(false)),
|
||||
};
|
||||
|
||||
if cfg!(not(any(feature = "bench", fuzzing, loom, miri))) {
|
||||
publisher.start();
|
||||
}
|
||||
|
||||
publisher
|
||||
}
|
||||
|
||||
pub fn start(&self) {
|
||||
let mut observer = self.observer.lock().unwrap();
|
||||
let observing = self.observing.clone();
|
||||
let store = self.store.clone();
|
||||
let history = self.history.clone();
|
||||
if observer.is_none() {
|
||||
let thread_subscribers = self.subscribers.clone();
|
||||
observing.store(true, Ordering::Release);
|
||||
debug!("start observing");
|
||||
let thread = spawn(move || {
|
||||
let mut last_update = store.read().unwrap().get_state_vector();
|
||||
let mut last_deletes = store.read().unwrap().delete_set.clone();
|
||||
loop {
|
||||
sleep(Duration::from_millis(OBSERVE_INTERVAL));
|
||||
if !observing.load(Ordering::Acquire) {
|
||||
debug!("stop observing");
|
||||
break;
|
||||
}
|
||||
|
||||
let subscribers = thread_subscribers.read().unwrap();
|
||||
if subscribers.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let store = store.read().unwrap();
|
||||
|
||||
let update = store.get_state_vector();
|
||||
let deletes = store.delete_set.clone();
|
||||
if update != last_update || deletes != last_deletes {
|
||||
trace!(
|
||||
"update: {:?}, last_update: {:?}, {:?}",
|
||||
update,
|
||||
last_update,
|
||||
current().id(),
|
||||
);
|
||||
trace!(
|
||||
"deletes: {:?}, last_deletes: {:?}, {:?}",
|
||||
deletes,
|
||||
last_deletes,
|
||||
current().id(),
|
||||
);
|
||||
|
||||
history.resolve_with_store(&store);
|
||||
let (binary, history) = match store.diff_state_vector(&last_update, false) {
|
||||
Ok(update) => {
|
||||
drop(store);
|
||||
|
||||
let history = history
|
||||
.parse_update(&update)
|
||||
.into_iter()
|
||||
.chain(history.parse_delete_sets(&last_deletes, &deletes))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut encoder = RawEncoder::default();
|
||||
if let Err(e) = update.write(&mut encoder) {
|
||||
warn!("Failed to encode document: {}", e);
|
||||
continue;
|
||||
}
|
||||
(encoder.into_inner(), history)
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to diff document: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
last_update = update;
|
||||
last_deletes = deletes;
|
||||
|
||||
for cb in subscribers.iter() {
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
// catch panic if callback throw
|
||||
catch_unwind(AssertUnwindSafe(|| {
|
||||
cb(&binary, &history);
|
||||
}))
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("Failed to call subscriber: {:?}", e);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
drop(store);
|
||||
}
|
||||
}
|
||||
});
|
||||
observer.replace(thread);
|
||||
} else {
|
||||
debug!("already observing");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
let mut observer = self.observer.lock().unwrap();
|
||||
if let Some(observer) = observer.take() {
|
||||
self.observing.store(false, Ordering::Release);
|
||||
observer.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn count(&self) -> usize {
|
||||
self.subscribers.read().unwrap().len()
|
||||
}
|
||||
|
||||
pub(crate) fn subscribe(&self, subscriber: impl Fn(&[u8], &[History]) + Send + Sync + 'static) {
|
||||
self.subscribers.write().unwrap().push(Box::new(subscriber));
|
||||
}
|
||||
|
||||
pub(crate) fn unsubscribe_all(&self) {
|
||||
self.subscribers.write().unwrap().clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DocPublisher {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("DocPublisher").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DocPublisher {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
self.unsubscribe_all();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::sync::AtomicUsize;
|
||||
|
||||
#[test]
|
||||
fn test_parse_update_history() {
|
||||
loom_model!({
|
||||
let doc = Doc::default();
|
||||
|
||||
let ret = [
|
||||
vec![vec!["(1, 0)", "test.key1", "val1"]],
|
||||
vec![
|
||||
vec!["(1, 1)", "test.key2", "val2"],
|
||||
vec!["(1, 2)", "test.key3", "val3"],
|
||||
],
|
||||
vec![
|
||||
vec!["(1, 3)", "array.0", "val1"],
|
||||
vec!["(1, 4)", "array.1", "val2"],
|
||||
vec!["(1, 5)", "array.2", "val3"],
|
||||
],
|
||||
];
|
||||
|
||||
let cycle = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
// update: 24
|
||||
// history change by (1, 0) at test.key1: val1
|
||||
// update: 43
|
||||
// history change by (1, 1) at test.key2: val2
|
||||
// history change by (1, 2) at test.key3: val3
|
||||
// update: 40
|
||||
// history change by (1, 3) at array.0: val1
|
||||
// history change by (1, 4) at array.1: val2
|
||||
// history change by (1, 5) at array.2: val3
|
||||
doc.subscribe(move |u, history| {
|
||||
println!("update: {}", u.len());
|
||||
let cycle = cycle.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
let ret = ret[cycle].clone();
|
||||
for (i, h) in history.iter().enumerate() {
|
||||
println!(
|
||||
"history change by {} at {}: {}",
|
||||
h.id,
|
||||
h.parent.join("."),
|
||||
h.content
|
||||
);
|
||||
// lost first update by unknown reason in asan test, skip it if asan enabled
|
||||
if option_env!("ASAN_OPTIONS").is_none() {
|
||||
let ret = &ret[i];
|
||||
assert_eq!(h.id, ret[0]);
|
||||
assert_eq!(h.parent.join("."), ret[1]);
|
||||
assert_eq!(h.content, ret[2]);
|
||||
}
|
||||
}
|
||||
});
|
||||
sleep(Duration::from_millis(500));
|
||||
|
||||
let mut map = doc.get_or_create_map("test").unwrap();
|
||||
map.insert("key1".to_string(), "val1").unwrap();
|
||||
|
||||
sleep(Duration::from_millis(500));
|
||||
|
||||
map.insert("key2".to_string(), "val2").unwrap();
|
||||
map.insert("key3".to_string(), "val3").unwrap();
|
||||
sleep(Duration::from_millis(500));
|
||||
|
||||
let mut array = doc.get_or_create_array("array").unwrap();
|
||||
array.push("val1").unwrap();
|
||||
array.push("val2").unwrap();
|
||||
array.push("val3").unwrap();
|
||||
|
||||
sleep(Duration::from_millis(500));
|
||||
|
||||
doc.publisher.stop();
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,216 @@
|
||||
use super::*;
|
||||
|
||||
impl_type!(Array);
|
||||
|
||||
impl ListType for Array {}
|
||||
|
||||
pub struct ArrayIter<'a>(ListIterator<'a>);
|
||||
|
||||
impl Iterator for ArrayIter<'_> {
|
||||
type Item = Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
for item in self.0.by_ref() {
|
||||
if let Some(item) = item.get() {
|
||||
if item.countable() {
|
||||
return Some(Value::from(&item.content));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Array {
|
||||
#[inline]
|
||||
pub fn len(&self) -> u64 {
|
||||
self.content_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn get(&self, index: u64) -> Option<Value> {
|
||||
let (item, offset) = self.get_item_at(index)?;
|
||||
|
||||
if let Some(item) = item.get() {
|
||||
// TODO: rewrite to content.read(&mut [Any])
|
||||
return match &item.content {
|
||||
Content::Any(any) => return any.get(offset as usize).map(|any| Value::Any(any.clone())),
|
||||
_ => Some(Value::from(&item.content)),
|
||||
};
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> ArrayIter {
|
||||
ArrayIter(self.iter_item())
|
||||
}
|
||||
|
||||
pub fn push<V: Into<Value>>(&mut self, val: V) -> JwstCodecResult {
|
||||
self.insert(self.len(), val)
|
||||
}
|
||||
|
||||
pub fn insert<V: Into<Value>>(&mut self, idx: u64, val: V) -> JwstCodecResult {
|
||||
self.insert_at(idx, val.into().into())
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, idx: u64, len: u64) -> JwstCodecResult {
|
||||
self.remove_at(idx, len)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for Array {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
use serde::ser::SerializeSeq;
|
||||
|
||||
let mut seq = serializer.serialize_seq(Some(self.len() as usize))?;
|
||||
|
||||
for item in self.iter() {
|
||||
seq.serialize_element(&item)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_yarray_insert() {
|
||||
let options = DocOptions::default();
|
||||
|
||||
loom_model!({
|
||||
let doc = Doc::with_options(options.clone());
|
||||
let mut array = doc.get_or_create_array("abc").unwrap();
|
||||
|
||||
array.insert(0, " ").unwrap();
|
||||
array.insert(0, "Hello").unwrap();
|
||||
array.insert(2, "World").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
array.get(0).unwrap(),
|
||||
Value::Any(Any::String("Hello".into()))
|
||||
);
|
||||
assert_eq!(array.get(1).unwrap(), Value::Any(Any::String(" ".into())));
|
||||
assert_eq!(
|
||||
array.get(2).unwrap(),
|
||||
Value::Any(Any::String("World".into()))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_ytext_equal() {
|
||||
use yrs::{Options, Text, Transact};
|
||||
let options = DocOptions::default();
|
||||
let yrs_options = Options::default();
|
||||
|
||||
loom_model!({
|
||||
let doc = yrs::Doc::with_options(yrs_options.clone());
|
||||
let array = doc.get_or_insert_text("abc");
|
||||
|
||||
let mut trx = doc.transact_mut();
|
||||
array.insert(&mut trx, 0, " ");
|
||||
array.insert(&mut trx, 0, "Hello");
|
||||
array.insert(&mut trx, 6, "World");
|
||||
array.insert(&mut trx, 11, "!");
|
||||
let buffer = trx.encode_update_v1();
|
||||
|
||||
let mut decoder = RawDecoder::new(&buffer);
|
||||
let update = Update::read(&mut decoder).unwrap();
|
||||
|
||||
let mut doc = Doc::with_options(options.clone());
|
||||
doc.apply_update(update).unwrap();
|
||||
let array = doc.get_or_create_array("abc").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
array.get(0).unwrap(),
|
||||
Value::Any(Any::String("Hello".into()))
|
||||
);
|
||||
assert_eq!(array.get(5).unwrap(), Value::Any(Any::String(" ".into())));
|
||||
assert_eq!(
|
||||
array.get(6).unwrap(),
|
||||
Value::Any(Any::String("World".into()))
|
||||
);
|
||||
assert_eq!(array.get(11).unwrap(), Value::Any(Any::String("!".into())));
|
||||
});
|
||||
|
||||
let options = DocOptions::default();
|
||||
let yrs_options = Options::default();
|
||||
|
||||
loom_model!({
|
||||
let doc = yrs::Doc::with_options(yrs_options.clone());
|
||||
let array = doc.get_or_insert_text("abc");
|
||||
|
||||
let mut trx = doc.transact_mut();
|
||||
array.insert(&mut trx, 0, "Hello");
|
||||
array.insert(&mut trx, 5, " ");
|
||||
array.insert(&mut trx, 6, "World");
|
||||
array.insert(&mut trx, 11, "!");
|
||||
let buffer = trx.encode_update_v1();
|
||||
|
||||
let mut decoder = RawDecoder::new(&buffer);
|
||||
let update = Update::read(&mut decoder).unwrap();
|
||||
|
||||
let mut doc = Doc::with_options(options.clone());
|
||||
doc.apply_update(update).unwrap();
|
||||
let array = doc.get_or_create_array("abc").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
array.get(0).unwrap(),
|
||||
Value::Any(Any::String("Hello".into()))
|
||||
);
|
||||
assert_eq!(array.get(5).unwrap(), Value::Any(Any::String(" ".into())));
|
||||
assert_eq!(
|
||||
array.get(6).unwrap(),
|
||||
Value::Any(Any::String("World".into()))
|
||||
);
|
||||
assert_eq!(array.get(11).unwrap(), Value::Any(Any::String("!".into())));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_yrs_array_decode() {
|
||||
use yrs::{Array, Transact};
|
||||
|
||||
loom_model!({
|
||||
let update = {
|
||||
let doc = yrs::Doc::new();
|
||||
let array = doc.get_or_insert_array("abc");
|
||||
let mut trx = doc.transact_mut();
|
||||
|
||||
array.insert(&mut trx, 0, "hello");
|
||||
array.insert(&mut trx, 1, "world");
|
||||
array.insert(&mut trx, 1, " ");
|
||||
|
||||
trx.encode_update_v1()
|
||||
};
|
||||
let doc = Doc::try_from_binary_v1_with_options(
|
||||
update.clone(),
|
||||
DocOptions {
|
||||
guid: String::from("1"),
|
||||
client_id: 1,
|
||||
gc: true,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let arr = doc.get_or_create_array("abc").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
arr.get(2).unwrap(),
|
||||
Value::Any(Any::String("world".to_string()))
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use super::*;
|
||||
|
||||
pub(crate) struct ListIterator<'a> {
|
||||
pub(super) _lock: RwLockReadGuard<'a, YType>,
|
||||
pub(super) cur: Somr<Item>,
|
||||
}
|
||||
|
||||
impl Iterator for ListIterator<'_> {
|
||||
type Item = Somr<Item>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(item) = self.cur.clone().get() {
|
||||
let cur = std::mem::replace(&mut self.cur, item.right.clone());
|
||||
if item.deleted() {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Some(cur);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
mod iterator;
|
||||
mod search_marker;
|
||||
|
||||
pub(crate) use iterator::ListIterator;
|
||||
pub(crate) use search_marker::MarkerList;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(crate) struct ItemPosition {
|
||||
pub parent: YTypeRef,
|
||||
pub left: ItemRef,
|
||||
pub right: ItemRef,
|
||||
pub index: u64,
|
||||
pub offset: u64,
|
||||
}
|
||||
|
||||
impl ItemPosition {
|
||||
pub fn forward(&mut self) {
|
||||
if let Some(right) = self.right.get() {
|
||||
if !right.deleted() {
|
||||
self.index += right.len();
|
||||
}
|
||||
|
||||
self.left = self.right.clone();
|
||||
self.right = right.right.clone();
|
||||
} else {
|
||||
// FAIL
|
||||
}
|
||||
}
|
||||
|
||||
/// we found a position cursor point in between a splitable item,
|
||||
/// we need to split the item by the offset.
|
||||
///
|
||||
/// before:
|
||||
/// ---------------------------------
|
||||
/// ^left ^right
|
||||
/// ^offset
|
||||
/// after:
|
||||
/// ---------------------------------
|
||||
/// ^left ^right
|
||||
pub fn normalize(&mut self, store: &mut DocStore) -> JwstCodecResult {
|
||||
if self.offset > 0 {
|
||||
debug_assert!(self.left.is_some());
|
||||
if let Some(left) = self.left.get() {
|
||||
let (left, right) = store.split_node(left.id, self.offset)?;
|
||||
self.left = left.as_item();
|
||||
self.right = right.as_item();
|
||||
self.index += self.offset;
|
||||
self.offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait ListType: AsInner<Inner = YTypeRef> {
|
||||
#[inline(always)]
|
||||
fn content_len(&self) -> u64 {
|
||||
self.as_inner().ty().unwrap().len
|
||||
}
|
||||
|
||||
fn iter_item(&self) -> ListIterator {
|
||||
let inner = self.as_inner().ty().unwrap();
|
||||
ListIterator {
|
||||
cur: inner.start.clone(),
|
||||
_lock: inner,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_pos(&self, inner: &YType, index: u64) -> Option<ItemPosition> {
|
||||
let mut remaining = index;
|
||||
let start = inner.start.clone();
|
||||
|
||||
let mut pos = ItemPosition {
|
||||
parent: self.as_inner().clone(),
|
||||
left: Somr::none(),
|
||||
right: start,
|
||||
index: 0,
|
||||
offset: 0,
|
||||
};
|
||||
|
||||
if pos.right.is_none() {
|
||||
return Some(pos);
|
||||
}
|
||||
|
||||
if let Some(markers) = &inner.markers {
|
||||
if let Some(marker) = markers.find_marker(inner, index) {
|
||||
if marker.index > remaining {
|
||||
remaining = 0
|
||||
} else {
|
||||
remaining -= marker.index;
|
||||
}
|
||||
pos.index = marker.index;
|
||||
pos.left = marker
|
||||
.ptr
|
||||
.get()
|
||||
.map(|ptr| ptr.left.clone())
|
||||
.unwrap_or_default();
|
||||
pos.right = marker.ptr;
|
||||
}
|
||||
};
|
||||
|
||||
while remaining > 0 {
|
||||
if let Some(item) = pos.right.get() {
|
||||
if !item.deleted() {
|
||||
let content_len = item.len();
|
||||
if remaining < content_len {
|
||||
pos.offset = remaining;
|
||||
remaining = 0;
|
||||
} else {
|
||||
pos.index += content_len;
|
||||
remaining -= content_len;
|
||||
}
|
||||
}
|
||||
|
||||
pos.left = pos.right.clone();
|
||||
pos.right = item.right.clone();
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(pos)
|
||||
}
|
||||
|
||||
fn insert_at(&mut self, index: u64, content: Content) -> JwstCodecResult {
|
||||
if index > self.content_len() {
|
||||
return Err(JwstCodecError::IndexOutOfBound(index));
|
||||
}
|
||||
|
||||
if let Some((mut store, mut ty)) = self.as_inner().write() {
|
||||
if let Some(mut pos) = self.find_pos(&ty, index) {
|
||||
pos.normalize(&mut store)?;
|
||||
Self::insert_after(&mut ty, &mut store, pos, content)?;
|
||||
}
|
||||
} else {
|
||||
return Err(JwstCodecError::DocReleased);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert_after(
|
||||
ty: &mut YType,
|
||||
store: &mut DocStore,
|
||||
pos: ItemPosition,
|
||||
content: Content,
|
||||
) -> JwstCodecResult {
|
||||
if let Some(markers) = &ty.markers {
|
||||
markers.update_marker_changes(pos.index, content.clock_len() as i64);
|
||||
}
|
||||
|
||||
let item = store.create_item(
|
||||
content,
|
||||
pos.left.clone(),
|
||||
pos.right.clone(),
|
||||
Some(Parent::Type(pos.parent)),
|
||||
None,
|
||||
);
|
||||
|
||||
store.integrate(Node::Item(item), 0, Some(ty))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_item_at(&self, index: u64) -> Option<(Somr<Item>, u64)> {
|
||||
if index >= self.content_len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ty = self.as_inner().ty().unwrap();
|
||||
|
||||
if let Some(pos) = self.find_pos(&ty, index) {
|
||||
if pos.offset == 0 {
|
||||
return Some((pos.right, 0));
|
||||
} else {
|
||||
return Some((pos.left, pos.offset));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn remove_at(&mut self, idx: u64, len: u64) -> JwstCodecResult {
|
||||
if len == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if idx >= self.content_len() {
|
||||
return Err(JwstCodecError::IndexOutOfBound(idx));
|
||||
}
|
||||
|
||||
if let Some((mut store, mut ty)) = self.as_inner().write() {
|
||||
if let Some(pos) = self.find_pos(&ty, idx) {
|
||||
Self::remove_after(&mut ty, &mut store, pos, len)?;
|
||||
}
|
||||
} else {
|
||||
return Err(JwstCodecError::DocReleased);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_after(
|
||||
ty: &mut YType,
|
||||
store: &mut DocStore,
|
||||
mut pos: ItemPosition,
|
||||
len: u64,
|
||||
) -> JwstCodecResult {
|
||||
pos.normalize(store)?;
|
||||
|
||||
let mut remaining = len;
|
||||
|
||||
while remaining > 0 {
|
||||
if let Some(item) = pos.right.get() {
|
||||
if !item.deleted() {
|
||||
let content_len = item.len();
|
||||
if remaining < content_len {
|
||||
store.split_node(item.id, remaining)?;
|
||||
remaining = 0;
|
||||
} else {
|
||||
remaining -= content_len;
|
||||
}
|
||||
|
||||
store.delete_item(item, Some(ty));
|
||||
}
|
||||
|
||||
pos.forward();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(markers) = &ty.markers {
|
||||
markers.update_marker_changes(pos.index, -((len - remaining) as i64));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
cmp::max,
|
||||
collections::VecDeque,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
const MAX_SEARCH_MARKER: usize = 80;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct SearchMarker {
|
||||
pub ptr: Somr<Item>,
|
||||
pub index: u64,
|
||||
}
|
||||
|
||||
impl SearchMarker {
|
||||
fn new(ptr: Somr<Item>, index: u64) -> Self {
|
||||
SearchMarker { ptr, index }
|
||||
}
|
||||
|
||||
fn overwrite_marker(&mut self, ptr: Somr<Item>, index: u64) {
|
||||
self.ptr = ptr;
|
||||
self.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for MarkerList {}
|
||||
|
||||
/// in yjs, a timestamp field is used to sort markers and the oldest marker is
|
||||
/// deleted once the limit is reached. this was designed for optimization
|
||||
/// purposes for v8. In Rust, we can simply use a [VecDeque] and trust the
|
||||
/// compiler to optimize. the [VecDeque] can naturally maintain the insertion
|
||||
/// order, allowing us to know which marker is the oldest without using an extra
|
||||
/// timestamp field.
|
||||
///
|
||||
/// NOTE:
|
||||
/// A [MarkerList] is always belonging to a [YType],
|
||||
/// which means whenever [MakerList] is used, we actually have a [YType]
|
||||
/// instance behind [RwLock] guard already, so it's safe to make the list
|
||||
/// internal mutable.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MarkerList(RefCell<VecDeque<SearchMarker>>);
|
||||
|
||||
impl Deref for MarkerList {
|
||||
type Target = RefCell<VecDeque<SearchMarker>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for MarkerList {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MarkerList {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MarkerList {
|
||||
pub fn new() -> Self {
|
||||
MarkerList(RefCell::new(VecDeque::new()))
|
||||
}
|
||||
|
||||
// mark pos and push to the end of the linked list
|
||||
fn mark_position(
|
||||
list: &mut VecDeque<SearchMarker>,
|
||||
ptr: Somr<Item>,
|
||||
index: u64,
|
||||
) -> Option<SearchMarker> {
|
||||
if list.len() >= MAX_SEARCH_MARKER {
|
||||
let mut oldest_marker = list.pop_front().unwrap();
|
||||
oldest_marker.overwrite_marker(ptr, index);
|
||||
list.push_back(oldest_marker);
|
||||
} else {
|
||||
let marker = SearchMarker::new(ptr, index);
|
||||
list.push_back(marker);
|
||||
}
|
||||
list.back().cloned()
|
||||
}
|
||||
|
||||
// update mark position if the index is within the range of the marker
|
||||
pub fn update_marker_changes(&self, index: u64, len: i64) {
|
||||
let mut list = self.borrow_mut();
|
||||
|
||||
for marker in list.iter_mut() {
|
||||
if len > 0 {
|
||||
while let Some(ptr) = marker.ptr.get() {
|
||||
if !ptr.indexable() {
|
||||
let left_ref = ptr.left.clone();
|
||||
if let Some(left) = left_ref.get() {
|
||||
if left.indexable() {
|
||||
marker.index -= left.len();
|
||||
}
|
||||
marker.ptr = left_ref;
|
||||
} else {
|
||||
// remove marker
|
||||
marker.index = 0;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if marker.ptr.is_some() && (index < marker.index || (len > 0 && index == marker.index)) {
|
||||
marker.index = max(index as i64, marker.index as i64 + len) as u64;
|
||||
}
|
||||
}
|
||||
|
||||
list.retain(|marker| marker.index > 0);
|
||||
}
|
||||
|
||||
// find and return the marker that is closest to the index
|
||||
pub fn find_marker(&self, parent: &YType, index: u64) -> Option<SearchMarker> {
|
||||
if parent.start.is_none() || index == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut list = self.borrow_mut();
|
||||
|
||||
let marker = list
|
||||
.iter_mut()
|
||||
.min_by_key(|m| (index as i64 - m.index as i64).abs());
|
||||
|
||||
let mut marker_index = marker.as_ref().map(|m| m.index).unwrap_or(0);
|
||||
|
||||
let mut item_ptr = marker
|
||||
.as_ref()
|
||||
.map(|m| m.ptr.clone())
|
||||
.unwrap_or_else(|| parent.start.clone());
|
||||
|
||||
// TODO: this logic here is a bit messy
|
||||
// i think it can be implemented with more streamlined code, and then optimized
|
||||
{
|
||||
// iterate to the right if possible
|
||||
while let Some(item) = item_ptr.clone().get() {
|
||||
if marker_index >= index {
|
||||
break;
|
||||
}
|
||||
|
||||
let right_ref: ItemRef = item.right.clone();
|
||||
if right_ref.is_some() {
|
||||
if item.indexable() {
|
||||
if index < marker_index + item.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
marker_index += item.len();
|
||||
}
|
||||
item_ptr = right_ref;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// iterate to the left if necessary (might be that marker_index > index)
|
||||
while let Some(item) = item_ptr.clone().get() {
|
||||
if marker_index <= index {
|
||||
break;
|
||||
}
|
||||
|
||||
let left_ref: ItemRef = item.left.clone();
|
||||
if let Some(left) = left_ref.get() {
|
||||
if left.indexable() {
|
||||
marker_index -= left.len();
|
||||
}
|
||||
item_ptr = left_ref;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// we want to make sure that item_ptr can't be merged with left, because that
|
||||
// would screw up everything in that case just return what we have
|
||||
// (it is most likely the best marker anyway) iterate to left until
|
||||
// item_ptr can't be merged with left
|
||||
while let Some(item) = item_ptr.clone().get() {
|
||||
let left_ref: ItemRef = item.left.clone();
|
||||
if let Some(left) = left_ref.get() {
|
||||
if left.id.client == item.id.client && left.id.clock + left.len() == item.id.clock {
|
||||
if left.indexable() {
|
||||
marker_index -= left.len();
|
||||
}
|
||||
item_ptr = left_ref;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match marker {
|
||||
Some(marker)
|
||||
if (marker.index as f64 - marker_index as f64).abs()
|
||||
< parent.len as f64 / MAX_SEARCH_MARKER as f64 =>
|
||||
{
|
||||
// adjust existing marker
|
||||
marker.overwrite_marker(item_ptr, marker_index);
|
||||
Some(marker.clone())
|
||||
}
|
||||
_ => {
|
||||
// create new marker
|
||||
Self::mark_position(&mut list, item_ptr, marker_index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_last_marker(&self) -> Option<SearchMarker> {
|
||||
self.borrow().back().cloned()
|
||||
}
|
||||
|
||||
pub fn replace_marker(&self, raw: Somr<Item>, new: Somr<Item>, len_shift: i64) {
|
||||
let mut list = self.borrow_mut();
|
||||
|
||||
for marker in list.iter_mut() {
|
||||
if marker.ptr == raw {
|
||||
marker.ptr = new.clone();
|
||||
marker.index = ((marker.index as i64) + len_shift) as u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[cfg(not(loom))]
|
||||
use rand::{Rng, SeedableRng};
|
||||
#[cfg(not(loom))]
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use yrs::{Array, Options, Transact};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_marker_list() {
|
||||
let options = DocOptions::default();
|
||||
let yrs_options = Options::default();
|
||||
|
||||
loom_model!({
|
||||
let (client_id, buffer) = if cfg!(miri) {
|
||||
let doc = Doc::with_options(options.clone());
|
||||
let mut array = doc.get_or_create_array("abc").unwrap();
|
||||
|
||||
array.insert(0, " ").unwrap();
|
||||
array.insert(0, "Hello").unwrap();
|
||||
array.insert(2, "World").unwrap();
|
||||
|
||||
(doc.client(), doc.encode_update_v1().unwrap())
|
||||
} else {
|
||||
let doc = yrs::Doc::with_options(yrs_options.clone());
|
||||
let array = doc.get_or_insert_array("abc");
|
||||
|
||||
let mut trx = doc.transact_mut();
|
||||
array.insert(&mut trx, 0, " ");
|
||||
array.insert(&mut trx, 0, "Hello");
|
||||
array.insert(&mut trx, 2, "World");
|
||||
|
||||
(doc.client_id(), trx.encode_update_v1())
|
||||
};
|
||||
|
||||
let mut decoder = RawDecoder::new(&buffer);
|
||||
let update = Update::read(&mut decoder).unwrap();
|
||||
|
||||
let mut doc = Doc::with_options(options.clone());
|
||||
doc.apply_update(update).unwrap();
|
||||
let array = doc.get_or_create_array("abc").unwrap();
|
||||
|
||||
let marker_list = MarkerList::new();
|
||||
|
||||
let marker = marker_list.find_marker(&array.0.ty().unwrap(), 8).unwrap();
|
||||
|
||||
assert_eq!(marker.index, 2);
|
||||
assert_eq!(
|
||||
marker.ptr,
|
||||
doc
|
||||
.store
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_node(Id::new(client_id, 2))
|
||||
.unwrap()
|
||||
.as_item()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_search_marker_flaky() {
|
||||
let options = DocOptions::default();
|
||||
|
||||
loom_model!({
|
||||
let doc = Doc::with_options(options.clone());
|
||||
let mut text = doc.get_or_create_text("test").unwrap();
|
||||
text.insert(0, "0").unwrap();
|
||||
text.insert(1, "1").unwrap();
|
||||
text.insert(0, "0").unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(loom))]
|
||||
fn search_with_seed(seed: u64) {
|
||||
let rand = ChaCha20Rng::seed_from_u64(seed);
|
||||
let iteration = 20;
|
||||
|
||||
let doc = Doc::with_client(1);
|
||||
let mut text = doc.get_or_create_text("test").unwrap();
|
||||
text.insert(0, "This is a string with length 32.").unwrap();
|
||||
let mut len = text.len();
|
||||
|
||||
for i in 0..iteration {
|
||||
let mut rand: ChaCha20Rng = rand.clone();
|
||||
let pos = rand.random_range(0..text.len());
|
||||
let str = format!("hello {i}");
|
||||
len += str.len() as u64;
|
||||
text.insert(pos, str).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(text.len(), len);
|
||||
assert_eq!(text.to_string().len() as u64, len);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(loom))]
|
||||
fn test_marker_list_with_seed() {
|
||||
search_with_seed(785590655803394607);
|
||||
search_with_seed(12958877733367615);
|
||||
search_with_seed(71776330571528794);
|
||||
search_with_seed(2207805473582911);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
use std::{collections::hash_map::Iter, rc::Rc};
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
doc::{AsInner, Node, Parent, YTypeRef},
|
||||
impl_type, JwstCodecResult,
|
||||
};
|
||||
|
||||
impl_type!(Map);
|
||||
|
||||
pub(crate) trait MapType: AsInner<Inner = YTypeRef> {
|
||||
fn _insert<V: Into<Value>>(&mut self, key: String, value: V) -> JwstCodecResult {
|
||||
if let Some((mut store, mut ty)) = self.as_inner().write() {
|
||||
let left = ty.map.get(&SmolStr::new(&key)).cloned();
|
||||
|
||||
let item = store.create_item(
|
||||
value.into().into(),
|
||||
left.unwrap_or(Somr::none()),
|
||||
Somr::none(),
|
||||
Some(Parent::Type(self.as_inner().clone())),
|
||||
Some(SmolStr::new(key)),
|
||||
);
|
||||
store.integrate(Node::Item(item), 0, Some(&mut ty))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn _get(&self, key: &str) -> Option<Value> {
|
||||
self.as_inner().ty().and_then(|ty| {
|
||||
ty.map.get(key).and_then(|item| {
|
||||
if let Some(item) = item.get() {
|
||||
if item.deleted() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Value::from(&item.content))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn _contains_key(&self, key: &str) -> bool {
|
||||
if let Some(ty) = self.as_inner().ty() {
|
||||
ty.map
|
||||
.get(key)
|
||||
.and_then(|item| item.get())
|
||||
.is_some_and(|item| !item.deleted())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn _remove(&mut self, key: &str) {
|
||||
if let Some((mut store, mut ty)) = self.as_inner().write() {
|
||||
if let Some(item) = ty.map.get(key).cloned() {
|
||||
if let Some(item) = item.get() {
|
||||
store.delete_item(item, Some(&mut ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _len(&self) -> u64 {
|
||||
self._keys().count() as u64
|
||||
}
|
||||
|
||||
fn _iter(&self) -> EntriesInnerIterator {
|
||||
let ty = self.as_inner().ty();
|
||||
|
||||
if let Some(ty) = ty {
|
||||
let ty = Rc::new(ty);
|
||||
|
||||
EntriesInnerIterator {
|
||||
iter: Some(unsafe { &*Rc::as_ptr(&ty) }.map.iter()),
|
||||
_lock: Some(ty),
|
||||
}
|
||||
} else {
|
||||
EntriesInnerIterator {
|
||||
_lock: None,
|
||||
iter: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _keys(&self) -> KeysIterator {
|
||||
KeysIterator(self._iter())
|
||||
}
|
||||
|
||||
fn _values(&self) -> ValuesIterator {
|
||||
ValuesIterator(self._iter())
|
||||
}
|
||||
|
||||
fn _entries(&self) -> EntriesIterator {
|
||||
EntriesIterator(self._iter())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct EntriesInnerIterator<'a> {
|
||||
_lock: Option<Rc<RwLockReadGuard<'a, YType>>>,
|
||||
iter: Option<Iter<'a, SmolStr, ItemRef>>,
|
||||
}
|
||||
|
||||
pub struct KeysIterator<'a>(EntriesInnerIterator<'a>);
|
||||
pub struct ValuesIterator<'a>(EntriesInnerIterator<'a>);
|
||||
pub struct EntriesIterator<'a>(EntriesInnerIterator<'a>);
|
||||
|
||||
impl<'a> Iterator for EntriesInnerIterator<'a> {
|
||||
type Item = (&'a str, &'a Item);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(iter) = &mut self.iter {
|
||||
for (k, v) in iter {
|
||||
if let Some(item) = v.get() {
|
||||
if !item.deleted() {
|
||||
return Some((k.as_str(), item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for KeysIterator<'a> {
|
||||
type Item = &'a str;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next().map(|(k, _)| k)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ValuesIterator<'_> {
|
||||
type Item = Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next().map(|(_, v)| Value::from(&v.content))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for EntriesIterator<'a> {
|
||||
type Item = (&'a str, Value);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next().map(|(k, v)| (k, Value::from(&v.content)))
|
||||
}
|
||||
}
|
||||
|
||||
impl MapType for Map {}
|
||||
|
||||
impl Map {
|
||||
#[inline(always)]
|
||||
pub fn insert<V: Into<Value>>(&mut self, key: String, value: V) -> JwstCodecResult {
|
||||
self._insert(key, value)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get(&self, key: &str) -> Option<Value> {
|
||||
self._get(key)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn contains_key(&self, key: &str) -> bool {
|
||||
self._contains_key(key)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn remove(&mut self, key: &str) {
|
||||
self._remove(key)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn len(&self) -> u64 {
|
||||
self._len()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn iter(&self) -> EntriesIterator {
|
||||
self._entries()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn entries(&self) -> EntriesIterator {
|
||||
self._entries()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn keys(&self) -> KeysIterator {
|
||||
self._keys()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn values(&self) -> ValuesIterator {
|
||||
self._values()
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for Map {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
let mut map = serializer.serialize_map(Some(self.len() as usize))?;
|
||||
for (key, value) in self.iter() {
|
||||
map.serialize_entry(&key, &value)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{loom_model, Any, Doc};
|
||||
|
||||
#[test]
|
||||
fn test_map_basic() {
|
||||
loom_model!({
|
||||
let doc = Doc::new();
|
||||
let mut map = doc.get_or_create_map("map").unwrap();
|
||||
map.insert("1".to_string(), "value").unwrap();
|
||||
assert_eq!(
|
||||
map.get("1").unwrap(),
|
||||
Value::Any(Any::String("value".to_string()))
|
||||
);
|
||||
assert!(!map.contains_key("nonexistent_key"));
|
||||
assert_eq!(map.len(), 1);
|
||||
assert!(map.contains_key("1"));
|
||||
map.remove("1");
|
||||
assert!(!map.contains_key("1"));
|
||||
assert_eq!(map.len(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_equal() {
|
||||
loom_model!({
|
||||
let doc = Doc::new();
|
||||
let mut map = doc.get_or_create_map("map").unwrap();
|
||||
map.insert("1".to_string(), "value").unwrap();
|
||||
map.insert("2".to_string(), false).unwrap();
|
||||
|
||||
let binary = doc.encode_update_v1().unwrap();
|
||||
let new_doc = Doc::try_from_binary_v1(binary).unwrap();
|
||||
let map = new_doc.get_or_create_map("map").unwrap();
|
||||
assert_eq!(
|
||||
map.get("1").unwrap(),
|
||||
Value::Any(Any::String("value".to_string()))
|
||||
);
|
||||
assert_eq!(map.get("2").unwrap(), Value::Any(Any::False));
|
||||
assert_eq!(map.len(), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_renew_value() {
|
||||
loom_model!({
|
||||
let doc = Doc::new();
|
||||
let mut map = doc.get_or_create_map("map").unwrap();
|
||||
map.insert("1".to_string(), "value").unwrap();
|
||||
map.insert("1".to_string(), "value2").unwrap();
|
||||
assert_eq!(
|
||||
map.get("1").unwrap(),
|
||||
Value::Any(Any::String("value2".to_string()))
|
||||
);
|
||||
assert_eq!(map.len(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_re_encode() {
|
||||
loom_model!({
|
||||
let binary = {
|
||||
let doc = Doc::new();
|
||||
let mut map = doc.get_or_create_map("map").unwrap();
|
||||
map.insert("1".to_string(), "value1").unwrap();
|
||||
map.insert("2".to_string(), "value2").unwrap();
|
||||
doc.encode_update_v1().unwrap()
|
||||
};
|
||||
|
||||
{
|
||||
let doc = Doc::try_from_binary_v1(binary).unwrap();
|
||||
let map = doc.get_or_create_map("map").unwrap();
|
||||
assert_eq!(
|
||||
map.get("1").unwrap(),
|
||||
Value::Any(Any::String("value1".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
map.get("2").unwrap(),
|
||||
Value::Any(Any::String("value2".to_string()))
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_iter() {
|
||||
loom_model!({
|
||||
let doc = Doc::new();
|
||||
let mut map = doc.get_or_create_map("map").unwrap();
|
||||
map.insert("1".to_string(), "value1").unwrap();
|
||||
map.insert("2".to_string(), "value2").unwrap();
|
||||
let mut vec = map.entries().collect::<Vec<_>>();
|
||||
|
||||
// hashmap iteration is in random order instead of insert order
|
||||
vec.sort_by(|a, b| a.0.cmp(b.0));
|
||||
|
||||
assert_eq!(
|
||||
vec,
|
||||
vec![
|
||||
("1", Value::Any(Any::String("value1".to_string()))),
|
||||
("2", Value::Any(Any::String("value2".to_string())))
|
||||
]
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
mod array;
|
||||
mod list;
|
||||
mod map;
|
||||
mod text;
|
||||
mod value;
|
||||
mod xml;
|
||||
|
||||
use std::{collections::hash_map::Entry, sync::Weak};
|
||||
|
||||
pub use array::*;
|
||||
use list::*;
|
||||
pub use map::*;
|
||||
pub use text::*;
|
||||
pub use value::*;
|
||||
pub use xml::*;
|
||||
|
||||
use super::{
|
||||
store::{StoreRef, WeakStoreRef},
|
||||
*,
|
||||
};
|
||||
use crate::{
|
||||
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
|
||||
Item, JwstCodecError, JwstCodecResult,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct YType {
|
||||
pub start: Somr<Item>,
|
||||
pub item: Somr<Item>,
|
||||
pub map: HashMap<SmolStr, Somr<Item>>,
|
||||
pub len: u64,
|
||||
/// The tag name of XMLElement and XMLHook type
|
||||
pub name: Option<String>,
|
||||
/// The name of the type that directly belongs the store.
|
||||
pub root_name: Option<String>,
|
||||
kind: YTypeKind,
|
||||
pub markers: Option<MarkerList>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub(crate) struct YTypeRef {
|
||||
pub store: WeakStoreRef,
|
||||
pub inner: Somr<RwLock<YType>>,
|
||||
}
|
||||
|
||||
impl PartialEq for YType {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.root_name == other.root_name
|
||||
|| (self.start.is_some() && self.start == other.start)
|
||||
|| self.map == other.map
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for YTypeRef {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.inner.ptr_eq(&other.inner)
|
||||
|| match (self.ty(), other.ty()) {
|
||||
(Some(l), Some(r)) => *l == *r,
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl YType {
|
||||
pub fn new(kind: YTypeKind, tag_name: Option<String>) -> Self {
|
||||
YType {
|
||||
kind,
|
||||
name: tag_name,
|
||||
..YType::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> YTypeKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
pub fn set_kind(&mut self, kind: YTypeKind) -> JwstCodecResult {
|
||||
std::debug_assert!(kind != YTypeKind::Unknown);
|
||||
|
||||
if self.kind() != kind {
|
||||
if self.kind == YTypeKind::Unknown {
|
||||
self.kind = kind;
|
||||
} else {
|
||||
return Err(JwstCodecError::TypeCastError(kind.as_str()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl YTypeRef {
|
||||
pub fn new(kind: YTypeKind, tag_name: Option<String>) -> Self {
|
||||
Self {
|
||||
inner: Somr::new(RwLock::new(YType::new(kind, tag_name))),
|
||||
store: Weak::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> Option<RwLockReadGuard<YType>> {
|
||||
self.inner.get().and_then(|ty| ty.read().ok())
|
||||
}
|
||||
|
||||
pub fn ty_mut(&self) -> Option<RwLockWriteGuard<YType>> {
|
||||
self.inner.get().and_then(|ty| ty.write().ok())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn store<'a>(&self) -> Option<RwLockReadGuard<'a, DocStore>> {
|
||||
if let Some(store) = self.store.upgrade() {
|
||||
let ptr = unsafe { &*Arc::as_ptr(&store) };
|
||||
|
||||
Some(ptr.read().unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store_mut<'a>(&self) -> Option<RwLockWriteGuard<'a, DocStore>> {
|
||||
if let Some(store) = self.store.upgrade() {
|
||||
let ptr = unsafe { &*Arc::as_ptr(&store) };
|
||||
|
||||
Some(ptr.write().unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn read(&self) -> Option<(RwLockReadGuard<DocStore>, RwLockReadGuard<YType>)> {
|
||||
self
|
||||
.store()
|
||||
.and_then(|store| self.ty().map(|ty| (store, ty)))
|
||||
}
|
||||
|
||||
pub fn write(&self) -> Option<(RwLockWriteGuard<DocStore>, RwLockWriteGuard<YType>)> {
|
||||
self
|
||||
.store_mut()
|
||||
.and_then(|store| self.ty_mut().map(|ty| (store, ty)))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct YTypeBuilder {
|
||||
store: StoreRef,
|
||||
/// The tag name of XMLElement and XMLHook type
|
||||
name: Option<String>,
|
||||
/// The name of the type that directly belongs the store.
|
||||
root_name: Option<String>,
|
||||
kind: YTypeKind,
|
||||
}
|
||||
|
||||
impl YTypeBuilder {
|
||||
pub fn new(store: StoreRef) -> Self {
|
||||
Self {
|
||||
store,
|
||||
name: None,
|
||||
root_name: None,
|
||||
kind: YTypeKind::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_kind(mut self, kind: YTypeKind) -> Self {
|
||||
self.kind = kind;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_name(mut self, name: String) -> Self {
|
||||
self.root_name = Some(name);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_tag_name(mut self, tag_name: String) -> Self {
|
||||
self.name = Some(tag_name);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build_exists<T: TryFrom<YTypeRef, Error = JwstCodecError>>(self) -> JwstCodecResult<T> {
|
||||
let store = self.store.read().unwrap();
|
||||
let ty = if let Some(root_name) = self.root_name {
|
||||
match store.types.get(&root_name) {
|
||||
Some(ty) => ty.clone(),
|
||||
None => {
|
||||
return Err(JwstCodecError::RootStructNotFound(root_name));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(JwstCodecError::TypeCastError("root_name is not set"));
|
||||
};
|
||||
|
||||
drop(store);
|
||||
|
||||
T::try_from(ty)
|
||||
}
|
||||
|
||||
pub fn build<T: TryFrom<YTypeRef, Error = JwstCodecError>>(self) -> JwstCodecResult<T> {
|
||||
let mut store = self.store.write().unwrap();
|
||||
let ty = if let Some(root_name) = self.root_name {
|
||||
match store.types.entry(root_name.clone()) {
|
||||
Entry::Occupied(e) => e.get().clone(),
|
||||
Entry::Vacant(e) => {
|
||||
let inner = Somr::new(RwLock::new(YType {
|
||||
kind: self.kind,
|
||||
name: self.name,
|
||||
root_name: Some(root_name),
|
||||
markers: Self::markers(self.kind),
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
let ty = YTypeRef {
|
||||
store: Arc::downgrade(&self.store),
|
||||
inner,
|
||||
};
|
||||
|
||||
let ty_ref = ty.clone();
|
||||
e.insert(ty);
|
||||
|
||||
ty_ref
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let inner = Somr::new(RwLock::new(YType {
|
||||
kind: self.kind,
|
||||
name: self.name,
|
||||
root_name: self.root_name.clone(),
|
||||
markers: Self::markers(self.kind),
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
let ty = YTypeRef {
|
||||
store: Arc::downgrade(&self.store),
|
||||
inner,
|
||||
};
|
||||
|
||||
let ty_ref = ty.clone();
|
||||
|
||||
store
|
||||
.dangling_types
|
||||
.insert(ty.inner.ptr().as_ptr() as usize, ty);
|
||||
|
||||
ty_ref
|
||||
};
|
||||
|
||||
drop(store);
|
||||
|
||||
T::try_from(ty)
|
||||
}
|
||||
|
||||
fn markers(kind: YTypeKind) -> Option<MarkerList> {
|
||||
match kind {
|
||||
YTypeKind::Map => None,
|
||||
_ => Some(MarkerList::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_variants {
|
||||
({$($name: ident: $codec_ref: literal),*}) => {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub enum YTypeKind {
|
||||
$($name,)*
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl YTypeKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
$(YTypeKind::$name => std::stringify!($name),)*
|
||||
YTypeKind::Unknown => "Unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for YTypeKind {
|
||||
fn from(value: u64) -> Self {
|
||||
match value {
|
||||
$($codec_ref => YTypeKind::$name,)*
|
||||
_ => YTypeKind::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<YTypeKind> for u64 {
|
||||
fn from(value: YTypeKind) -> Self {
|
||||
std::debug_assert!(value != YTypeKind::Unknown);
|
||||
match value {
|
||||
$(YTypeKind::$name => $codec_ref,)*
|
||||
_ => std::unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) trait AsInner {
|
||||
type Inner;
|
||||
fn as_inner(&self) -> &Self::Inner;
|
||||
}
|
||||
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_type {
|
||||
($name: ident) => {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct $name(pub(crate) super::YTypeRef);
|
||||
unsafe impl Sync for $name {}
|
||||
unsafe impl Send for $name {}
|
||||
|
||||
impl $name {
|
||||
pub(crate) fn new(inner: super::YTypeRef) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl super::AsInner for $name {
|
||||
type Inner = super::YTypeRef;
|
||||
|
||||
#[inline(always)]
|
||||
fn as_inner(&self) -> &Self::Inner {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<super::YTypeRef> for $name {
|
||||
type Error = $crate::JwstCodecError;
|
||||
|
||||
fn try_from(value: super::YTypeRef) -> Result<Self, Self::Error> {
|
||||
if let Some((_, mut inner)) = value.write() {
|
||||
match inner.kind {
|
||||
super::YTypeKind::$name => Ok($name::new(value.clone())),
|
||||
super::YTypeKind::Unknown => {
|
||||
inner.set_kind(super::YTypeKind::$name)?;
|
||||
Ok($name::new(value.clone()))
|
||||
}
|
||||
_ => Err($crate::JwstCodecError::TypeCastError(std::stringify!(
|
||||
$name
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
Err($crate::JwstCodecError::TypeCastError(std::stringify!(
|
||||
$name
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub(crate) fn from_unchecked(value: super::YTypeRef) -> Self {
|
||||
$name::new(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$name> for super::Value {
|
||||
fn from(value: $name) -> Self {
|
||||
Self::$name(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_variants!({
|
||||
Array: 0,
|
||||
Map: 1,
|
||||
Text: 2,
|
||||
XMLElement: 3,
|
||||
XMLFragment: 4,
|
||||
XMLHook: 5,
|
||||
XMLText: 6
|
||||
// Doc: 9?
|
||||
});
|
||||
@@ -0,0 +1,293 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::list::ListType;
|
||||
use crate::{impl_type, Content, JwstCodecResult};
|
||||
|
||||
impl_type!(Text);
|
||||
|
||||
impl ListType for Text {}
|
||||
|
||||
impl Text {
|
||||
#[inline]
|
||||
pub fn len(&self) -> u64 {
|
||||
self.content_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn insert<T: ToString>(&mut self, char_index: u64, str: T) -> JwstCodecResult {
|
||||
self.insert_at(char_index, Content::String(str.to_string()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remove(&mut self, char_index: u64, len: u64) -> JwstCodecResult {
|
||||
self.remove_at(char_index, len)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Text {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.iter_item().try_for_each(|item| {
|
||||
if let Content::String(str) = &item.get().unwrap().content {
|
||||
write!(f, "{}", str)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for Text {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use yrs::{Options, Text, Transact};
|
||||
|
||||
#[cfg(not(loom))]
|
||||
use crate::sync::{Arc, AtomicUsize, Ordering};
|
||||
use crate::{loom_model, sync::thread, Doc};
|
||||
|
||||
#[test]
|
||||
fn test_manipulate_text() {
|
||||
loom_model!({
|
||||
let doc = Doc::new();
|
||||
let mut text = doc.create_text().unwrap();
|
||||
|
||||
text.insert(0, "llo").unwrap();
|
||||
text.insert(0, "he").unwrap();
|
||||
text.insert(5, " world").unwrap();
|
||||
text.insert(6, "great ").unwrap();
|
||||
text.insert(17, '!').unwrap();
|
||||
|
||||
assert_eq!(text.to_string(), "hello great world!");
|
||||
assert_eq!(text.len(), 18);
|
||||
|
||||
text.remove(4, 4).unwrap();
|
||||
assert_eq!(text.to_string(), "helleat world!");
|
||||
assert_eq!(text.len(), 14);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(loom))]
|
||||
fn test_parallel_insert_text() {
|
||||
let seed = rand::rng().random();
|
||||
let rand = ChaCha20Rng::seed_from_u64(seed);
|
||||
let mut handles = Vec::new();
|
||||
|
||||
let doc = Doc::with_client(1);
|
||||
let mut text = doc.get_or_create_text("test").unwrap();
|
||||
text.insert(0, "This is a string with length 32.").unwrap();
|
||||
|
||||
let added_len = Arc::new(AtomicUsize::new(32));
|
||||
|
||||
// parallel editing text
|
||||
{
|
||||
for i in 0..2 {
|
||||
let mut text = text.clone();
|
||||
let mut rand = rand.clone();
|
||||
let len = added_len.clone();
|
||||
|
||||
handles.push(thread::spawn(move || {
|
||||
for j in 0..10 {
|
||||
let pos = rand.random_range(0..text.len());
|
||||
let string = format!("hello {}", i * j);
|
||||
|
||||
text.insert(pos, &string).unwrap();
|
||||
|
||||
len.fetch_add(string.len(), Ordering::SeqCst);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// parallel editing doc
|
||||
{
|
||||
for i in 0..2 {
|
||||
let doc = doc.clone();
|
||||
let mut rand = rand.clone();
|
||||
let len = added_len.clone();
|
||||
|
||||
handles.push(thread::spawn(move || {
|
||||
let mut text = doc.get_or_create_text("test").unwrap();
|
||||
for j in 0..10 {
|
||||
let pos = rand.random_range(0..text.len());
|
||||
let string = format!("hello doc{}", i * j);
|
||||
|
||||
text.insert(pos, &string).unwrap();
|
||||
|
||||
len.fetch_add(string.len(), Ordering::SeqCst);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(text.to_string().len(), added_len.load(Ordering::SeqCst));
|
||||
assert_eq!(text.len(), added_len.load(Ordering::SeqCst) as u64);
|
||||
}
|
||||
|
||||
#[cfg(not(loom))]
|
||||
fn parallel_ins_del_text(seed: u64, thread: i32, iteration: i32) {
|
||||
let doc = Doc::with_client(1);
|
||||
let rand = ChaCha20Rng::seed_from_u64(seed);
|
||||
let mut text = doc.get_or_create_text("test").unwrap();
|
||||
text.insert(0, "This is a string with length 32.").unwrap();
|
||||
|
||||
let mut handles = Vec::new();
|
||||
let len = Arc::new(AtomicUsize::new(32));
|
||||
|
||||
for i in 0..thread {
|
||||
let len = len.clone();
|
||||
let mut rand = rand.clone();
|
||||
let text = text.clone();
|
||||
handles.push(thread::spawn(move || {
|
||||
for j in 0..iteration {
|
||||
let len = len.clone();
|
||||
let mut text = text.clone();
|
||||
let ins = i % 2 == 0;
|
||||
let pos = rand.random_range(0..16);
|
||||
|
||||
if ins {
|
||||
let str = format!("hello {}", i * j);
|
||||
text.insert(pos, &str).unwrap();
|
||||
|
||||
len.fetch_add(str.len(), Ordering::SeqCst);
|
||||
} else {
|
||||
text.remove(pos, 6).unwrap();
|
||||
|
||||
len.fetch_sub(6, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(text.to_string().len(), len.load(Ordering::SeqCst));
|
||||
assert_eq!(text.len(), len.load(Ordering::SeqCst) as u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(loom))]
|
||||
fn test_parallel_ins_del_text() {
|
||||
// cases that ever broken
|
||||
// wrong left/right ref
|
||||
parallel_ins_del_text(973078538, 2, 2);
|
||||
parallel_ins_del_text(18414938500869652479, 2, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loom_parallel_ins_del_text() {
|
||||
let seed = rand::rng().random();
|
||||
let mut rand = ChaCha20Rng::seed_from_u64(seed);
|
||||
let ranges = (0..20)
|
||||
.map(|_| rand.random_range(0..16))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
loom_model!({
|
||||
let doc = Doc::new();
|
||||
let mut text = doc.get_or_create_text("test").unwrap();
|
||||
text.insert(0, "This is a string with length 32.").unwrap();
|
||||
|
||||
// enough for loom
|
||||
let handles = (0..2)
|
||||
.map(|i| {
|
||||
let text = text.clone();
|
||||
let ranges = ranges.clone();
|
||||
thread::spawn(move || {
|
||||
let mut text = text.clone();
|
||||
let ins = i % 2 == 0;
|
||||
let pos = ranges[i];
|
||||
|
||||
if ins {
|
||||
let str = format!("hello {}", i);
|
||||
text.insert(pos, &str).unwrap();
|
||||
} else {
|
||||
text.remove(pos, 6).unwrap();
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_recover_from_yjs_encoder() {
|
||||
let yrs_options = Options {
|
||||
client_id: rand::random(),
|
||||
guid: nanoid::nanoid!().into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
loom_model!({
|
||||
let binary = {
|
||||
let doc = yrs::Doc::with_options(yrs_options.clone());
|
||||
let text = doc.get_or_insert_text("greating");
|
||||
let mut trx = doc.transact_mut();
|
||||
text.insert(&mut trx, 0, "hello");
|
||||
text.insert(&mut trx, 5, " world!");
|
||||
text.remove_range(&mut trx, 11, 1);
|
||||
|
||||
trx.encode_update_v1()
|
||||
};
|
||||
// in loom loop
|
||||
#[allow(clippy::needless_borrow)]
|
||||
let doc = Doc::try_from_binary_v1(&binary).unwrap();
|
||||
let mut text = doc.get_or_create_text("greating").unwrap();
|
||||
|
||||
assert_eq!(text.to_string(), "hello world");
|
||||
|
||||
text.insert(6, "great ").unwrap();
|
||||
text.insert(17, '!').unwrap();
|
||||
assert_eq!(text.to_string(), "hello great world!");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recover_from_octobase_encoder() {
|
||||
loom_model!({
|
||||
let binary = {
|
||||
let doc = Doc::new();
|
||||
let mut text = doc.get_or_create_text("greating").unwrap();
|
||||
text.insert(0, "hello").unwrap();
|
||||
text.insert(5, " world!").unwrap();
|
||||
text.remove(11, 1).unwrap();
|
||||
|
||||
doc.encode_update_v1().unwrap()
|
||||
};
|
||||
|
||||
let doc = Doc::try_from_binary_v1(binary).unwrap();
|
||||
let mut text = doc.get_or_create_text("greating").unwrap();
|
||||
|
||||
assert_eq!(text.to_string(), "hello world");
|
||||
|
||||
text.insert(6, "great ").unwrap();
|
||||
text.insert(17, '!').unwrap();
|
||||
assert_eq!(text.to_string(), "hello great world!");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Value {
|
||||
Any(Any),
|
||||
Doc(Doc),
|
||||
Array(Array),
|
||||
Map(Map),
|
||||
Text(Text),
|
||||
XMLElement(XMLElement),
|
||||
XMLFragment(XMLFragment),
|
||||
XMLHook(XMLHook),
|
||||
XMLText(XMLText),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn to_any(&self) -> Option<Any> {
|
||||
match self {
|
||||
Value::Any(any) => Some(any.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_array(&self) -> Option<Array> {
|
||||
match self {
|
||||
Value::Array(array) => Some(array.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_map(&self) -> Option<Map> {
|
||||
match self {
|
||||
Value::Map(map) => Some(map.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_text(&self) -> Option<Text> {
|
||||
match self {
|
||||
Value::Text(text) => Some(text.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_vec<T: Into<Any>>(el: Vec<T>) -> Self {
|
||||
Value::Any(Any::Array(
|
||||
el.into_iter().map(|item| item.into()).collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Content> for Value {
|
||||
fn from(value: &Content) -> Value {
|
||||
match value {
|
||||
Content::Any(any) => Value::Any(if any.len() == 1 {
|
||||
any[0].clone()
|
||||
} else {
|
||||
Any::Array(any.clone())
|
||||
}),
|
||||
Content::String(s) => Value::Any(Any::String(s.clone())),
|
||||
Content::Json(json) => Value::Any(Any::Array(
|
||||
json
|
||||
.iter()
|
||||
.map(|item| {
|
||||
if let Some(s) = item {
|
||||
Any::String(s.clone())
|
||||
} else {
|
||||
Any::Undefined
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)),
|
||||
Content::Binary(buf) => Value::Any(Any::Binary(buf.clone())),
|
||||
Content::Embed(v) => Value::Any(v.clone()),
|
||||
Content::Type(ty) => match ty.ty().unwrap().kind {
|
||||
YTypeKind::Array => Value::Array(Array::from_unchecked(ty.clone())),
|
||||
YTypeKind::Map => Value::Map(Map::from_unchecked(ty.clone())),
|
||||
YTypeKind::Text => Value::Text(Text::from_unchecked(ty.clone())),
|
||||
YTypeKind::XMLElement => Value::XMLElement(XMLElement::from_unchecked(ty.clone())),
|
||||
YTypeKind::XMLFragment => Value::XMLFragment(XMLFragment::from_unchecked(ty.clone())),
|
||||
YTypeKind::XMLHook => Value::XMLHook(XMLHook::from_unchecked(ty.clone())),
|
||||
YTypeKind::XMLText => Value::XMLText(XMLText::from_unchecked(ty.clone())),
|
||||
// actually unreachable
|
||||
YTypeKind::Unknown => Value::Any(Any::Undefined),
|
||||
},
|
||||
Content::Doc { guid: _, opts } => Value::Doc(
|
||||
DocOptions::try_from(opts.clone())
|
||||
.expect("Failed to parse doc options")
|
||||
.build(),
|
||||
),
|
||||
Content::Format { .. } => unimplemented!(),
|
||||
// actually unreachable
|
||||
Content::Deleted(_) => Value::Any(Any::Undefined),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Value> for Content {
|
||||
fn from(value: Value) -> Self {
|
||||
match value {
|
||||
Value::Any(any) => Content::from(any),
|
||||
Value::Doc(doc) => Content::Doc {
|
||||
guid: doc.guid().to_owned(),
|
||||
opts: Any::from(doc.options().clone()),
|
||||
},
|
||||
Value::Array(v) => Content::Type(v.0),
|
||||
Value::Map(v) => Content::Type(v.0),
|
||||
Value::Text(v) => Content::Type(v.0),
|
||||
Value::XMLElement(v) => Content::Type(v.0),
|
||||
Value::XMLFragment(v) => Content::Type(v.0),
|
||||
Value::XMLHook(v) => Content::Type(v.0),
|
||||
Value::XMLText(v) => Content::Type(v.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<Any>> From<T> for Value {
|
||||
fn from(value: T) -> Self {
|
||||
Value::Any(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Doc> for Value {
|
||||
fn from(value: Doc) -> Self {
|
||||
Value::Doc(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Value::Any(any) => write!(f, "{}", any),
|
||||
Value::Text(text) => write!(f, "{}", text),
|
||||
_ => write!(f, ""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for Value {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::Any(any) => any.serialize(serializer),
|
||||
Self::Array(array) => array.serialize(serializer),
|
||||
Self::Map(map) => map.serialize(serializer),
|
||||
Self::Text(text) => text.serialize(serializer),
|
||||
// Self::XMLElement(xml_element) => xml_element.serialize(serializer),
|
||||
// Self::XMLFragment(xml_fragment) => xml_fragment.serialize(serializer),
|
||||
// Self::XMLHook(xml_hook) => xml_hook.serialize(serializer),
|
||||
// Self::XMLText(xml_text) => xml_text.serialize(serializer),
|
||||
// Self::Doc(doc) => doc.serialize(serializer),
|
||||
_ => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
use super::list::ListType;
|
||||
use crate::impl_type;
|
||||
|
||||
impl_type!(XMLElement);
|
||||
impl ListType for XMLElement {}
|
||||
|
||||
impl_type!(XMLFragment);
|
||||
impl ListType for XMLFragment {}
|
||||
|
||||
impl_type!(XMLText);
|
||||
impl ListType for XMLText {}
|
||||
|
||||
impl_type!(XMLHook);
|
||||
impl ListType for XMLHook {}
|
||||
@@ -0,0 +1,28 @@
|
||||
use super::*;
|
||||
|
||||
pub fn encode_awareness_as_message(awareness: AwarenessStates) -> JwstCodecResult<Vec<u8>> {
|
||||
let mut buffer = Vec::new();
|
||||
write_sync_message(&mut buffer, &SyncMessage::Awareness(awareness))
|
||||
.map_err(|e| JwstCodecError::InvalidWriteBuffer(e.to_string()))?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn encode_update_as_message(update: Vec<u8>) -> JwstCodecResult<Vec<u8>> {
|
||||
let mut buffer = Vec::new();
|
||||
write_sync_message(&mut buffer, &SyncMessage::Doc(DocMessage::Update(update)))
|
||||
.map_err(|e| JwstCodecError::InvalidWriteBuffer(e.to_string()))?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn merge_updates_v1<V: AsRef<[u8]>, I: IntoIterator<Item = V>>(
|
||||
updates: I,
|
||||
) -> JwstCodecResult<Update> {
|
||||
let updates = updates
|
||||
.into_iter()
|
||||
.map(Update::decode_v1)
|
||||
.collect::<JwstCodecResult<Vec<_>>>()?;
|
||||
|
||||
Ok(Update::merge(updates))
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
[]
|
||||
Binary file not shown.
@@ -0,0 +1,67 @@
|
||||
#[forbid(unsafe_code)]
|
||||
mod codec;
|
||||
mod doc;
|
||||
mod protocol;
|
||||
mod sync;
|
||||
|
||||
pub use codec::*;
|
||||
pub use doc::{
|
||||
encode_awareness_as_message, encode_update_as_message, merge_updates_v1, Any, Array, Awareness,
|
||||
AwarenessEvent, Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, Doc,
|
||||
DocOptions, HashMap as AHashMap, HashMapExt, History, HistoryOptions, Id, Map, RawDecoder,
|
||||
RawEncoder, StateVector, StoreHistory, Text, Update, Value,
|
||||
};
|
||||
pub(crate) use doc::{Content, Item};
|
||||
use log::{debug, warn};
|
||||
use nom::IResult;
|
||||
pub use protocol::{
|
||||
read_sync_message, write_sync_message, AwarenessState, AwarenessStates, DocMessage, SyncMessage,
|
||||
SyncMessageScanner,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
pub enum JwstCodecError {
|
||||
#[error("Unexpected Scenario")]
|
||||
Unexpected,
|
||||
#[error("Damaged document: corrupt json data")]
|
||||
DamagedDocumentJson,
|
||||
#[error("Incomplete document: {0}")]
|
||||
IncompleteDocument(String),
|
||||
#[error("Invalid write buffer: {0}")]
|
||||
InvalidWriteBuffer(String),
|
||||
#[error("Content does not support splitting in {0}")]
|
||||
ContentSplitNotSupport(u64),
|
||||
#[error("GC or Skip does not support splitting")]
|
||||
ItemSplitNotSupport,
|
||||
#[error("update is empty")]
|
||||
UpdateIsEmpty,
|
||||
#[error("invalid update")]
|
||||
UpdateInvalid(#[from] nom::Err<nom::error::Error<usize>>),
|
||||
#[error("update not fully consumed: {0}")]
|
||||
UpdateNotFullyConsumed(usize),
|
||||
#[error("invalid struct clock, expect {expect}, actually {actually}")]
|
||||
StructClockInvalid { expect: u64, actually: u64 },
|
||||
#[error("cannot find struct {clock} in {client_id}")]
|
||||
StructSequenceInvalid { client_id: u64, clock: u64 },
|
||||
#[error("struct {0} not exists")]
|
||||
StructSequenceNotExists(u64),
|
||||
#[error("Invalid parent")]
|
||||
InvalidParent,
|
||||
#[error("Parent not found")]
|
||||
ParentNotFound,
|
||||
#[error("Invalid struct type, expect item, actually {0}")]
|
||||
InvalidStructType(&'static str),
|
||||
#[error("Can not cast known type to {0}")]
|
||||
TypeCastError(&'static str),
|
||||
#[error("Can not found root struct with name: {0}")]
|
||||
RootStructNotFound(String),
|
||||
#[error("Index {0} out of bound")]
|
||||
IndexOutOfBound(u64),
|
||||
#[error("Document has been released")]
|
||||
DocReleased,
|
||||
#[error("Unexpected type, expect {0}")]
|
||||
UnexpectedType(&'static str),
|
||||
}
|
||||
|
||||
pub type JwstCodecResult<T = ()> = Result<T, JwstCodecError>;
|
||||
@@ -0,0 +1,151 @@
|
||||
use nom::{multi::count, Parser};
|
||||
|
||||
use super::*;
|
||||
|
||||
const NULL_STR: &str = "null";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
|
||||
pub struct AwarenessState {
|
||||
#[cfg_attr(test, proptest(strategy = "0..u32::MAX as u64"))]
|
||||
pub(crate) clock: u64,
|
||||
// content is usually a json
|
||||
pub(crate) content: String,
|
||||
}
|
||||
|
||||
impl AwarenessState {
|
||||
pub fn new(clock: u64, content: String) -> Self {
|
||||
AwarenessState { clock, content }
|
||||
}
|
||||
|
||||
pub fn clock(&self) -> u64 {
|
||||
self.clock
|
||||
}
|
||||
|
||||
pub fn content(&self) -> &str {
|
||||
&self.content
|
||||
}
|
||||
|
||||
pub fn is_deleted(&self) -> bool {
|
||||
self.content == NULL_STR
|
||||
}
|
||||
|
||||
pub(crate) fn add_clock(&mut self) {
|
||||
self.clock += 1;
|
||||
}
|
||||
|
||||
pub(crate) fn set_clock(&mut self, clock: u64) {
|
||||
self.clock = clock;
|
||||
}
|
||||
|
||||
pub fn set_content(&mut self, content: String) {
|
||||
self.add_clock();
|
||||
self.content = content;
|
||||
}
|
||||
|
||||
pub fn delete(&mut self) {
|
||||
self.set_content(NULL_STR.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AwarenessState {
|
||||
fn default() -> Self {
|
||||
AwarenessState {
|
||||
clock: 0,
|
||||
content: NULL_STR.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_awareness_state(input: &[u8]) -> IResult<&[u8], (u64, AwarenessState)> {
|
||||
let (tail, client_id) = read_var_u64(input)?;
|
||||
let (tail, clock) = read_var_u64(tail)?;
|
||||
let (tail, content) = read_var_string(tail)?;
|
||||
|
||||
Ok((tail, (client_id, AwarenessState { clock, content })))
|
||||
}
|
||||
|
||||
fn write_awareness_state<W: Write>(
|
||||
buffer: &mut W,
|
||||
client_id: u64,
|
||||
state: &AwarenessState,
|
||||
) -> Result<(), IoError> {
|
||||
write_var_u64(buffer, client_id)?;
|
||||
write_var_u64(buffer, state.clock)?;
|
||||
write_var_string(buffer, state.content.clone())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub type AwarenessStates = HashMap<u64, AwarenessState>;
|
||||
|
||||
pub fn read_awareness(input: &[u8]) -> IResult<&[u8], AwarenessStates> {
|
||||
let (tail, len) = read_var_u64(input)?;
|
||||
let (tail, messages) = count(read_awareness_state, len as usize).parse(tail)?;
|
||||
|
||||
Ok((tail, messages.into_iter().collect()))
|
||||
}
|
||||
|
||||
pub fn write_awareness<W: Write>(buffer: &mut W, clients: &AwarenessStates) -> Result<(), IoError> {
|
||||
write_var_u64(buffer, clients.len() as u64)?;
|
||||
|
||||
for (client_id, state) in clients {
|
||||
write_awareness_state(buffer, *client_id, state)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO(@darkskygit): impl reader/writer
|
||||
// awareness state message
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct AwarenessMessage {
|
||||
clients: AwarenessStates,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_awareness() {
|
||||
let input = [
|
||||
3, // count of state
|
||||
1, 5, 1, 1, // first state
|
||||
2, 10, 2, 2, 3, // second state
|
||||
5, 5, 5, 1, 2, 3, 4, 5, // third state
|
||||
];
|
||||
|
||||
let expected = HashMap::from([
|
||||
(
|
||||
1,
|
||||
AwarenessState::new(5, String::from_utf8(vec![1]).unwrap()),
|
||||
),
|
||||
(
|
||||
2,
|
||||
AwarenessState::new(10, String::from_utf8(vec![2, 3]).unwrap()),
|
||||
),
|
||||
(
|
||||
5,
|
||||
AwarenessState::new(5, String::from_utf8(vec![1, 2, 3, 4, 5]).unwrap()),
|
||||
),
|
||||
]);
|
||||
|
||||
{
|
||||
let (tail, result) = read_awareness(&input).unwrap();
|
||||
assert!(tail.is_empty());
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
{
|
||||
let mut buffer = Vec::new();
|
||||
// hashmap has not a ordered keys, so buffer not equal each write
|
||||
// we need re-parse the buffer to check result
|
||||
write_awareness(&mut buffer, &expected).unwrap();
|
||||
let (tail, result) = read_awareness(&buffer).unwrap();
|
||||
assert!(tail.is_empty());
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
use super::*;
|
||||
|
||||
// doc sync message
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
|
||||
pub enum DocMessage {
|
||||
// state vector
|
||||
// TODO: temporarily skipped in the test, because yrs decoding needs to ensure that the update
|
||||
// in step1 is the correct state vector binary and any data can be included in our
|
||||
// implementation (we will ensure the correctness of encoding and decoding in the subsequent
|
||||
// decoding process)
|
||||
#[cfg_attr(test, proptest(skip))]
|
||||
Step1(Vec<u8>),
|
||||
// update
|
||||
Step2(Vec<u8>),
|
||||
// update
|
||||
Update(Vec<u8>),
|
||||
}
|
||||
|
||||
const DOC_MESSAGE_STEP1: u64 = 0;
|
||||
const DOC_MESSAGE_STEP2: u64 = 1;
|
||||
const DOC_MESSAGE_UPDATE: u64 = 2;
|
||||
|
||||
pub fn read_doc_message(input: &[u8]) -> IResult<&[u8], DocMessage> {
|
||||
let (tail, step) = read_var_u64(input)?;
|
||||
|
||||
match step {
|
||||
DOC_MESSAGE_STEP1 => {
|
||||
let (tail, sv) = read_var_buffer(tail)?;
|
||||
// TODO: decode state vector
|
||||
Ok((tail, DocMessage::Step1(sv.into())))
|
||||
}
|
||||
DOC_MESSAGE_STEP2 => {
|
||||
let (tail, update) = read_var_buffer(tail)?;
|
||||
// TODO: decode update
|
||||
Ok((tail, DocMessage::Step2(update.into())))
|
||||
}
|
||||
DOC_MESSAGE_UPDATE => {
|
||||
let (tail, update) = read_var_buffer(tail)?;
|
||||
// TODO: decode update
|
||||
Ok((tail, DocMessage::Update(update.into())))
|
||||
}
|
||||
_ => Err(nom::Err::Error(Error::new(input, ErrorKind::Tag))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_doc_message<W: Write>(buffer: &mut W, msg: &DocMessage) -> Result<(), IoError> {
|
||||
match msg {
|
||||
DocMessage::Step1(sv) => {
|
||||
write_var_u64(buffer, DOC_MESSAGE_STEP1)?;
|
||||
write_var_buffer(buffer, sv)?;
|
||||
}
|
||||
DocMessage::Step2(update) => {
|
||||
write_var_u64(buffer, DOC_MESSAGE_STEP2)?;
|
||||
write_var_buffer(buffer, update)?;
|
||||
}
|
||||
DocMessage::Update(update) => {
|
||||
write_var_u64(buffer, DOC_MESSAGE_UPDATE)?;
|
||||
write_var_buffer(buffer, update)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_doc_message() {
|
||||
let messages = [
|
||||
DocMessage::Step1(vec![0x01, 0x02, 0x03]),
|
||||
DocMessage::Step2(vec![0x04, 0x05, 0x06]),
|
||||
DocMessage::Update(vec![0x07, 0x08, 0x09]),
|
||||
];
|
||||
|
||||
for msg in messages {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
write_doc_message(&mut buffer, &msg).unwrap();
|
||||
let (tail, decoded) = read_doc_message(&buffer).unwrap();
|
||||
|
||||
assert_eq!(tail.len(), 0);
|
||||
assert_eq!(decoded, msg);
|
||||
}
|
||||
|
||||
// test invalid msg
|
||||
{
|
||||
let mut buffer = Vec::new();
|
||||
let msg = DocMessage::Step1(vec![0x01, 0x02, 0x03]);
|
||||
|
||||
write_doc_message(&mut buffer, &msg).unwrap();
|
||||
buffer[0] = 0xff; // Inject error in message tag
|
||||
let res = read_doc_message(&buffer);
|
||||
|
||||
match res.as_ref().unwrap_err() {
|
||||
nom::Err::Error(error) => assert_eq!(error.code, ErrorKind::Tag),
|
||||
_ => panic!("Expected error ErrorKind::Tag, but got {:?}", res),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
mod awareness;
|
||||
mod doc;
|
||||
mod scanner;
|
||||
mod sync;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{Error as IoError, Write},
|
||||
};
|
||||
|
||||
use awareness::{read_awareness, write_awareness};
|
||||
pub use awareness::{AwarenessState, AwarenessStates};
|
||||
pub use doc::DocMessage;
|
||||
use doc::{read_doc_message, write_doc_message};
|
||||
use log::debug;
|
||||
use nom::{
|
||||
error::{Error, ErrorKind},
|
||||
IResult,
|
||||
};
|
||||
pub use scanner::SyncMessageScanner;
|
||||
pub use sync::{read_sync_message, write_sync_message, SyncMessage};
|
||||
|
||||
use super::*;
|
||||
@@ -0,0 +1,64 @@
|
||||
use super::*;
|
||||
|
||||
pub struct SyncMessageScanner<'a> {
|
||||
buffer: &'a [u8],
|
||||
}
|
||||
|
||||
impl SyncMessageScanner<'_> {
|
||||
pub fn new(buffer: &[u8]) -> SyncMessageScanner {
|
||||
SyncMessageScanner { buffer }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SyncMessageScanner<'a> {
|
||||
type Item = Result<SyncMessage, nom::Err<nom::error::Error<&'a [u8]>>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.buffer.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match read_sync_message(self.buffer) {
|
||||
Ok((tail, message)) => {
|
||||
self.buffer = tail;
|
||||
Some(Ok(message))
|
||||
}
|
||||
Err(nom::Err::Incomplete(_))
|
||||
| Err(nom::Err::Error(nom::error::Error {
|
||||
code: nom::error::ErrorKind::Eof,
|
||||
..
|
||||
}))
|
||||
| Err(nom::Err::Failure(nom::error::Error {
|
||||
code: nom::error::ErrorKind::Eof,
|
||||
..
|
||||
})) => {
|
||||
debug!("incomplete sync message");
|
||||
None
|
||||
}
|
||||
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use proptest::{collection::vec, prelude::*};
|
||||
|
||||
use super::*;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_sync_message_scanner(messages in vec(any::<SyncMessage>(), 0..10)) {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
for message in &messages {
|
||||
write_sync_message(&mut buffer, message).unwrap();
|
||||
}
|
||||
|
||||
let result: Result<Vec<SyncMessage>, _> = SyncMessageScanner::new(&buffer).collect();
|
||||
assert_eq!(result.unwrap(), messages);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
use byteorder::WriteBytesExt;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum MessageType {
|
||||
Auth,
|
||||
Awareness,
|
||||
AwarenessQuery,
|
||||
Doc,
|
||||
}
|
||||
|
||||
fn read_sync_tag(input: &[u8]) -> IResult<&[u8], MessageType> {
|
||||
let (tail, tag) = read_var_u64(input)?;
|
||||
let tag = match tag {
|
||||
0 => MessageType::Doc,
|
||||
1 => MessageType::Awareness,
|
||||
2 => MessageType::Auth,
|
||||
3 => MessageType::AwarenessQuery,
|
||||
_ => return Err(nom::Err::Error(Error::new(input, ErrorKind::Tag))),
|
||||
};
|
||||
|
||||
Ok((tail, tag))
|
||||
}
|
||||
|
||||
fn write_sync_tag<W: Write>(buffer: &mut W, tag: MessageType) -> Result<(), IoError> {
|
||||
let tag: u64 = match tag {
|
||||
MessageType::Doc => 0,
|
||||
MessageType::Awareness => 1,
|
||||
MessageType::Auth => 2,
|
||||
MessageType::AwarenessQuery => 3,
|
||||
};
|
||||
|
||||
write_var_u64(buffer, tag)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// sync message
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
|
||||
pub enum SyncMessage {
|
||||
Auth(Option<String>),
|
||||
Awareness(AwarenessStates),
|
||||
AwarenessQuery,
|
||||
Doc(DocMessage),
|
||||
}
|
||||
|
||||
pub fn read_sync_message(input: &[u8]) -> IResult<&[u8], SyncMessage> {
|
||||
let (tail, tag) = read_sync_tag(input)?;
|
||||
|
||||
let (tail, message) = match tag {
|
||||
MessageType::Doc => {
|
||||
let (tail, doc) = read_doc_message(tail)?;
|
||||
(tail, SyncMessage::Doc(doc))
|
||||
}
|
||||
MessageType::Awareness => {
|
||||
let (tail, update) = read_var_buffer(tail)?;
|
||||
(
|
||||
tail,
|
||||
SyncMessage::Awareness({
|
||||
let (awareness_tail, awareness) = read_awareness(update)?;
|
||||
let tail_len = awareness_tail.len();
|
||||
if tail_len > 0 {
|
||||
debug!("awareness update has trailing bytes: {}", tail_len);
|
||||
debug_assert!(tail_len > 0, "awareness update has trailing bytes");
|
||||
}
|
||||
awareness
|
||||
}),
|
||||
)
|
||||
}
|
||||
MessageType::Auth => {
|
||||
let (tail, success) = read_var_u64(tail)?;
|
||||
|
||||
if success == 1 {
|
||||
(tail, SyncMessage::Auth(None))
|
||||
} else {
|
||||
let (tail, reason) = read_var_string(tail)?;
|
||||
(tail, SyncMessage::Auth(Some(reason)))
|
||||
}
|
||||
}
|
||||
MessageType::AwarenessQuery => (tail, SyncMessage::AwarenessQuery),
|
||||
};
|
||||
|
||||
Ok((tail, message))
|
||||
}
|
||||
|
||||
pub fn write_sync_message<W: Write>(buffer: &mut W, msg: &SyncMessage) -> Result<(), IoError> {
|
||||
match msg {
|
||||
SyncMessage::Auth(reason) => {
|
||||
const PERMISSION_DENIED: u8 = 0;
|
||||
const PERMISSION_GRANTED: u8 = 1;
|
||||
|
||||
write_sync_tag(buffer, MessageType::Auth)?;
|
||||
if let Some(reason) = reason {
|
||||
buffer.write_u8(PERMISSION_DENIED)?;
|
||||
write_var_string(buffer, reason)?;
|
||||
} else {
|
||||
buffer.write_u8(PERMISSION_GRANTED)?;
|
||||
}
|
||||
}
|
||||
SyncMessage::AwarenessQuery => {
|
||||
write_sync_tag(buffer, MessageType::AwarenessQuery)?;
|
||||
}
|
||||
SyncMessage::Awareness(awareness) => {
|
||||
write_sync_tag(buffer, MessageType::Awareness)?;
|
||||
write_var_buffer(buffer, &{
|
||||
let mut update = Vec::new();
|
||||
write_awareness(&mut update, awareness)?;
|
||||
update
|
||||
})?;
|
||||
}
|
||||
SyncMessage::Doc(doc) => {
|
||||
write_sync_tag(buffer, MessageType::Doc)?;
|
||||
write_doc_message(buffer, doc)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{awareness::AwarenessState, *};
|
||||
|
||||
#[test]
|
||||
fn test_sync_tag() {
|
||||
let messages = [
|
||||
MessageType::Auth,
|
||||
MessageType::Awareness,
|
||||
MessageType::AwarenessQuery,
|
||||
MessageType::Doc,
|
||||
];
|
||||
|
||||
for msg in messages {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
write_sync_tag(&mut buffer, msg.clone()).unwrap();
|
||||
let (tail, decoded) = read_sync_tag(&buffer).unwrap();
|
||||
|
||||
assert_eq!(tail.len(), 0);
|
||||
assert_eq!(decoded, msg);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_message() {
|
||||
let messages = [
|
||||
SyncMessage::Auth(Some("reason".to_string())),
|
||||
SyncMessage::Awareness(HashMap::from([(1, AwarenessState::new(1, "test".into()))])),
|
||||
SyncMessage::AwarenessQuery,
|
||||
SyncMessage::Doc(DocMessage::Step1(vec![4, 5, 6])),
|
||||
SyncMessage::Doc(DocMessage::Step2(vec![7, 8, 9])),
|
||||
SyncMessage::Doc(DocMessage::Update(vec![10, 11, 12])),
|
||||
];
|
||||
|
||||
for msg in messages {
|
||||
let mut buffer = Vec::new();
|
||||
write_sync_message(&mut buffer, &msg).unwrap();
|
||||
let (tail, decoded) = read_sync_message(&buffer).unwrap();
|
||||
assert_eq!(tail.len(), 0);
|
||||
assert_eq!(decoded, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#[allow(unused)]
|
||||
#[cfg(not(loom))]
|
||||
pub(crate) use std::sync::{
|
||||
atomic::{AtomicBool, AtomicU16, AtomicU32, AtomicU8, Ordering},
|
||||
Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard,
|
||||
};
|
||||
pub use std::sync::{Arc, Weak};
|
||||
#[cfg(all(test, not(loom)))]
|
||||
pub(crate) use std::{
|
||||
sync::{atomic::AtomicUsize, MutexGuard},
|
||||
thread,
|
||||
};
|
||||
|
||||
#[cfg(loom)]
|
||||
pub(crate) use loom::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU16, AtomicU8, AtomicUsize, Ordering},
|
||||
Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard,
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! loom_model {
|
||||
($test:block) => {
|
||||
#[cfg(loom)]
|
||||
loom::model(move || $test);
|
||||
|
||||
#[cfg(not(loom))]
|
||||
$test
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user