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

Reply via email to