2023-08-20 09:13:21 +02:00
|
|
|
//! # MultiPlayer Code Editinglib
|
2023-08-19 21:44:27 +02:00
|
|
|
//!
|
2023-08-20 03:34:48 +02:00
|
|
|
//! ![just a nice pic](https://alemi.dev/img/about-slice-1.png)
|
|
|
|
//!
|
2023-08-19 21:44:27 +02:00
|
|
|
//! This is the core library of the codemp project.
|
|
|
|
//!
|
2023-08-20 09:13:21 +02:00
|
|
|
//! ## structure
|
2023-08-20 00:46:55 +02:00
|
|
|
//! The main entrypoint is the [Client] object, that maintains a connection and can
|
2023-08-19 21:44:27 +02:00
|
|
|
//! be used to join workspaces or attach to buffers.
|
|
|
|
//!
|
|
|
|
//! Some actions will return structs implementing the [Controller] trait. These can be polled
|
2023-08-20 00:46:55 +02:00
|
|
|
//! for new stream events ([Controller::recv]), which will be returned in order. Blocking and
|
|
|
|
//! callback variants are also implemented. The [Controller] can also be used to send new
|
|
|
|
//! events to the server ([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 the trait [buffer::OperationFactory].
|
2023-08-19 21:44:27 +02:00
|
|
|
//!
|
2023-08-20 09:13:21 +02:00
|
|
|
//! ## features
|
2023-08-20 04:00:52 +02:00
|
|
|
//! * `proto` : include GRCP protocol definitions under [proto] (default enabled)
|
2023-08-19 21:44:27 +02:00
|
|
|
//! * `global`: provide a lazy_static global INSTANCE in [instance::global]
|
|
|
|
//! * `sync` : wraps the [instance::a_sync::Instance] holder into a sync variant: [instance::sync::Instance]
|
|
|
|
//!
|
2023-08-20 09:13:21 +02:00
|
|
|
//! ## examples
|
|
|
|
//! while the [Client] itself is the core structure implementing all methods, plugins will mostly
|
|
|
|
//! interact with [Instance] managers.
|
|
|
|
//!
|
|
|
|
//! ### async
|
2023-08-20 09:16:20 +02:00
|
|
|
//! this library is natively async and thus async usage should be preferred if possible with
|
|
|
|
//! [instance::a_sync::Instance]
|
2023-08-20 09:13:21 +02:00
|
|
|
//!
|
|
|
|
//! ```rust,no_run
|
|
|
|
//! use codemp::Controller;
|
|
|
|
//! use codemp::buffer::OperationFactory;
|
|
|
|
//!
|
|
|
|
//! # async fn async_example() -> codemp::Result<()> {
|
|
|
|
//! let session = codemp::Instance::default(); // create global session
|
|
|
|
//! session.connect("http://alemi.dev:50051").await?; // connect to remote server
|
2023-08-20 00:46:55 +02:00
|
|
|
//!
|
2023-08-20 09:13:21 +02:00
|
|
|
//! // join a remote workspace, obtaining a cursor controller
|
|
|
|
//! let cursor = session.join("some_workspace").await?;
|
|
|
|
//! cursor.send( // move cursor
|
|
|
|
//! codemp::proto::CursorPosition {
|
|
|
|
//! buffer: "test.txt".into(),
|
|
|
|
//! start: Some(codemp::proto::RowCol { row: 0, col: 0 }),
|
|
|
|
//! end: Some(codemp::proto::RowCol { row: 0, col: 1 }),
|
2023-08-20 00:46:55 +02:00
|
|
|
//! }
|
2023-08-20 09:13:21 +02:00
|
|
|
//! )?;
|
|
|
|
//! let op = cursor.recv().await?; // listen for event
|
|
|
|
//! println!("received cursor event: {:?}", op);
|
2023-08-20 00:46:55 +02:00
|
|
|
//!
|
2023-08-20 09:13:21 +02:00
|
|
|
//! // 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
|
|
|
|
//! buffer.send(buffer.insert("hello", 0))?; // insert some text
|
|
|
|
//! if let Some(operation) = buffer.delta(4, "o world", 5) {
|
|
|
|
//! buffer.send(operation)?; // replace with precision, if valid
|
2023-08-20 00:46:55 +02:00
|
|
|
//! }
|
2023-08-20 09:13:21 +02:00
|
|
|
//! assert_eq!(buffer.content(), "hello world");
|
|
|
|
//! #
|
|
|
|
//! # Ok(())
|
|
|
|
//! # }
|
2023-08-20 00:46:55 +02:00
|
|
|
//! ```
|
|
|
|
//!
|
2023-08-20 09:13:21 +02:00
|
|
|
//! ### sync
|
2023-08-20 09:16:20 +02:00
|
|
|
//! if async is not viable, including the feature `sync` will provide a sync-only [instance::sync::Instance] variant
|
2023-08-20 09:13:21 +02:00
|
|
|
//!
|
|
|
|
//! ```rust,no_run
|
|
|
|
//! # use codemp::instance::sync::Instance;
|
|
|
|
//! # use std::sync::Arc;
|
|
|
|
//! # use codemp::Controller;
|
|
|
|
//! #
|
|
|
|
//! # fn sync_example() -> codemp::Result<()> {
|
|
|
|
//! 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
|
|
|
|
//! println!("received buffer event: {:?}", op);
|
|
|
|
//! }
|
|
|
|
//! #
|
|
|
|
//! # Ok(())
|
|
|
|
//! # }
|
|
|
|
//! ```
|
|
|
|
//!
|
|
|
|
//! ### global
|
|
|
|
//! if instantiating the [Instance] manager is not possible, adding the feature `global` will
|
2023-08-20 09:16:20 +02:00
|
|
|
//! provide a static lazyly-allocated global reference: [struct@instance::global::INSTANCE].
|
2023-08-20 09:13:21 +02:00
|
|
|
//!
|
|
|
|
//! ```rust,no_run
|
|
|
|
//! # use codemp::instance::sync::Instance;
|
|
|
|
//! # use std::sync::Arc;
|
|
|
|
//! use codemp::prelude::*; // prelude includes everything with "Codemp" in front
|
|
|
|
//! # async fn global_example() -> codemp::Result<()> {
|
|
|
|
//! CODEMP_INSTANCE.connect("http://alemi.dev:50051").await?; // connect to server
|
|
|
|
//! let cursor = CODEMP_INSTANCE.join("some_workspace").await?; // join workspace
|
|
|
|
//! while let Ok(event) = cursor.recv().await { // receive cursor events
|
|
|
|
//! println!("received cursor event: {:?}", event);
|
2023-08-20 00:46:55 +02:00
|
|
|
//! }
|
2023-08-20 09:13:21 +02:00
|
|
|
//! #
|
|
|
|
//! # Ok(())
|
|
|
|
//! # }
|
2023-08-20 00:46:55 +02:00
|
|
|
//! ```
|
2023-08-20 03:34:48 +02:00
|
|
|
//!
|
2023-08-20 09:13:21 +02:00
|
|
|
//! ## references
|
2023-08-20 03:34:48 +02:00
|
|
|
//!
|
|
|
|
//! ![another cool pic coz why not](https://alemi.dev/img/about-slice-2.png)
|
|
|
|
//!
|
|
|
|
//! check [codemp-vscode](https://github.com/codewithotherpeopleandchangenamelater/codemp-vscode)
|
|
|
|
//! or [codemp-nvim](https://github.com/codewithotherpeopleandchangenamelater/codemp-nvim)
|
|
|
|
//! or [codemp-server](https://github.com/codewithotherpeopleandchangenamelater/codemp-server) for
|
2023-08-20 06:51:47 +02:00
|
|
|
//! reference implementations.
|
|
|
|
//!
|
|
|
|
//! keep track of feature completedness with the
|
|
|
|
//! [feature comparison matrix](https://github.com/orgs/codewithotherpeopleandchangenamelater/projects/3)
|
2023-08-20 03:34:48 +02:00
|
|
|
//!
|
2023-08-19 21:44:27 +02:00
|
|
|
|
|
|
|
|
|
|
|
/// cursor related types and controller
|
2023-04-17 14:56:00 +02:00
|
|
|
pub mod cursor;
|
2023-08-19 21:44:27 +02:00
|
|
|
|
|
|
|
/// buffer operations, factory, controller and types
|
2023-07-30 17:48:55 +02:00
|
|
|
pub mod buffer;
|
2023-08-19 21:44:27 +02:00
|
|
|
|
|
|
|
/// crate error types and helpers
|
2023-08-19 04:36:59 +02:00
|
|
|
pub mod errors;
|
2023-08-19 21:44:27 +02:00
|
|
|
|
|
|
|
/// underlying client session manager
|
2023-08-16 18:58:42 +02:00
|
|
|
pub mod client;
|
2023-08-19 21:44:27 +02:00
|
|
|
|
|
|
|
/// client wrapper to handle memory persistence
|
2023-08-16 23:09:47 +02:00
|
|
|
pub mod instance;
|
|
|
|
|
2023-08-19 21:44:27 +02:00
|
|
|
/// all-in-one imports : `use codemp::prelude::*;`
|
2023-08-17 02:58:55 +02:00
|
|
|
pub mod prelude;
|
|
|
|
|
2023-08-19 21:44:27 +02:00
|
|
|
/// underlying OperationalTransform library used, re-exported
|
2023-07-02 23:59:04 +02:00
|
|
|
pub use operational_transform as ot;
|
2023-07-01 13:54:34 +02:00
|
|
|
|
2023-08-19 21:44:27 +02:00
|
|
|
pub use client::Client;
|
|
|
|
|
|
|
|
#[cfg(feature = "sync")] pub use instance::sync::Instance;
|
|
|
|
#[cfg(not(feature = "sync"))] pub use instance::a_sync::Instance;
|
|
|
|
|
|
|
|
/// protocol types and services auto-generated by grpc
|
2023-07-30 22:58:24 +02:00
|
|
|
#[cfg(feature = "proto")]
|
2023-08-16 17:08:31 +02:00
|
|
|
#[allow(non_snake_case)]
|
2023-07-30 22:58:24 +02:00
|
|
|
pub mod proto {
|
2023-08-16 17:08:31 +02:00
|
|
|
tonic::include_proto!("codemp.buffer");
|
|
|
|
tonic::include_proto!("codemp.cursor");
|
2023-07-30 22:58:24 +02:00
|
|
|
}
|
2023-07-30 17:48:55 +02:00
|
|
|
|
2023-08-17 02:58:55 +02:00
|
|
|
pub use errors::Error;
|
2023-08-19 21:44:27 +02:00
|
|
|
pub use errors::Result;
|
2023-07-30 22:58:24 +02:00
|
|
|
|
2023-08-17 04:36:59 +02:00
|
|
|
use std::sync::Arc;
|
2023-08-19 21:44:27 +02:00
|
|
|
use tokio::runtime::Runtime;
|
2023-08-17 04:36:59 +02:00
|
|
|
|
2023-07-30 22:58:24 +02:00
|
|
|
#[tonic::async_trait] // TODO move this somewhere?
|
2023-08-19 21:44:27 +02:00
|
|
|
pub(crate) trait ControllerWorker<T : Sized + Send + Sync> {
|
2023-08-16 18:58:42 +02:00
|
|
|
type Controller : Controller<T>;
|
|
|
|
type Tx;
|
|
|
|
type Rx;
|
|
|
|
|
|
|
|
fn subscribe(&self) -> Self::Controller;
|
|
|
|
async fn work(self, tx: Self::Tx, rx: Self::Rx);
|
2023-07-30 17:48:55 +02:00
|
|
|
}
|
|
|
|
|
2023-08-19 21:44:27 +02:00
|
|
|
/// async and threadsafe handle to a generic bidirectional stream
|
|
|
|
///
|
|
|
|
/// this generic trait is implemented by actors managing stream procedures.
|
2023-08-20 03:36:50 +02:00
|
|
|
/// events can be enqueued for dispatching without blocking ([Controller::send]), and an async blocking
|
2023-08-19 21:44:27 +02:00
|
|
|
/// api ([Controller::recv]) is provided to wait for server events. Additional sync blocking
|
|
|
|
/// ([Controller::blocking_recv]) and callback-based ([Controller::callback]) are implemented.
|
2023-07-30 22:58:24 +02:00
|
|
|
#[tonic::async_trait]
|
2023-08-19 21:44:27 +02:00
|
|
|
pub trait Controller<T : Sized + Send + Sync> : Sized + Send + Sync {
|
|
|
|
/// type of upstream values, used in [Self::send]
|
2023-08-16 18:58:42 +02:00
|
|
|
type Input;
|
|
|
|
|
2023-08-19 21:44:27 +02:00
|
|
|
/// enqueue a new value to be sent
|
|
|
|
fn send(&self, x: Self::Input) -> Result<()>;
|
|
|
|
|
|
|
|
/// get next value from stream, blocking until one is available
|
2023-08-20 04:00:52 +02:00
|
|
|
///
|
|
|
|
/// this is just an async trait function wrapped by `async_trait`:
|
|
|
|
///
|
2023-08-20 08:37:19 +02:00
|
|
|
/// `async fn recv(&self) -> codemp::Result<T>;`
|
2023-08-19 21:44:27 +02:00
|
|
|
async fn recv(&self) -> Result<T>;
|
|
|
|
|
|
|
|
/// sync variant of [Self::recv], blocking invoking thread
|
|
|
|
fn blocking_recv(&self, rt: &Runtime) -> Result<T> {
|
|
|
|
rt.block_on(self.recv())
|
|
|
|
}
|
2023-08-17 04:36:59 +02:00
|
|
|
|
2023-08-19 21:44:27 +02:00
|
|
|
/// register a callback to be called for each received stream value
|
|
|
|
///
|
|
|
|
/// this will spawn a new task on given runtime invoking [Self::recv] in loop and calling given
|
|
|
|
/// callback for each received value. a stop channel should be provided, and first value sent
|
|
|
|
/// into it will stop the worker loop.
|
|
|
|
///
|
|
|
|
/// note: creating a callback handler will hold an Arc reference to the given controller,
|
|
|
|
/// preventing it from being dropped (and likely disconnecting). using the stop channel is
|
|
|
|
/// important for proper cleanup
|
2023-08-19 04:02:21 +02:00
|
|
|
fn callback<F>(
|
2023-08-20 00:46:55 +02:00
|
|
|
self: &Arc<Self>,
|
2023-08-19 04:02:21 +02:00
|
|
|
rt: &tokio::runtime::Runtime,
|
|
|
|
mut stop: tokio::sync::mpsc::UnboundedReceiver<()>,
|
|
|
|
mut cb: F
|
|
|
|
) where
|
|
|
|
Self : 'static,
|
|
|
|
F : FnMut(T) + Sync + Send + 'static
|
2023-08-17 04:36:59 +02:00
|
|
|
{
|
2023-08-20 00:46:55 +02:00
|
|
|
let _self = self.clone();
|
2023-08-18 03:58:42 +02:00
|
|
|
rt.spawn(async move {
|
2023-08-19 04:02:21 +02:00
|
|
|
loop {
|
|
|
|
tokio::select! {
|
2023-08-20 00:46:55 +02:00
|
|
|
Ok(data) = _self.recv() => cb(data),
|
2023-08-19 04:02:21 +02:00
|
|
|
Some(()) = stop.recv() => break,
|
|
|
|
else => break,
|
|
|
|
}
|
2023-08-17 04:36:59 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2023-07-30 17:48:55 +02:00
|
|
|
}
|