tirea_state/doc_cell.rs
1//! Shared mutable document for write-through-read state access.
2//!
3//! `DocCell` wraps a `Mutex<Value>` so that state writes (via `PatchSink` hooks)
4//! immediately update the document and subsequent reads see the latest values.
5
6use crate::apply::apply_op;
7use crate::{Op, TireaResult};
8use serde_json::Value;
9use std::sync::{Mutex, MutexGuard};
10
11/// Shared mutable document for write-through-read state access.
12///
13/// All state reads lock briefly to fetch the current value, and all writes
14/// apply the operation in-place so the next read sees the update.
15pub struct DocCell(Mutex<Value>);
16
17impl DocCell {
18 /// Create a new `DocCell` with the given initial value.
19 pub fn new(value: Value) -> Self {
20 Self(Mutex::new(value))
21 }
22
23 /// Acquire a read lock on the document.
24 ///
25 /// The returned guard dereferences to `&Value`. Callers should clone
26 /// any needed data before dropping the guard.
27 #[inline]
28 pub fn get(&self) -> MutexGuard<'_, Value> {
29 self.0.lock().unwrap()
30 }
31
32 /// Apply a single operation to the document in-place.
33 ///
34 /// Apply an operation and return any validation/type error.
35 pub fn apply(&self, op: &Op) -> TireaResult<()> {
36 let mut guard = self
37 .0
38 .lock()
39 .map_err(|_| crate::TireaError::invalid_operation("state document mutex poisoned"))?;
40 apply_op(&mut guard, op)
41 }
42
43 /// Consume the `DocCell` and return the inner value.
44 pub fn into_inner(self) -> Value {
45 self.0.into_inner().unwrap()
46 }
47
48 /// Clone the current document value.
49 pub fn snapshot(&self) -> Value {
50 self.get().clone()
51 }
52}
53
54impl Default for DocCell {
55 fn default() -> Self {
56 Self::new(Value::Object(Default::default()))
57 }
58}
59
60impl Clone for DocCell {
61 fn clone(&self) -> Self {
62 Self::new(self.snapshot())
63 }
64}
65
66impl std::fmt::Debug for DocCell {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 f.debug_tuple("DocCell").field(&"<Value>").finish()
69 }
70}