use dashmap::{DashMap, Entry}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Formatter; use std::sync::Arc; use thiserror::Error; use crate::block::ClientID; use crate::sync::{Clock, Timestamp}; use crate::updates::decoder::{Decode, Decoder}; use crate::updates::encoder::{Encode, Encoder}; use crate::{Doc, Observer, Origin}; const NULL_STR: &str = "null"; #[cfg(feature = "sync")] type AwarenessUpdateFn = Box) + Send + Sync + 'static>; #[cfg(not(feature = "sync"))] type AwarenessUpdateFn = Box) + 'static>; /// The Awareness class implements a simple shared state protocol that can be used for non-persistent /// data like awareness information (cursor, username, status, ..). Each client can update its own /// local state and listen to state changes of remote clients. /// /// Each client is identified by a unique client id (something we borrow from `doc.clientID`). /// A client can override its own state by propagating a message with an increasing timestamp /// (`clock`). If such a message is received, it is applied if the known state of that client is /// older than the new state (`clock < new_clock`). If a client thinks that a remote client is /// offline, it may propagate a message with `{ clock, state: null, client }`. If such a message is /// received, and the known clock of that client equals the received clock, it will clean the state. /// /// Before a client disconnects, it should propagate a `null` state with an updated clock. pub struct Awareness { doc: Doc, states: DashMap, clock: Arc, on_update: Observer, on_change: Observer, } impl Awareness { /// Creates a new instance of [Awareness] struct, which operates over a given document. /// Awareness instance has full ownership of that document. If necessary it can be accessed /// using either [Awareness::doc] or [Awareness::doc_mut] methods. #[cfg(not(target_family = "wasm"))] pub fn new(doc: Doc) -> Self { Self::with_clock(doc, crate::sync::time::SystemClock) } /// Creates a new instance of [Awareness] struct, which operates over a given document. /// Awareness instance has full ownership of that document. If necessary it can be accessed /// using either [Awareness::doc] or [Awareness::doc_mut] methods. pub fn with_clock(doc: Doc, clock: C) -> Self where C: Clock + 'static, { Awareness { doc, states: DashMap::new(), clock: Arc::new(clock), on_update: Observer::new(), on_change: Observer::new(), } } /// Returns a channel receiver for an incoming awareness events. This channel can be cloned. #[cfg(feature = "sync")] pub fn on_update(&self, f: F) -> crate::Subscription where F: Fn(&Awareness, &Event, Option<&Origin>) + Send + Sync + 'static, { self.on_update.subscribe(Box::new(f)) } /// Returns a channel receiver for an incoming awareness events. This channel can be cloned. #[cfg(not(feature = "sync"))] pub fn on_update(&self, f: F) -> crate::Subscription where F: Fn(&Awareness, &Event, Option<&Origin>) + 'static, { self.on_update.subscribe(Box::new(f)) } /// Returns a channel receiver for an incoming awareness events. This channel can be cloned. #[cfg(feature = "sync")] pub fn on_update_with(&self, key: K, f: F) where K: Into, F: Fn(&Awareness, &Event, Option<&Origin>) + Send + Sync + 'static, { self.on_update.subscribe_with(key.into(), Box::new(f)) } /// Returns a channel receiver for an incoming awareness events. This channel can be cloned. #[cfg(not(feature = "sync"))] pub fn on_update_with(&self, key: K, f: F) where K: Into, F: Fn(&Awareness, &Event, Option<&Origin>) + 'static, { self.on_update.subscribe_with(key.into(), Box::new(f)) } /// Returns a channel receiver for an incoming awareness events. This channel can be cloned. pub fn unobserve_update(&self, key: K) -> bool where K: Into, { self.on_update.unsubscribe(&key.into()) } /// Returns a channel receiver for an incoming awareness events. This channel can be cloned. #[cfg(feature = "sync")] pub fn on_change(&self, f: F) -> crate::Subscription where F: Fn(&Awareness, &Event, Option<&Origin>) + Send + Sync + 'static, { self.on_change.subscribe(Box::new(f)) } /// Returns a channel receiver for an incoming awareness events. This channel can be cloned. #[cfg(not(feature = "sync"))] pub fn on_change(&self, f: F) -> crate::Subscription where F: Fn(&Awareness, &Event, Option<&Origin>) + 'static, { self.on_change.subscribe(Box::new(f)) } /// Returns a channel receiver for an incoming awareness events. This channel can be cloned. #[cfg(feature = "sync")] pub fn on_change_with(&self, key: K, f: F) where K: Into, F: Fn(&Awareness, &Event, Option<&Origin>) + Send + Sync + 'static, { self.on_change.subscribe_with(key.into(), Box::new(f)) } /// Returns a channel receiver for an incoming awareness events. This channel can be cloned. #[cfg(not(feature = "sync"))] pub fn on_change_with(&self, key: K, f: F) where K: Into, F: Fn(&Awareness, &Event, Option<&Origin>) + 'static, { self.on_change.subscribe_with(key.into(), Box::new(f)) } /// Returns a channel receiver for an incoming awareness events. This channel can be cloned. pub fn unobserve_change(&self, key: K) -> bool where K: Into, { self.on_change.unsubscribe(&key.into()) } /// Returns a read-only reference to an underlying [Doc]. pub fn doc(&self) -> &Doc { &self.doc } /// Returns a read-write reference to an underlying [Doc]. pub fn doc_mut(&mut self) -> &mut Doc { &mut self.doc } /// Returns a globally unique client ID of an underlying [Doc]. pub fn client_id(&self) -> ClientID { self.doc.client_id() } /// Returns a state map of all the clients tracked by current [Awareness] instance. Those /// states are identified by their corresponding [ClientID]s. The associated state is /// represented and replicated to other clients as a JSON string. pub fn iter(&self) -> AwarenessIter { AwarenessIter(self.states.iter()) } /// Returns a JSON string state representation of a current [Awareness] instance. pub fn local_state(&self) -> Option { let json_str = self.local_state_raw()?; serde_json::from_str(&json_str).ok() } /// Returns a JSON string state representation of a current [Awareness] instance. pub fn local_state_raw(&self) -> Option> { let e = self.states.get(&self.doc.client_id())?; let json_str = e.value().clone().data?; Some(json_str) } /// Clears out a state of a given client, effectively marking it as disconnected. pub fn remove_state(&self, client_id: ClientID) { let is_removed = match self.states.entry(client_id) { Entry::Occupied(mut e) => { let state = e.get_mut(); state.data = None; state.clock += 1; true } Entry::Vacant(e) => { e.insert(ClientState::new(1, self.clock.now(), None)); false } }; if is_removed && self.on_update.has_subscribers() || self.on_change.has_subscribers() { let e = Event::new(Vec::default(), Vec::default(), vec![client_id]); self.on_change.trigger(|fun| fun(self, &e, None)); self.on_update.trigger(|fun| fun(self, &e, None)); } } /// Gets the state of a particular client. pub fn state(&self, client_id: ClientID) -> Option { let state = self.states.get(&client_id)?; let json = state.data.clone()?; serde_json::from_str(&json).ok() } /// Get the `(sequence number, last updated timestamp)` metadata associated with particular client. pub fn meta(&self, client_id: ClientID) -> Option<(u32, Timestamp)> { let state = self.states.get(&client_id)?; Some((state.clock, state.last_updated)) } /// Clears out a state of a current client (see: [Awareness::client_id]), /// effectively marking it as disconnected. pub fn clean_local_state(&self) { let client_id = self.doc.client_id(); self.remove_state(client_id); } /// Sets a current [Awareness] instance state to a corresponding JSON string. This state will /// be replicated to other clients as part of the [AwarenessUpdate] and it will trigger an event /// to be emitted if current instance was created using [Awareness::with_observer] method. pub fn set_local_state(&self, state: S) -> Result<(), Error> { let json = serde_json::to_string(&state)?; self.set_local_state_raw(json); Ok(()) } /// Sets a current [Awareness] instance state to a corresponding JSON string. This state will /// be replicated to other clients as part of the [AwarenessUpdate] and it will trigger an event /// to be emitted if current instance was created using [Awareness::with_observer] method. pub fn set_local_state_raw>>(&self, json: S) { let client_id = self.doc.client_id(); let now = self.clock.now(); let json = json.into(); let prev = match self.states.entry(client_id) { Entry::Occupied(mut e) => { let state = e.get_mut(); state.last_updated = now; state.clock += 1; state.data.replace(json.clone()) } Entry::Vacant(e) => { e.insert(ClientState::new(1, now, Some(json.clone()))); None } }; self.notify_subscribers(client_id, prev, json); } fn notify_subscribers( &self, client_id: ClientID, prev_state: Option>, curr_state: Arc, ) { if self.on_update.has_subscribers() || self.on_change.has_subscribers() { let mut added = vec![]; let mut updated = vec![]; let mut changed = vec![]; match prev_state { None => added.push(client_id), Some(prev) => { updated.push(client_id); if prev != curr_state { changed.push(client_id); } } } let mut e = Event::new(added, changed, Vec::default()); if !e.is_empty() { self.on_change.trigger(|fun| fun(self, &e, None)); } e.summary.updated = updated; if !e.is_empty() { self.on_update.trigger(|fun| fun(self, &e, None)); } } } /// Returns a serializable update object which is representation of a current Awareness state. /// This doesn't include states that have been removed in the past. pub fn update(&self) -> Result { let clients: Vec<_> = self .states .iter() .flat_map(|e| { if e.data.is_none() { None } else { Some(*e.key()) } }) .collect(); self.update_with_clients(clients) } /// Returns a serializable update object which is representation of a current Awareness state. /// Unlike [Awareness::update], this method variant allows to prepare update only for a subset /// of known clients. These clients must all be known to a current [Awareness] instance, /// otherwise a [Error::ClientNotFound] error will be returned. pub fn update_with_clients>( &self, clients: I, ) -> Result { let mut res = HashMap::new(); for client_id in clients { let (clock, json) = if let Some(meta) = self.states.get(&client_id) { let data = meta.data.clone().unwrap_or_else(|| NULL_STR.into()); (meta.clock, data) } else { return Err(Error::ClientNotFound(client_id)); }; res.insert(client_id, AwarenessUpdateEntry { clock, json }); } Ok(AwarenessUpdate { clients: res }) } /// Applies an update (incoming from remote channel or generated using [Awareness::update] / /// [Awareness::update_with_clients] methods) and modifies a state of a current instance. /// /// If current instance has an observer channel (see: [Awareness::with_observer]), applied /// changes will also be emitted as events. pub fn apply_update(&self, update: AwarenessUpdate) -> Result<(), Error> { let gen_summary = self.on_update.has_subscribers() || self.on_change.has_subscribers(); self.apply_update_internal(update, None, gen_summary)?; Ok(()) } /// Applies an update (incoming from remote channel or generated using [Awareness::update] / /// [Awareness::update_with_clients] methods) and modifies a state of a current instance. /// /// If current instance has an observer channel (see: [Awareness::with_observer]), applied /// changes will also be emitted as events. pub fn apply_update_with>( &self, update: AwarenessUpdate, origin: O, ) -> Result<(), Error> { let gen_summary = self.on_update.has_subscribers() || self.on_change.has_subscribers(); self.apply_update_internal(update, Some(origin.into()), gen_summary)?; Ok(()) } /// Applies an update (incoming from remote channel or generated using [Awareness::update] / /// [Awareness::update_with_clients] methods) and modifies a state of a current instance. /// Returns an [AwarenessUpdateSummary] object informing about the changes that were applied. /// /// If current instance has an observer channel (see: [Awareness::with_observer]), applied /// changes will also be emitted as events. pub fn apply_update_summary( &self, update: AwarenessUpdate, ) -> Result, Error> { self.apply_update_internal(update, None, true) } /// Applies an update (incoming from remote channel or generated using [Awareness::update] / /// [Awareness::update_with_clients] methods) and modifies a state of a current instance. /// Returns an [AwarenessUpdateSummary] object informing about the changes that were applied. /// /// If current instance has an observer channel (see: [Awareness::with_observer]), applied /// changes will also be emitted as events. pub fn apply_update_summary_with>( &self, update: AwarenessUpdate, origin: O, ) -> Result, Error> { self.apply_update_internal(update, Some(origin.into()), true) } fn apply_update_internal( &self, update: AwarenessUpdate, origin: Option, generate_summary: bool, ) -> Result, Error> { let now = self.clock.now(); let mut added = Vec::new(); let mut updated = Vec::new(); let mut changed = Vec::new(); let mut removed = Vec::new(); for (client_id, entry) in update.clients { let mut clock = entry.clock; let new: Option<_> = if entry.json.as_ref() == NULL_STR { None } else { Some(entry.json) }; match self.states.entry(client_id) { Entry::Occupied(mut e) => { let state: &mut ClientState = e.get_mut(); let is_removed = state.clock == clock && new.is_none() && state.data.is_some(); if state.clock < clock || is_removed { match new { None => { // never let a remote client remove this local state if client_id == self.doc.client_id() && state.data.is_some() { // remote client removed the local state. Do not remove state. Broadcast a message indicating // that this client still exists by increasing the clock clock += 1; } else { state.data = None; if generate_summary { removed.push(client_id); } } } new => { if generate_summary { updated.push(client_id); if state.data.is_some() && state.data != new { changed.push(client_id); } } state.data = new; } } state.clock = clock; state.last_updated = now; true } else { false } } Entry::Vacant(e) => { let has_data = new.is_some(); e.insert(ClientState::new(clock, now, new)); if has_data { if generate_summary { added.push(client_id); } true } else { false } } }; } if !added.is_empty() || !updated.is_empty() || !removed.is_empty() { let summary = if self.on_update.has_subscribers() || self.on_change.has_subscribers() { let mut e = Event::new(added, changed, removed); if !e.is_empty() { self.on_change.trigger(|fun| fun(self, &e, origin.as_ref())); } e.summary.updated = updated; self.on_update.trigger(|fun| fun(self, &e, origin.as_ref())); e.summary } else { AwarenessUpdateSummary { added, updated, removed, } }; Ok(Some(summary)) } else { Ok(None) } } } #[cfg(not(target_family = "wasm"))] impl Default for Awareness { fn default() -> Self { Awareness::new(Doc::new()) } } impl std::fmt::Debug for Awareness { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut s = f.debug_struct("Awareness"); s.field("doc", &self.doc); s.field("states", &self.states); s.finish() } } pub struct AwarenessIter<'a>(dashmap::iter::Iter<'a, ClientID, ClientState>); impl<'a> Iterator for AwarenessIter<'a> { type Item = (ClientID, ClientState); fn next(&mut self) -> Option { let e = self.0.next()?; Some((*e.key(), e.value().clone())) } } /// Summary of applying an [AwarenessUpdate] over [Awareness::apply_update_summary] method. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct AwarenessUpdateSummary { /// New clients added as part of the update. pub added: Vec, /// Existing clients that have been changed by the update. pub updated: Vec, /// Existing clients that have been removed by the update. pub removed: Vec, } impl AwarenessUpdateSummary { /// Returns a collection of all clients that have been changed by the [AwarenessUpdate]. pub fn all_changes(&self) -> Vec { let mut res = Vec::with_capacity(self.added.len() + self.updated.len() + self.removed.len()); res.extend_from_slice(&self.added); res.extend_from_slice(&self.updated); res.extend_from_slice(&self.removed); res } } /// A structure that represents an encodable state of an [Awareness] struct. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct AwarenessUpdate { /// Client updates. pub clients: HashMap, } impl Encode for AwarenessUpdate { fn encode(&self, encoder: &mut E) { encoder.write_var(self.clients.len()); for (&client_id, e) in self.clients.iter() { encoder.write_var(client_id); encoder.write_var(e.clock); encoder.write_string(&e.json); } } } impl Decode for AwarenessUpdate { fn decode(decoder: &mut D) -> Result { let len: usize = decoder.read_var()?; let mut clients = HashMap::with_capacity(len); for _ in 0..len { let client_id: ClientID = decoder.read_var()?; let clock: u32 = decoder.read_var()?; let json: Arc = decoder.read_string()?.into(); clients.insert(client_id, AwarenessUpdateEntry { clock, json }); } Ok(AwarenessUpdate { clients }) } } /// A single client entry of an [AwarenessUpdate]. It consists of logical clock and JSON client /// state represented as a string. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct AwarenessUpdateEntry { /// Timestamp used to recognize which update is the latest one. pub clock: u32, /// String with JSON payload containing user data. pub json: Arc, } /// Errors generated by an [Awareness] struct methods. #[derive(Error, Debug)] pub enum Error { /// Client ID was not found in [Awareness] metadata. #[error("client ID `{0}` not found")] ClientNotFound(ClientID), #[error("couldn't serialize awareness state: {0}")] Serde(#[from] serde_json::Error), } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ClientState { pub clock: u32, pub last_updated: Timestamp, pub data: Option>, } impl ClientState { fn new(clock: u32, last_updated: Timestamp, state: Option>) -> Self { ClientState { clock, last_updated, data: state, } } } /// Event type emitted by an [Awareness] struct. #[derive(Debug, Clone, PartialEq)] pub struct Event { summary: AwarenessUpdateSummary, } impl Event { pub fn new(added: Vec, updated: Vec, removed: Vec) -> Self { Event { summary: AwarenessUpdateSummary { added, updated, removed, }, } } pub fn summary(&self) -> &AwarenessUpdateSummary { &self.summary } /// Collection of new clients that have been added to an [Awareness] struct, that was not known /// before. Actual client state can be accessed via `awareness.clients().get(client_id)`. pub fn added(&self) -> &[ClientID] { &self.summary.added } /// Collection of new clients that have been updated within an [Awareness] struct since the last /// update. Actual client state can be accessed via `awareness.clients().get(client_id)`. pub fn updated(&self) -> &[ClientID] { &self.summary.updated } /// Collection of new clients that have been removed from [Awareness] struct since the last /// update. pub fn removed(&self) -> &[ClientID] { &self.summary.removed } /// Returns all the changed client IDs (added, updated and removed) combined. pub fn all_changes(&self) -> Vec { self.summary.all_changes() } fn is_empty(&self) -> bool { self.summary.added.is_empty() && self.summary.updated.is_empty() && self.summary.removed.is_empty() } } #[cfg(test)] mod test { use arc_swap::ArcSwapOption; use serde_json::{json, Value}; use std::collections::HashMap; use std::sync::Arc; use crate::sync::awareness::{AwarenessUpdateSummary, Event}; use crate::sync::Awareness; use crate::Doc; #[test] fn awareness() { fn exchange(recv: &ArcSwapOption, from: &Awareness, to: &mut Awareness) { let e = recv.swap(None); if let Some(e) = e.as_deref() { let total = [e.added(), e.updated(), e.removed()].concat(); let u = from.update_with_clients(total).unwrap(); to.apply_update(u).unwrap(); } } let local = Awareness::new(Doc::with_client_id(1)); let last_change_local = Arc::new(ArcSwapOption::default()); let update = Arc::new(ArcSwapOption::default()); let _sub_update = { let update = update.clone(); local.on_update(move |_, e, _| update.store(Some(Arc::new(e.clone())))) }; let _sub_local = { let last_change_local = last_change_local.clone(); local.on_change(move |_, e, _| last_change_local.store(Some(Arc::new(e.clone())))) }; let mut remote = Awareness::new(Doc::with_client_id(2)); let last_change_remote = Arc::new(ArcSwapOption::default()); let _sub_remote = { let last_change_remote = last_change_remote.clone(); remote.on_change(move |_, e, _| last_change_remote.store(Some(Arc::new(e.clone())))) }; assert!(local.on_change.has_subscribers(), "local has subscribers"); assert!(remote.on_change.has_subscribers(), "remote has subscribers"); local.set_local_state(json!({"x":3})).unwrap(); exchange(&update, &local, &mut remote); let _e_local = last_change_local.swap(None).unwrap(); assert_eq!(remote.state::(1).unwrap(), json!({"x":3})); assert_eq!(remote.states.get(&1).unwrap().clock, 1); assert_eq!(last_change_remote.swap(None).unwrap().added(), &[1]); local.set_local_state(json!({"x":4})).unwrap(); exchange(&update, &local, &mut remote); let e_local = last_change_local.swap(None).unwrap(); let e_remote = last_change_remote.swap(None).unwrap(); assert_eq!(remote.state::(1).unwrap(), json!({"x":4})); assert_eq!( e_remote.summary, AwarenessUpdateSummary { added: vec![], updated: vec![1], removed: vec![] } ); assert_eq!(e_remote.summary, e_local.summary); local.set_local_state(json!({"x":4})).unwrap(); exchange(&update, &local, &mut remote); let e_local = last_change_local.swap(None); let e_remote = last_change_remote.swap(None); assert_eq!(remote.state::(1).unwrap(), json!({"x":4})); assert_eq!(remote.states.get(&1).unwrap().clock, 3); assert_eq!(e_remote, e_local); local.clean_local_state(); exchange(&update, &local, &mut remote); let e_local = last_change_local.swap(None).unwrap(); let e_remote = last_change_remote.swap(None).unwrap(); assert_eq!(e_remote.removed().len(), 1); assert!(local.state::(1).is_none()); assert_eq!(e_remote.summary, e_local.summary); assert_eq!(e_remote, e_local); } #[test] fn awareness_summary() -> Result<(), Box> { let local = Awareness::new(Doc::with_client_id(1)); let remote = Awareness::new(Doc::with_client_id(2)); local.set_local_state(json!({"x":3})).unwrap(); let update = local.update_with_clients([local.client_id()])?; let summary = remote.apply_update_summary(update)?; assert_eq!(remote.state::(1).unwrap(), json!({"x":3})); assert_eq!(remote.states.get(&1).unwrap().clock, 1); assert_eq!( summary, Some(AwarenessUpdateSummary { added: vec![1], updated: vec![], removed: vec![] }) ); local.set_local_state(json!({"x":4})).unwrap(); let update = local.update_with_clients([local.client_id()])?; let summary = remote.apply_update_summary(update)?; assert_eq!(remote.state::(1).unwrap(), json!({"x":4})); assert_eq!( summary, Some(AwarenessUpdateSummary { added: vec![], updated: vec![1], removed: vec![] }) ); let local_states: HashMap<_, _> = local.iter().collect(); let remote_states: HashMap<_, _> = remote.iter().collect(); assert_eq!(local_states, remote_states); local.clean_local_state(); let update = local.update_with_clients([local.client_id()])?; let summary = remote.apply_update_summary(update)?; assert_eq!( summary, Some(AwarenessUpdateSummary { added: vec![], updated: vec![], removed: vec![1] }) ); assert!(local.state::(1).is_none()); let local_states: HashMap<_, _> = local.iter().collect(); let remote_states: HashMap<_, _> = remote.iter().collect(); assert_eq!(local_states, remote_states); Ok(()) } }