diff options
Diffstat (limited to 'src/document')
| -rw-r--r-- | src/document/replica.rs | 29 | ||||
| -rw-r--r-- | src/document/store.rs | 23 |
2 files changed, 50 insertions, 2 deletions
diff --git a/src/document/replica.rs b/src/document/replica.rs index af807ea..e24f93c 100644 --- a/src/document/replica.rs +++ b/src/document/replica.rs @@ -9,7 +9,7 @@ use std::fmt; use std::ops::{Deref, DerefMut}; -use automerge::AutoCommit; +use automerge::{AutoCommit, ChangeHash}; /// One generation of the swarm document. /// @@ -77,14 +77,39 @@ impl fmt::Debug for Epoch { /// /// Derefs to the document, so code that only needs the `AutoCommit` keeps working /// with a guard of `Replica`. +/// +/// The heads are cached: `AutoCommit::get_heads` takes `&mut self` (it closes a pending +/// transaction), so without the cache every keepalive and status read would need the +/// write lock. Every path that changes the document under the write lock calls +/// [`Replica::refresh_heads`] before releasing it (`AppState::write_doc`, `mutate_doc`, +/// `switch_epoch`, the sync apply); readers take the read lock and use +/// [`Replica::heads`]. pub struct Replica { pub epoch: Epoch, pub doc: AutoCommit, + heads: Vec<ChangeHash>, } impl Replica { pub fn new(epoch: Epoch, doc: AutoCommit) -> Self { - Self { epoch, doc } + let mut replica = Self { + epoch, + doc, + heads: Vec::new(), + }; + replica.refresh_heads(); + replica + } + + /// The document's heads as of the last refresh, sorted. + pub fn heads(&self) -> &[ChangeHash] { + &self.heads + } + + /// Call after changing the document (under the write lock). + pub fn refresh_heads(&mut self) { + self.heads = self.doc.get_heads(); + self.heads.sort_unstable(); } } diff --git a/src/document/store.rs b/src/document/store.rs index 9e18693..573b811 100644 --- a/src/document/store.rs +++ b/src/document/store.rs @@ -243,6 +243,29 @@ pub fn keys_at(doc: &AutoCommit, path: &[String]) -> Result<Vec<String>, StoreEr Ok(merged_keys(doc, &maps).into_iter().collect()) } +/// Every concurrent value at `path`, newest first; empty if there is none. Where +/// `read_path` picks one (or merges concurrent maps into one), this keeps them apart. +pub(crate) fn read_candidates( + doc: &AutoCommit, + path: &[String], +) -> Result<Vec<JsonValue>, StoreError> { + let (leaf, parents) = path.split_last().ok_or(StoreError::NotFound)?; + let mut maps = vec![automerge::ROOT]; + for segment in parents { + maps = match resolve(doc, &maps, segment, None)? { + Some(Resolved::Maps(next)) => next, + _ => return Ok(Vec::new()), + }; + } + candidates(doc, &maps, leaf)? + .into_iter() + .map(|(value, id)| match value { + Value::Scalar(scalar) => scalar_to_json(scalar.into_owned()), + Value::Object(_) => object_to_json(doc, &id, None), + }) + .collect() +} + /// Writes (replaces) the value at `path`, creating intermediate maps. pub fn write_path( doc: &mut AutoCommit, |
