edemaine / cocreate

Cocreate Shared Whiteboard/Drawing
MIT License
209 stars 27 forks source link

Align history with undo stack #41

Open edemaine opened 4 years ago

edemaine commented 4 years ago

I think the history borders on useless because there are too many events:

This was already fixed with the undo stack: only when an operation is "complete" (pen lifted, move complete, line/rectangle drawn) does the operation get into the undo stack.

We should mimic this in history storage: just store the final undoable operation (generally on mouseup) instead of every edit getting recorded in history. Some approaches:

  1. Use a separate method objectDiff for storing diffs, and rely on the client to do this at the right time. (Seems relatively safe: client could do operations not in the diff, but still can't destroy history. Ah, but what if client disconnects before completing the operation...)
  2. All diffs get recorded as is, but only final ones get marked "final". (This is similar to how Coauthor works. It would enable finer history view if we ever wanted to...) Client could mark a diff (object?) as final when doing undoableOp, or server could mark as such after inactivity or client disconnect. Eh, but final diffs need to actually be coalescing of many diffs...
  3. Diffs to the same object overwrite (via coalescing) unless previous one marked "final". This is somewhat tricky to implement -- requires getting the last update to a given object.
  4. Diffs get recorded as is, but post-processing on the server (both on a timer and on startup, in case server crashes) looks for any "nonfinal" diffs and coalesces them into final diffs to the extent possible. (This would also include bootstrapping on first deploy.) Define coalescing to merge together consecutive appends and consecutive edits with same keys. [This will behave poorly if someone is drag-moving an object while someone else is still creating that object... but seems rare.] Don't coalesce if diffs are far enough apart in time (1 second? 10 seconds?). Client can also mark current version of object as final by triggering coalescing for that object now, to make diffs correspond to user-level operations (e.g. separate drag operations). But now not a problem if client fails to do so because of disconnection.
  5. Diffs are updated dynamically. When a new object gets created or edited, the client also gets the created diff id, and future updates can specify that diff id to glom on the additional changes (updates or append) to the same diff (same operation). This behaves perfectly on disconnection. We'll also want what's currently a 'multi' diff to be representable by a single diff; e.g., deletion should specify a list.

Option 5 seems the best, but Explain Everything points out an actual use-case for a zillion diff events: if playing back the sequence to make a real-time stream like recorded audio/video. So if we do this, it should probably be in addition to existing diffs so we still have this ability...