Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Lets talk Undo Systems.

Discussion in 'Scripting' started by techmage, Oct 1, 2014.

  1. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    So I am attempted to put together an Undo system in kind of a world builder app.

    I am curious to know if anyone has put alot of thought into this already and how did you go about it?

    The ultimate system that came to my mind is some class through which all operations are abstracted and stored as very generic types. So you would issue a command like, move position to here, create memory link to here, set to null, and it would always be stored as base level memory operations and memory id's. So one function through which operations are routed could do anything and everything, set position on a Transform, add or remove custom classes from generic lists. Whatever. However I can't conceive of how you would do this in C#. I am imagine it'd probably be something heavy with generics programming, but I'm not a master at that. Anyone highly knowledgable on this, could you potentially add some thought to this? Or how else one could do a really automated, slick, undo system?
     
  2. cranky

    cranky

    Joined:
    Jun 11, 2014
    Posts:
    180
    Nah, you just need to make an ICommand interface. I usually add three functions to the interface: Do(), Undo(), and Redo(). Then you write a MoveCommand class and a class for every other action your program can perform. Make some variables and set them either in the constructor, with a property, or just make them public (Vector3 oldPosition, Vector3 newPosition), and implement the three functions. The reason Do and Redo are separate is mostly to eliminate the need for expensive calculations (for example, lightmapping) from needing to be recalculated.

    Then make an UndoManager class. Use two Stacks of ICommand, one the Undo stack and the other the Redo stack. Then make a function like ExecuteCommand(ICommand command). That function should call ICommand.Do(), and then push the command onto the Undo stack. Add Undo() and Redo() to the UndoManager. Inside Undo(), pop the Undo stack, execute the Undo() of the ICommand, then push it onto the Redo stack. Redo should work similarly (pop Redo stack, execute Redo(), push onto Undo stack).

    Also, don't forget that ExecuteCommand should clear the Redo stack!

    I think that's a general overview. Undo/Redo always surprised me as it seems like the most basic function a program can have, but actually requires a lot of thought to effectively implement!
     
    blizzy likes this.
  3. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I agree, @cranky's approach is the pretty standard design pattern. I would only add that you don't really need two separate stacks; I keep just one stack, plus an index into the current position. So anything before that position is for undo; anything at or after that position is for redo. When you "push" a new action, you just resize the list to the current position first (wiping out all the redo options).

    And yeah, getting Undo/Redo right is a pain. It was one of the chief innovations when the Mac came along, and even today, developers often don't get it right (or bother to do it at all!).
     
    blizzy likes this.
  4. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    Why use the ICommand interface? That seems like someone built into .NET specific to .NET windows form UI. Is that true? Or does it have some benefit to use ICommand in Unity where it won't be touching any windows form ui?
     
  5. cranky

    cranky

    Joined:
    Jun 11, 2014
    Posts:
    180
    No, I mean make your own interface. I just used ICommand as an example name, sorry. IAction is probably a little better of a name anyways.
     
  6. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Of course the cool thing about namespaces is, you could call your own interface ICommand, and this would be entirely unrelated to System.Windows.Input.ICommand.

    (Not that you should, since as @techmage cleverly demonstrated, this could lead to confusion. But you could if you really wanted to!)