1use std::collections::VecDeque;
2
3use crate::compact::Remap;
4use crate::{ChangeSet, Mesh, Patch, PatchSide, Result, StoreError};
5
6pub struct History {
26 undo: VecDeque<Patch>,
27 redo: Vec<Patch>,
28 budget: usize,
29 bytes: usize,
30 mesh_uid: Option<u64>,
32}
33
34impl History {
35 pub fn new(budget_bytes: usize) -> Self {
37 Self {
38 undo: VecDeque::new(),
39 redo: Vec::new(),
40 budget: budget_bytes,
41 bytes: 0,
42 mesh_uid: None,
43 }
44 }
45
46 fn check_mesh(&self, mesh: &Mesh) -> Result<()> {
47 match self.mesh_uid {
48 Some(uid) if uid != mesh.uid => Err(StoreError::PatchMismatch),
49 _ => Ok(()),
50 }
51 }
52
53 pub fn push(&mut self, mesh: &mut Mesh, patch: Patch) -> Result<()> {
57 self.check_mesh(mesh)?;
58 if patch.mesh_uid != mesh.uid || patch.revision != mesh.revision {
59 return Err(StoreError::PatchMismatch);
60 }
61 self.mesh_uid = Some(mesh.uid);
62 if patch.is_empty() {
63 return Ok(());
64 }
65 for p in self.redo.drain(..) {
66 self.bytes -= p.byte_size();
67 p.release(mesh, PatchSide::Redoable);
68 }
69 self.bytes += patch.byte_size();
70 self.undo.push_back(patch);
71 self.evict(mesh);
72 Ok(())
73 }
74
75 pub fn undo(&mut self, mesh: &mut Mesh) -> Result<Option<ChangeSet>> {
77 self.check_mesh(mesh)?;
78 let Some(patch) = self.undo.pop_back() else {
79 return Ok(None);
80 };
81 match patch.revert(mesh) {
82 Ok(changes) => {
83 self.redo.push(patch);
84 Ok(Some(changes))
85 }
86 Err(e) => {
87 self.undo.push_back(patch);
88 Err(e)
89 }
90 }
91 }
92
93 pub fn redo(&mut self, mesh: &mut Mesh) -> Result<Option<ChangeSet>> {
95 self.check_mesh(mesh)?;
96 let Some(patch) = self.redo.pop() else {
97 return Ok(None);
98 };
99 match patch.apply(mesh) {
100 Ok(changes) => {
101 self.undo.push_back(patch);
102 Ok(Some(changes))
103 }
104 Err(e) => {
105 self.redo.push(patch);
106 Err(e)
107 }
108 }
109 }
110
111 pub fn clear(&mut self, mesh: &mut Mesh) -> Result<()> {
113 self.check_mesh(mesh)?;
114 for p in self.undo.drain(..) {
115 p.release(mesh, PatchSide::Undoable);
116 }
117 for p in self.redo.drain(..) {
118 p.release(mesh, PatchSide::Redoable);
119 }
120 self.bytes = 0;
121 Ok(())
122 }
123
124 pub fn compact(&mut self, mesh: &mut Mesh) -> Result<Remap> {
126 self.check_mesh(mesh)?;
127 let remap = mesh.compact()?;
128 self.undo.clear();
130 self.redo.clear();
131 self.bytes = 0;
132 Ok(remap)
133 }
134
135 pub fn set_budget(&mut self, mesh: &mut Mesh, budget_bytes: usize) -> Result<()> {
137 self.check_mesh(mesh)?;
138 self.budget = budget_bytes;
139 self.evict(mesh);
140 Ok(())
141 }
142
143 fn evict(&mut self, mesh: &mut Mesh) {
144 while self.bytes > self.budget {
145 let Some(p) = self.undo.pop_front() else {
146 break;
147 };
148 self.bytes -= p.byte_size();
149 p.release(mesh, PatchSide::Undoable);
150 }
151 }
152
153 pub fn can_undo(&self) -> bool {
154 !self.undo.is_empty()
155 }
156
157 pub fn can_redo(&self) -> bool {
158 !self.redo.is_empty()
159 }
160
161 pub fn undo_len(&self) -> usize {
162 self.undo.len()
163 }
164
165 pub fn redo_len(&self) -> usize {
166 self.redo.len()
167 }
168
169 pub fn bytes(&self) -> usize {
172 self.bytes
173 }
174
175 pub fn budget(&self) -> usize {
176 self.budget
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 fn quad() -> Mesh {
185 Mesh::from_buffers(
186 &[0., 0., 0., 1., 0., 0., 1., 1., 0., 0., 1., 0.],
187 &[0, 1, 2, 0, 2, 3],
188 )
189 .unwrap()
190 }
191
192 fn move_vertex(m: &mut Mesh, h: &mut History, v: u32, x: f32) {
193 let mut tx = m.begin_edit();
194 tx.set_positions(&[v], &[[x, 0., 0.]]).unwrap();
195 let p = tx.commit().unwrap();
196 h.push(m, p).unwrap();
197 }
198
199 #[test]
200 fn undo_and_redo_walk_the_stack() {
201 let mut m = quad();
202 let mut h = History::new(1 << 20);
203 move_vertex(&mut m, &mut h, 0, 5.0);
204 move_vertex(&mut m, &mut h, 0, 6.0);
205 assert_eq!(h.undo(&mut m).unwrap().unwrap().vertices_moved, vec![0]);
206 assert_eq!(m.position(0).unwrap()[0], 5.0);
207 h.undo(&mut m).unwrap();
208 assert_eq!(m.position(0).unwrap()[0], 0.0);
209 assert!(h.undo(&mut m).unwrap().is_none());
210 h.redo(&mut m).unwrap();
211 h.redo(&mut m).unwrap();
212 assert_eq!(m.position(0).unwrap()[0], 6.0);
213 assert!(h.redo(&mut m).unwrap().is_none());
214 }
215
216 #[test]
217 fn a_new_commit_discards_redo() {
218 let mut m = quad();
219 let mut h = History::new(1 << 20);
220 move_vertex(&mut m, &mut h, 0, 5.0);
221 h.undo(&mut m).unwrap();
222 move_vertex(&mut m, &mut h, 1, 7.0);
223 assert!(!h.can_redo());
224 assert_eq!(h.undo_len(), 1);
225 }
226
227 #[test]
228 fn empty_commits_keep_redo() {
229 let mut m = quad();
230 let mut h = History::new(1 << 20);
231 move_vertex(&mut m, &mut h, 0, 5.0);
232 h.undo(&mut m).unwrap();
233 let empty = m.begin_edit().commit().unwrap();
234 h.push(&mut m, empty).unwrap();
235 assert!(h.can_redo());
236 }
237
238 #[test]
239 fn budget_evicts_the_oldest_patches() {
240 let mut m = quad();
241 let mut h = History::new(1 << 20);
242 move_vertex(&mut m, &mut h, 0, 1.0);
243 let one = h.bytes();
244 h.set_budget(&mut m, one * 2).unwrap();
245 move_vertex(&mut m, &mut h, 0, 2.0);
246 move_vertex(&mut m, &mut h, 0, 3.0);
247 assert_eq!(h.undo_len(), 2);
248 assert!(h.bytes() <= h.budget());
249 h.undo(&mut m).unwrap();
250 h.undo(&mut m).unwrap();
251 assert_eq!(
252 m.position(0).unwrap()[0],
253 1.0,
254 "the first edit can no longer be undone"
255 );
256 }
257
258 #[test]
259 fn removed_slots_are_reserved_until_their_patch_is_evicted() {
260 let mut m = quad();
261 let mut h = History::new(1 << 20);
262 let mut tx = m.begin_edit();
263 tx.remove_triangles(&[1]).unwrap();
264 tx.remove_vertices(&[3]).unwrap();
265 let p = tx.commit().unwrap();
266 h.push(&mut m, p).unwrap();
267 assert!(m.free_vertex_slots().is_empty(), "reserved while undoable");
268
269 let mut tx = m.begin_edit();
270 assert_eq!(
271 tx.add_vertices(&[[0.; 3]]).unwrap(),
272 vec![4],
273 "slot 3 is not reused"
274 );
275 tx.rollback();
276
277 h.clear(&mut m).unwrap();
278 assert_eq!(m.free_vertex_slots(), &[3]);
279 assert_eq!(m.free_triangle_slots(), &[1]);
280 }
281
282 #[test]
283 fn created_slots_of_discarded_redo_are_released() {
284 let mut m = quad();
285 let mut h = History::new(1 << 20);
286 let mut tx = m.begin_edit();
287 tx.add_vertices(&[[0.; 3]]).unwrap();
288 let p = tx.commit().unwrap();
289 h.push(&mut m, p).unwrap();
290 h.undo(&mut m).unwrap();
291 assert!(m.free_vertex_slots().is_empty(), "reserved for redo");
292 move_vertex(&mut m, &mut h, 0, 1.0);
293 assert_eq!(m.free_vertex_slots(), &[4]);
294 }
295
296 #[test]
297 fn history_compact_empties_history_and_applies_remap() {
298 let mut m = quad();
299 let mut h = History::new(1 << 20);
300 let mut tx = m.begin_edit();
301 tx.remove_triangles(&[0]).unwrap();
302 tx.remove_vertices(&[1]).unwrap();
303 let p = tx.commit().unwrap();
304 h.push(&mut m, p).unwrap();
305
306 let remap = h.compact(&mut m).unwrap();
307 assert!(!h.can_undo());
308 assert_eq!(h.bytes(), 0);
309 assert_eq!(remap.vertex(1), None);
310 assert_eq!(remap.vertex(2), Some(1));
311 assert_eq!(remap.vertex(3), Some(2));
312 assert_eq!(remap.triangle(0), None);
313 assert_eq!(remap.triangle(1), Some(0));
314 assert_eq!(m.vertex_count(), 3);
315 assert_eq!(m.position(1), Some([1., 1., 0.]));
316 assert_eq!(m.triangle(0), Some([0, 1, 2]));
317 }
318
319 fn points() -> Mesh {
321 Mesh::from_buffers(&[0.; 12], &[]).unwrap()
322 }
323
324 fn remove_vertex(m: &mut Mesh, h: &mut History, v: u32) {
325 let mut tx = m.begin_edit();
326 tx.remove_vertices(&[v]).unwrap();
327 let p = tx.commit().unwrap();
328 h.push(m, p).unwrap();
329 }
330
331 #[test]
332 fn direct_compact_then_clear_does_not_corrupt() {
333 let mut m = quad();
334 let mut h = History::new(1 << 20);
335 let mut tx = m.begin_edit();
336 tx.remove_triangles(&[0]).unwrap();
337 tx.remove_vertices(&[1]).unwrap();
338 let p = tx.commit().unwrap();
339 h.push(&mut m, p).unwrap();
340 m.compact().unwrap();
341 h.clear(&mut m).unwrap();
344 assert!(m.free_vertex_slots().is_empty());
345 assert!(m.free_triangle_slots().is_empty());
346 let mut tx = m.begin_edit();
347 assert_eq!(tx.add_vertices(&[[0.; 3]]).unwrap(), vec![3]);
348 }
349
350 #[test]
351 fn direct_compact_then_push_or_set_budget_does_not_corrupt() {
352 let mut m = points();
353 let mut h = History::new(1 << 20);
354 remove_vertex(&mut m, &mut h, 0);
355 h.clear(&mut m).unwrap();
356 assert_eq!(m.free_vertex_slots(), &[0]);
357 let mut tx = m.begin_edit();
359 assert_eq!(tx.add_vertices(&[[1.; 3]]).unwrap(), vec![0]);
360 let p = tx.commit().unwrap();
361 h.push(&mut m, p).unwrap();
362 remove_vertex(&mut m, &mut h, 3);
363 h.undo(&mut m).unwrap();
364 h.undo(&mut m).unwrap();
365 m.compact().unwrap();
367 assert_eq!(m.vertex_count(), 3);
368 move_vertex(&mut m, &mut h, 0, 5.0); assert!(m.free_vertex_slots().is_empty(), "slot 0 is alive again");
370 h.set_budget(&mut m, 0).unwrap();
371 assert!(m.free_vertex_slots().is_empty());
372 let mut tx = m.begin_edit();
373 assert_eq!(tx.add_vertices(&[[0.; 3]]).unwrap(), vec![3]);
374 }
375
376 #[test]
377 fn undo_of_a_patch_made_stale_by_direct_compact_fails_cleanly() {
378 let mut m = points();
379 let mut h = History::new(1 << 20);
380 remove_vertex(&mut m, &mut h, 1);
381 m.compact().unwrap();
382 let revision = m.revision();
383 assert_eq!(h.undo(&mut m).err(), Some(StoreError::StalePatch));
384 assert_eq!(m.revision(), revision);
385 assert_eq!(m.vertex_count(), 3);
386 h.clear(&mut m).unwrap();
387 assert!(m.free_vertex_slots().is_empty());
388 }
389
390 #[test]
391 fn history_used_with_a_second_mesh_is_rejected() {
392 let mut a = points();
393 let mut b = points();
394 let mut h = History::new(1 << 20);
395 remove_vertex(&mut a, &mut h, 1);
396 assert_eq!(h.clear(&mut b).err(), Some(StoreError::PatchMismatch));
397 assert_eq!(
398 h.set_budget(&mut b, 0).err(),
399 Some(StoreError::PatchMismatch)
400 );
401 assert_eq!(h.undo(&mut b).err(), Some(StoreError::PatchMismatch));
402 assert_eq!(h.redo(&mut b).err(), Some(StoreError::PatchMismatch));
403 assert_eq!(h.compact(&mut b).err(), Some(StoreError::PatchMismatch));
404 let mut tx = b.begin_edit();
405 tx.remove_vertices(&[2]).unwrap();
406 let pb = tx.commit().unwrap();
407 assert_eq!(h.push(&mut b, pb).err(), Some(StoreError::PatchMismatch));
408 assert!(b.free_vertex_slots().is_empty());
409 assert_eq!(b.vertex_slots(), 4, "b was not compacted");
410 assert_eq!(h.undo_len(), 1, "history is unchanged");
411 h.clear(&mut a).unwrap();
412 assert_eq!(a.free_vertex_slots(), &[1]);
413 }
414
415 #[test]
416 fn rejects_patches_that_are_not_the_latest_commit() {
417 let mut m = quad();
418 let mut h = History::new(1 << 20);
419 let mut tx = m.begin_edit();
420 tx.set_positions(&[0], &[[1.; 3]]).unwrap();
421 let first = tx.commit().unwrap();
422 let mut tx = m.begin_edit();
423 tx.set_positions(&[0], &[[2.; 3]]).unwrap();
424 let _second = tx.commit().unwrap();
425 assert_eq!(h.push(&mut m, first).err(), Some(StoreError::PatchMismatch));
426 }
427}