truffle_core/crdt_doc/
mod.rs1pub mod backend;
25pub mod sync;
26pub mod types;
27
28#[cfg(test)]
29mod tests;
30
31pub use backend::{CrdtBackend, CrdtFileBackend, MemoryCrdtBackend};
32pub use types::{CrdtDocError, CrdtDocEvent, CrdtSyncMessage};
33
34use std::sync::Arc;
35
36use loro::LoroDoc;
37use tokio::sync::{broadcast, mpsc};
38use tokio::task::JoinHandle;
39
40use crate::network::NetworkProvider;
41use crate::node::Node;
42
43pub struct CrdtDoc {
55 doc_id: String,
57 doc: Arc<std::sync::Mutex<LoroDoc>>,
63 event_tx: broadcast::Sender<CrdtDocEvent>,
65 task_handle: tokio::sync::Mutex<Option<JoinHandle<()>>>,
67 backend: Arc<dyn CrdtBackend>,
69 compact_tx: mpsc::UnboundedSender<()>,
72 _local_update_sub: loro::Subscription,
75}
76
77impl CrdtDoc {
78 pub async fn new<N: NetworkProvider + 'static>(
82 node: Arc<Node<N>>,
83 doc_id: &str,
84 ) -> Result<Arc<Self>, CrdtDocError> {
85 Self::new_with_backend(node, doc_id, Arc::new(MemoryCrdtBackend)).await
86 }
87
88 pub async fn new_with_backend<N: NetworkProvider + 'static>(
93 node: Arc<Node<N>>,
94 doc_id: &str,
95 backend: Arc<dyn CrdtBackend>,
96 ) -> Result<Arc<Self>, CrdtDocError> {
97 let doc = LoroDoc::new();
98
99 if let Some(snapshot) = backend.load_snapshot(doc_id) {
101 doc.import(&snapshot)
102 .map_err(|e| CrdtDocError::Decode(format!("failed to import snapshot: {e}")))?;
103 }
104 for update in backend.load_updates(doc_id) {
105 doc.import(&update)
106 .map_err(|e| CrdtDocError::Decode(format!("failed to import update: {e}")))?;
107 }
108
109 let doc = Arc::new(std::sync::Mutex::new(doc));
110 let (event_tx, _) = broadcast::channel(256);
111 let (local_update_tx, local_update_rx) = mpsc::unbounded_channel::<Vec<u8>>();
112 let (compact_tx, compact_rx) = mpsc::unbounded_channel::<()>();
113
114 let sub = {
117 let d = doc.lock().unwrap();
118 d.subscribe_local_update(Box::new(move |bytes| {
119 let _ = local_update_tx.send(bytes.to_vec());
120 true }))
122 };
123
124 let task_handle = sync::spawn_sync_task(
126 node,
127 doc.clone(),
128 doc_id.to_string(),
129 backend.clone(),
130 event_tx.clone(),
131 local_update_rx,
132 compact_rx,
133 );
134
135 Ok(Arc::new(Self {
136 doc_id: doc_id.to_string(),
137 doc,
138 event_tx,
139 task_handle: tokio::sync::Mutex::new(Some(task_handle)),
140 backend,
141 compact_tx,
142 _local_update_sub: sub,
143 }))
144 }
145
146 pub fn map(&self, name: &str) -> loro::LoroMap {
150 let doc = self.doc.lock().unwrap();
151 doc.get_map(name)
152 }
153
154 pub fn list(&self, name: &str) -> loro::LoroList {
156 let doc = self.doc.lock().unwrap();
157 doc.get_list(name)
158 }
159
160 pub fn text(&self, name: &str) -> loro::LoroText {
162 let doc = self.doc.lock().unwrap();
163 doc.get_text(name)
164 }
165
166 pub fn tree(&self, name: &str) -> loro::LoroTree {
168 let doc = self.doc.lock().unwrap();
169 doc.get_tree(name)
170 }
171
172 pub fn movable_list(&self, name: &str) -> loro::LoroMovableList {
174 let doc = self.doc.lock().unwrap();
175 doc.get_movable_list(name)
176 }
177
178 pub fn counter(&self, name: &str) -> loro::LoroCounter {
180 let doc = self.doc.lock().unwrap();
181 doc.get_counter(name)
182 }
183
184 pub fn get_deep_value(&self) -> loro::LoroValue {
188 let doc = self.doc.lock().unwrap();
189 doc.get_deep_value()
190 }
191
192 pub fn commit(&self) {
195 let doc = self.doc.lock().unwrap();
196 doc.commit();
197 }
198
199 pub fn export_snapshot(&self) -> Result<Vec<u8>, CrdtDocError> {
201 let doc = self.doc.lock().unwrap();
202 doc.export(loro::ExportMode::Snapshot)
203 .map_err(|e| CrdtDocError::Encode(format!("snapshot export failed: {e}")))
204 }
205
206 pub fn export_updates_since(&self, vv: &loro::VersionVector) -> Result<Vec<u8>, CrdtDocError> {
208 let doc = self.doc.lock().unwrap();
209 doc.export(loro::ExportMode::updates(vv))
210 .map_err(|e| CrdtDocError::Encode(format!("updates export failed: {e}")))
211 }
212
213 pub fn import(&self, data: &[u8]) -> Result<(), CrdtDocError> {
215 let doc = self.doc.lock().unwrap();
216 doc.import(data)
217 .map(|_| ())
218 .map_err(|e| CrdtDocError::Decode(format!("import failed: {e}")))
219 }
220
221 pub fn version_vector(&self) -> loro::VersionVector {
223 let doc = self.doc.lock().unwrap();
224 doc.state_vv()
225 }
226
227 pub fn subscribe(&self) -> broadcast::Receiver<CrdtDocEvent> {
229 self.event_tx.subscribe()
230 }
231
232 pub fn doc_id(&self) -> &str {
234 &self.doc_id
235 }
236
237 pub fn compact(&self) -> Result<(), CrdtDocError> {
240 let snapshot = {
241 let doc = self.doc.lock().unwrap();
242 doc.export(loro::ExportMode::Snapshot)
243 .map_err(|e| CrdtDocError::Encode(format!("compact snapshot failed: {e}")))?
244 };
245 self.backend.save_snapshot(&self.doc_id, &snapshot);
246 let _ = self.compact_tx.send(());
248 Ok(())
249 }
250
251 pub fn checkout(&self, frontiers: &loro::Frontiers) -> Result<(), CrdtDocError> {
256 let doc = self.doc.lock().unwrap();
257 doc.checkout(frontiers)
258 .map_err(|e| CrdtDocError::Loro(format!("checkout failed: {e}")))
259 }
260
261 pub fn checkout_to_latest(&self) {
263 let doc = self.doc.lock().unwrap();
264 doc.checkout_to_latest();
265 }
266
267 pub fn is_detached(&self) -> bool {
270 let doc = self.doc.lock().unwrap();
271 doc.is_detached()
272 }
273
274 pub fn fork(&self) -> Result<LoroDoc, CrdtDocError> {
276 let doc = self.doc.lock().unwrap();
277 Ok(doc.fork())
278 }
279
280 pub fn with_doc<F, R>(&self, f: F) -> R
285 where
286 F: FnOnce(&LoroDoc) -> R,
287 {
288 let doc = self.doc.lock().unwrap();
289 f(&doc)
290 }
291
292 pub async fn stop(&self) {
296 let mut handle = self.task_handle.lock().await;
297 if let Some(h) = handle.take() {
298 h.abort();
299 tracing::info!(doc_id = self.doc_id.as_str(), "crdt_doc: stopped");
300 }
301 }
302}
303
304impl Drop for CrdtDoc {
305 fn drop(&mut self) {
306 if let Ok(mut handle) = self.task_handle.try_lock() {
308 if let Some(h) = handle.take() {
309 h.abort();
310 }
311 }
312 }
313}