Skip to main content

loro_ffi/
event.rs

1use std::{
2    borrow::Cow,
3    collections::HashMap,
4    sync::{Arc, Mutex},
5};
6
7use loro::{EventTriggerKind, FractionalIndex, TreeID};
8
9use crate::{
10    convert_trait_to_v_or_container, ContainerID, LoroValue, TreeParentId, ValueOrContainer,
11};
12
13#[uniffi::trait_interface]
14pub trait Subscriber: Sync + Send {
15    fn on_diff(&self, diff: DiffEvent);
16}
17
18pub struct DiffEvent {
19    /// How the event is triggered.
20    pub triggered_by: EventTriggerKind,
21    /// The origin of the event.
22    pub origin: String,
23    /// The current receiver of the event.
24    pub current_target: Option<ContainerID>,
25    /// The diffs of the event.
26    pub events: Vec<ContainerDiff>,
27}
28
29impl From<loro::event::DiffEvent<'_>> for DiffEvent {
30    fn from(diff_event: loro::event::DiffEvent) -> Self {
31        Self {
32            triggered_by: diff_event.triggered_by,
33            origin: diff_event.origin.to_string(),
34            current_target: diff_event.current_target.map(|v| v.into()),
35            events: diff_event.events.iter().map(ContainerDiff::from).collect(),
36        }
37    }
38}
39
40pub struct PathItem {
41    pub container: ContainerID,
42    pub index: Index,
43}
44
45/// A diff of a container.
46pub struct ContainerDiff {
47    /// The target container id of the diff.
48    pub target: ContainerID,
49    /// The path of the diff.
50    pub path: Vec<PathItem>,
51    /// Whether the diff is from unknown container.
52    pub is_unknown: bool,
53    /// The diff
54    pub diff: Diff,
55}
56
57#[derive(Debug, Clone)]
58pub enum Index {
59    Key { key: String },
60    Seq { index: u32 },
61    Node { target: TreeID },
62}
63
64pub enum Diff {
65    /// A list diff.
66    List { diff: Vec<ListDiffItem> },
67    /// A text diff.
68    Text { diff: Vec<TextDelta> },
69    /// A map diff.
70    Map { diff: MapDelta },
71    /// A tree diff.
72    Tree { diff: TreeDiff },
73    /// A counter diff.
74    Counter { diff: f64 },
75    /// An unknown diff.
76    Unknown,
77}
78
79pub enum TextDelta {
80    Retain {
81        retain: u32,
82        attributes: Option<HashMap<String, LoroValue>>,
83    },
84    Insert {
85        insert: String,
86        attributes: Option<HashMap<String, LoroValue>>,
87    },
88    Delete {
89        delete: u32,
90    },
91}
92
93impl From<TextDelta> for loro::TextDelta {
94    fn from(value: TextDelta) -> Self {
95        match value {
96            TextDelta::Retain { retain, attributes } => loro::TextDelta::Retain {
97                retain: retain as usize,
98                attributes: attributes.as_ref().map(|a| {
99                    a.iter()
100                        .map(|(k, v)| (k.to_string(), v.clone().into()))
101                        .collect()
102                }),
103            },
104            TextDelta::Insert { insert, attributes } => loro::TextDelta::Insert {
105                insert,
106                attributes: attributes.as_ref().map(|a| {
107                    a.iter()
108                        .map(|(k, v)| (k.to_string(), v.clone().into()))
109                        .collect()
110                }),
111            },
112            TextDelta::Delete { delete } => loro::TextDelta::Delete {
113                delete: delete as usize,
114            },
115        }
116    }
117}
118
119impl From<loro::TextDelta> for TextDelta {
120    fn from(value: loro::TextDelta) -> Self {
121        match value {
122            loro::TextDelta::Retain { retain, attributes } => TextDelta::Retain {
123                retain: retain as u32,
124                attributes: attributes.as_ref().map(|a| {
125                    a.iter()
126                        .map(|(k, v)| (k.to_string(), v.clone().into()))
127                        .collect()
128                }),
129            },
130            loro::TextDelta::Insert { insert, attributes } => TextDelta::Insert {
131                insert,
132                attributes: attributes.as_ref().map(|a| {
133                    a.iter()
134                        .map(|(k, v)| (k.to_string(), v.clone().into()))
135                        .collect()
136                }),
137            },
138            loro::TextDelta::Delete { delete } => TextDelta::Delete {
139                delete: delete as u32,
140            },
141        }
142    }
143}
144
145impl From<ListDiffItem> for loro::event::ListDiffItem {
146    fn from(value: ListDiffItem) -> Self {
147        match value {
148            ListDiffItem::Insert { insert, is_move } => loro::event::ListDiffItem::Insert {
149                insert: insert
150                    .into_iter()
151                    .map(convert_trait_to_v_or_container)
152                    .collect(),
153                is_move,
154            },
155            ListDiffItem::Delete { delete } => loro::event::ListDiffItem::Delete {
156                delete: delete as usize,
157            },
158            ListDiffItem::Retain { retain } => loro::event::ListDiffItem::Retain {
159                retain: retain as usize,
160            },
161        }
162    }
163}
164
165impl From<MapDelta> for loro::event::MapDelta<'static> {
166    fn from(value: MapDelta) -> Self {
167        loro::event::MapDelta {
168            updated: value
169                .updated
170                .into_iter()
171                .map(|(k, v)| (Cow::Owned(k), v.map(convert_trait_to_v_or_container)))
172                .collect(),
173        }
174    }
175}
176
177impl From<TreeDiffItem> for loro::TreeDiffItem {
178    fn from(value: TreeDiffItem) -> Self {
179        let target: TreeID = value.target;
180        let action = match value.action {
181            TreeExternalDiff::Create {
182                parent,
183                index,
184                fractional_index,
185            } => loro::TreeExternalDiff::Create {
186                parent: parent.into(),
187                index: index as usize,
188                position: FractionalIndex::from_hex_string(fractional_index),
189            },
190            TreeExternalDiff::Move {
191                parent,
192                index,
193                fractional_index,
194                old_parent,
195                old_index,
196            } => loro::TreeExternalDiff::Move {
197                parent: parent.into(),
198                index: index as usize,
199                position: FractionalIndex::from_hex_string(fractional_index),
200                old_parent: old_parent.into(),
201                old_index: old_index as usize,
202            },
203            TreeExternalDiff::Delete {
204                old_parent,
205                old_index,
206            } => loro::TreeExternalDiff::Delete {
207                old_parent: old_parent.into(),
208                old_index: old_index as usize,
209            },
210        };
211        loro::TreeDiffItem { target, action }
212    }
213}
214
215pub enum ListDiffItem {
216    /// Insert a new element into the list.
217    Insert {
218        /// The new elements to insert.
219        insert: Vec<Arc<dyn ValueOrContainer>>,
220        /// Whether the new elements are created by moving
221        is_move: bool,
222    },
223    /// Delete n elements from the list at the current index.
224    Delete {
225        /// The number of elements to delete.
226        delete: u32,
227    },
228    /// Retain n elements in the list.
229    ///
230    /// This is used to keep the current index unchanged.
231    Retain {
232        /// The number of elements to retain.
233        retain: u32,
234    },
235}
236
237pub struct MapDelta {
238    /// All the updated keys and their new values.
239    pub updated: HashMap<String, Option<Arc<dyn ValueOrContainer>>>,
240}
241
242pub struct TreeDiff {
243    pub diff: Vec<TreeDiffItem>,
244}
245
246pub struct TreeDiffItem {
247    pub target: TreeID,
248    pub action: TreeExternalDiff,
249}
250
251pub enum TreeExternalDiff {
252    Create {
253        parent: TreeParentId,
254        index: u32,
255        fractional_index: String,
256    },
257    Move {
258        parent: TreeParentId,
259        index: u32,
260        fractional_index: String,
261        old_parent: TreeParentId,
262        old_index: u32,
263    },
264    Delete {
265        old_parent: TreeParentId,
266        old_index: u32,
267    },
268}
269
270impl<'a> From<&loro::event::ContainerDiff<'a>> for ContainerDiff {
271    fn from(value: &loro::event::ContainerDiff<'a>) -> Self {
272        Self {
273            target: value.target.into(),
274            path: value
275                .path
276                .iter()
277                .map(|(id, index)| PathItem {
278                    container: id.into(),
279                    index: index.into(),
280                })
281                .collect(),
282            is_unknown: value.is_unknown,
283            diff: (&value.diff).into(),
284        }
285    }
286}
287
288impl From<&loro::Index> for Index {
289    fn from(value: &loro::Index) -> Self {
290        match value {
291            loro::Index::Key(key) => Index::Key {
292                key: key.to_string(),
293            },
294            loro::Index::Seq(index) => Index::Seq {
295                index: *index as u32,
296            },
297            loro::Index::Node(target) => Index::Node { target: *target },
298        }
299    }
300}
301
302impl From<Index> for loro::Index {
303    fn from(value: Index) -> loro::Index {
304        match value {
305            Index::Key { key } => loro::Index::Key(key.into()),
306            Index::Seq { index } => loro::Index::Seq(index as usize),
307            Index::Node { target } => loro::Index::Node(target),
308        }
309    }
310}
311
312impl From<&loro::event::Diff<'_>> for Diff {
313    fn from(value: &loro::event::Diff) -> Self {
314        match value {
315            loro::event::Diff::List(l) => {
316                let mut ans = Vec::with_capacity(l.len());
317                for item in l.iter() {
318                    match item {
319                        loro::event::ListDiffItem::Insert { insert, is_move } => {
320                            let mut new_insert = Vec::with_capacity(insert.len());
321                            for v in insert.iter() {
322                                new_insert.push(Arc::new(v.clone()) as Arc<dyn ValueOrContainer>);
323                            }
324                            ans.push(ListDiffItem::Insert {
325                                insert: new_insert,
326                                is_move: *is_move,
327                            });
328                        }
329                        loro::event::ListDiffItem::Delete { delete } => {
330                            ans.push(ListDiffItem::Delete {
331                                delete: *delete as u32,
332                            });
333                        }
334                        loro::event::ListDiffItem::Retain { retain } => {
335                            ans.push(ListDiffItem::Retain {
336                                retain: *retain as u32,
337                            });
338                        }
339                    }
340                }
341                Diff::List { diff: ans }
342            }
343            loro::event::Diff::Text(t) => Diff::Text {
344                diff: t.iter().map(|i| i.clone().into()).collect(),
345            },
346            loro::event::Diff::Map(m) => {
347                let mut updated = HashMap::new();
348                for (key, value) in m.updated.iter() {
349                    updated.insert(
350                        key.to_string(),
351                        value
352                            .as_ref()
353                            .map(|v| Arc::new(v.clone()) as Arc<dyn ValueOrContainer>),
354                    );
355                }
356
357                Diff::Map {
358                    diff: MapDelta { updated },
359                }
360            }
361            loro::event::Diff::Tree(t) => {
362                let mut diff = Vec::new();
363                for item in t.iter() {
364                    diff.push(TreeDiffItem {
365                        target: item.target,
366                        action: match &item.action {
367                            loro::TreeExternalDiff::Create {
368                                parent,
369                                index,
370                                position,
371                            } => TreeExternalDiff::Create {
372                                parent: (*parent).into(),
373                                index: *index as u32,
374                                fractional_index: position.to_string(),
375                            },
376                            loro::TreeExternalDiff::Move {
377                                parent,
378                                index,
379                                position,
380                                old_parent,
381                                old_index,
382                            } => TreeExternalDiff::Move {
383                                parent: (*parent).into(),
384                                index: *index as u32,
385                                fractional_index: position.to_string(),
386                                old_parent: (*old_parent).into(),
387                                old_index: *old_index as u32,
388                            },
389                            loro::TreeExternalDiff::Delete {
390                                old_parent,
391                                old_index,
392                            } => TreeExternalDiff::Delete {
393                                old_parent: (*old_parent).into(),
394                                old_index: *old_index as u32,
395                            },
396                        },
397                    });
398                }
399                Diff::Tree {
400                    diff: TreeDiff { diff },
401                }
402            }
403            loro::event::Diff::Counter(c) => Diff::Counter { diff: *c },
404            loro::event::Diff::Unknown => Diff::Unknown,
405        }
406    }
407}
408
409impl From<Diff> for loro::event::Diff<'static> {
410    fn from(value: Diff) -> Self {
411        match value {
412            Diff::List { diff } => {
413                loro::event::Diff::List(diff.into_iter().map(|i| i.into()).collect())
414            }
415            Diff::Text { diff } => {
416                loro::event::Diff::Text(diff.into_iter().map(|i| i.into()).collect())
417            }
418            Diff::Map { diff } => loro::event::Diff::Map(diff.into()),
419            Diff::Tree { diff } => loro::event::Diff::Tree(Cow::Owned(loro::TreeDiff {
420                diff: diff.diff.into_iter().map(|i| i.into()).collect(),
421            })),
422            Diff::Counter { diff } => loro::event::Diff::Counter(diff),
423            Diff::Unknown => loro::event::Diff::Unknown,
424        }
425    }
426}
427
428#[derive(Debug, Clone, Default)]
429pub struct DiffBatch(Arc<Mutex<loro::event::DiffBatch>>);
430
431impl DiffBatch {
432    pub fn new() -> Self {
433        Self(Default::default())
434    }
435
436    /// Push a new event to the batch.
437    ///
438    /// If the cid already exists in the batch, return Err
439    pub fn push(&self, cid: ContainerID, diff: Diff) -> Option<Diff> {
440        let mut batch = self.0.lock().unwrap();
441        if let Err(diff) = batch.push(cid.into(), diff.into()) {
442            Some((&diff).into())
443        } else {
444            None
445        }
446    }
447
448    /// Returns an iterator over the diffs in this batch, in the order they were added.
449    ///
450    /// The iterator yields tuples of `(&ContainerID, &Diff)` where:
451    /// - `ContainerID` is the ID of the container that was modified
452    /// - `Diff` contains the actual changes made to that container
453    ///
454    /// The order of the diffs is preserved from when they were originally added to the batch.
455    pub fn get_diff(&self) -> Vec<ContainerIDAndDiff> {
456        let batch = self.0.lock().unwrap();
457        batch
458            .iter()
459            .map(|(id, diff)| ContainerIDAndDiff {
460                cid: id.into(),
461                diff: diff.into(),
462            })
463            .collect()
464    }
465}
466
467impl From<DiffBatch> for loro::event::DiffBatch {
468    fn from(value: DiffBatch) -> Self {
469        value.0.lock().unwrap().clone()
470    }
471}
472
473impl From<loro::event::DiffBatch> for DiffBatch {
474    fn from(value: loro::event::DiffBatch) -> Self {
475        Self(Arc::new(Mutex::new(value)))
476    }
477}
478
479pub struct ContainerIDAndDiff {
480    pub cid: ContainerID,
481    pub diff: Diff,
482}