Le 5 juil. 2011 à 22:39, Eric Wasylishen a écrit : > Hey Christopher, > Thanks a lot for the additional explanation!
Yes, that's quite detailed :-) >>> > Unfortunately I haven't coded anything since our chat a few months ago >>> > :-(. I'm still thinking about how to build an editor with persistent >>> > undo/redo including actions like selection changes or revision control >>> > operations like revert or merge. >>> >>> I had some thoughts about how to make this work. The main idea is to >>> introduce a concept of branches or branch pointers, where we not only store >>> the history of the object, but also the linear undo/redo history of the >>> branch pointer. >>> >>> Each time the user makes a change, we update the branch pointer and we also >>> create an undo history node of the change we made to the branch pointer in >>> a sort of branch history. Views then point to a branch pointer, and not to >>> an object. Importantly, views do not have history (they are merely >>> persisted/restored). >>> >>> Things like selections are handled by storing them in the branch pointer >>> history, not in the object or view. This way, multiple views of the same >>> object can exist with different selections in each. Selections can then be >>> undone. Multiple views pointing to the same branch would have the same >>> branch history. >>> >>> Cloning an object would involve creating a new branch with the first >>> history node pointing to the same revision of the object. >>> >>> I don't know anything about Quentin's history track idea, so this could be >>> the same thing. The history track idea was about supporting arbitrary views on the entire CoreObject graph history. By 'view', I mean a database-like view. For example, if you have a photo library, each photo is a core object and the library is a core object too. All these core objects are persistent roots. For a Photo Manager unlike an Image Editor, you are not interested in the history of each photo individually, but in an aggregated history that combines the photo histories + the library history. An history track could be used to model such an history. So history tracks would give the possibility to manage multiple tracks of user actions based on arbitrary criterias (group of objects, kind of user actions, edited data type etc.) . In addition, history tracks might provide a simple mechanism to record an history if no core objects are involved… For example, to provide undo/redo on user actions such as window move, resize, scrolling etc. Eric started to work on a preliminary version in ObjectMerging4, see COHistoryTrack. >> I thought I should provide some details about how I think this should be >> implemented, as I get the impression I wasn't very clear :-(. >> >> Each element in the core-object history is stored as it is presently, i.e. >> with a revision number and the set of changes to all the objects in a >> core-object tree. Parallel to this is a concept of named branches on a >> object. Each object can have zero or more branches that "point" to a >> particular revision of an object. > >> For simplicity, object branches should probably be named to distinguish them >> from one another, but we could autogenerate the names to make sure they are >> unique for an object and permit the user to rename them later as they saw >> fit. > > Regarding names, Quentin's idea was "no name-based references in CoreObject; > UUID's everywhere" - which is pretty much what you suggest. You can then map > human-readable names to UUID's, freely edit the names without fear of > breaking anything, full-text-search index the names, etc. :-) > >> The branch also has a history. This consists of nodes that store a pointer >> value to an object revision, and the previous and next node in the history. >> These nodes will probably need some space for auxilliary information so that >> they can store things like selection state. The present state of the branch >> is a pointer to one of these history nodes (instead of a pointer to an >> object revision). >> >> Each time the user makes a change to an object, the system >> * creates a new object revision, >> * create a new branch pointer history node pointing to the new object >> revision >> * set the branch to point to the new branch pointer history node >> * set the back pointer of the history node to point to the previous branch >> history node >> * changes the forward pointer of the previous history node to point to the >> new branch history node. > > Sounds good. I think I was working along similar lines in my first > "ObjectMerging" prototype. The COHistoryGraphNode objects were the branch > history nodes. I was pretty much stealing Git's idea of a Commit object > (http://book.git-scm.com/1_the_git_object_model.html) > >> An undo is performed as just winding back to a previous branch pointer >> history node by following the back pointer in the history node and updating >> the branch to point to the history node. A redo is similar (follow the >> forward pointer in the history node). A change in the object revision (for >> example from a new change, a merge or a revert to a previous version) is >> just by creating a new branch pointer history node. A selection is also a >> new branch pointer history node, but with the same object revision and the >> selection information stored in the auxilliary field. By 'selection', do you mean switching to another branch, navigating to a past version in read-only mode or something else? >> All these operations suddenly become undoable because they are separate from >> the object history. Yes. Now that I think about it, the main advantage I see is that it should be easier to debug than a 'in-band' approach. Although the 'in-band' sounds conceptually nice and clean, from I have observed when working on the CoreObject from trunk, the history can be hard to inspect and reason about it. >> Furthermore, cloning an object is just a matter of creating a new branch on >> it with no branch history. Sounds related to cheap copies as branches. > Right, I think this is essentially the idea of using immutable objects for > undo/redo: > http://stackoverflow.com/questions/1812978/undo-redo-with-immutable-objects The history changes are usually deltas, so I'm not sure of what you mean here? >> Each time a new node is created, we set the back pointer to point to the >> previous history node, and we update the forward pointer of the previous >> node to point to the new node. That way, when the user moves a one or more >> of paces back along the node tree, and then starts making new changes, it is >> still possible to follow the track both backwards and forwards again. In >> this case, there will be history node tracks that are no longer used, but >> they could still be visualised somehow if the user wanted to explore them >> and view them, or just deleted (as they can no longer be followed). > > Right. > >> The primary consequence is that a view merely references a branch, and that >> a view has NO history. I'm not sure if this is a bad limitation - if a view >> stores only the state of the view (such as e.g. list mode or icon mode in a >> file explorer), this is not the kind of operation that should be "undoable". >> On the other hand, if it is possible to edit a view (such as the layout of >> toolbar buttons) in a special editing mode, a view should have history like >> any other object. My thinking is that this is workable if a view's design is >> its own object with its own branch. > > Yeah, I was thinking about this as well. > I could be interesting to have switching from list mode to icon mode be > undoable.. > > As another example, Xcode has those little back/forward buttons which act as > undo/redo for scrolling and navigating between files. Sometimes it's really > handy when editing a huge file to have undo for scrolling. I think they are two aspects to the problem. Either you are in UI editing/building mode and the UI is the model, or you are in the UI interaction mode. The first case is easy (at least conceptually with EtoileUI), it's basically like a Nib file, except the UI is both a compound document and a core object rather than a Nib file. The second case requires some extra support to record UI actions in a history track or something similar. The toolbar editing case is interesting because it is an hybrid case. If we use our own machinery (CoreObject, EtoileUI and a compound document editor) to build the UI rather than Gorm/IB, then it can be quite nice, because we can treat UI customization as branching over the UI that was shipped with the application. However we won't have a complete UI builder infrastructure before a while, so we could initially record the customization changes in a history track (probably snapshots of the user defaults for the toolbar). Cheers, Quentin. _______________________________________________ Etoile-dev mailing list Etoile-dev@gna.org https://mail.gna.org/listinfo/etoile-dev