Greetings webodfers, I'm spending some time in the next 3 weeks improving the behaviour of undo slightly. I am specifically looking at fixing how undo states are created to group things more intuitively for the user. For a nice clear example of the existing problem, try the following steps: 1) Open the editor (http://www.webodf.org/demo) 2) Select 2 or 3 paragraphs 3) Indent these 1 step left 4) Now hit undo If you persist long enough, you can eventually unindent each paragraph. Every second undo appears to do nothing, as this is rolling back the AddStyle operation, which is not really user visible. You'll notice if you look at the Session.enqueue function that it takes an array. In most places, we're quite careful to group all operations from a single user operation into one group. In order to get the next level of improvement to undo, I really need to know what group an operation original executed in. This would allow me to ensure all operations from a single group appear in a single undo state (for example). I have two ideas for how to do this cleanly. Option 1) Add an argument to the signalOperationStart & signalOperationEnd that specify the group being started. The group id can simply be an incrementing number or similar. Pros - * Easy to implement * Group id only needs to be locally unique * Definitely not over-engineered :) * Very transient. Can't be accidentally leaked to other clients etc. * Maximum flexibility to how the server batches up operations e.g., the server can choose to group multiple text inputs in the same region into a single operation Cons - * Very transient! No way to regain the group after the signal has fired * No remote client will ever known how a batch of operations are divided up Option 2) Add a groupid (or transactionid) to each operation. Have the session assign a guid as the groupid so it is generally likely to be globally unique across all clients. Pros - * Clean extension of current system. Easy to implement * Allows remote clients to reconstruct sensible undo states similar to the local client (not that we support collab-undo yet) * Accessible to the local undo, or other consumers that might want to be aware of this (e.g., save points?) Cons - * Increases amount of data sent * No firm decisions as to how remote undo should work yet, so this may not have any bearing on the "right" solution * Server cannot group operations from multiple clients or groups (note, this doesn't stop LOCAL clients from performing clever optimizations however) There are other variations on the theme, but the thoughts are: * Are groups permanent or temporary? * Can groups be modified after being added to the session? (e.g., similar to how SQL transactions work) * How widely known is a group (local or remote)? * If groups are transmitted remotely, how do other clients know when they have received all operations in a single group? * Again, if remotely received, can remote clients interleave ops from multiple groups? I would like to open the email for discussion. Do people have any preference between option 1 or 2 (or another suggestion entirely)? Are there other opinions about how these groups should work? Is there obvious issues I've missed? I plan to start cutting code next week Tuesday AEST, so there are some time pressures for reply. Having said that... we can always tweak it later. Neither option is a lot of code so far :) Cheers, -- Philip Peitsch Mob: 0439 810 260
Am Freitag, 17. Januar 2014, 22:39:37 schrieb Philip Peitsch: > Greetings webodfers, > > I'm spending some time in the next 3 weeks improving the behaviour of > undo slightly. I am specifically looking at fixing how undo states > are created to group things more intuitively for the user. Currently I see two places for grouping operations together (just stating, guess everyone sees the same): a) when creating a set of operations based on a single state of the document, where the parameters of the (n+1)-th op could depend on the effects of the n- th op. The set needs to be passed as one group, to be also applied in the same order. Once they are serialized, the grouping has no more meaning. b) in the UI logic, because UI actions are often not directly mapped to a single operation, but multiple ones (UI actions could be seen as CISC, translated to operations which could be seen as RISC). To stay consistent in the UI, possible listings of what actions different users did and also the option to undo/redo those actions would be always described in the UI action terms. So this grouping (info) has to stay also when serialized, because it will be used later. User-defined macros could be seen similar to UI actions, just that they extend the "CISC" set. Any c) etc.?> I have two ideas for how to do this cleanly. > > Option 1) > Add an argument to the signalOperationStart & signalOperationEnd that > specify the group being started. The group id can simply be an > incrementing number or similar. > > Pros - > * Easy to implement > * Group id only needs to be locally unique > * Definitely not over-engineered :) > * Very transient. Can't be accidentally leaked to other clients etc. > * Maximum flexibility to how the server batches up operations > e.g., the server can choose to group multiple text inputs in the > same region into a single operation > > Cons - > * Very transient! No way to regain the group after the signal has > fired > * No remote client will ever known how a batch of operations are > divided up > > Option 2) > Add a groupid (or transactionid) to each operation. Have the session > assign a guid as the groupid so it is generally likely to be globally > unique across all clients. > > Pros - > * Clean extension of current system. Easy to implement > * Allows remote clients to reconstruct sensible undo states similar > to the local client (not that we support collab-undo yet) > * Accessible to the local undo, or other consumers that might want to > be aware of this (e.g., save points?) > > Cons - > * Increases amount of data sent > * No firm decisions as to how remote undo should work yet, so this > may not have any bearing on the "right" solution > * Server cannot group operations from multiple clients or groups > (note, this doesn't stop LOCAL clients from performing clever > optimizations however) > > There are other variations on the theme, but the thoughts are: > * Are groups permanent or temporary? > * Can groups be modified after being added to the session? (e.g., > similar to how SQL transactions work) > * How widely known is a group (local or remote)? > * If groups are transmitted remotely, how do other clients know > when they have received all operations in a single group? > * Again, if remotely received, can remote clients interleave ops > from multiple groups? >From above use-cases for groups I would think: * UI-action groups are permanent * UI-action groups cannot be modified after being streamed (ignoring OT- modification here) * UI-action groups should be known remotely as well, ideally by a defined action set (but not required). A known UI action set would help with display (think action name translation). But custom macro names have translation issues in any case. Being known e.g. allows users do see in UI action terms what other users are doing (and undo that) * a group start/end could be marked by setting start/end tag in the specs. If we also support nested groups (e.g. created by macros calling other macros) those tags would need to support that. Another option would be to have a tree structure in the serialization to express that, but I do not see a real gain, as some clients might just not care. * at least with the idea of UI-action groups I see no reason to support interleaving, also no sense. Hm, comes the need e.g. from grouping consecutive insert operations by one user? Related thought: Grouped actions possibly need some kind of local lock, to make sure all operations belonging to the same UI action end up being applied/routed without other ops sneaking in. E.g. some UI action could be done in an interactive way, with intermediate steps already applied. That could also be useful for grouping related operations into one, like the consecutive inserting. Because in the timespan where one is quickly typing a word updates from other users might be not wanted as well, so such a lock could be synced with the timeout for when inserts will no longer be auto-grouped? Open problem: Given that at least with the OT approach different clients have different (groups of) operations applied, and usually not only in a different order. So they would also have different Undo stacks. So should the history be normalized somehow? What to do about groups whose operations are transformed completely into noops so would be currently just be forgotten? So somehow I prefer option 1) for now, until there is a grand plan compiled. Unless we are quick enough with compiling one now. Cheers Friedrich -- Friedrich W. H. Kossebau // KO GmbH http://kogmbh.com/legal/
On 21 January 2014 01:39, Friedrich W. H. Kossebau
Am Freitag, 17. Januar 2014, 22:39:37 schrieb Philip Peitsch:
Greetings webodfers,
I'm spending some time in the next 3 weeks improving the behaviour of undo slightly. I am specifically looking at fixing how undo states are created to group things more intuitively for the user.
Currently I see two places for grouping operations together (just stating, guess everyone sees the same):
a) when creating a set of operations based on a single state of the document, where the parameters of the (n+1)-th op could depend on the effects of the n- th op. The set needs to be passed as one group, to be also applied in the same order. Once they are serialized, the grouping has no more meaning.
b) in the UI logic, because UI actions are often not directly mapped to a single operation, but multiple ones (UI actions could be seen as CISC, translated to operations which could be seen as RISC). To stay consistent in the UI, possible listings of what actions different users did and also the option to undo/redo those actions would be always described in the UI action terms. So this grouping (info) has to stay also when serialized, because it will be used later. User-defined macros could be seen similar to UI actions, just that they extend the "CISC" set.
Any c) etc.?
This all makes sense to me. We've already started encountering issues with the UI actions not being easily discernable from their group. Gazing into my crystal ball, I predict a coming feature eventually that changes session.enqueue to take a localizable string describe the group alongside the array of operations. That sounds like next month's problem however!
I have two ideas for how to do this cleanly. ... from multiple groups?
From above use-cases for groups I would think: * UI-action groups are permanent * UI-action groups cannot be modified after being streamed (ignoring OT- modification here) * UI-action groups should be known remotely as well, ideally by a defined action set (but not required). A known UI action set would help with display (think action name translation). But custom macro names have translation issues in any case. Being known e.g. allows users do see in UI action terms what other users are doing (and undo that) * a group start/end could be marked by setting start/end tag in the specs. If we also support nested groups (e.g. created by macros calling other macros) those tags would need to support that. Another option would be to have a tree structure in the serialization to express that, but I do not see a real gain, as some clients might just not care. * at least with the idea of UI-action groups I see no reason to support interleaving, also no sense. Hm, comes the need e.g. from grouping consecutive insert operations by one user?
+1. This also aligns with my own thoughts on the matter.
Related thought: Grouped actions possibly need some kind of local lock, to make sure all operations belonging to the same UI action end up being applied/routed without other ops sneaking in. E.g. some UI action could be done in an interactive way, with intermediate steps already applied. That could also be useful for grouping related operations into one, like the consecutive inserting. Because in the timespan where one is quickly typing a word updates from other users might be not wanted as well, so such a lock could be synced with the timeout for when inserts will no longer be auto-grouped?
This makes sense for other times as well. E.g., updates arriving while selecting with a mouse will likely break the selection or move the view around. Updates arriving while the view is scrolling might have similar issues. I agree that we will (eventually) need some predictable critical regions during which remote updates cannot be received or processed. For now, if I stick with option 1, we can avoid answering these questions.
Open problem: Given that at least with the OT approach different clients have different (groups of) operations applied, and usually not only in a different order. So they would also have different Undo stacks. So should the history be normalized somehow? What to do about groups whose operations are transformed completely into noops so would be currently just be forgotten?
The likely implementation of collaborative undo is going to be inverse-ops. In this case, all the normal OT behaviours remain, and all the undo stack is decide which inverse-ops to send to other clients. (Of course, I reserve my rights to change my mind on this fictional implementation at any time in the future...) The undo history being different per user is only an issue if the users are sitting side-by-side and expect undo to perform the same action on both machines. I don't think there is any technical issue with the undo stacks being different per client, but there will potentially be some significant user-experience bugs.
So somehow I prefer option 1) for now, until there is a grand plan compiled. Unless we are quick enough with compiling one now.
I am in agreement with you for this. Option 1 is the least code impact at the moment, and allows me to avoid the inherent monsters lurking within the "collaborative undo" feature for a few weeks longer :-). Based on this, I'll roll code for option 1 and get it up for review soon. We can then extend or modify as desired. As ever, thanks for your comprehensive feedback! -- Philip Peitsch Mob: 0439 810 260
Am Dienstag, 21. Januar 2014, 16:56:36 schrieb Philip Peitsch:
On 21 January 2014 01:39, Friedrich W. H. Kossebau
wrote: Am Freitag, 17. Januar 2014, 22:39:37 schrieb Philip Peitsch:
I have two ideas for how to do this cleanly. ... Related thought: Grouped actions possibly need some kind of local lock, to make sure all operations belonging to the same UI action end up being applied/routed without other ops sneaking in. E.g. some UI action could be done in an interactive way, with intermediate steps already applied. That could also be useful for grouping related operations into one, like the consecutive inserting. Because in the timespan where one is quickly typing a word updates from other users might be not wanted as well, so such a lock could be synced with the timeout for when inserts will no longer be auto-grouped?
This makes sense for other times as well. E.g., updates arriving while selecting with a mouse will likely break the selection or move the view around. Updates arriving while the view is scrolling might have similar issues. I agree that we will (eventually) need some predictable critical regions during which remote updates cannot be received or processed.
Yes, good examples as well.
For now, if I stick with option 1, we can avoid answering these questions.
Possibly the lock mechanism is on my plate to do meanwhile :) The examples actually show that this is a real problem already, at least for good user experience.
Open problem: Given that at least with the OT approach different clients have different (groups of) operations applied, and usually not only in a different order. So they would also have different Undo stacks. So should the history be normalized somehow? What to do about groups whose operations are transformed completely into noops so would be currently just be forgotten?
The likely implementation of collaborative undo is going to be inverse-ops. In this case, all the normal OT behaviours remain, and all the undo stack is decide which inverse-ops to send to other clients. (Of course, I reserve my rights to change my mind on this fictional implementation at any time in the future...)
The undo history being different per user is only an issue if the users are sitting side-by-side and expect undo to perform the same action on both machines.
Ideally in collab situations users should feel like they are sitting side-by- side ;) (/me dreams of voicechat/chat enhanced solutions) I imagine one day there should be some action-timeline (as in: UI action, not operations), some kind of official document history, so people can also see by time who did what. So if they talk about the history they need to see the same on all shared instances. So IMHO the OT we do is only for good user experience. There still should be also some official single history, and that should ideally reflect as much as possible what all users did (no idea yet how to properly represent actions that overlap or shadow each other, to be solved). This history then also would be the shared list to pick things to undo from. IMO at least the default undo should just undo the actions done by the local user, with the same UI patterns as used to from single-user-editing, so that noone has to relearn their editing habits or switch them for WebODF or if suddenly someone else joins the editing. Undoing selected actions from other users should still be doable, but with a special UI perhaps.
So somehow I prefer option 1) for now, until there is a grand plan compiled. Unless we are quick enough with compiling one now.
I am in agreement with you for this. Option 1 is the least code impact at the moment, and allows me to avoid the inherent monsters lurking within the "collaborative undo" feature for a few weeks longer :-).
Okay with me. In the meantime we should develop the "grand plan" for collaborative Undo in a dedicated place, will see to come up with something soonish. Cheers Friedrich -- Friedrich W. H. Kossebau // KO GmbH http://kogmbh.com/legal/
participants (2)
-
Friedrich W. H. Kossebau
-
Philip Peitsch