WIP prototype migrations

This commit is contained in:
Dennis Kobert
2026-07-14 13:54:41 +02:00
parent 97f8113fe4
commit 6c084fb954
17 changed files with 827 additions and 3 deletions
+34 -1
View File
@@ -10,7 +10,7 @@
use std::collections::HashMap;
use crate::{AttributesWrite, CrdtError, Delta, Rev, TimeStamp};
use crate::{AttributesWrite, CrdtError, Delta, RegistryDelta, Rev, TimeStamp};
#[derive(Clone, Debug, Default)]
pub struct History {
@@ -178,3 +178,36 @@ impl History {
Ok(())
}
}
/// Recompute every delta's content-addressed `Rev` after delta payloads were rewritten, walking in
/// topological order and remapping parent links (including `Merge` extra parents) along the way.
/// Returns the old → new mapping for remapping cursors (`head`, redo stack, broadcast tip).
pub fn rehash_deltas(deltas: &mut [Delta]) -> HashMap<Rev, Rev> {
fn remap_merge_parents(kind: &mut RegistryDelta, mapping: &HashMap<Rev, Rev>) {
if let RegistryDelta::Merge { extra_parents } = kind {
for parent in extra_parents.iter_mut() {
if let Some(new) = mapping.get(parent) {
*parent = *new;
}
}
}
}
let mut mapping: HashMap<Rev, Rev> = HashMap::new();
for delta in deltas.iter_mut() {
if let Some(parent) = delta.parent.as_mut()
&& let Some(new) = mapping.get(parent)
{
*parent = *new;
}
remap_merge_parents(&mut delta.kind, &mapping);
remap_merge_parents(&mut delta.reverse, &mapping);
let new_id = delta.recomputed_id();
mapping.insert(delta.id, new_id);
delta.id = new_id;
}
mapping
}
+1 -1
View File
@@ -21,7 +21,7 @@ pub mod to_runtime;
pub use attributes::*;
pub use crdt::*;
pub use document::*;
pub use history::History;
pub use history::{History, rehash_deltas};
pub use ids::*;
pub use model::*;
pub use registry::*;
+24
View File
@@ -11,6 +11,15 @@ pub struct Node {
}
impl Node {
pub fn new(implementation: Implementation, inputs: Vec<InputSlot>, attributes: Attributes, network: NetworkId) -> Self {
Self {
implementation,
inputs,
attributes,
network,
}
}
pub fn implementation(&self) -> &Implementation {
&self.implementation
}
@@ -24,6 +33,21 @@ impl Node {
self.network
}
// Mutable access for edits on a registry clone that is diffed back into deltas;
// live-session edits go through the delta paths instead.
pub fn implementation_mut(&mut self) -> &mut Implementation {
&mut self.implementation
}
pub fn inputs_mut(&mut self) -> &mut Vec<InputSlot> {
&mut self.inputs
}
pub fn attributes_mut(&mut self) -> &mut Attributes {
&mut self.attributes
}
pub fn set_network(&mut self, network: NetworkId) {
self.network = network;
}
/// True if both nodes agree on every value-bearing field, ignoring slot/attribute timestamps.
pub fn value_equal(&self, other: &Self) -> bool {
if self.implementation != other.implementation || self.network != other.network {
+1 -1
View File
@@ -45,7 +45,7 @@ impl<'de> Deserialize<'de> for ResourceEntry {
}
let Raw { mut sources, hash, hash_timestamp } = Raw::deserialize(deserializer)?;
sources.sort_by(|(a, _), (b, _)| a.cmp(b));
sources.sort_by_key(|(key, _)| *key);
sources.dedup_by(|(later_key, later_value), (kept_key, kept_value)| {
// `dedup_by` keeps the first of each run; sorting is stable, so resolve duplicates by LWW.
if later_key != kept_key {
+21
View File
@@ -92,6 +92,27 @@ impl Session {
Ok(conversion.network_ids)
}
/// Diff the working registry against `target` and stage the difference as hot ops, so bulk
/// registry mutations commit through the ordinary hot-op path without hand-built deltas.
pub fn stage_registry_replace(&mut self, target: &Registry) -> Result<Vec<HotOp>, CrdtError> {
let ops = crate::delta::compute_deltas(&self.document.working_registry, target);
self.stage_ops(ops)
}
/// Retire every pending hot op as one gesture. Convenience over [`Session::retire`] for callers
/// that don't track hot-op timestamps themselves.
pub fn retire_all(&mut self) -> Result<Vec<Rev>, CrdtError> {
match self.document.hot_log.iter().map(|hot_op| hot_op.timestamp).max() {
Some(up_to) => self.retire(up_to),
None => Ok(Vec::new()),
}
}
/// Mint a fresh peer-scoped `NodeId`. Forwards to [`Document::next_node_id`].
pub fn next_node_id(&mut self) -> NodeId {
self.document.next_node_id()
}
/// Register a content-addressed resource as a single `DataSource::Embedded` source resolved to
/// `hash`, staged as one `AddResource` hot op. The caller owns `id` allocation, persists the
/// returned hot frame, retires, and persists the bytes into its byte store separately.