|
|
… |
45 unchanged lines |
| 46 |
46 |
|
atoms: HashMap<AtomKey, AtomInfo>, |
| 47 |
47 |
|
/// Every vertex, with its direct successors. |
| 48 |
48 |
|
succ: HashMap<Vertex, Edges>, |
|
49 |
+ |
/// The live atoms' order, once known to be the graph's only one. |
|
50 |
+ |
order: OrderSlot, |
| 49 |
51 |
|
} |
|
52 |
+ |
|
|
53 |
+ |
/// A file graph's live atoms in order, when the graph orders them one way |
|
54 |
+ |
/// only: found by [`FileGraph::materialize`] and brought up to date by |
|
55 |
+ |
/// [`Order::splice`] as a change's edits apply, so a long-lived file |
|
56 |
+ |
/// renders in time that follows its live lines rather than every line it |
|
57 |
+ |
/// ever had. Any other change to the graph drops it. |
|
58 |
+ |
#[derive(Debug)] |
|
59 |
+ |
pub struct Order { |
|
60 |
+ |
atoms: Vec<AtomKey>, |
|
61 |
+ |
/// Atoms deleted since the order was last confirmed: it holds while |
|
62 |
+ |
/// none of them is a ghost (spec §9.1), which only a render can ask, |
|
63 |
+ |
/// since it needs to know how the changes depend on each other. |
|
64 |
+ |
unchecked: Vec<AtomKey>, |
|
65 |
+ |
confirmed: std::sync::atomic::AtomicBool, |
|
66 |
+ |
} |
|
67 |
+ |
|
|
68 |
+ |
/// Holds a graph's [`Order`]; filled in by a render, which only reads the |
|
69 |
+ |
/// graph. |
|
70 |
+ |
#[derive(Debug, Default)] |
|
71 |
+ |
struct OrderSlot(std::sync::Mutex<Option<std::sync::Arc<Order>>>); |
|
72 |
+ |
|
|
73 |
+ |
impl OrderSlot { |
|
74 |
+ |
fn get(&self) -> Option<std::sync::Arc<Order>> { |
|
75 |
+ |
self.0.lock().unwrap_or_else(|e| e.into_inner()).clone() |
|
76 |
+ |
} |
|
77 |
+ |
fn set(&self, o: Option<std::sync::Arc<Order>>) { |
|
78 |
+ |
*self.0.lock().unwrap_or_else(|e| e.into_inner()) = o; |
|
79 |
+ |
} |
|
80 |
+ |
fn take(&mut self) -> Option<std::sync::Arc<Order>> { |
|
81 |
+ |
self.0.get_mut().unwrap_or_else(|e| e.into_inner()).take() |
|
82 |
+ |
} |
|
83 |
+ |
} |
|
84 |
+ |
|
|
85 |
+ |
impl Clone for OrderSlot { |
|
86 |
+ |
fn clone(&self) -> Self { |
|
87 |
+ |
OrderSlot(std::sync::Mutex::new(self.get())) |
|
88 |
+ |
} |
|
89 |
+ |
} |
|
90 |
+ |
|
|
91 |
+ |
impl Order { |
|
92 |
+ |
fn known(atoms: Vec<AtomKey>) -> Order { |
|
93 |
+ |
Order { |
|
94 |
+ |
atoms, |
|
95 |
+ |
unchecked: Vec::new(), |
|
96 |
+ |
confirmed: std::sync::atomic::AtomicBool::new(true), |
|
97 |
+ |
} |
|
98 |
+ |
} |
| 50 |
99 |
|
|
|
100 |
+ |
/// The order after change `h` inserts the runs `inserts` (after, |
|
101 |
+ |
/// before, first index, count) and deletes `deleted`, as its edits |
|
102 |
+ |
/// apply; `None` where that is not certain to be the graph's only |
|
103 |
+ |
/// order: an anchor not in the order, live lines between an insert's |
|
104 |
+ |
/// anchors, or two inserts with nothing kept between them. |
|
105 |
+ |
pub fn splice( |
|
106 |
+ |
&self, |
|
107 |
+ |
h: ChangeHash, |
|
108 |
+ |
inserts: &[(Vertex, Vertex, u32, u32)], |
|
109 |
+ |
deleted: &[AtomKey], |
|
110 |
+ |
) -> Option<std::sync::Arc<Order>> { |
|
111 |
+ |
let mut want: HashSet<AtomKey> = deleted.iter().copied().collect(); |
|
112 |
+ |
for (a, b, _, _) in inserts { |
|
113 |
+ |
for v in [a, b] { |
|
114 |
+ |
if let Vertex::Atom(k) = v { |
|
115 |
+ |
want.insert(*k); |
|
116 |
+ |
} |
|
117 |
+ |
} |
|
118 |
+ |
} |
|
119 |
+ |
let mut pos: HashMap<AtomKey, usize> = HashMap::default(); |
|
120 |
+ |
for (i, k) in self.atoms.iter().enumerate() { |
|
121 |
+ |
if want.contains(k) { |
|
122 |
+ |
pos.insert(*k, i); |
|
123 |
+ |
} |
|
124 |
+ |
} |
|
125 |
+ |
let len = self.atoms.len(); |
|
126 |
+ |
let mut gone = vec![false; len]; |
|
127 |
+ |
let mut unchecked = if self.confirmed.load(std::sync::atomic::Ordering::Relaxed) { |
|
128 |
+ |
Vec::new() |
|
129 |
+ |
} else { |
|
130 |
+ |
self.unchecked.clone() |
|
131 |
+ |
}; |
|
132 |
+ |
for k in deleted { |
|
133 |
+ |
if k.0 == h { |
|
134 |
+ |
return None; |
|
135 |
+ |
} |
|
136 |
+ |
if let Some(&i) = pos.get(k) { |
|
137 |
+ |
if !gone[i] { |
|
138 |
+ |
gone[i] = true; |
|
139 |
+ |
unchecked.push(*k); |
|
140 |
+ |
} |
|
141 |
+ |
} |
|
142 |
+ |
} |
|
143 |
+ |
// Each run goes in the gap its anchors leave (slot `i`: before |
|
144 |
+ |
// `atoms[i]`), which must hold nothing that stays. |
|
145 |
+ |
let mut slots: Vec<(usize, usize, u32, u32)> = Vec::with_capacity(inserts.len()); |
|
146 |
+ |
for &(a, b, first, count) in inserts { |
|
147 |
+ |
let from = match a { |
|
148 |
+ |
Vertex::Start => 0, |
|
149 |
+ |
Vertex::Atom(k) => pos.get(&k)? + 1, |
|
150 |
+ |
Vertex::End => return None, |
|
151 |
+ |
}; |
|
152 |
+ |
let to = match b { |
|
153 |
+ |
Vertex::End => len, |
|
154 |
+ |
Vertex::Atom(k) => *pos.get(&k)?, |
|
155 |
+ |
Vertex::Start => return None, |
|
156 |
+ |
}; |
|
157 |
+ |
if from > to || gone[from..to].iter().any(|g| !g) { |
|
158 |
+ |
return None; |
|
159 |
+ |
} |
|
160 |
+ |
slots.push((from, to, first, count)); |
|
161 |
+ |
} |
|
162 |
+ |
// Runs one after another need a kept line between them, or the |
|
163 |
+ |
// graph would not say which comes first. |
|
164 |
+ |
slots.sort_unstable(); |
|
165 |
+ |
for w in slots.windows(2) { |
|
166 |
+ |
let (end, next) = (w[0].1, w[1].0); |
|
167 |
+ |
if end >= next || !gone[end..next].iter().any(|g| !g) { |
|
168 |
+ |
return None; |
|
169 |
+ |
} |
|
170 |
+ |
} |
|
171 |
+ |
let added: usize = slots.iter().map(|s| s.3 as usize).sum(); |
|
172 |
+ |
let mut atoms = Vec::with_capacity(len + added); |
|
173 |
+ |
let mut slots = slots.into_iter().peekable(); |
|
174 |
+ |
let mut runs_at = |i: usize, atoms: &mut Vec<AtomKey>| { |
|
175 |
+ |
while let Some(&(from, _, first, count)) = slots.peek() { |
|
176 |
+ |
if from != i { |
|
177 |
+ |
break; |
|
178 |
+ |
} |
|
179 |
+ |
atoms.extend((first..first + count).map(|j| (h, j))); |
|
180 |
+ |
slots.next(); |
|
181 |
+ |
} |
|
182 |
+ |
}; |
|
183 |
+ |
for (i, (k, g)) in self.atoms.iter().zip(&gone).enumerate() { |
|
184 |
+ |
runs_at(i, &mut atoms); |
|
185 |
+ |
if !g { |
|
186 |
+ |
atoms.push(*k); |
|
187 |
+ |
} |
|
188 |
+ |
} |
|
189 |
+ |
runs_at(len, &mut atoms); |
|
190 |
+ |
let confirmed = unchecked.is_empty(); |
|
191 |
+ |
Some(std::sync::Arc::new(Order { |
|
192 |
+ |
atoms, |
|
193 |
+ |
unchecked, |
|
194 |
+ |
confirmed: std::sync::atomic::AtomicBool::new(confirmed), |
|
195 |
+ |
})) |
|
196 |
+ |
} |
|
197 |
+ |
} |
|
198 |
+ |
|
| 51 |
199 |
|
/// A vertex's direct successors, sorted and without repeats. Nearly every |
| 52 |
200 |
|
/// vertex has one, which is kept inline without an allocation. |
| 53 |
201 |
|
#[derive(Clone, Debug, Default, PartialEq, Eq)] |
|
|
… |
90 unchanged lines |
| 144 |
292 |
|
let mut g = FileGraph { |
| 145 |
293 |
|
atoms: HashMap::default(), |
| 146 |
294 |
|
succ: HashMap::default(), |
|
295 |
+ |
order: OrderSlot::default(), |
| 147 |
296 |
|
}; |
| 148 |
297 |
|
g.edge(Vertex::Start, Vertex::End); |
| 149 |
298 |
|
g |
|
|
… |
27 unchanged lines |
| 177 |
326 |
|
before: Vertex, |
| 178 |
327 |
|
lines: Lines, |
| 179 |
328 |
|
) { |
|
329 |
+ |
self.order.take(); |
| 180 |
330 |
|
self.atoms.reserve(lines.len()); |
| 181 |
331 |
|
self.succ.reserve(lines.len() + 1); |
| 182 |
332 |
|
let mut prev = after; |
|
|
… |
11 unchanged lines |
| 194 |
344 |
|
|
| 195 |
345 |
|
/// Apply a `Delete` of `range` by change `by`. |
| 196 |
346 |
|
pub fn delete(&mut self, by: ChangeHash, range: &AtomRange) { |
|
347 |
+ |
self.order.take(); |
| 197 |
348 |
|
for i in range.from..range.to { |
| 198 |
349 |
|
self.atoms |
| 199 |
350 |
|
.entry((range.change, i)) |
|
|
… |
183 unchanged lines |
| 383 |
534 |
|
|
| 384 |
535 |
|
/// Order the live atoms and locate conflicts. |
| 385 |
536 |
|
pub fn materialize(&self, deps: &dyn DepOracle) -> Materialized { |
| 386 |
|
- |
if let Some(order) = self.as_chain().or_else(|| self.as_order(deps)) { |
| 387 |
|
- |
return Materialized { |
| 388 |
|
- |
segments: if order.is_empty() { |
| 389 |
|
- |
vec![] |
| 390 |
|
- |
} else { |
| 391 |
|
- |
vec![Segment::Lines(order)] |
| 392 |
|
- |
}, |
| 393 |
|
- |
}; |
|
537 |
+ |
let lines = |order: Vec<AtomKey>| Materialized { |
|
538 |
+ |
segments: if order.is_empty() { |
|
539 |
+ |
vec![] |
|
540 |
+ |
} else { |
|
541 |
+ |
vec![Segment::Lines(order)] |
|
542 |
+ |
}, |
|
543 |
+ |
}; |
|
544 |
+ |
use std::sync::atomic::Ordering::Relaxed; |
|
545 |
+ |
if let Some(o) = self.order.get() { |
|
546 |
+ |
let holds = o.confirmed.load(Relaxed) |
|
547 |
+ |
|| o.unchecked.iter().all(|k| { |
|
548 |
+ |
self.atoms |
|
549 |
+ |
.get(k) |
|
550 |
+ |
.is_none_or(|info| !self.is_ghost(k, info, deps)) |
|
551 |
+ |
}); |
|
552 |
+ |
if holds { |
|
553 |
+ |
o.confirmed.store(true, Relaxed); |
|
554 |
+ |
return lines(o.atoms.clone()); |
|
555 |
+ |
} |
|
556 |
+ |
self.order.set(None); |
|
557 |
+ |
} |
|
558 |
+ |
// A chain (nothing ever deleted) is quick to walk, and a fresh tree |
|
559 |
+ |
// is all chains: no order is kept for one. |
|
560 |
+ |
if let Some(order) = self.as_chain() { |
|
561 |
+ |
return lines(order); |
| 394 |
562 |
|
} |
|
563 |
+ |
if let Some(order) = self.as_order(deps) { |
|
564 |
+ |
self.order |
|
565 |
+ |
.set(Some(std::sync::Arc::new(Order::known(order.clone())))); |
|
566 |
+ |
return lines(order); |
|
567 |
+ |
} |
| 395 |
568 |
|
self.materialize_general(deps) |
|
569 |
+ |
} |
|
570 |
+ |
|
|
571 |
+ |
/// Take the kept order, to [`Order::splice`] it once a change's edits |
|
572 |
+ |
/// have applied (they drop it). |
|
573 |
+ |
pub fn take_order(&mut self) -> Option<std::sync::Arc<Order>> { |
|
574 |
+ |
self.order.take() |
|
575 |
+ |
} |
|
576 |
+ |
|
|
577 |
+ |
/// Keep `order`, spliced by the edits just applied. |
|
578 |
+ |
pub fn keep_order(&mut self, order: Option<std::sync::Arc<Order>>) { |
|
579 |
+ |
*self.order.0.get_mut().unwrap_or_else(|e| e.into_inner()) = order; |
| 396 |
580 |
|
} |
| 397 |
581 |
|
|
| 398 |
582 |
|
/// The live atoms in order when, with the dead ones contracted away, |
|
|
… |
400 unchanged lines |
| 799 |
983 |
|
let mut g = FileGraph { |
| 800 |
984 |
|
atoms: HashMap::default(), |
| 801 |
985 |
|
succ: HashMap::default(), |
|
986 |
+ |
order: OrderSlot::default(), |
| 802 |
987 |
|
}; |
| 803 |
988 |
|
let atoms = array(field(m, 0, "atoms")?, "atoms")?; |
| 804 |
989 |
|
// Every atom's line in one buffer. |
|
|
… |
185 unchanged lines |
| 990 |
1175 |
|
g.insert(h(3), 0, a, Vertex::End, &lines(&["c\n"])); |
| 991 |
1176 |
|
assert!(g.as_order(&NoDeps).is_none()); |
| 992 |
1177 |
|
assert!(g.materialize(&NoDeps).is_conflicted()); |
|
1178 |
+ |
} |
|
1179 |
+ |
|
|
1180 |
+ |
/// Applied as the state applies a change's edits: the kept order is |
|
1181 |
+ |
/// spliced by them. |
|
1182 |
+ |
fn apply_spliced( |
|
1183 |
+ |
g: &mut FileGraph, |
|
1184 |
+ |
h: ChangeHash, |
|
1185 |
+ |
inserts: &[(Vertex, Vertex, usize)], |
|
1186 |
+ |
deleted: &[AtomKey], |
|
1187 |
+ |
) { |
|
1188 |
+ |
let order = g.take_order(); |
|
1189 |
+ |
let mut runs = Vec::new(); |
|
1190 |
+ |
let mut next = 0u32; |
|
1191 |
+ |
for (i, &(a, b, n)) in inserts.iter().enumerate() { |
|
1192 |
+ |
let text: Vec<String> = (0..n).map(|j| format!("{i}.{j}\n")).collect(); |
|
1193 |
+ |
let text: Vec<&str> = text.iter().map(String::as_str).collect(); |
|
1194 |
+ |
g.insert(h, next, a, b, &lines(&text)); |
|
1195 |
+ |
runs.push((a, b, next, n as u32)); |
|
1196 |
+ |
next += n as u32; |
|
1197 |
+ |
} |
|
1198 |
+ |
for k in deleted { |
|
1199 |
+ |
g.delete( |
|
1200 |
+ |
h, |
|
1201 |
+ |
&AtomRange { |
|
1202 |
+ |
change: k.0, |
|
1203 |
+ |
from: k.1, |
|
1204 |
+ |
to: k.1 + 1, |
|
1205 |
+ |
}, |
|
1206 |
+ |
); |
|
1207 |
+ |
} |
|
1208 |
+ |
if let Some(o) = order { |
|
1209 |
+ |
g.keep_order(o.splice(h, &runs, deleted)); |
|
1210 |
+ |
} |
|
1211 |
+ |
} |
|
1212 |
+ |
|
|
1213 |
+ |
/// Edits as a diff makes them from the order `l`: hunks with a kept |
|
1214 |
+ |
/// line between them, each deleting some lines and inserting some. |
|
1215 |
+ |
fn random_edits( |
|
1216 |
+ |
l: &[AtomKey], |
|
1217 |
+ |
rnd: &mut impl FnMut(usize) -> usize, |
|
1218 |
+ |
) -> (Vec<(Vertex, Vertex, usize)>, Vec<AtomKey>) { |
|
1219 |
+ |
let (mut inserts, mut deleted) = (Vec::new(), Vec::new()); |
|
1220 |
+ |
let mut i = rnd(3); |
|
1221 |
+ |
while i <= l.len() { |
|
1222 |
+ |
let del = rnd(3).min(l.len() - i); |
|
1223 |
+ |
let add = rnd(3); |
|
1224 |
+ |
let after = if i == 0 { |
|
1225 |
+ |
Vertex::Start |
|
1226 |
+ |
} else { |
|
1227 |
+ |
Vertex::Atom(l[i - 1]) |
|
1228 |
+ |
}; |
|
1229 |
+ |
let before = l.get(i).map(|k| Vertex::Atom(*k)).unwrap_or(Vertex::End); |
|
1230 |
+ |
deleted.extend(&l[i..i + del]); |
|
1231 |
+ |
if add > 0 { |
|
1232 |
+ |
inserts.push((after, before, add)); |
|
1233 |
+ |
} |
|
1234 |
+ |
i += del + 1 + rnd(6); |
|
1235 |
+ |
} |
|
1236 |
+ |
(inserts, deleted) |
|
1237 |
+ |
} |
|
1238 |
+ |
|
|
1239 |
+ |
/// The kept order, spliced change by change, is what the general |
|
1240 |
+ |
/// algorithm finds, through sequential edits and through concurrent |
|
1241 |
+ |
/// ones that leave conflicts and ghosts (where it must stand aside). |
|
1242 |
+ |
#[test] |
|
1243 |
+ |
fn the_kept_order_follows_edits() { |
|
1244 |
+ |
let hn = |i: usize| { |
|
1245 |
+ |
let mut b = [0u8; 32]; |
|
1246 |
+ |
b[..2].copy_from_slice(&(i as u16).to_be_bytes()); |
|
1247 |
+ |
ObjectRef(b) |
|
1248 |
+ |
}; |
|
1249 |
+ |
let mut seed = 7u64; |
|
1250 |
+ |
let mut rnd = move |n: usize| { |
|
1251 |
+ |
seed = seed |
|
1252 |
+ |
.wrapping_mul(6364136223846793005) |
|
1253 |
+ |
.wrapping_add(1442695040888963407); |
|
1254 |
+ |
((seed >> 33) as usize) % n.max(1) |
|
1255 |
+ |
}; |
|
1256 |
+ |
let mut deps = Deps(HashMap::default()); |
|
1257 |
+ |
let mut g = FileGraph::new(); |
|
1258 |
+ |
let first: Vec<String> = (0..40).map(|i| format!("{i}\n")).collect(); |
|
1259 |
+ |
let first: Vec<&str> = first.iter().map(String::as_str).collect(); |
|
1260 |
+ |
g.insert(hn(1), 0, Vertex::Start, Vertex::End, &lines(&first)); |
|
1261 |
+ |
let mut known: Vec<ChangeHash> = vec![hn(1)]; |
|
1262 |
+ |
let mut c = 2; |
|
1263 |
+ |
let (mut spliced, mut conflicted) = (0, 0); |
|
1264 |
+ |
for step in 0..400 { |
|
1265 |
+ |
let l = g.materialize(&deps).live_atoms(); |
|
1266 |
+ |
// Now and then two changes from the same base, neither knowing |
|
1267 |
+ |
// the other. |
|
1268 |
+ |
let pair = step % 7 == 3; |
|
1269 |
+ |
let base = known.clone(); |
|
1270 |
+ |
for _ in 0..if pair { 2 } else { 1 } { |
|
1271 |
+ |
let h = hn(c); |
|
1272 |
+ |
c += 1; |
|
1273 |
+ |
let (inserts, deleted) = random_edits(&l, &mut rnd); |
|
1274 |
+ |
deps.0.insert(h, base.clone()); |
|
1275 |
+ |
apply_spliced(&mut g, h, &inserts, &deleted); |
|
1276 |
+ |
known.push(h); |
|
1277 |
+ |
if g.order.get().is_some() { |
|
1278 |
+ |
spliced += 1; |
|
1279 |
+ |
} |
|
1280 |
+ |
let m = g.materialize(&deps); |
|
1281 |
+ |
assert_eq!(m, g.materialize_general(&deps), "step {step}"); |
|
1282 |
+ |
if m.is_conflicted() { |
|
1283 |
+ |
conflicted += 1; |
|
1284 |
+ |
} |
|
1285 |
+ |
} |
|
1286 |
+ |
// A change that knows everything settles any conflict by |
|
1287 |
+ |
// rewriting the file. |
|
1288 |
+ |
if g.materialize(&deps).is_conflicted() { |
|
1289 |
+ |
let h = hn(c); |
|
1290 |
+ |
c += 1; |
|
1291 |
+ |
let all = g.materialize(&deps).live_atoms(); |
|
1292 |
+ |
deps.0.insert(h, known.clone()); |
|
1293 |
+ |
apply_spliced(&mut g, h, &[(Vertex::Start, Vertex::End, 5)], &all); |
|
1294 |
+ |
known.push(h); |
|
1295 |
+ |
assert_eq!(g.materialize(&deps), g.materialize_general(&deps)); |
|
1296 |
+ |
} |
|
1297 |
+ |
} |
|
1298 |
+ |
assert!( |
|
1299 |
+ |
spliced > 100 && conflicted > 0, |
|
1300 |
+ |
"{spliced} spliced, {conflicted} conflicted" |
|
1301 |
+ |
); |
| 993 |
1302 |
|
} |
| 994 |
1303 |
|
|
| 995 |
1304 |
|
/// How long rendering a long-lived file takes (run by hand, in release). |
|
|
… |
495 unchanged lines |