Skip to content

Instantly share code, notes, and snippets.

@henrahmagix
Last active August 6, 2016 15:06
Show Gist options
  • Save henrahmagix/1b6b7e55ee99109ca397dc49b05846f4 to your computer and use it in GitHub Desktop.
Save henrahmagix/1b6b7e55ee99109ca397dc49b05846f4 to your computer and use it in GitHub Desktop.
An undo/redo example using only the prototype, no history array saving, whilst still allowing garbage collection.
class ValueAction {
constructor(value = 0) {
this.actions = 0;
this.redos = 0;
this.limit = -1;
this.data = {
value: value
};
this.latest = this.data;
}
run() {
this.actions++;
if (this.limit >= 0 && this.actions > this.limit) {
this.trimHistory();
}
this.redos = 0;
this.data = Object.create(this.data);
this.latest = this.data;
return this.data;
}
setLimit(l) {
this.limit = l;
console.log('limit', this.limit);
}
trimHistory() {
// Clear the prototype so trimmed data can be garbage-collected.
var data = this.latest;
for (var i = 1; i < this.limit; i++) {
data = Object.getPrototypeOf(data);
}
console.log('trim', Object.getPrototypeOf(data));
Object.setPrototypeOf(data, {});
// Disallow accessing the state before trimmed: Object.getPrototypeOf()
// will return one more state than there are in the history, since the
// ver first state has a prototype of {}. So we cannot rely on that
// method to determine the end of the history.
this.actions = this.limit;
}
undo() {
if (this.actions === 0) {
console.log('cannot undo any further');
return;
}
this.actions--;
this.redos++;
this.data = Object.getPrototypeOf(this.data);
console.log('undo', this.data);
}
redo() {
if (this.redos === 0) {
console.log('cannot redo any further');
return;
}
this.redos--;
this.actions++;
var data = this.latest;
for (var i = 0; i < this.redos; i++) {
data = Object.getPrototypeOf(data);
}
this.data = data;
console.log('redo', this.data);
}
add(n) {
this.run();
this.data.value += n;
console.log('add', this.data);
}
minus(n) {
this.run();
this.data.value -= n;
console.log('minus', this.data);
}
}
var value = new ValueAction(0);
value.setLimit(3);
value.add(1);
// add { value: 1 }
value.add(1);
// add { value: 2 }
value.add(1);
// add { value: 3 }
value.add(1);
// trim { value: 0 }
// add { value: 4 }
value.add(1);
// trim { value: 1 }
// add { value: 5 }
value.undo();
// undo { value: 4 }
value.undo();
// undo { value: 3 }
value.undo();
// undo { value: 2 }
value.undo();
// cannot undo any further
value.redo();
// redo { value: 3 }
value.redo();
// redo { value: 4 }
value.redo();
// redo { value: 5 }
value.redo();
// cannot redo any further
var o = {value: 0};
console.time('create');
for (var i = 0; i < 10000; i++) {
o = Object.create(o);
o.value++;
}
console.timeEnd('create');
console.time('rewind');
for (var i = 0; i < 4000; i++) {
o = Object.getPrototypeOf(o);
}
console.timeEnd('rewind');
console.log(o);
// Run 5 times in Node 6.1.0
/*
create: 82.584ms
rewind: 0.690ms
{ value: 6000 }
create: 83.686ms
rewind: 0.591ms
{ value: 6000 }
create: 90.928ms
rewind: 0.640ms
{ value: 6000 }
create: 79.846ms
rewind: 0.593ms
{ value: 6000 }
create: 78.309ms
rewind: 0.622ms
{ value: 6000 }
*/
@henrahmagix
Copy link
Author

For a more applicable undo/redo system, see https://github.com/ArthurClemens/Javascript-Undo-Manager (first example I found, seems lightweight, good, easy 👍)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment