Akira: Implement History Manager

Created on 15 Jan 2020  路  10Comments  路  Source: akiraux/Akira

Let's kick-off the conversation on how to implement an History Manager to handle undo and redo actions.

Memory management
The first approach is going the standard route of implementing a memory based history manager, and giving users the ability to define the memory usage limit.

Version control
The other approach we could consider is to save the various actions in a local history file if the user hasn't saved the current project, or save it directly inside the file in case it exists.
This will give us the flexibility to have a potential infinite history without taxing on memory management.
If we decide to go this route, we will need to implement a manual "clear file history" for the user in order to better control the file size. Even adding an option like "Clear file history on shutdown" might be useful.

Thoughts?

help wanted needs design

Most helpful comment

That sounds like a smart solution to me, perhaps with an option to limit how far undo/redo history is saved to avoid getting too large files and using too much memory. Something like saving the last 50 or last 100 actions, depending on the setting.

All 10 comments

You can do in any way you want. Serialization branch should allow to store the canvas state and object properties at every moment, so we just need to add signals to trigger new history steps.

I know at one point there was some discussion around using libgit for versioning. Since SVG is text it seems like using an existing and very robust and optimized system would be good.

This would also make it easy to share and collaborate on Akira projects if they鈥檙e just git repos. You could get history saved even if you鈥檙e not necessarily running Akira.

I wonder if some kind of system could be worked out so you get every commit from the last 24 hours and then commits each day for a week and then weekly commits for a month etc. So that you still get fairly granular history and it just gets slightly less detailed with time

Yes, the libgit is most likely gonna be used to store the various states of a saved file (whenever the user hits CTRL+s), and offer a built-in version control inside the .akira file.
I guess we could extend that to incorporate history management, so a your project comes with both all the saved versions and the full history.

I guess we could downfall to memory based history if the user didn't save the file yet.

It鈥檚 [current year], why design an app with manual saving :)

I understand the argument, and I agree for the majority of the cases, but for a design application I wouldn't implement that type of automation out of the box.
The main reasons are:

  • Don't automatically save a new file in a location not decided by the user.
  • Don't prompt the user with a save dialog when a new project starts.
  • Don't save changes that might be temporary (eg. open a file, delete an element to export something quickly, close the file without saving because the change shouldn't be permanent.)
  • Don't save a new file when not necessary (eg. create a file to trim an image and export it as smaller PNG, close it without saving the file since the user doesn't need that as a project.)

I might consider adding an option like "Autosave the file at every change", but I prefer to have it as opt-in and not opt-out, especially if no initial file has been saved.

I'm more than happy to have my opinions crushed and my ideas changed, but only if we can find solutions that don't lock the users into a workflow they might not want.

I agree with above post: auto-saving can cause problems in many situations and I don't like when software do it. There is also the issue of reducing battery time on laptops by spinning up disks when saving is not needed.

Hey, I had some thoughts on how history management could work for Akira. I feel like there are some hybrid approaches not yet considered that could provide a best-of-both-worlds solution. It sounds like between this issue and #90 there are several features desired by users:

  1. Users want a way to undo/redo atomic operations (e.g. rotating, scaling, coloring canvas items)
  2. Users want a way to undo/redo large chunks of changes (i.e. checking out/reverting git commits)
  3. Users don't want to worry about losing their work if the program crashes, but they also do not want a pure auto-save feature.

Here's my 2 cents on how I would implement a solution for all three user requirements:

Maintain an in-memory stack of edit operations for undo/redo

This is fairly straightforward. Each atomic operation/action would be defined as a class inheriting from a common interface/abstract class (e.g. IInvertableOperation). Whenever someone makes a change to a canvas item, that would be placed on an in-memory stack. To undo that change, you simply pop the operation off the top of the stack and call it's undo() implementation.

In reality, I'd imagine this may be better implemented as a doubly-linked list, moving up and down the list as the user calls undo/redo. If a user then does undo and makes a new edit without calling redo, every operation in the list past your current pointer would be deleted and the new operation would be appended.

Whenever a user hits save, serialize both the current project state and the undo/redo stack

The project state can continue to be saved as a JSON file, and the undo/redo stack can be stored as a separate JSON file as an array. By saving both, the user can commit changes to the local git repo without having to worry about losing the ability to undo atomic operations for each commit. Then in effect:

  • Each commit would represent a manual save
  • The undo/redo stack would represent the list of changes in chronological order between saves

To avoid accidental data loss, regularly automatically save in a separate branch of the git repo

Whenever the user manually chooses to save, this branch can then be merged into the master branch and deleted. The next time the program starts, if it detects the backup branch still exists it should act as though the program crashed and use the backup branch to reload lost changes.

Let me know if that aligns with what everyone was going for. I'd be happy to try to mock up some UML class diagrams for the undo/redo feature when I get some free time and possibly start to take a stab at it in code (though I've never worked w/ Vala before, so it might be a learning curve).

That sounds like a smart solution to me, perhaps with an option to limit how far undo/redo history is saved to avoid getting too large files and using too much memory. Something like saving the last 50 or last 100 actions, depending on the setting.

I guess it makes sense to first get standard undo/redo in place.
Just like @JasonKraft, I would also suggest an in-memory-stack of invertable actions (see e.g. draw2D implementation )

Once undo/redo is in place, consider versioning.

There seem to be several established options or interfaces for this. I personally like googleDoc鈥檚/Etherpads approach, where you can "star" and name states of the history (sadly not retrospectively, but this should be no problem, actually)

gDocs sidebar-style: image

Etherpads time-slider: image

Both (gDocs, etherpad), however, use operational state transforms where the whole history is kept, anyway.
However, a flag-this-for-versioning UI could also work with approaches where the history is not kept or is only kept to a limit and then "baked" into one fully stored state without history.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

TixieBorg picture TixieBorg  路  4Comments

isneezy picture isneezy  路  5Comments

Alecaddd picture Alecaddd  路  3Comments

Alecaddd picture Alecaddd  路  6Comments

AUNaseef picture AUNaseef  路  4Comments