Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- @keyframes spin{
- to{transform:rotate(360deg)}
- //! Eventually consistent server-side logic for Rustpad.
- use std::collections::HashMap;
- use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
- use anyhow::{bail, Context, Result};
- use futures::prelude::*;
- use log::{info, warn};
- use operational_transform::OperationSeq;
- use parking_lot::{RwLock, RwLockUpgradableReadGuard};
- use serde::{Deserialize, Serialize};
- use tokio::sync::{broadcast, Notify};
- use warp::ws::{Message, WebSocket};
- use crate::{database::PersistedDocument, ot::transform_index};
- /// The main object representing a collaborative session.
- pub struct Rustpad {
- /// State modified by critical sections of the code.
- state: RwLock<State>,
- /// Incremented to obtain unique user IDs.
- count: AtomicU64,
- /// Used to notify clients of new text operations.
- notify: Notify,
- /// Used to inform all clients of metadata updates.
- update: broadcast::Sender<ServerMsg>,
- /// Set to true when the document is destroyed.
- killed: AtomicBool,
- }
- /// Shared state involving multiple users, protected by a lock.
- #[derive(Default)]
- struct State {
- operations: Vec<UserOperation>,
- text: String,
- language: Option<String>,
- users: HashMap<u64, UserInfo>,
- cursors: HashMap<u64, CursorData>,
- }
- #[derive(Clone, Debug, Serialize, Deserialize)]
- struct UserOperation {
- id: u64,
- operation: OperationSeq,
- }
- #[derive(Clone, Debug, Serialize, Deserialize)]
- struct UserInfo {
- name: String,
- hue: u32,
- }
- #[derive(Clone, Debug, Serialize, Deserialize)]
- struct CursorData {
- cursors: Vec<u32>,
- selections: Vec<(u32, u32)>,
- }
- /// A message received from the client over WebSocket.
- #[derive(Clone, Debug, Serialize, Deserialize)]
- enum ClientMsg {
- /// Represents a sequence of local edits from the user.
- Edit {
- revision: usize,
- operation: OperationSeq,
- },
- /// Sets the language of the editor.
- SetLanguage(String),
- /// Sets the user's current information.
- ClientInfo(UserInfo),
- /// Sets the user's cursor and selection positions.
- CursorData(CursorData),
- }
- /// A message sent to the client over WebSocket.
- #[derive(Clone, Debug, Serialize, Deserialize)]
- enum ServerMsg {
- /// Informs the client of their unique socket ID.
- Identity(u64),
- /// Broadcasts text operations to all clients.
- History {
- start: usize,
- operations: Vec<UserOperation>,
- },
- /// Broadcasts the current language, last writer wins.
- Language(String),
- /// Broadcasts a user's information, or `None` on disconnect.
- UserInfo { id: u64, info: Option<UserInfo> },
- /// Broadcasts a user's cursor position.
- UserCursor { id: u64, data: CursorData },
- }
- impl From<ServerMsg> for Message {
- fn from(msg: ServerMsg) -> Self {
- let serialized = serde_json::to_string(&msg).expect("failed serialize");
- Message::text(serialized)
- }
- }
- impl Default for Rustpad {
- fn default() -> Self {
- let (tx, _) = broadcast::channel(16);
- Self {
- state: Default::default(),
- count: Default::default(),
- notify: Default::default(),
- update: tx,
- killed: AtomicBool::new(false),
- }
- }
- }
- impl From<PersistedDocument> for Rustpad {
- fn from(document: PersistedDocument) -> Self {
- let mut operation = OperationSeq::default();
- operation.insert(&document.text);
- let rustpad = Self::default();
- {
- let mut state = rustpad.state.write();
- state.text = document.text;
- state.language = document.language;
- state.operations.push(UserOperation {
- id: u64::MAX,
- operation,
- })
- }
- rustpad
- }
- }
- impl Rustpad {
- /// Handle a connection from a WebSocket.
- pub async fn on_connection(&self, socket: WebSocket) {
- let id = self.count.fetch_add(1, Ordering::Relaxed);
- info!("connection! id = {}", id);
- if let Err(e) = self.handle_connection(id, socket).await {
- warn!("connection terminated early: {}", e);
- }
- info!("disconnection, id = {}", id);
- self.state.write().users.remove(&id);
- self.state.write().cursors.remove(&id);
- self.update
- .send(ServerMsg::UserInfo { id, info: None })
- .ok();
- }
- /// Returns a snapshot of the latest text.
- pub fn text(&self) -> String {
- let state = self.state.read();
- state.text.clone()
- }
- /// Returns a snapshot of the current document for persistence.
- pub fn snapshot(&self) -> PersistedDocument {
- let state = self.state.read();
- PersistedDocument {
- text: state.text.clone(),
- language: state.language.clone(),
- }
- }
- /// Returns the current revision.
- pub fn revision(&self) -> usize {
- let state = self.state.read();
- state.operations.len()
- }
- /// Kill this object immediately, dropping all current connections.
- pub fn kill(&self) {
- self.killed.store(true, Ordering::Relaxed);
- self.notify.notify_waiters();
- }
- /// Returns if this Rustpad object has been killed.
- pub fn killed(&self) -> bool {
- self.killed.load(Ordering::Relaxed)
- }
- async fn handle_connection(&self, id: u64, mut socket: WebSocket) -> Result<()> {
- let mut update_rx = self.update.subscribe();
- let mut revision: usize = self.send_initial(id, &mut socket).await?;
- loop {
- // In order to avoid the "lost wakeup" problem, we first request a
- // notification, **then** check the current state for new revisions.
- // This is the same approach that `tokio::sync::watch` takes.
- let notified = self.notify.notified();
- if self.killed() {
- break;
- }
- if self.revision() > revision {
- revision = self.send_history(revision, &mut socket).await?
- }
- tokio::select! {
- _ = notified => {}
- update = update_rx.recv() => {
- socket.send(update?.into()).await?;
- }
- result = socket.next() => {
- match result {
- None => break,
- Some(message) => {
- self.handle_message(id, message?).await?;
- }
- }
- }
- }
- }
- Ok(())
- }
- async fn send_initial(&self, id: u64, socket: &mut WebSocket) -> Result<usize> {
- socket.send(ServerMsg::Identity(id).into()).await?;
- let mut messages = Vec::new();
- let revision = {
- let state = self.state.read();
- if !state.operations.is_empty() {
- messages.push(ServerMsg::History {
- start: 0,
- operations: state.operations.clone(),
- });
- }
- if let Some(language) = &state.language {
- messages.push(ServerMsg::Language(language.clone()));
- }
- for (&id, info) in &state.users {
- messages.push(ServerMsg::UserInfo {
- id,
- info: Some(info.clone()),
- });
- }
- for (&id, data) in &state.cursors {
- messages.push(ServerMsg::UserCursor {
- id,
- data: data.clone(),
- });
- }
- state.operations.len()
- };
- for msg in messages {
- socket.send(msg.into()).await?;
- }
- Ok(revision)
- }
- async fn send_history(&self, start: usize, socket: &mut WebSocket) -> Result<usize> {
- let operations = {
- let state = self.state.read();
- let len = state.operations.len();
- if start < len {
- state.operations[start..].to_owned()
- } else {
- Vec::new()
- }
- };
- let num_ops = operations.len();
- if num_ops > 0 {
- let msg = ServerMsg::History { start, operations };
- socket.send(msg.into()).await?;
- }
- Ok(start + num_ops)
- }
- async fn handle_message(&self, id: u64, message: Message) -> Result<()> {
- let msg: ClientMsg = match message.to_str() {
- Ok(text) => serde_json::from_str(text).context("failed to deserialize message")?,
- Err(()) => return Ok(()), // Ignore non-text messages
- };
- match msg {
- ClientMsg::Edit {
- revision,
- operation,
- } => {
- self.apply_edit(id, revision, operation)
- .context("invalid edit operation")?;
- self.notify.notify_waiters();
- }
- ClientMsg::SetLanguage(language) => {
- self.state.write().language = Some(language.clone());
- self.update.send(ServerMsg::Language(language)).ok();
- }
- ClientMsg::ClientInfo(info) => {
- self.state.write().users.insert(id, info.clone());
- let msg = ServerMsg::UserInfo {
- id,
- info: Some(info),
- };
- self.update.send(msg).ok();
- }
- ClientMsg::CursorData(data) => {
- self.state.write().cursors.insert(id, data.clone());
- let msg = ServerMsg::UserCursor { id, data };
- self.update.send(msg).ok();
- }
- }
- Ok(())
- }
- fn apply_edit(&self, id: u64, revision: usize, mut operation: OperationSeq) -> Result<()> {
- info!(
- "edit: id = {}, revision = {}, base_len = {}, target_len = {}",
- id,
- revision,
- operation.base_len(),
- operation.target_len()
- );
- let state = self.state.upgradable_read();
- let len = state.operations.len();
- if revision > len {
- bail!("got revision {}, but current is {}", revision, len);
- }
- for history_op in &state.operations[revision..] {
- operation = operation.transform(&history_op.operation)?.0;
- }
- if operation.target_len() > 256 * 1024 {
- bail!(
- "target length {} is greater than 256 KiB maximum",
- operation.target_len()
- );
- }
- let new_text = operation.apply(&state.text)?;
- let mut state = RwLockUpgradableReadGuard::upgrade(state);
- for (_, data) in state.cursors.iter_mut() {
- for cursor in data.cursors.iter_mut() {
- *cursor = transform_index(&operation, *cursor);
- }
- for (start, end) in data.selections.iter_mut() {
- *start = transform_index(&operation, *start);
- *end = transform_index(&operation, *end);
- }
- }
- state.operations.push(UserOperation { id, operation });
- state.text = new_text;
- Ok(())
- }
- }
- START "" C:\docs\mypath\common\aod.pyw %1
- <script src="https://pastebin.com/embed_js/a2hFnzi5?theme=dark"></script>
- //! Eventually consistent server-side logic for Rustpad.
- use std::collections::HashMap;
- use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
- use anyhow::{bail, Context, Result};
- use futures::prelude::*;
- use log::{info, warn};
- use operational_transform::OperationSeq;
- use parking_lot::{RwLock, RwLockUpgradableReadGuard};
- use serde::{Deserialize, Serialize};
- use tokio::sync::{broadcast, Notify};
- use warp::ws::{Message, WebSocket};
- use crate::{database::PersistedDocument, ot::transform_index};
- /// The main object representing a collaborative session.
- pub struct Rustpad {
- /// State modified by critical sections of the code.
- state: RwLock<State>,
- /// Incremented to obtain unique user IDs.
- count: AtomicU64,
- /// Used to notify clients of new text operations.
- notify: Notify,
- /// Used to inform all clients of metadata updates.
- update: broadcast::Sender<ServerMsg>,
- /// Set to true when the document is destroyed.
- killed: AtomicBool,
- }
- /// Shared state involving multiple users, protected by a lock.
- #[derive(Default)]
- struct State {
- operations: Vec<UserOperation>,
- text: String,
- language: Option<String>,
- users: HashMap<u64, UserInfo>,
- cursors: HashMap<u64, CursorData>,
- }
- #[derive(Clone, Debug, Serialize, Deserialize)]
- struct UserOperation {
- id: u64,
- operation: OperationSeq,
- }
- #[derive(Clone, Debug, Serialize, Deserialize)]
- struct UserInfo {
- name: String,
- hue: u32,
- }
- #[derive(Clone, Debug, Serialize, Deserialize)]
- struct CursorData {
- cursors: Vec<u32>,
- selections: Vec<(u32, u32)>,
- }
- /// A message received from the client over WebSocket.
- #[derive(Clone, Debug, Serialize, Deserialize)]
- enum ClientMsg {
- /// Represents a sequence of local edits from the user.
- Edit {
- revision: usize,
- operation: OperationSeq,
- },
- /// Sets the language of the editor.
- SetLanguage(String),
- /// Sets the user's current information.
- ClientInfo(UserInfo),
- /// Sets the user's cursor and selection positions.
- CursorData(CursorData),
- }
- /// A message sent to the client over WebSocket.
- #[derive(Clone, Debug, Serialize, Deserialize)]
- enum ServerMsg {
- /// Informs the client of their unique socket ID.
- Identity(u64),
- /// Broadcasts text operations to all clients.
- History {
- start: usize,
- operations: Vec<UserOperation>,
- },
- /// Broadcasts the current language, last writer wins.
- Language(String),
- /// Broadcasts a user's information, or `None` on disconnect.
- UserInfo { id: u64, info: Option<UserInfo> },
- /// Broadcasts a user's cursor position.
- UserCursor { id: u64, data: CursorData },
- }
- impl From<ServerMsg> for Message {
- fn from(msg: ServerMsg) -> Self {
- let serialized = serde_json::to_string(&msg).expect("failed serialize");
- Message::text(serialized)
- }
- }
- impl Default for Rustpad {
- fn default() -> Self {
- let (tx, _) = broadcast::channel(16);
- Self {
- state: Default::default(),
- count: Default::default(),
- notify: Default::default(),
- update: tx,
- killed: AtomicBool::new(false),
- }
- }
- }
- impl From<PersistedDocument> for Rustpad {
- fn from(document: PersistedDocument) -> Self {
- let mut operation = OperationSeq::default();
- operation.insert(&document.text);
- let rustpad = Self::default();
- {
- let mut state = rustpad.state.write();
- state.text = document.text;
- state.language = document.language;
- state.operations.push(UserOperation {
- id: u64::MAX,
- operation,
- })
- }
- rustpad
- }
- }
- impl Rustpad {
- /// Handle a connection from a WebSocket.
- pub async fn on_connection(&self, socket: WebSocket) {
- let id = self.count.fetch_add(1, Ordering::Relaxed);
- info!("connection! id = {}", id);
- if let Err(e) = self.handle_connection(id, socket).await {
- warn!("connection terminated early: {}", e);
- }
- info!("disconnection, id = {}", id);
- self.state.write().users.remove(&id);
- self.state.write().cursors.remove(&id);
- self.update
- .send(ServerMsg::UserInfo { id, info: None })
- .ok();
- }
- /// Returns a snapshot of the latest text.
- pub fn text(&self) -> String {
- let state = self.state.read();
- state.text.clone()
- }
- /// Returns a snapshot of the current document for persistence.
- pub fn snapshot(&self) -> PersistedDocument {
- let state = self.state.read();
- PersistedDocument {
- text: state.text.clone(),
- language: state.language.clone(),
- }
- }
- /// Returns the current revision.
- pub fn revision(&self) -> usize {
- let state = self.state.read();
- state.operations.len()
- }
- /// Kill this object immediately, dropping all current connections.
- pub fn kill(&self) {
- self.killed.store(true, Ordering::Relaxed);
- self.notify.notify_waiters();
- }
- /// Returns if this Rustpad object has been killed.
- pub fn killed(&self) -> bool {
- self.killed.load(Ordering::Relaxed)
- }
- async fn handle_connection(&self, id: u64, mut socket: WebSocket) -> Result<()> {
- let mut update_rx = self.update.subscribe();
- let mut revision: usize = self.send_initial(id, &mut socket).await?;
- loop {
- // In order to avoid the "lost wakeup" problem, we first request a
- // notification, **then** check the current state for new revisions.
- // This is the same approach that `tokio::sync::watch` takes.
- let notified = self.notify.notified();
- if self.killed() {
- break;
- }
- if self.revision() > revision {
- revision = self.send_history(revision, &mut socket).await?
- }
- tokio::select! {
- _ = notified => {}
- update = update_rx.recv() => {
- socket.send(update?.into()).await?;
- }
- result = socket.next() => {
- match result {
- None => break,
- Some(message) => {
- self.handle_message(id, message?).await?;
- }
- }
- }
- }
- }
- Ok(())
- }
- async fn send_initial(&self, id: u64, socket: &mut WebSocket) -> Result<usize> {
- socket.send(ServerMsg::Identity(id).into()).await?;
- let mut messages = Vec::new();
- let revision = {
- let state = self.state.read();
- if !state.operations.is_empty() {
- messages.push(ServerMsg::History {
- start: 0,
- operations: state.operations.clone(),
- });
- }
- if let Some(language) = &state.language {
- messages.push(ServerMsg::Language(language.clone()));
- }
- for (&id, info) in &state.users {
- messages.push(ServerMsg::UserInfo {
- id,
- info: Some(info.clone()),
- });
- }
- for (&id, data) in &state.cursors {
- messages.push(ServerMsg::UserCursor {
- id,
- data: data.clone(),
- });
- }
- state.operations.len()
- };
- for msg in messages {
- socket.send(msg.into()).await?;
- }
- Ok(revision)
- }
- async fn send_history(&self, start: usize, socket: &mut WebSocket) -> Result<usize> {
- let operations = {
- let state = self.state.read();
- let len = state.operations.len();
- if start < len {
- state.operations[start..].to_owned()
- } else {
- Vec::new()
- }
- };
- let num_ops = operations.len();
- if num_ops > 0 {
- let msg = ServerMsg::History { start, operations };
- socket.send(msg.into()).await?;
- }
- Ok(start + num_ops)
- }
- async fn handle_message(&self, id: u64, message: Message) -> Result<()> {
- let msg: ClientMsg = match message.to_str() {
- Ok(text) => serde_json::from_str(text).context("failed to deserialize message")?,
- Err(()) => return Ok(()), // Ignore non-text messages
- };
- match msg {
- ClientMsg::Edit {
- revision,
- operation,
- } => {
- self.apply_edit(id, revision, operation)
- .context("invalid edit operation")?;
- self.notify.notify_waiters();
- }
- ClientMsg::SetLanguage(language) => {
- self.state.write().language = Some(language.clone());
- self.update.send(ServerMsg::Language(language)).ok();
- }
- ClientMsg::ClientInfo(info) => {
- self.state.write().users.insert(id, info.clone());
- let msg = ServerMsg::UserInfo {
- id,
- info: Some(info),
- };
- self.update.send(msg).ok();
- }
- ClientMsg::CursorData(data) => {
- self.state.write().cursors.insert(id, data.clone());
- let msg = ServerMsg::UserCursor { id, data };
- self.update.send(msg).ok();
- }
- }
- Ok(())
- }
- fn apply_edit(&self, id: u64, revision: usize, mut operation: OperationSeq) -> Result<()> {
- info!(
- "edit: id = {}, revision = {}, base_len = {}, target_len = {}",
- id,
- revision,
- operation.base_len(),
- operation.target_len()
- );
- let state = self.state.upgradable_read();
- let len = state.operations.len();
- if revision > len {
- bail!("got revision {}, but current is {}", revision, len);
- }
- for history_op in &state.operations[revision..] {
- operation = operation.transform(&history_op.operation)?.0;
- }
- if operation.target_len() > 256 * 1024 {
- bail!(
- "target length {} is greater than 256 KiB maximum",
- operation.target_len()
- );
- }
- let new_text = operation.apply(&state.text)?;
- let mut state = RwLockUpgradableReadGuard::upgrade(state);
- for (_, data) in state.cursors.iter_mut() {
- for cursor in data.cursors.iter_mut() {
- *cursor = transform_index(&operation, *cursor);
- }
- for (start, end) in data.selections.iter_mut() {
- *start = transform_index(&operation, *start);
- *end = transform_index(&operation, *end);
- }
- }
- state.operations.push(UserOperation { id, operation });
- state.text = new_text;
- Ok(())
- }
- }
Advertisement