I'd like to hear what the canonical way of doing the following sequence, is:
# Start a chain of undo actions
# ... Whatever actions that need undo and their associated calls to doc.AddUndo
# Call EventAdd() before ending the undo chain
# ..., then end the undo chain
# Start a chain of undo actions
# ... Whatever actions that need undo and their associated calls to doc.AddUndo()
# End the undo chain first
# ..., and then call c4d.EventAdd()
In other words, should the call to c4d.EventAdd() come before or after the call to EndUndo() on the document object. I've seen code doing it both ways and am wondering if makes any difference or if there is some advantage to one of the two orderings of calls.
If the two calls are done one right after the other I don't think it really matters.
However, from point of view of scope it would make more sense to first perform doc.EndUndo(), in order to "close" the do/undo as a whole, and then inform the application that something has changed, calling c4d.EventAdd().
Hi @mikegold10 as @C4DS in reality it doesn't matter that much.
First of all, remember that both calls can only be done from the main thread, so that means the main thread will be blocked until your script is executed completely.
doc.EndUndo, will set the current state of doc to be the final state of the undo handling, this is a synchronous call when this line is executed, this is at this time that the state is saved.
EventAdd, while being also an asynchronous call, will only have the duties to put an update event on the core event stack.
This core event stack is processed by the main thread when the main thread is free, and since you are also in the main thread, its most likely that your doc.EndUndo will be in any case executed and processed previously.
But to avoid any issue, it's safer to call EndUndo before.
@m_adam Hi Maxime, thanks for your detailed reply. I had a feeling that EventAdd() performed an asynchronous operation (i.e., placed an event on the event queue) and therefore it didn't matter, since control of the main thread would not be relinquished until both EndUndo() were called (regardless of sequence). However, as C4DS and you yourself pointed out, it makes more sense to call EndUndo() prior to EventAdd().
This is not only true from a safety perspective, but the calls to StartUndo() and EndUndo() can be wrapped in a Python Context Manager, along exception handling and cleanup to be done automatically and correctly when a scope is exited. If one is going to undo whatever operation in case of an error, anyways (e.g., as part of said exception handling), it makes sense to call c4d.EventAdd() after we know that the entire action was successfully performed.
I am going on the assumption that if my code performs some action inside of a StartUndo() / EndUndo() sequence, possibly consisting of multiple sub-actions and changes resulting in multiple AddUndo() calls, then sees mid-action that there is an and it cannot continue, and assuming the Start/End undo sequence is wrapped in a Python context manager class. Python will call EndUndo() on my behalf, as part of exiting the block governed by the context manager and then in the exception handler, I can perform an undo of the last action, undoing whatever sub-action did perform successfully (and added their AddUndo() pieces). Since this will hopefully leave everything in the same state that it was before the action was started, I am going to assume that there is no point in calling c4d.EventAdd() after the undo of the action is performed in my exception handler.
To summarize, here is an example scenario:
with MyUndoContextManager(doc) as undo_manager: # Calls StartUndo() as part of its Context Manager __enter__() method
# undo_manager will make the calls to AddUndo() based on our actions
DoOneLastThingAndCallAddUndoOnTheDoc(undo_manager); # Oh, oh, fails and throws exception
# Note: EndUndo() will get called as part of the implicit call to MyUndoContextManager's __exit__() method
# which will automatically get called when we exit this block due to the exception being thrown
c4d.EventAdd(); # This code will not get reached due to exception being thrown above
# An error occurred, get back to the initial state prior to the action
# Undo whichever action sub-steps were performed successfully and AddUndo()s got called for, if any
# Since the undo_manager object no longer exists, call DoUndo() on the doc object, directly
# Since, we reverted to the initial state before the action, there is no need to call c4d.EventAdd(), right?