Undo system events

So I was going to use built in Undo system for my custom property, but the problem is that my property performs some logic when it’s value changed and it seems like Undo can’t handle that.
I’ve decided to use Undo callbacks to do that and succeed with that, when undo performed I check if it is performed on certain object and if yes I perform logic.
Now I want some event that fires when Undo stack is cleared or something like that, so I can clear Undo data from my property since I’m using stack of Undo groups to identify on which object it has to be performed (Since Undo callbacks are static so they’re called on any Undo operation).
I thought that Undo.willFlushUndoRecord is the event I need but no.
So is there any workaround to do what I want?

You can see the working example of Undo.RecordObject here

//Name this script "EffectRadiusEditor"
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(EffectRadius))]
public class EffectRadiusEditor : Editor
{
    public void OnSceneGUI()
    {
        EffectRadius t = (target as EffectRadius);

        // The Begin and EndChangeChecks check for changes in the GUI state. This is not required for
        // Undo.RecordObject. Undo.RecordObject only registers changes to the target
        // after the call to Undo.RecordObject.
        EditorGUI.BeginChangeCheck();
        float areaOfEffect = Handles.RadiusHandle(Quaternion.identity, t.transform.position, t.areaOfEffect);
        if (EditorGUI.EndChangeCheck())
        {
            Undo.RecordObject(target, "Changed Area Of Effect");
            t.areaOfEffect = areaOfEffect;
        }
    }
}

If EndChangeCheck is not sufficient for your case then you manually check your property for a change to occur. For primitive data this is as simple as storing a copy into old variable before you do your post-change logic, then matching the final value with Equals or ==.

Thanks you for the answer!
But my main problem is to get notified when Undo stack is cleared if it’s even possible.

I’m not sure what you mean by “cleared” here. The Undo stack is usually not really cleared, especially when changing single properties. After Undo, things go to the Redo side, and after Redo, things go to the Undo side. Having your own Undo implementation that’s independent from Unity’s serialization is really hard. Also, sometimes it’s not even enough, as there are multiple ways things can change without touching Undo/Redo (e.g. Version Control).

If you tell us more about the logic that needs to run when a property changes, I think you’d probably get more useful information. For now, I can suggest looking into ISerializationCallbackReceiver and OnValidate for other ways to know when something changed that may work better for you.

1 Like

Thank you for the answer!
Well I was just making ObservableField class that can be modified from editor an it fires and event when the value is changed, I has custom property drawer for it, and I wanted it to work with Undo so it also will fire event when Undo/Redo performed and the only way I figured out to do it was subscribing to Undo.undoRedoEvent, but the problem was that it is a static event and it will fire on any Undo/Redo, so I had to add a Stack to my ObservableField that contains index of Undo/Redo group, so that it can check if Undo/Redo referes to it and if yes it will fire an event.
So basically I’m subscribing all instances of ObservableField properties to the Undo.undoRedoEvent and each of them just check if this Undo/Redo action applies to them, and I end up with a bunch of stacks with indices that would be good to clear at some point, but I wasn’t able to figure out at which point it should be cleared so I was looking into something like Undo.OnUndoStackClearedEvent.
Maybe you will have other suggestions, but at the moment the best idea came to me is to check if undo group index is bigger than the peek of ObservableField’s stack of indices each time ObservableField is being modified and if index of current Undo/Redo group is smaller to clear stack in ObservableField.
Hope I explained my point properly.

: ) No problem.

Hm. I think I understand now about the stack being “cleared”. While the Undo side is rarely cleared, the Redo side is often made obsolete by user changes. Maybe you could use Undo.WillFlushUndoRecord to know when a new undoable action is registered, which often invalidates the Redo stack. I don’t know if it’s triggered by selection changes, though (those don’t invalidate the Redo stack).

From experience, tracking the Undo stack seems too hard. It’s very dependent on Editor quirks and it needs to cover too many complex cases. I’d recommend looking for another approach. Some ideas:

  • If the observable event only needs to fire from MonoBehaviours in the Scene on Play mode, maybe it’s limited enough that you could remove Undo support in those cases. You could do it by binding the fields manually and using ApplyModifiedPropertiesWithoutUndo.

  • You could have a non-serialized copy of the field’s value and compare it with the serialized value to know if it changed. Then you could compare both values in ISerializationCallbackReceiver or OnValidate to detect if it changed. Use EditorApplication.delayCall to fire your event from those hooks, in case your event does something that’s not allowed in those moments. One quirk with this idea is that the change check would be triggered when the Object is loaded; you may need to add a flag to skip the first change check to deal with this.

  • You could add a small button to your UI so that users can explicitly send a change event whenever they want.

1 Like

I will look into these, thank you very much :slight_smile:

1 Like