diff options
| author | tv <tv@krebsco.de> | 2026-10-31 13:28:55 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-31 13:28:55 +0100 |
| commit | 1d0b1646c5cbb9cb177369189539a2bf0d132ba8 (patch) | |
| tree | 4fd36872fdddda3c315665c0b9050eaae3c586de | |
| parent | 36ffffb89b1dfeca0163b6ecd9c46239aca6465e (diff) | |
one function for changed paths
| -rw-r--r-- | src/document/store.rs | 61 | ||||
| -rw-r--r-- | src/remake/delta.rs | 33 | ||||
| -rw-r--r-- | src/swarm/changes.rs | 33 |
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) }) |
