What's been wrought using the Piece Table? (2014)
web.archive.org
web.archive.org
I wound up using a stack-based architecture that pushed operations stored as functions onto a main stack then its reverse operation onto an undo stack. Once I worked out the state management problems it worked out quite well.
I've always wondered what other approaches others have done if tasked with a similar problem.
that's quite common: it's called the Command pattern. https://en.wikipedia.org/wiki/Command_pattern
Another common approach is Memento (basically, "save everything")
A nice thing that can be added is a pair of serialization / deserialization functions for the state needed for your commands. This way you can save them regularly and restore them if a crash or something like this happens ; the user can then continue to work while keeping its undo / redo stack
https://github.com/musescore/MuseScore/blob/master/libmscore...
https://github.com/musescore/MuseScore/blob/master/libmscore...
https://github.com/musescore/MuseScore/blob/c4af0c22bc90a6ba...
https://github.com/OSSIA/score/blob/master/base/lib/core/com...
https://github.com/OSSIA/score/blob/master/base/plugins/scor...
https://github.com/OSSIA/score/blob/master/base/plugins/scor...
https://github.com/OSSIA/score/blob/master/base/plugins/scor...
Also once your commands are serialized you can send them over the network for distributed edition
I wonder if this was the origin of modal text editors. Maybe they were designed for machine/programmer convenience more than machine convenience originally.
Using keyboard commands is often faster than pointing with a mouse, if you take advantage of structures in the text; e.g. "select everything inside the parens." And if you're doing that all day, it becomes second nature to build macros on the fly.
Of course there are also mouse-based editors, which my second paragraph addresses.
Example:
- (void)setTitle:(NSString *title) {
[[NSUndoManager prepareWithInvocationTarget:self]
setTitle:currentTitle_];
currentTitle_ = title;
}
This setTitle: method may be undone by calling setTitle: again with the old title. We actually send that to the undo manager, which records it.When the user invokes undo, the undo manager replays the message it was sent. setTitle again records how to undo the replayed operation, but this time it gets pushed onto the redo stack!
What is nice about this approach is there is no need to manually reify commands (i.e. Command pattern). It's just a message send.
An undo then became going back to the original image plus a redo of everything on the stack minus the last operation. Rather simplistic, but it worked well for our purposes and was easy to implement.
In order to undo without a redo list, you would have to keep a complete graphical representation of the image and each operation. With images the memory usage goes up very quickly in that case, copying large blocks of memory on each operation makes your image editing slow down too.
Since we had all the visualization bits written, we reused them and displayed it as a tree and you'd try different approaches as branches by clicking between them to compare. I guess it was basically as a seeing the history graph of a git repo, but this was about 5 years before git was created.
This seems nice since you don't have to figure out inverse operations (which I've seen get tricky), but I imagine the performance penalty of having to store all operations and rebuild the entire state every time could be problematic in some cases.
Then again, I imagine there are good standard optimizations for dealing with this kind of thing. One thing that occurs to me is that operations which 'cancel' each other could be sought out and eliminated before rebuilding the state.
I'd be glad for any more information on the subject!
Edit: more specifically I'm wondering if those optimizations do in fact exist, and if so what they are.
EDIT: actually reading the wiki more carefully, I think they were talking about making a BST persistent specifically rather than any data structure. In that case looking at implementations such as rrb-vectors might be more interesting: https://github.com/clojure/core.rrb-vector
You can be far mor efficient than that. The trick is to use immutable data structures to represent your state. When something changes, return a new state. Parts of the state that didn’t change are still referring to chunks from the previous state, so it’s actually sharing most of the memory from the previous state.
There are data structures in libraries in different languages that support this idea, for example system.collections.immutable in .NET. Anything you build in Erlang or Elixir will basically automatically do this. (As an aside, Erlang IOlists are a related concept to the piece tables the article talks about.)
Using this approach you don’t actually have to rebuild a previous or redo state, you just keep a list of states and move between them for undo and redo.
As someone who's not directly programmed GPUs before, they look quite intimidating and opaque; certainly, that's the sense I get from HN threads and the like.
If anyone wants to check out the comments: https://news.ycombinator.com/item?id=15381886.
Our undo/redo feature was suggested to be implemented as a stack.
https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_sea...
http://e98cuenc.free.fr/wordprocessor/piecetable.html
A years later it was integrated in AbiWord.
There was a major mistake in the profiling. The dots in the graphs were the best of 3 measuraments. At the time I thought that was the fairest to prevent outliers due to concurrent process. It also warms the data in the cache making the best measurement super fast.
Proceeds to go write a program to manipulate memory-mapped files.
_But_ non-technical users do not understand how it works, which means that they were _very_ surprised to find state other than the obviously visible stuff being saved along with documents, and thus sent accidentally to recipients.
If you're going to make use of this sort of clever optimization, you owe it to your users to at least document the consequences, if not the specific implementation.
Edited: I think you read more criticism into my post than was intended on my part.
Found it in the wikipedia footnotes :)
Furthermore with ropes, every operation returns a new rope data structure, that means ropes are immutable. If you want to implement undo, you only keep references to the former ropes. There is no inserting or adding via looking up the changes in an undo list.
I think maybe if you implemented a piece table, where every piece spans one character, used a binary tree and made it immutable, you'd get a ropes data structure.
For more information about persistence vs immutability this link might help: https://stackoverflow.com/questions/10034537/persistent-vs-i...
The Boehm paper on ropes[1] (which is about the only academic literature on the subject that I know of) does not do that (and explicitly suggests one should not), nor does any real-world implementation, (e.g., the Boehm implementation, SGI's impl) do that. It would be incredibly inefficient, and for no real gain.
A good rope implementation will store an array of characters/bytes in the leaves, up to some threshold.
> Furthermore with ropes, every operation returns a new rope data structure, that means ropes are immutable.
There is nothing inherently immutable about ropes, and it is certainly possible to mutate a rope. (For example, appending a single character to a leaf with space is much quicker if the rope as a whole is mutable.)
Look at the SGI implementation for an example here; their reference docs[2] contain sufficient details to see that the rope itself is mutable.
Now, a rope typically just describes a sequence of characters. It is entirely possible for a leaf node in a specialized rope to reference on-disk content, and other leaves to reference in-memory content. In that regards, it can be like a piece table. Implementing an undo/redo on top of a rope is less straight-forward; whereas a piece table's undo history can essentially share large portions of the linked list (see the lovely diagrams here[3]) I'm not sure the same can be done on a Rope's tree, due to the tree's need to balance and re-balance. That is, without copying the tree, since that kind of defeats a lot of the benefits that a piece table has — not needing to copy the entire "file", even if that's just a bunch of spans.
Now, one could make the leaves in a rope ref-counted (so they could be shared) and then copy the Concat nodes for undo/redo levels. Keeping the concat nodes as copies means the copies can balance independently, and since concat nodes are small (~2 pointers for left/right and a depth, IIRC) that isn't too much copying (the bulk of the data, the text, is refcounted in the leaves). But the simplicity of the piece table really starts to shine at this point.
[1]: http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.14.9...
My interest in ropes was arisen from the "data structures for text editors" post here on HN. Most resources were haskell implementations. IBM Developerworks had an article about ropes and a corresponding java library, the article stated ropes were immutable. https://www.ibm.com/developerworks/library/j-ropes/index.htm...
I admit being wrong on every node storing a single character, that misconception stems from a graph I saw representing ropes on yesterday's article.
Undoing with immutable ropes is very straightforward I think. You don't copy the whole file, but just the references. I admit it is heavier on the memory, but you could store an arbitrary amount of previous "states" or versions in ram. The benchmark on the Java library may prove this point.
I will try to read the original article to gain more insight. Thanks again for the resources.
Given that people are still attempting to re-invent text editors, it would be interesting to know if anyone has unknowingly come up with similar ideas in their design.
(1988 is also around the time of a famous DRAM shortage BTW)
I know now, which I hope is the important part.
One secret of HN titles is that sometimes the non-original title helps a story make the front page but then reverting it to the original makes it more interesting again.
That nobody is going "oh boy, so what has been wrought using the piece table?" is part of their point.