use webassembly with the server acting only as an intermediate

This commit is contained in:
Sebastian Hugentobler 2023-03-09 08:35:24 +01:00
parent 9aa1130c35
commit 7d0ef62c42
Signed by: shu
GPG key ID: BB32CF3CA052C2F0
29 changed files with 850 additions and 325 deletions

7
dist_text/Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "dist_text"
version = "0.1.0"
edition = "2021"
[dependencies]
crdts = "7.3.0"

3
dist_text/src/lib.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod text;
pub use crdts;

180
dist_text/src/text.rs Normal file
View file

@ -0,0 +1,180 @@
use std::fmt::{Display, Formatter};
use crdts::{CmRDT, List};
use crdts::list::Op;
#[derive(Clone)]
pub struct Text {
inner: List<u16, String>,
}
impl Default for Text {
fn default() -> Self {
Self {
inner: List::new()
}
}
}
impl Display for Text {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str_bytes: Vec<u16> = self.inner.to_owned().read_into();
let str_repr = String::from_utf16_lossy(&str_bytes);
write!(f, "{}", str_repr)
}
}
impl Text {
pub fn apply_ops(&mut self, ops: Vec<Op<u16, String>>) {
ops.iter().for_each(move |op| self.inner.apply(op.to_owned()));
}
pub fn insert_linebreak(&mut self, start: usize, end: usize, src: &str) -> Vec<Op<u16, String>> {
self.insert(start, end, "\n", src)
}
pub fn insert(&mut self, start: usize, end: usize, data: &str, src: &str) -> Vec<Op<u16, String>> {
let mut ops: Vec<Op<u16, String>> = Vec::new();
if start < end {
let mut delete_ops = self.delete(start, end, src);
ops.append(&mut delete_ops);
}
let data: Vec<u16> = data.encode_utf16().collect();
let mut insert_idx = start;
for byte in data.iter() {
let op = self.inner.insert_index(insert_idx, *byte, src.to_owned());
self.inner.apply(op.to_owned());
insert_idx += 1;
ops.push(op);
}
ops
}
pub fn delete_backward(&mut self, start: usize, end: usize, src: &str) -> Vec<Op<u16, String>> {
if (start == 0 && start == end) || end > self.inner.len() { return Vec::new(); }
let (end, start) = if start == end { (start, start - 1) } else { (end, start) };
self.delete(start, end, src)
}
pub fn delete_forward(&mut self, start: usize, end: usize, src: &str) -> Vec<Op<u16, String>> {
if start >= self.inner.len() { return Vec::new(); }
let end = if start == end { start + 1 } else { end };
self.delete(start, end, src)
}
pub fn delete(&mut self, start: usize, end: usize, src: &str) -> Vec<Op<u16, String>> {
let mut start_idx = start;
let mut end_idx = end;
while let Some(byte) = self.inner.position(start_idx) {
if !matches!(byte, 0xDC00..=0xDFFF) {
break;
}
start_idx -= 1;
}
if let Some(end_byte) = self.inner.position(end_idx - 1) {
if end_idx < self.inner.len() && end_byte > &0xD800 && end_byte < &0xDC00 {
end_idx += 1;
}
}
let mut ops: Vec<Op<u16, String>> = Vec::new();
for _ in start_idx..end_idx {
if let Some(op) = self.inner.delete_index(start_idx, src.to_owned()) {
self.inner.apply(op.to_owned());
ops.push(op);
}
}
ops
}
}
#[cfg(test)]
mod tests {
use crate::text::Text;
#[test]
fn insert_delete() {
let mut t = Text::new();
t.insert(0, 0, "Hello", "A");
t.insert(5, 5, "world!", "A");
t.insert(5, 5, ", ", "B");
assert_eq!(t.to_string(), "Hello, world!");
t.insert(7, 12, "🌍", "A");
assert_eq!(t.to_string(), "Hello, 🌍!");
t.delete(8, 9, "B");
t.insert(7, 7, "🚀", "C");
assert_eq!(t.to_string(), "Hello, 🚀!");
}
#[test]
fn backspace() {
let mut t = Text::new();
t.insert(0, 0, "Hello", "A");
t.delete_backward(5, 5, "A");
assert_eq!(t.to_string(), "Hell");
t.delete_backward(0, 4, "A");
assert_eq!(t.to_string(), "");
}
#[test]
fn delete() {
let mut t = Text::new();
t.insert(0, 0, "Hello", "A");
t.delete_forward(4, 4, "A");
assert_eq!(t.to_string(), "Hell");
t.delete_forward(0, 4, "A");
assert_eq!(t.to_string(), "");
}
#[test]
fn linebreak() {
let mut t = Text::new();
t.insert(0, 0, "Hello, world!", "A");
t.insert_linebreak(6, 6, "A");
assert_eq!(t.to_string(), "Hello,\n world!");
t.delete_backward(7, 7, "B");
t.insert_linebreak(6, 7, "A");
assert_eq!(t.to_string(), "Hello,\nworld!");
}
#[test]
fn apply_ops() {
let mut t_a = Text::new();
let ops_a = t_a.insert(0, 0, "Hello, world!", "A");
let mut t_b = Text::new();
t_b.apply_ops(ops_a);
assert_eq!(t_b.to_string(), "Hello, world!");
let ops_a = t_a.insert(7,7, "distributed ", "A");
t_b.apply_ops(ops_a);
let ops_b = t_b.insert(7,7, "cruel ", "B");
t_a.apply_ops(ops_b);
assert_eq!(t_a.to_string(), "Hello, cruel distributed world!");
assert_eq!(t_b.to_string(), "Hello, cruel distributed world!");
}
}