loro_internal/
event.rs

1use enum_as_inner::EnumAsInner;
2use fxhash::FxHashMap;
3use itertools::Itertools;
4use loro_delta::{array_vec::ArrayVec, delta_trait::DeltaAttr, DeltaItem, DeltaRope};
5use serde::{Deserialize, Serialize};
6use smallvec::SmallVec;
7
8use crate::{
9    container::richtext::richtext_state::RichtextStateChunk,
10    delta::{Delta, MapDelta, Meta, MovableListInnerDelta, ResolvedMapDelta, TreeDelta, TreeDiff},
11    diff_calc::DiffMode,
12    handler::ValueOrHandler,
13    op::SliceWithId,
14    utils::string_slice::StringSlice,
15    InternalString,
16};
17
18use std::{borrow::Cow, hash::Hash};
19
20use loro_common::{ContainerID, LoroValue, TreeID};
21
22use crate::{container::idx::ContainerIdx, version::Frontiers};
23
24#[derive(Debug, Clone)]
25pub struct ContainerDiff {
26    pub id: ContainerID,
27    pub path: Vec<(ContainerID, Index)>,
28    pub(crate) idx: ContainerIdx,
29    pub is_unknown: bool,
30    pub diff: Diff,
31}
32
33/// The kind of the event trigger.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub enum EventTriggerKind {
36    /// The event is triggered by a local transaction.
37    Local,
38    /// The event is triggered by importing
39    Import,
40    /// The event is triggered by checkout
41    Checkout,
42}
43
44impl std::fmt::Display for EventTriggerKind {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match self {
47            EventTriggerKind::Local => write!(f, "local"),
48            EventTriggerKind::Import => write!(f, "import"),
49            EventTriggerKind::Checkout => write!(f, "checkout"),
50        }
51    }
52}
53
54impl EventTriggerKind {
55    #[inline]
56    pub fn is_local(&self) -> bool {
57        matches!(self, EventTriggerKind::Local)
58    }
59
60    #[inline]
61    pub fn is_import(&self) -> bool {
62        matches!(self, EventTriggerKind::Import)
63    }
64
65    #[inline]
66    pub fn is_checkout(&self) -> bool {
67        matches!(self, EventTriggerKind::Checkout)
68    }
69}
70
71#[derive(Debug, Clone)]
72pub struct DiffEvent<'a> {
73    /// The receiver of the event.
74    pub current_target: Option<ContainerID>,
75    /// A list of events that should be received by the current target.
76    pub events: &'a [&'a ContainerDiff],
77    pub event_meta: &'a DocDiff,
78}
79
80/// It's the exposed event type.
81/// It's exposed to the user. The user can use this to apply the diff to their local state.
82///
83/// [DocDiff] may include the diff that calculated from several transactions and imports.
84/// They all should have the same origin and local flag.
85#[derive(Debug, Clone)]
86pub struct DocDiff {
87    pub from: Frontiers,
88    pub to: Frontiers,
89    pub origin: InternalString,
90    pub by: EventTriggerKind,
91    pub diff: Vec<ContainerDiff>,
92}
93
94#[derive(Debug, Clone)]
95pub(crate) struct InternalContainerDiff {
96    pub(crate) idx: ContainerIdx,
97    /// Indicates whether this event was generated by a container that has been resurrected during a checkout operation.
98    pub(crate) bring_back: bool,
99    pub(crate) is_container_deleted: bool,
100    pub(crate) diff: DiffVariant,
101    /// This mode decides how should we apply the diff.
102    pub(crate) diff_mode: DiffMode,
103}
104
105#[derive(Default, Debug, Clone, EnumAsInner)]
106pub(crate) enum DiffVariant {
107    #[default]
108    None,
109    Internal(InternalDiff),
110    External(Diff),
111}
112
113/// It's used for transmitting and recording the diff internally.
114///
115/// It can be convert into a [DocDiff].
116// Internally, we need to batch the diff then calculate the event. Because
117// we need to sort the diff by containers' created time, to make sure the
118// the path to each container is up-to-date.
119#[derive(Debug, Clone)]
120pub(crate) struct InternalDocDiff<'a> {
121    pub(crate) origin: InternalString,
122    pub(crate) by: EventTriggerKind,
123    /// The values inside this array is in random order
124    pub(crate) diff: Cow<'a, [InternalContainerDiff]>,
125    pub(crate) new_version: Cow<'a, Frontiers>,
126}
127
128impl InternalDocDiff<'_> {
129    pub fn into_owned(self) -> InternalDocDiff<'static> {
130        InternalDocDiff {
131            origin: self.origin,
132            by: self.by,
133            diff: Cow::Owned((*self.diff).to_owned()),
134            new_version: Cow::Owned((*self.new_version).to_owned()),
135        }
136    }
137
138    pub fn can_merge(&self, other: &Self) -> bool {
139        self.by == other.by
140    }
141}
142
143pub type Path = SmallVec<[Index; 4]>;
144
145#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, enum_as_inner::EnumAsInner)]
146pub enum Index {
147    Key(InternalString),
148    Seq(usize),
149    Node(TreeID),
150}
151
152impl std::fmt::Debug for Index {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        match self {
155            Self::Key(arg0) => write!(f, "Index::Key(\"{}\")", arg0),
156            Self::Seq(arg0) => write!(f, "Index::Seq({})", arg0),
157            Self::Node(arg0) => write!(f, "Index::Node({})", arg0),
158        }
159    }
160}
161
162impl std::fmt::Display for Index {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        match self {
165            Index::Key(key) => write!(f, "{}", key),
166            Index::Seq(s) => write!(f, "{}", s),
167            Index::Node(id) => write!(f, "{}@{}", id.peer, id.counter),
168        }
169    }
170}
171
172impl From<usize> for Index {
173    fn from(s: usize) -> Self {
174        Index::Seq(s)
175    }
176}
177
178impl TryFrom<&str> for Index {
179    type Error = &'static str;
180    fn try_from(s: &str) -> Result<Self, &'static str> {
181        if s.is_empty() {
182            return Ok(Index::Key(InternalString::default()));
183        }
184
185        let c = s.chars().next().unwrap();
186        if c.is_ascii_digit() {
187            if let Ok(seq) = s.parse::<usize>() {
188                Ok(Index::Seq(seq))
189            } else if let Ok(id) = s.try_into() {
190                Ok(Index::Node(id))
191            } else {
192                Ok(Index::Key(InternalString::from(s)))
193            }
194        } else {
195            Ok(Index::Key(InternalString::from(s)))
196        }
197    }
198}
199
200impl DiffVariant {
201    pub fn compose(self, other: Self) -> Result<Self, Self> {
202        match (self, other) {
203            (DiffVariant::Internal(a), DiffVariant::Internal(b)) => {
204                Ok(DiffVariant::Internal(a.compose(b)?))
205            }
206            (DiffVariant::External(a), DiffVariant::External(b)) => {
207                Ok(DiffVariant::External(a.compose(b)?))
208            }
209            (a, _) => Err(a),
210        }
211    }
212
213    pub fn is_empty(&self) -> bool {
214        match self {
215            DiffVariant::Internal(diff) => diff.is_empty(),
216            DiffVariant::External(diff) => diff.is_empty(),
217            DiffVariant::None => true,
218        }
219    }
220}
221
222#[non_exhaustive]
223#[derive(Clone, Debug, EnumAsInner)]
224pub(crate) enum InternalDiff {
225    ListRaw(Delta<SliceWithId>),
226    /// This always uses entity indexes.
227    RichtextRaw(DeltaRope<RichtextStateChunk, ()>),
228    Map(MapDelta),
229    Tree(TreeDelta),
230    MovableList(MovableListInnerDelta),
231    #[cfg(feature = "counter")]
232    Counter(f64),
233    Unknown,
234}
235
236impl From<InternalDiff> for DiffVariant {
237    fn from(diff: InternalDiff) -> Self {
238        DiffVariant::Internal(diff)
239    }
240}
241
242#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
243pub struct ListDeltaMeta {
244    /// Whether the content of the insert is moved from
245    /// a deletion in the same delta and **the value is not changed**.
246    ///
247    /// If true, this op must be a move op under the hood.
248    /// But an insert created by a move op doesn't necessarily
249    /// have this flag, because the insert content may not
250    /// be moved from a deletion in the same delta.
251    pub from_move: bool,
252}
253
254impl Meta for ListDeltaMeta {
255    fn is_empty(&self) -> bool {
256        !self.from_move
257    }
258
259    fn compose(
260        &mut self,
261        other: &Self,
262        type_pair: (crate::delta::DeltaType, crate::delta::DeltaType),
263    ) {
264        // We can't have two Some because we don't have `move_from` for Retain.
265        // And this function is only called when composing a insert/retain with a retain.
266        if let (crate::delta::DeltaType::Insert, crate::delta::DeltaType::Insert) = type_pair {
267            unreachable!()
268        }
269
270        self.from_move = self.from_move || other.from_move;
271    }
272
273    fn is_mergeable(&self, other: &Self) -> bool {
274        self.from_move == other.from_move
275    }
276
277    fn merge(&mut self, _other: &Self) {}
278}
279
280impl DeltaAttr for ListDeltaMeta {
281    fn compose(&mut self, other: &Self) {
282        self.from_move = self.from_move || other.from_move;
283    }
284
285    fn attr_is_empty(&self) -> bool {
286        !self.from_move
287    }
288}
289
290pub type ListDiffInsertItem = ArrayVec<ValueOrHandler, 8>;
291pub type ListDiffItem = DeltaItem<ListDiffInsertItem, ListDeltaMeta>;
292pub type ListDiff = DeltaRope<ListDiffInsertItem, ListDeltaMeta>;
293
294#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
295pub struct TextMeta(pub FxHashMap<String, LoroValue>);
296
297impl From<TextMeta> for FxHashMap<String, LoroValue> {
298    fn from(value: TextMeta) -> Self {
299        value.0
300    }
301}
302
303impl From<FxHashMap<String, LoroValue>> for TextMeta {
304    fn from(value: FxHashMap<String, LoroValue>) -> Self {
305        TextMeta(value)
306    }
307}
308
309pub type TextDiffItem = DeltaItem<StringSlice, TextMeta>;
310pub type TextDiff = DeltaRope<StringSlice, TextMeta>;
311
312impl DeltaAttr for TextMeta {
313    fn compose(&mut self, other: &Self) {
314        for (key, value) in other.0.iter() {
315            self.0.insert(key.clone(), value.clone());
316        }
317    }
318
319    fn attr_is_empty(&self) -> bool {
320        self.0.is_empty()
321    }
322}
323
324/// Diff is the diff between two versions of a container.
325/// It's used to describe the change of a container and the events.
326///
327/// # Internal
328///
329/// Text index variants:
330///
331/// - When `wasm` is enabled, it should use utf16 indexes.
332/// - When `wasm` is disabled, it should use unicode indexes.
333#[non_exhaustive]
334#[derive(Clone, Debug, EnumAsInner)]
335pub enum Diff {
336    List(ListDiff),
337    /// - When feature `wasm` is enabled, it should use utf16 indexes.
338    /// - When feature `wasm` is disabled, it should use unicode indexes.
339    Text(TextDiff),
340    Map(ResolvedMapDelta),
341    Tree(TreeDiff),
342    #[cfg(feature = "counter")]
343    Counter(f64),
344    Unknown,
345}
346
347impl From<Diff> for DiffVariant {
348    fn from(diff: Diff) -> Self {
349        DiffVariant::External(diff)
350    }
351}
352
353impl InternalDiff {
354    pub(crate) fn is_empty(&self) -> bool {
355        match self {
356            InternalDiff::ListRaw(s) => s.is_empty(),
357            InternalDiff::RichtextRaw(t) => t.is_empty(),
358            InternalDiff::Map(m) => m.updated.is_empty(),
359            InternalDiff::Tree(t) => t.is_empty(),
360            InternalDiff::MovableList(t) => t.is_empty(),
361            #[cfg(feature = "counter")]
362            InternalDiff::Counter(c) => c.abs() < f64::EPSILON,
363            InternalDiff::Unknown => true,
364        }
365    }
366
367    pub(crate) fn compose(self, diff: InternalDiff) -> Result<Self, Self> {
368        // PERF: avoid clone
369        match (self, diff) {
370            (InternalDiff::ListRaw(a), InternalDiff::ListRaw(b)) => {
371                Ok(InternalDiff::ListRaw(a.compose(b)))
372            }
373            (InternalDiff::RichtextRaw(a), InternalDiff::RichtextRaw(b)) => {
374                let mut ans = a.clone();
375                ans.compose(&b);
376                Ok(InternalDiff::RichtextRaw(ans))
377            }
378            (InternalDiff::Map(a), InternalDiff::Map(b)) => Ok(InternalDiff::Map(a.compose(b))),
379            (InternalDiff::Tree(a), InternalDiff::Tree(b)) => Ok(InternalDiff::Tree(a.compose(b))),
380            (a, _) => Err(a),
381        }
382    }
383}
384
385impl Diff {
386    pub fn compose_ref(&mut self, diff: &Diff) {
387        // PERF: avoid clone
388        match (self, diff) {
389            (Diff::List(a), Diff::List(b)) => {
390                a.compose(b);
391            }
392            (Diff::Text(a), Diff::Text(b)) => {
393                a.compose(b);
394            }
395            (Diff::Map(a), Diff::Map(b)) => {
396                *a = a.clone().compose(b.clone());
397            }
398            (Diff::Tree(a), Diff::Tree(b)) => {
399                *a = a.clone().compose(b.clone());
400            }
401            #[cfg(feature = "counter")]
402            (Diff::Counter(a), Diff::Counter(b)) => *a += b,
403            (_, _) => unreachable!(),
404        }
405    }
406
407    pub fn compose(self, diff: Diff) -> Result<Self, Self> {
408        // PERF: avoid clone
409        match (self, diff) {
410            (Diff::List(mut a), Diff::List(b)) => {
411                a.compose(&b);
412                Ok(Diff::List(a))
413            }
414            (Diff::Text(mut a), Diff::Text(b)) => {
415                a.compose(&b);
416                Ok(Diff::Text(a))
417            }
418            (Diff::Map(a), Diff::Map(b)) => Ok(Diff::Map(a.compose(b))),
419
420            (Diff::Tree(a), Diff::Tree(b)) => Ok(Diff::Tree(a.compose(b))),
421            #[cfg(feature = "counter")]
422            (Diff::Counter(a), Diff::Counter(b)) => Ok(Diff::Counter(a + b)),
423            (a, _) => Err(a),
424        }
425    }
426
427    // Transform this diff based on the other diff
428    pub fn transform(&mut self, other: &Self, left_prior: bool) {
429        match (self, other) {
430            (Diff::List(a), Diff::List(b)) => a.transform_(b, left_prior),
431            (Diff::Text(a), Diff::Text(b)) => a.transform_(b, left_prior),
432            (Diff::Map(a), Diff::Map(b)) => a.transform(b, left_prior),
433            (Diff::Tree(a), Diff::Tree(b)) => a.transform(b, left_prior),
434            #[cfg(feature = "counter")]
435            (Diff::Counter(a), Diff::Counter(b)) => {
436                if left_prior {
437                    *a += b;
438                } else {
439                    *a -= b;
440                }
441            }
442            _ => {}
443        }
444    }
445
446    pub fn is_empty(&self) -> bool {
447        match self {
448            Diff::List(s) => s.is_empty(),
449            Diff::Text(t) => t.is_empty(),
450            Diff::Map(m) => m.updated.is_empty(),
451            Diff::Tree(t) => t.diff.is_empty(),
452            #[cfg(feature = "counter")]
453            Diff::Counter(c) => c.abs() < f64::EPSILON,
454            Diff::Unknown => true,
455        }
456    }
457
458    #[allow(unused)]
459    pub(crate) fn concat(self, diff: Diff) -> Diff {
460        match (self, diff) {
461            (Diff::List(mut a), Diff::List(b)) => {
462                a.compose(&b);
463                Diff::List(a)
464            }
465            (Diff::Text(mut a), Diff::Text(b)) => {
466                a.compose(&b);
467                Diff::Text(a)
468            }
469            (Diff::Map(a), Diff::Map(b)) => {
470                let mut a = a;
471                for (k, v) in b.updated {
472                    a = a.with_entry(k, v);
473                }
474                Diff::Map(a)
475            }
476
477            (Diff::Tree(a), Diff::Tree(b)) => Diff::Tree(a.extend(b.diff)),
478            #[cfg(feature = "counter")]
479            (Diff::Counter(a), Diff::Counter(b)) => Diff::Counter(a + b),
480            _ => unreachable!(),
481        }
482    }
483
484    /// Transform the cursor based on this diff
485    pub(crate) fn transform_cursor(&self, pos: usize, left_prior: bool) -> usize {
486        match self {
487            Diff::List(list) => list.transform_pos(pos, left_prior),
488            Diff::Text(text) => text.transform_pos(pos, left_prior),
489            _ => pos,
490        }
491    }
492}
493
494pub fn str_to_path(s: &str) -> Option<Vec<Index>> {
495    s.split('/').map(|x| x.try_into()).try_collect().ok()
496}
497
498pub fn path_to_str(path: &[Index]) -> String {
499    path.iter().map(|x| x.to_string()).join("/")
500}
501
502#[cfg(test)]
503mod test {
504    use std::sync::Arc;
505
506    use itertools::Itertools;
507    use loro_common::LoroValue;
508
509    use crate::{ApplyDiff, LoroDoc};
510
511    #[test]
512    fn test_text_event() {
513        let loro = LoroDoc::new();
514        let _g = loro.subscribe_root(Arc::new(|event| {
515            let mut value = LoroValue::String(Default::default());
516            value.apply_diff(&event.events.iter().map(|x| x.diff.clone()).collect_vec());
517            assert_eq!(value, "h223ello".into());
518        }));
519        let mut txn = loro.txn().unwrap();
520        let text = loro.get_text("id");
521        text.insert_with_txn(&mut txn, 0, "hello").unwrap();
522        text.insert_with_txn(&mut txn, 1, "223").unwrap();
523        txn.commit().unwrap();
524    }
525}