loro_internal/
configure.rs1use 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 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}