Skip to main content

loro_internal/
configure.rs

1use crate::sync::{Mutex, RwLock};
2use loro_common::ContainerID;
3use rustc_hash::FxHashSet;
4
5pub use crate::container::richtext::config::{StyleConfig, StyleConfigMap};
6use crate::LoroDoc;
7use std::sync::atomic::{AtomicBool, AtomicI64};
8use std::sync::Arc;
9
10#[derive(Clone, Debug)]
11pub struct Configure {
12    pub(crate) text_style_config: Arc<RwLock<StyleConfigMap>>,
13    record_timestamp: Arc<AtomicBool>,
14    pub(crate) merge_interval_in_s: Arc<AtomicI64>,
15    pub(crate) editable_detached_mode: Arc<AtomicBool>,
16    pub(crate) deleted_root_containers: Arc<Mutex<FxHashSet<ContainerID>>>,
17    pub(crate) hide_empty_root_containers: Arc<AtomicBool>,
18}
19
20impl LoroDoc {
21    pub(crate) fn set_config(&self, config: &Configure) {
22        self.config_text_style(config.text_style_config.read().clone());
23        self.set_record_timestamp(config.record_timestamp());
24        self.set_change_merge_interval(config.merge_interval());
25        self.set_detached_editing(config.detached_editing());
26    }
27}
28
29impl Default for Configure {
30    fn default() -> Self {
31        Self {
32            text_style_config: Arc::new(RwLock::new(StyleConfigMap::default_rich_text_config())),
33            record_timestamp: Arc::new(AtomicBool::new(false)),
34            editable_detached_mode: Arc::new(AtomicBool::new(false)),
35            merge_interval_in_s: Arc::new(AtomicI64::new(1000)),
36            deleted_root_containers: Arc::new(Mutex::new(Default::default())),
37            hide_empty_root_containers: Arc::new(AtomicBool::new(false)),
38        }
39    }
40}
41
42impl Configure {
43    pub fn fork(&self) -> Self {
44        Self {
45            text_style_config: Arc::new(RwLock::new(self.text_style_config.read().clone())),
46            record_timestamp: Arc::new(AtomicBool::new(
47                self.record_timestamp
48                    .load(std::sync::atomic::Ordering::Relaxed),
49            )),
50            merge_interval_in_s: Arc::new(AtomicI64::new(
51                self.merge_interval_in_s
52                    .load(std::sync::atomic::Ordering::Relaxed),
53            )),
54            editable_detached_mode: Arc::new(AtomicBool::new(
55                self.editable_detached_mode
56                    .load(std::sync::atomic::Ordering::Relaxed),
57            )),
58            deleted_root_containers: Arc::new(Mutex::new(
59                self.deleted_root_containers.lock().clone(),
60            )),
61            hide_empty_root_containers: Arc::new(AtomicBool::new(
62                self.hide_empty_root_containers
63                    .load(std::sync::atomic::Ordering::Relaxed),
64            )),
65        }
66    }
67
68    pub fn text_style_config(&self) -> &Arc<RwLock<StyleConfigMap>> {
69        &self.text_style_config
70    }
71
72    pub fn record_timestamp(&self) -> bool {
73        self.record_timestamp
74            .load(std::sync::atomic::Ordering::Relaxed)
75    }
76
77    pub fn set_record_timestamp(&self, record: bool) {
78        self.record_timestamp
79            .store(record, std::sync::atomic::Ordering::Relaxed);
80    }
81
82    pub fn detached_editing(&self) -> bool {
83        self.editable_detached_mode
84            .load(std::sync::atomic::Ordering::Relaxed)
85    }
86
87    pub fn set_detached_editing(&self, mode: bool) {
88        self.editable_detached_mode
89            .store(mode, std::sync::atomic::Ordering::Relaxed);
90    }
91
92    pub fn merge_interval(&self) -> i64 {
93        self.merge_interval_in_s
94            .load(std::sync::atomic::Ordering::Relaxed)
95    }
96
97    pub fn set_merge_interval(&self, interval: i64) {
98        self.merge_interval_in_s
99            .store(interval, std::sync::atomic::Ordering::Relaxed);
100    }
101
102    pub fn set_hide_empty_root_containers(&self, hide: bool) {
103        self.hide_empty_root_containers
104            .store(hide, std::sync::atomic::Ordering::Relaxed);
105    }
106}
107
108#[derive(Debug)]
109pub struct DefaultRandom;
110
111#[cfg(test)]
112use std::sync::atomic::AtomicU64;
113#[cfg(test)]
114static mut TEST_RANDOM: AtomicU64 = AtomicU64::new(0);
115
116impl SecureRandomGenerator for DefaultRandom {
117    fn fill_byte(&self, dest: &mut [u8]) {
118        #[cfg(not(test))]
119        getrandom::getrandom(dest).unwrap();
120
121        #[cfg(test)]
122        // SAFETY: this is only used in test
123        unsafe {
124            #[allow(static_mut_refs)]
125            let bytes = TEST_RANDOM
126                .fetch_add(1, std::sync::atomic::Ordering::Release)
127                .to_le_bytes();
128            dest.copy_from_slice(&bytes[..dest.len()]);
129        }
130    }
131}
132
133pub trait SecureRandomGenerator: Send + Sync {
134    fn fill_byte(&self, dest: &mut [u8]);
135    fn next_u64(&self) -> u64 {
136        let mut buf = [0u8; 8];
137        self.fill_byte(&mut buf);
138        u64::from_le_bytes(buf)
139    }
140
141    fn next_u32(&self) -> u32 {
142        let mut buf = [0u8; 4];
143        self.fill_byte(&mut buf);
144        u32::from_le_bytes(buf)
145    }
146
147    fn next_i64(&self) -> i64 {
148        let mut buf = [0u8; 8];
149        self.fill_byte(&mut buf);
150        i64::from_le_bytes(buf)
151    }
152
153    fn next_i32(&self) -> i32 {
154        let mut buf = [0u8; 4];
155        self.fill_byte(&mut buf);
156        i32::from_le_bytes(buf)
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use std::sync::{atomic::Ordering, Mutex, OnceLock};
163
164    use loro_common::{ContainerID, ContainerType, InternalString};
165
166    use crate::container::richtext::ExpandType;
167
168    use super::*;
169
170    fn random_lock() -> &'static Mutex<()> {
171        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
172        LOCK.get_or_init(|| Mutex::new(()))
173    }
174
175    #[test]
176    fn configure_default_values_and_setters_match_the_public_contract() {
177        let config = Configure::default();
178
179        assert!(!config.record_timestamp());
180        assert!(!config.detached_editing());
181        assert_eq!(config.merge_interval(), 1000);
182        assert!(!config.hide_empty_root_containers.load(Ordering::Relaxed));
183        assert!(config.deleted_root_containers.lock().is_empty());
184
185        let styles = config.text_style_config.read();
186        assert_eq!(
187            styles.get(&InternalString::from("bold")),
188            Some(StyleConfig {
189                expand: ExpandType::After,
190            })
191        );
192        assert_eq!(
193            styles.get(&InternalString::from("italic")),
194            Some(StyleConfig {
195                expand: ExpandType::After,
196            })
197        );
198        assert_eq!(
199            styles.get(&InternalString::from("link")),
200            Some(StyleConfig {
201                expand: ExpandType::None,
202            })
203        );
204        assert_eq!(styles.get(&InternalString::from("missing")), None);
205
206        config.set_record_timestamp(true);
207        config.set_detached_editing(true);
208        config.set_merge_interval(42);
209        config.set_hide_empty_root_containers(true);
210
211        assert!(config.record_timestamp());
212        assert!(config.detached_editing());
213        assert_eq!(config.merge_interval(), 42);
214        assert!(config.hide_empty_root_containers.load(Ordering::Relaxed));
215    }
216
217    #[test]
218    fn configure_fork_copies_current_state_and_then_diverges() {
219        let config = Configure::default();
220        config.set_record_timestamp(true);
221        config.set_detached_editing(true);
222        config.set_merge_interval(25);
223        config.set_hide_empty_root_containers(true);
224        config
225            .deleted_root_containers
226            .lock()
227            .insert(ContainerID::Root {
228                name: InternalString::from("root"),
229                container_type: ContainerType::Map,
230            });
231        config.text_style_config.write().insert(
232            InternalString::from("custom"),
233            StyleConfig {
234                expand: ExpandType::None,
235            },
236        );
237
238        let forked = config.fork();
239
240        assert!(forked.record_timestamp());
241        assert!(forked.detached_editing());
242        assert_eq!(forked.merge_interval(), 25);
243        assert!(forked.hide_empty_root_containers.load(Ordering::Relaxed));
244        assert_eq!(forked.deleted_root_containers.lock().len(), 1);
245        assert_eq!(
246            forked
247                .text_style_config
248                .read()
249                .get(&InternalString::from("custom")),
250            Some(StyleConfig {
251                expand: ExpandType::None,
252            })
253        );
254
255        config.set_record_timestamp(false);
256        config.set_detached_editing(false);
257        config.set_merge_interval(99);
258        config.set_hide_empty_root_containers(false);
259        config.deleted_root_containers.lock().clear();
260        config
261            .text_style_config
262            .write()
263            .insert(InternalString::from("fork-only"), StyleConfig::default());
264
265        assert!(forked.record_timestamp());
266        assert!(forked.detached_editing());
267        assert_eq!(forked.merge_interval(), 25);
268        assert!(forked.hide_empty_root_containers.load(Ordering::Relaxed));
269        assert_eq!(forked.deleted_root_containers.lock().len(), 1);
270        assert!(forked
271            .text_style_config
272            .read()
273            .get(&InternalString::from("fork-only"))
274            .is_none());
275    }
276
277    #[test]
278    fn default_random_test_mode_uses_the_incrementing_counter_for_integer_helpers() {
279        let _guard = random_lock().lock().unwrap();
280        let random = DefaultRandom;
281
282        let a = random.next_u64();
283        let b = random.next_u32();
284        let c = random.next_i64();
285        let d = random.next_i32();
286
287        assert_eq!(b as u64, (a + 1) as u32 as u64);
288        assert_eq!(c as u64, a + 2);
289        assert_eq!(d as u64, (a + 3) as u32 as u64);
290    }
291}