Correctly defer undo operations when messages arrive out of order
Co-Authored-By: Nathan Sobo <nathan@zed.dev> Co-Authored-By: Max Brunsfeld <max@zed.dev>
This commit is contained in:
parent
410ee124af
commit
556d9cc53f
5 changed files with 31 additions and 31 deletions
|
@ -25,7 +25,13 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation {
|
||||||
replica_id: undo.id.replica_id as u32,
|
replica_id: undo.id.replica_id as u32,
|
||||||
local_timestamp: undo.id.value,
|
local_timestamp: undo.id.value,
|
||||||
lamport_timestamp: lamport_timestamp.value,
|
lamport_timestamp: lamport_timestamp.value,
|
||||||
ranges: undo.ranges.iter().map(serialize_range).collect(),
|
version: From::from(&undo.version),
|
||||||
|
transaction_ranges: undo
|
||||||
|
.transaction_ranges
|
||||||
|
.iter()
|
||||||
|
.map(serialize_range)
|
||||||
|
.collect(),
|
||||||
|
transaction_version: From::from(&undo.transaction_version),
|
||||||
counts: undo
|
counts: undo
|
||||||
.counts
|
.counts
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -35,7 +41,6 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation {
|
||||||
count: *count,
|
count: *count,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
version: From::from(&undo.version),
|
|
||||||
}),
|
}),
|
||||||
Operation::UpdateSelections {
|
Operation::UpdateSelections {
|
||||||
selections,
|
selections,
|
||||||
|
@ -183,6 +188,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
||||||
replica_id: undo.replica_id as ReplicaId,
|
replica_id: undo.replica_id as ReplicaId,
|
||||||
value: undo.local_timestamp,
|
value: undo.local_timestamp,
|
||||||
},
|
},
|
||||||
|
version: undo.version.into(),
|
||||||
counts: undo
|
counts: undo
|
||||||
.counts
|
.counts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -196,8 +202,12 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
ranges: undo.ranges.into_iter().map(deserialize_range).collect(),
|
transaction_ranges: undo
|
||||||
version: undo.version.into(),
|
.transaction_ranges
|
||||||
|
.into_iter()
|
||||||
|
.map(deserialize_range)
|
||||||
|
.collect(),
|
||||||
|
transaction_version: undo.transaction_version.into(),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
proto::operation::Variant::UpdateSelections(message) => {
|
proto::operation::Variant::UpdateSelections(message) => {
|
||||||
|
|
|
@ -629,9 +629,10 @@ message Operation {
|
||||||
uint32 replica_id = 1;
|
uint32 replica_id = 1;
|
||||||
uint32 local_timestamp = 2;
|
uint32 local_timestamp = 2;
|
||||||
uint32 lamport_timestamp = 3;
|
uint32 lamport_timestamp = 3;
|
||||||
repeated Range ranges = 4;
|
repeated VectorClockEntry version = 4;
|
||||||
repeated VectorClockEntry version = 5;
|
repeated Range transaction_ranges = 5;
|
||||||
repeated UndoCount counts = 6;
|
repeated VectorClockEntry transaction_version = 6;
|
||||||
|
repeated UndoCount counts = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateSelections {
|
message UpdateSelections {
|
||||||
|
|
|
@ -5,4 +5,4 @@ pub mod proto;
|
||||||
pub use conn::Connection;
|
pub use conn::Connection;
|
||||||
pub use peer::*;
|
pub use peer::*;
|
||||||
|
|
||||||
pub const PROTOCOL_VERSION: u32 = 8;
|
pub const PROTOCOL_VERSION: u32 = 9;
|
||||||
|
|
|
@ -520,7 +520,8 @@ pub struct EditOperation {
|
||||||
pub struct UndoOperation {
|
pub struct UndoOperation {
|
||||||
pub id: clock::Local,
|
pub id: clock::Local,
|
||||||
pub counts: HashMap<clock::Local, u32>,
|
pub counts: HashMap<clock::Local, u32>,
|
||||||
pub ranges: Vec<Range<FullOffset>>,
|
pub transaction_ranges: Vec<Range<FullOffset>>,
|
||||||
|
pub transaction_version: clock::Global,
|
||||||
pub version: clock::Global,
|
pub version: clock::Global,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1039,7 +1040,7 @@ impl Buffer {
|
||||||
let mut edits = Patch::default();
|
let mut edits = Patch::default();
|
||||||
self.snapshot.undo_map.insert(undo);
|
self.snapshot.undo_map.insert(undo);
|
||||||
|
|
||||||
let mut cx = undo.version.clone();
|
let mut cx = undo.transaction_version.clone();
|
||||||
for edit_id in undo.counts.keys().copied() {
|
for edit_id in undo.counts.keys().copied() {
|
||||||
cx.observe(edit_id);
|
cx.observe(edit_id);
|
||||||
}
|
}
|
||||||
|
@ -1047,7 +1048,7 @@ impl Buffer {
|
||||||
|
|
||||||
let mut old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>();
|
let mut old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>();
|
||||||
let mut new_fragments = old_fragments.slice(
|
let mut new_fragments = old_fragments.slice(
|
||||||
&VersionedFullOffset::Offset(undo.ranges[0].start),
|
&VersionedFullOffset::Offset(undo.transaction_ranges[0].start),
|
||||||
Bias::Right,
|
Bias::Right,
|
||||||
&cx,
|
&cx,
|
||||||
);
|
);
|
||||||
|
@ -1055,7 +1056,7 @@ impl Buffer {
|
||||||
RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0));
|
RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0));
|
||||||
new_ropes.push_tree(new_fragments.summary().text);
|
new_ropes.push_tree(new_fragments.summary().text);
|
||||||
|
|
||||||
for range in &undo.ranges {
|
for range in &undo.transaction_ranges {
|
||||||
let mut end_offset = old_fragments.end(&cx).0.full_offset();
|
let mut end_offset = old_fragments.end(&cx).0.full_offset();
|
||||||
|
|
||||||
if end_offset < range.start {
|
if end_offset < range.start {
|
||||||
|
@ -1073,7 +1074,7 @@ impl Buffer {
|
||||||
let mut fragment = fragment.clone();
|
let mut fragment = fragment.clone();
|
||||||
let fragment_was_visible = fragment.visible;
|
let fragment_was_visible = fragment.visible;
|
||||||
|
|
||||||
if fragment.was_visible(&undo.version, &self.undo_map)
|
if fragment.was_visible(&undo.transaction_version, &self.undo_map)
|
||||||
|| undo
|
|| undo
|
||||||
.counts
|
.counts
|
||||||
.contains_key(&fragment.insertion_timestamp.local())
|
.contains_key(&fragment.insertion_timestamp.local())
|
||||||
|
@ -1264,9 +1265,10 @@ impl Buffer {
|
||||||
|
|
||||||
let undo = UndoOperation {
|
let undo = UndoOperation {
|
||||||
id: self.local_clock.tick(),
|
id: self.local_clock.tick(),
|
||||||
|
version: self.version(),
|
||||||
counts,
|
counts,
|
||||||
ranges: transaction.ranges,
|
transaction_ranges: transaction.ranges,
|
||||||
version: transaction.start.clone(),
|
transaction_version: transaction.start.clone(),
|
||||||
};
|
};
|
||||||
self.apply_undo(&undo)?;
|
self.apply_undo(&undo)?;
|
||||||
let operation = Operation::Undo {
|
let operation = Operation::Undo {
|
||||||
|
|
|
@ -40,23 +40,10 @@ impl<T: Clone, R: rand::Rng> Network<T, R> {
|
||||||
for (replica, inbox) in self.inboxes.iter_mut() {
|
for (replica, inbox) in self.inboxes.iter_mut() {
|
||||||
if *replica != sender {
|
if *replica != sender {
|
||||||
for message in &messages {
|
for message in &messages {
|
||||||
let min_index = inbox
|
// Insert one or more duplicates of this message, potentially *before* the previous
|
||||||
.iter()
|
// message sent by this peer to simulate out-of-order delivery.
|
||||||
.enumerate()
|
|
||||||
.rev()
|
|
||||||
.find_map(|(index, envelope)| {
|
|
||||||
if sender == envelope.sender {
|
|
||||||
Some(index + 1)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
// Insert one or more duplicates of this message *after* the previous
|
|
||||||
// message delivered by this replica.
|
|
||||||
for _ in 0..self.rng.gen_range(1..4) {
|
for _ in 0..self.rng.gen_range(1..4) {
|
||||||
let insertion_index = self.rng.gen_range(min_index..inbox.len() + 1);
|
let insertion_index = self.rng.gen_range(0..inbox.len() + 1);
|
||||||
inbox.insert(
|
inbox.insert(
|
||||||
insertion_index,
|
insertion_index,
|
||||||
Envelope {
|
Envelope {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue