Can't you just track / record changes as part of your undo/redo mechanism?
Quick & dirty (and untested!) sketch:
enum Op {
case insert(index: Int, value: Double)
case remove(index: Int)
case change(index: Int, value: Double)
var name: String {
switch self {
case .insert(let index, let value):
return "insert \(value) at \(index)"
case .remove(let index):
return "remove at \(index)"
case .change(let index, let value):
return "set \(value) at \(index)"
}
}
}
struct TheState {
var elements: [Double] = []
var undoStack: [Op] = []
var redoStack: [Op] = []
func reverse(of op: Op) -> Op {
switch op {
case let .insert(index: index, value: _):
return .remove(index: index)
case let .remove(index: index):
let value = elements[index]
return .insert(index: index, value: value)
case let .change(index: index, value: _):
let value = elements[index]
return .change(index: index, value: value)
}
}
mutating func perform(_ op: Op, isUndoRedo: Bool = false) {
if !isUndoRedo {
undoStack.append(reverse(of: op))
redoStack = []
}
switch op {
case .insert(let index, let value):
elements.insert(value, at: index)
case .remove(let index):
elements.remove(at: index)
case .change(let index, let value):
elements[index] = value
}
draw("after \(op.name)")
}
mutating func undo() {
guard !undoStack.isEmpty else {
print("can't undo")
return
}
let op = undoStack.removeLast()
redoStack.append(reverse(of: op))
print("undo: ", terminator: "")
perform(op, isUndoRedo: true)
}
mutating func redo() {
guard !redoStack.isEmpty else {
print("can't redo")
return
}
let op = redoStack.removeLast()
undoStack.append(reverse(of: op))
print("redo: ", terminator: "")
perform(op, isUndoRedo: true)
}
func draw(_ title: String) {
print(title, terminator: ": [")
var i = 0
elements.forEach { v in
if i != 0 {
print(", ", terminator: "")
}
i += 1
print(v, terminator: "")
}
print("]")
}
}
func testState() {
var state = TheState()
state.perform(.insert(index: 0, value: 0)) // [0.0]
state.perform(.insert(index: 1, value: 1)) // [0.0, 1.0]
state.perform(.insert(index: 2, value: 2)) // [0.0, 1.0, 2.0]
state.perform(.insert(index: 3, value: 3)) // [0.0, 1.0, 2.0, 3.0]
state.perform(.remove(index: 1)) // [0.0, 2.0, 3.0]
state.perform(.insert(index: 0, value: 4)) // [4.0, 0.0, 2.0, 3.0]
state.redo() // can't redo
state.undo() // [0.0, 2.0, 3.0]
state.redo() // [4.0, 0.0, 2.0, 3.0]
state.undo() // [0.0, 2.0, 3.0]
state.undo() // [0.0, 1.0, 2.0, 3.0]
state.undo() // [0.0, 1.0, 2.0]
state.undo() // [0.0, 1.0]
state.undo() // [0.0]
state.undo() // []
state.undo() // can't undo
print("end") // end
state.perform(.insert(index: 0, value: 9)) // [9.0]
state.undo() // []
state.undo() // can't undo
state.redo() // [9.0]
state.undo() // []
state.undo() // can't undo
}
testState()
Outputs:
after insert 0.0 at 0: [0.0]
after insert 1.0 at 1: [0.0, 1.0]
after insert 2.0 at 2: [0.0, 1.0, 2.0]
after insert 3.0 at 3: [0.0, 1.0, 2.0, 3.0]
after remove at 1: [0.0, 2.0, 3.0]
after insert 4.0 at 0: [4.0, 0.0, 2.0, 3.0]
can't redo
undo: after remove at 0: [0.0, 2.0, 3.0]
redo: after insert 4.0 at 0: [4.0, 0.0, 2.0, 3.0]
undo: after remove at 0: [0.0, 2.0, 3.0]
undo: after insert 1.0 at 1: [0.0, 1.0, 2.0, 3.0]
undo: after remove at 3: [0.0, 1.0, 2.0]
undo: after remove at 2: [0.0, 1.0]
undo: after remove at 1: [0.0]
undo: after remove at 0: []
can't undo
after insert 9.0 at 0: [9.0]
undo: after remove at 0: []
can't undo
redo: after insert 9.0 at 0: [9.0]
undo: after remove at 0: []
can't undo
end