loro_internal/
event.rs

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