mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-22 07:14:50 +01:00
fix: kind of resolved local race condition...
... but it became so much spaghetti that I'm not eating any more pasta for a while seriously tho must be resolved, maybe moving this logic completely serverside?
This commit is contained in:
parent
1e3439e08e
commit
14145ec369
2 changed files with 39 additions and 58 deletions
|
@ -6,7 +6,7 @@
|
||||||
//! this module contains buffer-related operations and helpers to create Operation Sequences
|
//! this module contains buffer-related operations and helpers to create Operation Sequences
|
||||||
//! (the underlying chunks of changes sent over the wire)
|
//! (the underlying chunks of changes sent over the wire)
|
||||||
|
|
||||||
use std::{ops::Range, sync::Arc};
|
use std::ops::Range;
|
||||||
|
|
||||||
pub(crate) mod worker;
|
pub(crate) mod worker;
|
||||||
|
|
||||||
|
@ -19,8 +19,6 @@ pub mod factory;
|
||||||
pub use factory::OperationFactory;
|
pub use factory::OperationFactory;
|
||||||
pub use controller::BufferController as Controller;
|
pub use controller::BufferController as Controller;
|
||||||
|
|
||||||
use crate::proto::RowCol;
|
|
||||||
|
|
||||||
|
|
||||||
/// an editor-friendly representation of a text change in a buffer
|
/// an editor-friendly representation of a text change in a buffer
|
||||||
///
|
///
|
||||||
|
@ -31,31 +29,4 @@ pub struct TextChange {
|
||||||
pub span: Range<usize>,
|
pub span: Range<usize>,
|
||||||
/// content of text change, as string
|
/// content of text change, as string
|
||||||
pub content: String,
|
pub content: String,
|
||||||
/// reference to previous content of buffer
|
|
||||||
pub before: Arc<String>,
|
|
||||||
/// reference to current content of buffer
|
|
||||||
pub after: Arc<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextChange {
|
|
||||||
/// convert from byte index to row and column.
|
|
||||||
/// if `end` is true, span end will be used, otherwise span start
|
|
||||||
/// if `after` is true, buffer after change will be used, otherwise buffer before change
|
|
||||||
fn index_to_rowcol(&self, end: bool, after: bool) -> RowCol {
|
|
||||||
let txt = if after { &self.after } else { &self.before };
|
|
||||||
let index = if end { self.span.end } else { self.span.start };
|
|
||||||
let row = txt[..index].matches('\n').count() as i32;
|
|
||||||
let col = txt[..index].split('\n').last().unwrap_or("").len() as i32;
|
|
||||||
RowCol { row, col }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// retrn row and column of text change start
|
|
||||||
pub fn start(&self) -> RowCol {
|
|
||||||
self.index_to_rowcol(false, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// return row and column of text change end
|
|
||||||
pub fn end(&self) -> RowCol {
|
|
||||||
self.index_to_rowcol(true, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::{sync::Arc, collections::VecDeque};
|
use std::{sync::Arc, collections::VecDeque};
|
||||||
|
|
||||||
use operational_transform::{OperationSeq, OTError};
|
use operational_transform::OperationSeq;
|
||||||
use tokio::sync::{watch, mpsc, broadcast, Mutex};
|
use tokio::sync::{watch, mpsc, broadcast, Mutex};
|
||||||
use tonic::transport::Channel;
|
use tonic::transport::Channel;
|
||||||
use tonic::{async_trait, Streaming};
|
use tonic::{async_trait, Streaming};
|
||||||
|
|
||||||
use crate::errors::{IgnorableError, IgnorableDefaultableError};
|
use crate::errors::IgnorableError;
|
||||||
use crate::proto::{OperationRequest, RawOp};
|
use crate::proto::{OperationRequest, RawOp};
|
||||||
use crate::proto::buffer_client::BufferClient;
|
use crate::proto::buffer_client::BufferClient;
|
||||||
use crate::api::ControllerWorker;
|
use crate::api::ControllerWorker;
|
||||||
|
@ -50,21 +50,6 @@ impl BufferControllerWorker {
|
||||||
operation_tick: Arc::new(AtomicU64::new(0)),
|
operation_tick: Arc::new(AtomicU64::new(0)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, op: &OperationSeq) -> Result<TextChange, OTError> {
|
|
||||||
let before = Arc::new(self.buffer.clone());
|
|
||||||
let res = op.apply(&before)?;
|
|
||||||
self.content.send(res.clone())
|
|
||||||
.unwrap_or_warn("error showing updated buffer");
|
|
||||||
let after = Arc::new(res.clone());
|
|
||||||
self.buffer = res;
|
|
||||||
let skip = leading_noop(op.ops()) as usize;
|
|
||||||
let before_len = op.base_len();
|
|
||||||
let tail = tailing_noop(op.ops()) as usize;
|
|
||||||
let span = skip..before_len-tail;
|
|
||||||
let content = after[skip..after.len()-tail].to_string();
|
|
||||||
Ok(TextChange { span, content, before, after })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -87,6 +72,7 @@ impl ControllerWorker<TextChange> for BufferControllerWorker {
|
||||||
let mut clientside : VecDeque<OperationSeq> = VecDeque::new();
|
let mut clientside : VecDeque<OperationSeq> = VecDeque::new();
|
||||||
let mut serverside : VecDeque<OperationSeq> = VecDeque::new();
|
let mut serverside : VecDeque<OperationSeq> = VecDeque::new();
|
||||||
let mut last_seen_tick = 0;
|
let mut last_seen_tick = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
||||||
// block until one of these is ready
|
// block until one of these is ready
|
||||||
|
@ -109,7 +95,6 @@ impl ControllerWorker<TextChange> for BufferControllerWorker {
|
||||||
match res {
|
match res {
|
||||||
None => return tracing::warn!("client closed operation stream"),
|
None => return tracing::warn!("client closed operation stream"),
|
||||||
Some(op) => {
|
Some(op) => {
|
||||||
let _ = self.update(&op);
|
|
||||||
clientside.push_back(op.clone());
|
clientside.push_back(op.clone());
|
||||||
last_seen_tick = self.operation_tick.load(Ordering::Acquire);
|
last_seen_tick = self.operation_tick.load(Ordering::Acquire);
|
||||||
}
|
}
|
||||||
|
@ -130,22 +115,40 @@ impl ControllerWorker<TextChange> for BufferControllerWorker {
|
||||||
// our ops with server's ops but server won't transform its ops with ours. We must transform
|
// our ops with server's ops but server won't transform its ops with ours. We must transform
|
||||||
// ALL enqueued client ops: if a new one arrived before we could transform and update, we
|
// ALL enqueued client ops: if a new one arrived before we could transform and update, we
|
||||||
// should discard our progress and poll again.
|
// should discard our progress and poll again.
|
||||||
while let Some(mut operation) = serverside.get(0).cloned() {
|
while let Some(operation) = serverside.get(0).cloned() {
|
||||||
|
let mut transformed_op = operation.clone();
|
||||||
let mut queued_ops = clientside.clone();
|
let mut queued_ops = clientside.clone();
|
||||||
|
let mut txt_before = self.buffer.clone();
|
||||||
for op in queued_ops.iter_mut() {
|
for op in queued_ops.iter_mut() {
|
||||||
(*op, operation) = match op.transform(&operation) {
|
txt_before = match op.apply(&txt_before) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(_) => { tracing::error!("could not apply outgoing enqueued opseq to current buffer?"); break; },
|
||||||
|
};
|
||||||
|
(*op, transformed_op) = match op.transform(&transformed_op) {
|
||||||
|
Err(e) => { tracing::warn!("could not transform enqueued operation: {}", e); break; },
|
||||||
Ok((x, y)) => (x, y),
|
Ok((x, y)) => (x, y),
|
||||||
Err(e) => {
|
};
|
||||||
tracing::warn!("could not transform enqueued operation: {}", e);
|
|
||||||
break
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let skip = leading_noop(transformed_op.ops()) as usize;
|
||||||
|
let tail = tailing_noop(transformed_op.ops()) as usize;
|
||||||
|
let span = skip..(transformed_op.base_len() - tail);
|
||||||
|
let after = transformed_op.apply(&txt_before).expect("could not apply transformed op");
|
||||||
|
let change = TextChange { span, content: after[skip..after.len()-tail].to_string() };
|
||||||
|
|
||||||
let tick = self.operation_tick.load(std::sync::atomic::Ordering::Acquire);
|
let tick = self.operation_tick.load(std::sync::atomic::Ordering::Acquire);
|
||||||
if tick != last_seen_tick { break } // there are more ops to see first
|
if tick != last_seen_tick {
|
||||||
|
tracing::warn!("skipping downstream because there are ops");
|
||||||
|
break
|
||||||
|
} // there are more ops to see first
|
||||||
clientside = queued_ops;
|
clientside = queued_ops;
|
||||||
let change = self.update(&operation)
|
self.buffer = match operation.apply(&self.buffer) {
|
||||||
.unwrap_or_warn_default("coult not update with (transformed) remote operation");
|
Ok(x) => x,
|
||||||
|
Err(_) => { tracing::error!("wtf received op could not be applied?"); break; },
|
||||||
|
};
|
||||||
|
if clientside.is_empty() {
|
||||||
|
self.content.send(self.buffer.clone()).expect("could not broadcast new buffer content");
|
||||||
|
}
|
||||||
self.stream.send(change)
|
self.stream.send(change)
|
||||||
.unwrap_or_warn("could not send operation to server");
|
.unwrap_or_warn("could not send operation to server");
|
||||||
serverside.pop_front();
|
serverside.pop_front();
|
||||||
|
@ -172,8 +175,15 @@ impl ControllerWorker<TextChange> for BufferControllerWorker {
|
||||||
tracing::warn!("server rejected operation: {}", e);
|
tracing::warn!("server rejected operation: {}", e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
self.buffer = match op.apply(&self.buffer) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(_) => { tracing::error!("wtf accepted remote op could not be applied to our buffer????"); break; },
|
||||||
|
};
|
||||||
|
self.content.send(self.buffer.clone()).expect("could not broadcast buffer update");
|
||||||
clientside.pop_front();
|
clientside.pop_front();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
tracing::warn!("skipping upstream because there are ops");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue