ass_editor/core/
thread_safety.rs1#![allow(clippy::arc_with_non_send_sync)]
10
11use crate::commands::{CommandResult, EditorCommand};
12use crate::core::errors::EditorError;
13use crate::core::{EditorDocument, Result};
14
15#[cfg(feature = "std")]
16use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
17
18#[cfg(not(feature = "std"))]
19use alloc::sync::Arc;
20
21#[cfg(feature = "concurrency")]
32#[derive(Debug, Clone)]
33pub struct SyncDocument {
34 inner: Arc<RwLock<EditorDocument>>,
36
37 command_lock: Arc<Mutex<()>>,
39}
40
41#[cfg(feature = "concurrency")]
42impl SyncDocument {
43 pub fn new(document: EditorDocument) -> Self {
45 Self {
46 inner: Arc::new(RwLock::new(document)),
47 command_lock: Arc::new(Mutex::new(())),
48 }
49 }
50
51 pub fn from_document(document: EditorDocument) -> Self {
53 Self::new(document)
54 }
55
56 pub fn read(&self) -> Result<RwLockReadGuard<'_, EditorDocument>> {
58 self.inner
59 .read()
60 .map_err(|_| EditorError::ThreadSafetyError {
61 message: "Failed to acquire read lock".to_string(),
62 })
63 }
64
65 pub fn write(&self) -> Result<RwLockWriteGuard<'_, EditorDocument>> {
67 self.inner
68 .write()
69 .map_err(|_| EditorError::ThreadSafetyError {
70 message: "Failed to acquire write lock".to_string(),
71 })
72 }
73
74 pub fn execute_command<C: EditorCommand>(&self, command: C) -> Result<CommandResult> {
76 let _guard = self
78 .command_lock
79 .lock()
80 .map_err(|_| EditorError::ThreadSafetyError {
81 message: "Failed to acquire command lock".to_string(),
82 })?;
83
84 let mut doc = self.write()?;
86 command.execute(&mut doc)
87 }
88
89 pub fn try_read(&self) -> Option<RwLockReadGuard<'_, EditorDocument>> {
91 self.inner.try_read().ok()
92 }
93
94 pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, EditorDocument>> {
96 self.inner.try_write().ok()
97 }
98
99 pub fn text(&self) -> Result<String> {
101 let doc = self.read()?;
102 Ok(doc.text())
103 }
104
105 pub fn len(&self) -> Result<usize> {
107 let doc = self.read()?;
108 Ok(doc.len())
109 }
110
111 pub fn is_empty(&self) -> Result<bool> {
113 let doc = self.read()?;
114 Ok(doc.is_empty())
115 }
116
117 pub fn id(&self) -> Result<String> {
119 let doc = self.read()?;
120 Ok(doc.id().to_string())
121 }
122
123 pub fn with_read<F, R>(&self, f: F) -> Result<R>
125 where
126 F: FnOnce(&EditorDocument) -> R,
127 {
128 let doc = self.read()?;
129 Ok(f(&doc))
130 }
131
132 pub fn with_write<F, R>(&self, f: F) -> Result<R>
134 where
135 F: FnOnce(&mut EditorDocument) -> Result<R>,
136 {
137 let mut doc = self.write()?;
138 f(&mut doc)
139 }
140
141 pub fn clone_document(&self) -> Result<EditorDocument> {
143 let doc = self.read()?;
144 EditorDocument::from_content(&doc.text())
145 }
146
147 pub fn validate(&self) -> Result<()> {
149 let doc = self.read()?;
150 doc.validate()
151 }
152
153 pub fn validate_comprehensive(&self) -> Result<Vec<crate::utils::validator::ValidationIssue>> {
155 self.with_write(|doc| {
156 let result = doc.validate_comprehensive()?;
157 Ok(result.issues)
158 })
159 }
160}
161
162#[cfg(feature = "concurrency")]
164#[derive(Debug, Clone)]
165pub struct DocumentPool {
166 documents: Arc<RwLock<std::collections::HashMap<String, SyncDocument>>>,
168}
169
170#[cfg(feature = "concurrency")]
171impl DocumentPool {
172 pub fn new() -> Self {
174 Self {
175 documents: Arc::new(RwLock::new(std::collections::HashMap::new())),
176 }
177 }
178
179 pub fn add_document(&self, document: EditorDocument) -> Result<String> {
181 let id = document.id().to_string();
182 let sync_doc = SyncDocument::new(document);
183
184 let mut docs = self
185 .documents
186 .write()
187 .map_err(|_| EditorError::ThreadSafetyError {
188 message: "Failed to acquire pool write lock".to_string(),
189 })?;
190
191 docs.insert(id.clone(), sync_doc);
192 Ok(id)
193 }
194
195 pub fn get_document(&self, id: &str) -> Result<SyncDocument> {
197 let docs = self
198 .documents
199 .read()
200 .map_err(|_| EditorError::ThreadSafetyError {
201 message: "Failed to acquire pool read lock".to_string(),
202 })?;
203
204 docs.get(id)
205 .cloned()
206 .ok_or_else(|| EditorError::ValidationError {
207 message: format!("Document not found: {id}"),
208 })
209 }
210
211 pub fn remove_document(&self, id: &str) -> Result<()> {
213 let mut docs = self
214 .documents
215 .write()
216 .map_err(|_| EditorError::ThreadSafetyError {
217 message: "Failed to acquire pool write lock".to_string(),
218 })?;
219
220 docs.remove(id);
221 Ok(())
222 }
223
224 pub fn list_documents(&self) -> Result<Vec<String>> {
226 let docs = self
227 .documents
228 .read()
229 .map_err(|_| EditorError::ThreadSafetyError {
230 message: "Failed to acquire pool read lock".to_string(),
231 })?;
232
233 Ok(docs.keys().cloned().collect())
234 }
235
236 pub fn document_count(&self) -> Result<usize> {
238 let docs = self
239 .documents
240 .read()
241 .map_err(|_| EditorError::ThreadSafetyError {
242 message: "Failed to acquire pool read lock".to_string(),
243 })?;
244
245 Ok(docs.len())
246 }
247}
248
249#[cfg(feature = "concurrency")]
250impl Default for DocumentPool {
251 fn default() -> Self {
252 Self::new()
253 }
254}
255
256#[cfg(feature = "concurrency")]
258pub struct ScopedDocumentLock<'a> {
259 _guard: RwLockWriteGuard<'a, EditorDocument>,
260}
261
262#[cfg(feature = "concurrency")]
263impl<'a> ScopedDocumentLock<'a> {
264 pub fn new(document: &'a SyncDocument) -> Result<Self> {
266 let guard = document.write()?;
267 Ok(Self { _guard: guard })
268 }
269
270 pub fn document(&mut self) -> &mut EditorDocument {
272 &mut self._guard
273 }
274}
275
276#[cfg(all(feature = "concurrency", feature = "async"))]
278pub struct AsyncDocument {
279 sync_doc: SyncDocument,
280}
281
282#[cfg(all(feature = "concurrency", feature = "async"))]
283impl AsyncDocument {
284 pub fn new(document: EditorDocument) -> Self {
286 Self {
287 sync_doc: SyncDocument::new(document),
288 }
289 }
290
291 pub async fn text_async(&self) -> Result<String> {
293 self.sync_doc.text()
296 }
297
298 pub async fn execute_command_async<C: EditorCommand + Send + 'static>(
300 &self,
301 command: C,
302 ) -> Result<CommandResult> {
303 self.sync_doc.execute_command(command)
305 }
306}
307
308#[cfg(test)]
309#[cfg(feature = "concurrency")]
310mod tests {
311 use super::*;
312 use crate::commands::InsertTextCommand;
313 use crate::core::Position;
314 #[cfg(not(feature = "std"))]
315 use alloc::{format, string::ToString};
316
317 #[test]
318 fn test_sync_document_creation() {
319 let doc = EditorDocument::from_content("[Script Info]\nTitle: Test").unwrap();
320 let sync_doc = SyncDocument::new(doc);
321
322 let text = sync_doc.text().unwrap();
323 assert!(text.contains("Title: Test"));
324 }
325
326 #[test]
327 fn test_sync_document_modification() {
328 let doc = EditorDocument::from_content("[Script Info]\nTitle: Test").unwrap();
329 let sync_doc = SyncDocument::new(doc);
330
331 sync_doc
333 .with_write(|doc| doc.insert(Position::new(doc.len()), "\nAuthor: Test"))
334 .unwrap();
335
336 let text = sync_doc.text().unwrap();
338 assert!(text.contains("Author: Test"));
339 }
340
341 #[test]
342 fn test_document_pool() {
343 let pool = DocumentPool::new();
344
345 let doc1 = EditorDocument::from_content("[Script Info]\nTitle: Doc1").unwrap();
347 let id1 = pool.add_document(doc1).unwrap();
348
349 let doc2 = EditorDocument::from_content("[Script Info]\nTitle: Doc2").unwrap();
350 let id2 = pool.add_document(doc2).unwrap();
351
352 assert_eq!(pool.document_count().unwrap(), 2);
354
355 let sync_doc1 = pool.get_document(&id1).unwrap();
357 assert!(sync_doc1.text().unwrap().contains("Doc1"));
358
359 let sync_doc2 = pool.get_document(&id2).unwrap();
360 assert!(sync_doc2.text().unwrap().contains("Doc2"));
361
362 pool.remove_document(&id1).unwrap();
364 assert_eq!(pool.document_count().unwrap(), 1);
365 }
366
367 #[test]
368 fn test_concurrent_usage() {
369 let doc = EditorDocument::from_content("[Script Info]\nTitle: Test").unwrap();
374 let sync_doc = SyncDocument::new(doc);
375
376 for i in 0..5 {
378 sync_doc
379 .with_write(|doc| {
380 let pos = Position::new(doc.len());
381 doc.insert(pos, &format!("\nComment: Update {i}"))
382 })
383 .unwrap();
384 }
385
386 let final_text = sync_doc.text().unwrap();
388 assert!(final_text.contains("Comment: Update 4"));
389
390 let _write_guard = sync_doc.write().unwrap();
392 assert!(sync_doc.try_read().is_none());
393 }
394
395 #[test]
396 fn test_try_lock_operations() {
397 let doc = EditorDocument::from_content("[Script Info]\nTitle: Test").unwrap();
398 let sync_doc = SyncDocument::new(doc);
399
400 let _write_guard = sync_doc.write().unwrap();
402
403 assert!(sync_doc.try_read().is_none());
405 assert!(sync_doc.try_write().is_none());
406 }
407
408 #[test]
409 fn test_thread_safe_command_execution() {
410 let doc = EditorDocument::from_content("[Script Info]\nTitle: Test").unwrap();
411 let sync_doc = SyncDocument::new(doc);
412
413 let command = InsertTextCommand::new(Position::new(0), "[V4+ Styles]\n".to_string());
415
416 let result = sync_doc.execute_command(command).unwrap();
417 assert!(result.success);
418 assert!(result.content_changed);
419
420 let text = sync_doc.text().unwrap();
422 assert!(text.starts_with("[V4+ Styles]"));
423 }
424
425 #[test]
426 fn test_sync_document_validation() {
427 let doc = EditorDocument::from_content(
428 "[Script Info]\nTitle: Test\n\n[Events]\nDialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,Test"
429 ).unwrap();
430 let sync_doc = SyncDocument::new(doc);
431
432 sync_doc.validate().unwrap();
434
435 let issues = sync_doc.validate_comprehensive().unwrap();
437 for issue in &issues {
439 println!("Validation issue: {issue:?}");
440 }
441 assert!(issues.len() <= 1); }
445
446 #[test]
447 fn test_scoped_lock() {
448 let doc = EditorDocument::from_content("[Script Info]\nTitle: Test").unwrap();
449 let sync_doc = SyncDocument::new(doc);
450
451 {
453 let mut lock = ScopedDocumentLock::new(&sync_doc).unwrap();
454 let doc = lock.document();
455 doc.insert(Position::new(doc.len()), "\nAuthor: Test")
456 .unwrap();
457 doc.insert(Position::new(doc.len()), "\nVersion: 1.0")
458 .unwrap();
459 }
460
461 let text = sync_doc.text().unwrap();
463 assert!(text.contains("Author: Test"));
464 assert!(text.contains("Version: 1.0"));
465 }
466
467 #[test]
468 fn test_command_send_sync() {
469 fn assert_send_sync<T: Send + Sync>() {}
471
472 assert_send_sync::<InsertTextCommand>();
473 assert_send_sync::<crate::commands::DeleteTextCommand>();
474 assert_send_sync::<crate::commands::ReplaceTextCommand>();
475 assert_send_sync::<crate::commands::BatchCommand>();
476
477 }
482}