at main 159 lines 4.5 kB view raw
1//! CRDT document trait and sync state tracking. 2 3use loro::VersionVector; 4use weaver_api::com_atproto::repo::strong_ref::StrongRef; 5 6/// Sync state for a CRDT document. 7/// 8/// Tracks the edit root, last diff, and version at last sync. 9#[derive(Clone, Debug, Default)] 10pub struct SyncState { 11 /// StrongRef to the sh.weaver.edit.root record. 12 pub edit_root: Option<StrongRef<'static>>, 13 14 /// StrongRef to the most recent sh.weaver.edit.diff record. 15 pub last_diff: Option<StrongRef<'static>>, 16 17 /// Version vector at the time of last sync. 18 pub last_synced_version: Option<VersionVector>, 19} 20 21impl SyncState { 22 /// Create new empty sync state. 23 pub fn new() -> Self { 24 Self::default() 25 } 26 27 /// Check if we have an edit root (i.e., have synced at least once). 28 pub fn has_root(&self) -> bool { 29 self.edit_root.is_some() 30 } 31} 32 33/// Trait for CRDT documents that can be synced to AT Protocol PDS. 34/// 35/// Implementors provide access to the underlying CRDT operations 36/// and sync state tracking. 37pub trait CrdtDocument { 38 /// Export full snapshot bytes. 39 fn export_snapshot(&self) -> Vec<u8>; 40 41 /// Export updates since the last synced version. 42 /// Returns None if no changes since last sync. 43 fn export_updates_since_sync(&self) -> Option<Vec<u8>>; 44 45 /// Import remote changes. 46 fn import(&mut self, data: &[u8]) -> Result<(), crate::CrdtError>; 47 48 /// Get current version vector. 49 fn version(&self) -> VersionVector; 50 51 /// Get the edit root StrongRef. 52 fn edit_root(&self) -> Option<StrongRef<'static>>; 53 54 /// Set the edit root StrongRef. 55 fn set_edit_root(&mut self, root: Option<StrongRef<'static>>); 56 57 /// Get the last diff StrongRef. 58 fn last_diff(&self) -> Option<StrongRef<'static>>; 59 60 /// Set the last diff StrongRef. 61 fn set_last_diff(&mut self, diff: Option<StrongRef<'static>>); 62 63 /// Mark current version as synced. 64 fn mark_synced(&mut self); 65 66 /// Check if there are changes since last sync. 67 fn has_unsynced_changes(&self) -> bool; 68} 69 70// Blanket implementation for LoroTextBuffer with embedded SyncState 71// (Concrete types can provide their own implementations) 72 73/// A simple CRDT document wrapping LoroTextBuffer with sync state. 74pub struct SimpleCrdtDocument { 75 buffer: crate::LoroTextBuffer, 76 sync_state: SyncState, 77} 78 79impl SimpleCrdtDocument { 80 /// Create a new empty document. 81 pub fn new() -> Self { 82 Self { 83 buffer: crate::LoroTextBuffer::new(), 84 sync_state: SyncState::new(), 85 } 86 } 87 88 /// Create from snapshot. 89 pub fn from_snapshot(snapshot: &[u8]) -> Result<Self, crate::CrdtError> { 90 Ok(Self { 91 buffer: crate::LoroTextBuffer::from_snapshot(snapshot)?, 92 sync_state: SyncState::new(), 93 }) 94 } 95 96 /// Get the underlying buffer. 97 pub fn buffer(&self) -> &crate::LoroTextBuffer { 98 &self.buffer 99 } 100 101 /// Get mutable access to the buffer. 102 pub fn buffer_mut(&mut self) -> &mut crate::LoroTextBuffer { 103 &mut self.buffer 104 } 105} 106 107impl Default for SimpleCrdtDocument { 108 fn default() -> Self { 109 Self::new() 110 } 111} 112 113impl CrdtDocument for SimpleCrdtDocument { 114 fn export_snapshot(&self) -> Vec<u8> { 115 self.buffer.export_snapshot() 116 } 117 118 fn export_updates_since_sync(&self) -> Option<Vec<u8>> { 119 self.sync_state 120 .last_synced_version 121 .as_ref() 122 .and_then(|v| self.buffer.export_updates_since(v)) 123 } 124 125 fn import(&mut self, data: &[u8]) -> Result<(), crate::CrdtError> { 126 self.buffer.import(data) 127 } 128 129 fn version(&self) -> VersionVector { 130 self.buffer.version() 131 } 132 133 fn edit_root(&self) -> Option<StrongRef<'static>> { 134 self.sync_state.edit_root.clone() 135 } 136 137 fn set_edit_root(&mut self, root: Option<StrongRef<'static>>) { 138 self.sync_state.edit_root = root; 139 } 140 141 fn last_diff(&self) -> Option<StrongRef<'static>> { 142 self.sync_state.last_diff.clone() 143 } 144 145 fn set_last_diff(&mut self, diff: Option<StrongRef<'static>>) { 146 self.sync_state.last_diff = diff; 147 } 148 149 fn mark_synced(&mut self) { 150 self.sync_state.last_synced_version = Some(self.buffer.version()); 151 } 152 153 fn has_unsynced_changes(&self) -> bool { 154 match &self.sync_state.last_synced_version { 155 None => true, // Never synced 156 Some(last) => self.buffer.version() != *last, 157 } 158 } 159}