algorithmic modeling for Rhino
I find myself a bit puzzled about the Undo mechanism in the GH sdk. I know to call "RecordUndoEvent" and give it a name - but it seems to only record certain kinds of changes and not others. What exactly is happening when I call RecordUndoEvent - and what can I do to make it take into account other kinds of changes to my component/definition/whatever? I see the set of actions under Grasshopper.Kernel.Undo.Actions, but some of the things I want to make "undo-able" involve several of these things (adding objects, changing wires, persistent data changes all at the same time). I also see all the stuff living under GH_Document.GH_UndoUtil - but I'm not clear on the distinction between Create and Record.
Anything to clarify would be most helpful!
Tags:
It was designed as follows:
Let's take a very simple example. Enabling/Disabling a component should be an undoable event. Furthermore there is only a single boolean involved (Enabled=Yes or Enabled=No). So let's assume a component is currently Enabled=Yes and the user wants to disable it. BEFORE disabling it, an undo action is created which stores the value YES. Also, when this action is created, it will be in the UNDO state, meaning it can be undone, but not redone.
After the action is constructed, it is placed inside a new undo record (in this case the record only contains a single action) and this record is given the name "disable". The record btw. is also aware that it is in the UNDO state.
Finally the record is appended to the undo stack in the document undo server, thus becoming the most recent undo record.
After all that is done, the Enabled state of the object can be safely set to False, and the solution can be recomputed.
Phew.
Now when the user invokes an undo, our record is popped off the undo stack, it is kindly informed that its time has come and Grasshopper will wait for the record to perform the steps necessary to revert. The undo record will iterate over all actions, undoing them one-by-one. The action itself simply looks up the component it is associated with and caches the current Enabled state (it should be NO, but who can really know for sure ahead of time?). It then sets the Enabled state to whatever local value it remembered from before, it will replace the local value with the cached value and finally toggle its own state from UNDO to REDO.
After all actions (only one in our example!) have undone themselves and are all in the REDO state, the record itself will switch into the REDO state and it will be pushed onto the redo stack.
--------------------------------------------------------------------------------------------------------
td;dr
Actions and Records are always in either the UNDO or REDO state, meaning you cannot undo something twice without redoing it in between. Actions are the things which actually change the document, they can be very simple (as in toggling a single boolean) or they can be very complicated. Records contain lists of actions and provide a human-friendly name.
--------------------------------------------------------------------------------------------------------
When you use the RecordUndoEvent method, Grasshopper doesn't know what it is you're about to change, so it will in fact serialize the entire component and store it as a local byte array. It will also remember all the details regarding which wires went into and came out of the component. So this approach generates big undo actions. In fact quite a bit of overkill for when you're changing simple stuff like Enabled states or NickNames. Another problem with the sledge-hammer approach is that the actions it creates must replace the entire component with a different one because they don't know what properties (if any) have changed.
If the sledge-hammer approach fails to record (or re-instate) certain changes, I'd like to know more about that.
Thanks for the effort. This is the abstract side of the issue.
The RecordUndoEvent() method calls the write() function of the component? or just iterates over the default actions?
For example for the gumball, I should implement in my class the igh_UndoAction interface to record all necessary variables? or is there an easier way? If so, you could give an example of a custom gh_UndoAction to store internal variables?
It uses Write(), and then it'll use Read() when it undoes.
Thanks for the thorough reply!!! I am dealing with some actions that perform a lot of changes at once - adding new objects, disconnecting and rewiring params - and I wanted to be able to undo those changes. Thanks to your description I was able to get it to work.
The pattern I followed, (for the future reference of others:)
1. declare a new GH_UndoRecord, give it a name
2. for all major changes to objects (ObjectsAdded, Wires, other generic changes) declare a new corresponding GH_AddObjectAction or GH_WireAction or whatever
3. Add that Action to the GH_UndoRecord declared earlier
4. THEN and only then actually make the change (adding the object to the doc, changing the wires, etc)
5. after all the actions have been recorded, pass the UndoRecord to GH_Document.UndoUtil.RecordEvent.
ta da! Undo and Redo now work beautifully.
Thanks again for all the help!
Glad you got it to work, it's not the most straightforward part of the SDK.
+1
I also need to know about this, there is also very little documentation on the forum and about custom undo I think none. It is mysterious, because with the method RecordUndoEvent(name) you do not specify what you want to save. Perhaps this only work well when the component has assigned parameters in the internal table of values? I mean GH_Component.GetValue(name, default) / .SetValue(name, newvalue).
Andrew please let me take advantage of your question to ask the mine also.
I need to know how to store in the undo/redo table of the document a gh_chunk, is this possible? I think there are no direct methods to do this, and the problem that I have is that usually the write() methods require a parameter of type gh_iwriter, which I dont know what I should give it because I have not found how to access to that instance of the current document. How I can do this?
EDIT* written before David's response, published later.
You can create your own undo actions, since undo data is not currently stored in the file anyway that will not be a big problem. Once you have your own actions you can provide a much more efficient undo process. And of course you can choose to store whatever data you want inside your own actions.
Here's the code for the action which undoes/redoes the Locked (i.e. Enabled) state of an object. VB I'm afraid since it's Grasshopper core.
Public Class GH_LockedAction
Inherits GH_ObjectUndoAction
Private m_locked As Boolean
Public Sub New(ByVal obj As IGH_ActiveObject)
MyBase.New(obj.InstanceGuid)
m_locked = obj.Locked
End Sub
Public Overrides ReadOnly Property ExpiresSolution() As Boolean
Get
Return True
End Get
End Property
Protected Overrides Sub Object_Undo(ByVal doc As GH_Document, ByVal obj As IGH_DocumentObject)
If (Not TypeOf obj Is IGH_ActiveObject) Then Throw New GH_UndoException("Object is not of type IGH_ActiveObject")
Dim act_obj As IGH_ActiveObject = DirectCast(obj, IGH_ActiveObject)
Dim new_enabled As Boolean = act_obj.Locked
act_obj.Locked = m_locked
act_obj.Attributes.GetTopLevel.ExpireLayout()
m_locked = new_enabled
End Sub
Protected Overrides Sub Object_Redo(ByVal doc As GH_Document, ByVal obj As IGH_DocumentObject)
Object_Undo(doc, obj)
End Sub
End Class
Thank you David.
This code works with in a cicle true-false i guess... I am not capable of collecting more than one undo in a example with an integer. The second time I press ctrl+z appears me a gh breakpoint "Undo failed: conflicting undo state". Attached example.
"(...) since undo data is not currently stored in the file (...)"
is not writted in the file as binary you mean? But in the document as a temporary variable which is initiated when you open the document of the file? or stored otherwise with windows? If so, can you tell me about it a technical name to search for information?
...
...
Bff, I tried many different things without success, I do not know where I'm failing. D:
I guess this works as follows, please correct me if I'm wrong.
- Instantiate MyCustomUndoAction as CustomAction in somewhere.
- RecordUndoEvent ("Name", MyCustomUndoAction).
- MyCustomUndoAction is stored in a table of undo actions? But how is it stored? The same instance? a duplicate?
When pressed ctrl + z.
- The document runs the last GH_UndoAction of the UndoActions table/list.
- Runs the undo event. It depends on the class, and this is where I have doubts.
- This action is removed from the table and added to the list/table of redo actions?
I have a class that implements the interface IGH_UndoAction. This has an internal variable GH_LooseChunk. When undo is executed, first I make a new GH_LooseChunk that stores the serialization of the current gumball class. Then I de-serialize the internal variable and I send that data to the gumball. Then i assign the internal variable with the new chunk. But for some reason this is not working. The write() and read() methods of the interface, at what point are executed?
My problem was that I was using the same instance to store actions, rather than create a new instance for each new action. Also, strange things happened to have data stored in .GetValue("name "object), I have removed them.
Perhaps to someone will be useful this clarification.
Welcome to
Grasshopper
Added by Parametric House 0 Comments 0 Likes
Added by Parametric House 0 Comments 0 Likes
Added by Parametric House 0 Comments 0 Likes
Added by Parametric House 0 Comments 0 Likes
Added by Parametric House 0 Comments 0 Likes
© 2024 Created by Scott Davidson. Powered by