Deep Dive on Unreal Undo/Redo Transaction System

I’m looking for any low level information how how the editor’s Undo/Redo Transaction System works.

We are trying to investigate why an actor is in a bad state after an undo operation resolves.

This bad state is the actor ends up with a duplicate component, but only one is visible in the details view. After copy/paste of this bad actor the new actor has two components.

There is an some custom logic that fires in PreEditUndo to destroy the component in question (this is desired behaviour to resolve a different issue). It seems like destroying the component at this step causes it to be re-added at the end of the transaction. We want to understand why.

What is the intended use and best practices of PreEditUndo/PostEditUndo?

Hi Sora,

So generally we use PreEditUndo and PostEditUndo as way to clean data that can’t be track by the transaction system. The pattern is often something like this

PreEditUndo <- Release some tracked resource or cache some data

The transaction does its thing on the objects.

PostEditUndo <- Rebind to the tracked resource or use the cached data to determine some invalidation scheme.

Note that in UObject::PreEditUndo we do call PreEditChange and in the UObject::PostEditUndo we do call PostEditChange also.

The logic that does the Undo/Redo can be found here (EditorTransaction.cpp ->void FTransaction::Apply() ) if you are looking for the implementation details.

As for the transaction system it simply use the reflection system and some meta data to tell it what to track or not in the transactions. When we are recording a transaction the system will track the new object created if they are flagged with the RF_Transactable flag. For the other modifications, the system will simply capture the state of the object when modify is called on it and then capture the state of the object again at the end of the transaction. Then we simply store the delta between the first capture and the second one and some flag that where changed.

As to why you do end up with two components, I can’t give you a definitive answer.

I would suggest investigating the issue from two sides:

First you can check quickly if the transaction is creating the component in the undo by using the Transaction details view in the Undo History tab. Look if the capture has a pending kill flag change on the component that was deleted or created. If you don’t see that component there, It means that the component is created by some logic that is probably outside of the transaction system . The undo history tab can be found in the edit menu of the level editor.

[Image Removed]

The second way to find out what is happening would be to put a breakpoint somewhere that would allow you see what is creating or adding a component to the actor and then walk up the call stack. A data breakpoint the address of the num property of the component sets of your actor should be able to do that. Look for those on your actor via those members: OwnedComponents (All component owned by the actor, include the c++ created component and the other from the two next sets), InstanceComponents (Component added via the editor details panel for the actor or the editor scripting utilities) and finally take a look at BlueprintCreatedComponents (Those have a bit of unique lifecycle since they get destroyed and recreated by subobject graph on each modification to the instance of a blueprint actor).

Please let me know if this is not helping you.