1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// Copyright (C) 2021-2022 Daniel Mueller <deso@posteo.net>
// SPDX-License-Identifier: GPL-3.0-or-later
use std::marker::PhantomData;
use rbuf::RingBuf;
/// A trait representing a reversible operation.
pub trait Op<D, T> {
/// Execute the operation.
fn exec(&mut self, data: &mut D) -> T;
/// Undo the operation.
fn undo(&mut self, data: &mut D) -> T;
}
/// A "list" of executed operations.
///
/// It is actually a fixed size ring buffer, but conceptually we allow
/// for pushing of new operations and then moving back and forth again.
#[derive(Debug)]
pub struct Ops<O, D, T> {
/// A fixed size ring buffer storing operations performed on tasks as
/// well as their inverse (i.e., allowing us to "undo").
ops: RingBuf<Option<O>>,
_phantom: PhantomData<(D, T)>,
}
impl<O, D, T> Ops<O, D, T> {
pub fn new(max_count: usize) -> Self {
Self {
// We add one to the maximum undo step count to account for the
// one sentinel value that we insert that separates the "top of
// the stack" from earlier operations that were overwritten.
ops: RingBuf::new(max_count + 1),
_phantom: PhantomData,
}
}
}
impl<O, D, T> Ops<O, D, T>
where
O: Op<D, T>,
{
/// Execute an operation and stash it away for later.
pub fn exec(&mut self, mut op: O, data: &mut D) -> T {
let result = op.exec(data);
self.ops.push_front(Some(op));
// We just inserted a new element, which means that if we still have
// some operations in the ring buffer that we undid earlier, now is
// the time to just drop them (we only keep one linear line of
// operations, not a tree of sorts). Hence, insert a sentinel value
// replacing the least recently executed operation.
*self.ops.back_mut() = None;
result
}
/// Undo the most recent operation, returning the result of the action
/// if one was performed, or `None`.
pub fn undo(&mut self, data: &mut D) -> Option<T> {
if let Some(op) = self.ops.front_mut() {
let result = op.undo(data);
let op = self.ops.pop_front();
// We didn't actually need to remove the operation from the ring
// buffer, but there is no method for just decrementing the front
// pointer or similar. As such, just put the element back in at
// what is now the back. This way, it will still be available
// should we decide to `redo` it.
*self.ops.back_mut() = op;
Some(result)
} else {
None
}
}
/// Re-do the next operation, returning the result of the action
/// if one was performed, or `None`.
pub fn redo(&mut self, data: &mut D) -> Option<T> {
if let Some(op) = self.ops.back_mut() {
let result = op.exec(data);
// There is no way for us to tell the ring buffer to just advance
// the "front" pointer. So we actually have to take a peek at the
// "back" element and then push it in there to become the new
// front.
let op = self.ops.back_mut().take();
self.ops.push_front(op);
Some(result)
} else {
None
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
struct AddOp(usize);
impl Op<usize, ()> for AddOp {
fn exec(&mut self, data: &mut usize) {
*data += self.0;
}
fn undo(&mut self, data: &mut usize) {
*data -= self.0;
}
}
struct MulOp(usize);
impl Op<usize, ()> for MulOp {
fn exec(&mut self, data: &mut usize) {
*data *= self.0;
}
fn undo(&mut self, data: &mut usize) {
*data /= self.0;
}
}
impl<D, T> Op<D, T> for &mut dyn Op<D, T> {
fn exec(&mut self, data: &mut D) -> T {
(*self).exec(data)
}
fn undo(&mut self, data: &mut D) -> T {
(*self).undo(data)
}
}
/// Check that we can execute, undo, and then redo operations.
#[test]
fn exec_undo_redo() {
let mut data = 0;
let mut ops = Ops::<&mut dyn Op<usize, ()>, usize, ()>::new(3);
let mut op1 = AddOp(4);
let mut op2 = MulOp(3);
let mut op3 = MulOp(7);
ops.exec(&mut op1, &mut data);
assert_eq!(data, 4);
ops.exec(&mut op2, &mut data);
assert_eq!(data, 12);
ops.exec(&mut op3, &mut data);
assert_eq!(data, 84);
assert!(ops.undo(&mut data).is_some());
assert_eq!(data, 12);
assert!(ops.redo(&mut data).is_some());
assert_eq!(data, 84);
assert!(ops.undo(&mut data).is_some());
assert_eq!(data, 12);
assert!(ops.undo(&mut data).is_some());
assert_eq!(data, 4);
assert!(ops.redo(&mut data).is_some());
assert_eq!(data, 12);
assert!(ops.undo(&mut data).is_some());
assert_eq!(data, 4);
assert!(ops.undo(&mut data).is_some());
assert_eq!(data, 0);
}
/// Check that we can undo and redp the correct number of operations.
#[test]
fn undo_limit() {
let mut data = 0;
let mut ops = Ops::<&mut dyn Op<usize, ()>, usize, ()>::new(2);
let mut op1 = AddOp(2);
let mut op2 = MulOp(3);
let mut op3 = MulOp(5);
ops.exec(&mut op1, &mut data);
assert_eq!(data, 2);
ops.exec(&mut op2, &mut data);
assert_eq!(data, 6);
// Given that we only have room for two operations, this one should
// evict `op1` from our memory.
ops.exec(&mut op3, &mut data);
assert_eq!(data, 30);
assert!(ops.undo(&mut data).is_some());
assert_eq!(data, 6);
assert!(ops.undo(&mut data).is_some());
assert_eq!(data, 2);
// We should only be able to undo two operations.
for _ in 0..5 {
assert!(ops.undo(&mut data).is_none());
assert_eq!(data, 2);
}
assert!(ops.redo(&mut data).is_some());
assert_eq!(data, 6);
assert!(ops.redo(&mut data).is_some());
assert_eq!(data, 30);
// Similarly, we may only ever redo two operations.
for _ in 0..5 {
assert!(ops.redo(&mut data).is_none());
assert_eq!(data, 30);
}
assert!(ops.undo(&mut data).is_some());
assert_eq!(data, 6);
assert!(ops.undo(&mut data).is_some());
assert_eq!(data, 2);
for _ in 0..5 {
assert!(ops.undo(&mut data).is_none());
assert_eq!(data, 2);
}
}
}