diff --git a/src/api/controller.rs b/src/api/controller.rs index 8a4a5b5..5866f6b 100644 --- a/src/api/controller.rs +++ b/src/api/controller.rs @@ -20,20 +20,22 @@ pub(crate) trait ControllerWorker { /// this generic trait is implemented by actors managing stream procedures. /// events can be enqueued for dispatching without blocking ([Controller::send]), and an async blocking /// api ([Controller::recv]) is provided to wait for server events. Additional sync blocking -/// ([Controller::blocking_recv]) and callback-based ([Controller::callback]) are implemented. +/// ([Controller::blocking_recv]) is implemented if feature `sync` is enabled. /// -/// * if possible, prefer a pure [Controller::recv] consumer -/// * a second possibility in preference is using a [Controller::callback] -/// * if neither is feasible a [Controller::poll]/[Controller::try_recv] approach is available +/// * if possible, prefer a pure [Controller::recv] consumer, awaiting for events +/// * if async is not feasible a [Controller::poll]/[Controller::try_recv] approach is possible #[async_trait::async_trait] pub trait Controller : Sized + Send + Sync { /// type of upstream values, used in [Self::send] type Input; - /// enqueue a new value to be sent + /// enqueue a new value to be sent to all other users + /// + /// success or failure of this function does not imply validity of sent operation, + /// because it's integrated asynchronously on the background worker fn send(&self, x: Self::Input) -> Result<()>; - /// get next value from stream, blocking until one is available + /// get next value from other users, blocking until one is available /// /// this is just an async trait function wrapped by `async_trait`: /// @@ -47,7 +49,7 @@ pub trait Controller : Sized + Send + Sync { Ok(self.try_recv()?.expect("no message available after polling")) } - /// block until next value is added to the stream without removing any element + /// block until next value is available without consuming it /// /// this is just an async trait function wrapped by `async_trait`: /// @@ -55,9 +57,14 @@ pub trait Controller : Sized + Send + Sync { async fn poll(&self) -> Result<()>; /// attempt to receive a value without blocking, return None if nothing is available + /// + /// note that this function does not circumvent race conditions, returning errors if it would + /// block. it's usually safe to ignore such errors and retry fn try_recv(&self) -> Result>; /// sync variant of [Self::recv], blocking invoking thread + /// this calls [Controller::recv] inside a [tokio::runtime::Runtime::block_on] + #[cfg(feature = "sync")] fn blocking_recv(&self, rt: &tokio::runtime::Handle) -> Result { rt.block_on(self.recv()) } diff --git a/src/buffer/controller.rs b/src/buffer/controller.rs index 52f86e2..825371a 100644 --- a/src/buffer/controller.rs +++ b/src/buffer/controller.rs @@ -25,9 +25,6 @@ use crate::api::TextChange; /// queues, transforming outbound delayed ops and applying remote changes /// to the local buffer /// -/// this controller implements [crate::api::OperationFactory], allowing to produce -/// Operation Sequences easily -/// /// upon dropping this handle will stop the associated worker #[derive(Debug, Clone)] pub struct BufferController { diff --git a/src/client.rs b/src/client.rs index 5a729f2..3adb1c8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -120,11 +120,8 @@ impl Client { /// attach to a buffer, starting a buffer controller and returning a new reference to it /// - /// to interact with such buffer [crate::api::Controller::send] operation sequences - /// or [crate::api::Controller::recv] for text events using its [crate::buffer::Controller]. - /// to generate operation sequences use the [crate::api::OperationFactory] - /// methods, which are implemented on [crate::buffer::Controller], such as - /// [crate::api::OperationFactory::diff]. + /// to interact with such buffer use [crate::api::Controller::send] or + /// [crate::api::Controller::recv] to exchange [crate::api::TextChange] pub async fn attach(&mut self, path: &str) -> Result, Error> { if let Some(workspace) = &mut self.workspace { let mut client = self.client.buffer.clone(); diff --git a/src/cursor/mod.rs b/src/cursor/mod.rs index 16d0950..0e34aa7 100644 --- a/src/cursor/mod.rs +++ b/src/cursor/mod.rs @@ -26,13 +26,20 @@ impl From::<(i32, i32)> for RowCol { } } +impl RowCol { + /// create a RowCol and wrap into an Option, to help build protocol packets + pub fn wrap(row: i32, col: i32) -> Option { + Some(RowCol { row, col }) + } +} + impl CursorPosition { - /// extract start position, defaulting to (0,0) + /// extract start position, defaulting to (0,0), to help build protocol packets pub fn start(&self) -> RowCol { self.start.clone().unwrap_or((0, 0).into()) } - /// extract end position, defaulting to (0,0) + /// extract end position, defaulting to (0,0), to help build protocol packets pub fn end(&self) -> RowCol { self.end.clone().unwrap_or((0, 0).into()) } diff --git a/src/lib.rs b/src/lib.rs index a3b47c8..5d3059e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,17 +14,14 @@ //! Blocking and callback variants are also implemented. The [api::Controller] can also be used to send new //! events to the server ([api::Controller::send]). //! -//! Each operation on a buffer is represented as an [ot::OperationSeq]. -//! A visualization about how OperationSeqs work is available -//! [here](http://operational-transformation.github.io/index.html), -//! but to use this library it's only sufficient to know that they can only -//! be applied on buffers of some length and are transformable to be able to be -//! applied in a different order while maintaining the same result. -//! -//! To generate Operation Sequences use helper methods from module [api::factory] (trait [api::OperationFactory]). +//! Each operation on a buffer is represented as an [woot::crdt::Op]. The underlying buffer is a +//! [WOOT CRDT](https://inria.hal.science/file/index/docid/71240/filename/RR-5580.pdf), +//! but to use this library it's only sufficient to know that all WOOT buffers that have received +//! the same operations converge to the same state, and that operations might not get integrated +//! immediately but instead deferred until compatible. //! //! ## features -//! * `ot` : include the underlying operational transform library (default enabled) +//! * `woot` : include the underlying CRDT library and re-exports it (default enabled) //! * `api` : include traits for core interfaces under [api] (default enabled) //! * `proto` : include GRCP protocol definitions under [proto] (default enabled) //! * `client`: include the local [client] implementation (default enabled) @@ -40,7 +37,7 @@ //! [instance::a_sync::Instance] //! //! ```rust,no_run -//! use codemp::api::{Controller, OperationFactory}; +//! use codemp::api::{Controller, TextChange}; //! # use codemp::instance::a_sync::Instance; //! //! # async fn async_example() -> codemp::Result<()> { @@ -62,12 +59,9 @@ //! // attach to a new buffer and execute operations on it //! session.create("test.txt", None).await?; // create new buffer //! let buffer = session.attach("test.txt").await?; // attach to it -//! let text = buffer.content(); // any string can be used as operation factory -//! buffer.send(text.ins("hello", 0))?; // insert some text -//! if let Some(operation) = text.diff(4, "o world", 5) { -//! buffer.send(operation)?; // replace with precision, if valid -//! } -//! assert_eq!(buffer.content(), "hello world"); +//! let local_change = TextChange { span: 0..0, content: "hello!".into() }; +//! buffer.send(local_change)?; // insert some text +//! let remote_change = buffer.recv().await?; //! # //! # Ok(()) //! # } @@ -85,16 +79,6 @@ //! let session = Instance::default(); // instantiate sync variant //! session.connect("http://alemi.dev:50051")?; // connect to server //! -//! // join remote workspace and handle cursor events with a callback -//! let cursor = session.join("some_workspace")?; // join workspace -//! let (stop, stop_rx) = tokio::sync::mpsc::unbounded_channel(); // create stop channel -//! Arc::new(cursor).callback( // register callback -//! session.rt(), stop_rx, // pass instance runtime and stop channel receiver -//! | cursor_event | { -//! println!("received cursor event: {:?}", cursor_event); -//! } -//! ); -//! //! // attach to buffer and blockingly receive events //! let buffer = session.attach("test.txt")?; // attach to buffer, must already exist //! while let Ok(op) = buffer.blocking_recv(session.rt()) { // must pass runtime