aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/document/store.rs61
-rw-r--r--src/remake/delta.rs33
-rw-r--r--src/swarm/changes.rs33
3 files changed, 72 insertions, 55 deletions
diff --git a/src/document/store.rs b/src/document/store.rs
index eeebda8..dff0cfb 100644
--- a/src/document/store.rs
+++ b/src/document/store.rs
@@ -16,6 +16,67 @@ pub(crate) struct ChangeSet {
pub(crate) paths: Vec<Vec<String>>,
}
+/// How `changed` names a change inside a list.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub(crate) enum InLists {
+ /// By the item's index: `servers/0/name` (the change publisher).
+ Index,
+ /// By the list: `servers` (remakes replay a list whole).
+ Whole,
+}
+
+/// A store path changes touched, and whether concurrent writes left a conflict there.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct Changed {
+ pub(crate) path: Vec<String>,
+ pub(crate) conflict: bool,
+}
+
+/// The store paths the changes from `before` to `after` touched, as the document
+/// reports them (a path may come more than once). A map entry by its key, a counter
+/// incremented in a map by its key, and list and text edits by the containing object;
+/// inside a list as `lists` says.
+pub(crate) fn changed(
+ doc: &mut AutoCommit,
+ before: &[automerge::ChangeHash],
+ after: &[automerge::ChangeHash],
+ lists: InLists,
+) -> Vec<Changed> {
+ doc.diff(before, after)
+ .iter()
+ .map(|patch| Changed {
+ path: patch_path(patch, lists),
+ conflict: matches!(
+ patch.action,
+ automerge::PatchAction::PutMap { conflict: true, .. }
+ ),
+ })
+ .collect()
+}
+
+fn patch_path(patch: &automerge::Patch, lists: InLists) -> Vec<String> {
+ use automerge::PatchAction;
+ let mut path = Vec::new();
+ for (_, prop) in &patch.path {
+ match (prop, lists) {
+ (Prop::Map(key), _) => path.push(key.clone()),
+ (Prop::Seq(index), InLists::Index) => path.push(index.to_string()),
+ (Prop::Seq(_), InLists::Whole) => return path,
+ }
+ }
+ match &patch.action {
+ PatchAction::PutMap { key, .. }
+ | PatchAction::DeleteMap { key, .. }
+ | PatchAction::Increment {
+ prop: Prop::Map(key),
+ ..
+ } => path.push(key.clone()),
+ // List and text edits: the containing object.
+ _ => {}
+ }
+ path
+}
+
/// Whether one path is within the other (a change at either touches both).
pub(crate) fn paths_related(a: &[String], b: &[String]) -> bool {
a.starts_with(b) || b.starts_with(a)
diff --git a/src/remake/delta.rs b/src/remake/delta.rs
index 80e5443..aebd048 100644
--- a/src/remake/delta.rs
+++ b/src/remake/delta.rs
@@ -23,12 +23,12 @@
use std::collections::{BTreeMap, HashSet};
-use automerge::{ActorId, AutoCommit, ChangeHash, PatchAction, Prop};
+use automerge::{ActorId, AutoCommit, ChangeHash};
use tracing::warn;
use super::kind::{Translation, Translator};
use crate::document::meta::{self, EPOCHS, REMAKES, VOTES};
-use crate::document::store::{self, StoreError};
+use crate::document::store::{self, InLists, StoreError};
#[derive(Debug, Default, Clone)]
pub(crate) struct ReplayStats {
@@ -229,7 +229,10 @@ pub(crate) fn changed_paths(
before: &[ChangeHash],
after: &[ChangeHash],
) -> Vec<Vec<String>> {
- let mut paths: Vec<Vec<String>> = doc.diff(before, after).iter().map(patch_path).collect();
+ let mut paths: Vec<Vec<String>> = store::changed(doc, before, after, InLists::Whole)
+ .into_iter()
+ .map(|changed| changed.path)
+ .collect();
paths.sort();
paths.dedup();
@@ -245,30 +248,6 @@ pub(crate) fn changed_paths(
minimal
}
-fn patch_path(patch: &automerge::Patch) -> Vec<String> {
- let mut path = Vec::new();
- for (_, prop) in &patch.path {
- match prop {
- Prop::Map(key) => path.push(key.clone()),
- // Inside a list: the list is replayed as a whole.
- Prop::Seq(_) => return path,
- }
- }
-
- match &patch.action {
- PatchAction::PutMap { key, .. } | PatchAction::DeleteMap { key, .. } => {
- path.push(key.clone())
- }
- PatchAction::Increment {
- prop: Prop::Map(key),
- ..
- } => path.push(key.clone()),
- // List and text edits: the containing object.
- _ => {}
- }
- path
-}
-
#[cfg(test)]
mod tests {
use super::*;
diff --git a/src/swarm/changes.rs b/src/swarm/changes.rs
index 10a3bfe..6831081 100644
--- a/src/swarm/changes.rs
+++ b/src/swarm/changes.rs
@@ -6,28 +6,9 @@ use std::sync::Arc;
use tracing::{info, warn};
-use crate::document::store::ChangeSet;
+use crate::document::store::{self, ChangeSet, InLists};
use crate::state::AppState;
-fn patch_store_path(patch: &automerge::Patch) -> Vec<String> {
- let mut path: Vec<String> = patch
- .path
- .iter()
- .map(|(_, prop)| match prop {
- automerge::Prop::Map(key) => key.clone(),
- automerge::Prop::Seq(index) => index.to_string(),
- })
- .collect();
-
- match &patch.action {
- automerge::PatchAction::PutMap { key, .. }
- | automerge::PatchAction::DeleteMap { key, .. } => path.push(key.clone()),
- // List, text and counter changes: the containing object changed.
- _ => {}
- }
- path
-}
-
pub(crate) async fn run_change_publisher(state: AppState) {
let mut changed = state.change_tx.subscribe();
let mut last_heads = state.write_doc(|doc| doc.get_heads()).await;
@@ -52,15 +33,11 @@ pub(crate) async fn run_change_publisher(state: AppState) {
let mut paths = Vec::new();
let mut conflicts = Vec::new();
- for patch in doc.diff(&before, &heads) {
- let path = patch_store_path(&patch);
- if matches!(
- patch.action,
- automerge::PatchAction::PutMap { conflict: true, .. }
- ) {
- conflicts.push(path.clone());
+ for changed in store::changed(doc, &before, &heads, InLists::Index) {
+ if changed.conflict {
+ conflicts.push(changed.path.clone());
}
- paths.push(path);
+ paths.push(changed.path);
}
(heads, paths, conflicts)
})