Search Unity

How to know if an object was affected by UNDO operation?

Discussion in 'Scripting' started by EnriqueGato, Sep 14, 2017.

  1. EnriqueGato

    EnriqueGato

    Joined:
    Sep 3, 2016
    Posts:
    81
    I really need help on this one guys.

    I have multiple objects on a scene that captures UNDO operation in the editor through OnUndoPerformed event, and does heavy tasks in the editor when that event is triggered.

    Code (CSharp):
    1. // Catch UNDO operation
    2. void OnValidate()
    3. {
    4.     Undo.undoRedoPerformed += OnUndoPerformed;
    5. }
    6.  
    7. void OnUndoPerformed()
    8. {
    9.     <Do Heavy Operations>
    10. }
    The thing is, every time you hit Ctrl+z the performance of the scene is very low because of these tasks. I'm trying to optimize the code doing those heavy operations only when UNDO operation changes the object with this script. Something like this:

    Code (CSharp):
    1. void OnUndoPerformed()
    2. {
    3.     If (<this object was changed by UNDO>)
    4.         <Do Heavy Operations>
    5. }
    Is there any way to know what objects where affected by UNDO operation?
     
    Last edited: Sep 22, 2017
    burningmime likes this.
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
  3. EnriqueGato

    EnriqueGato

    Joined:
    Sep 3, 2016
    Posts:
    81
    Thanks for your answer. I've updated the very first post to be more clear on what I need.
     
  4. EnriqueGato

    EnriqueGato

    Joined:
    Sep 3, 2016
    Posts:
    81
  5. TaleOf4Gamers

    TaleOf4Gamers

    Joined:
    Nov 15, 2013
    Posts:
    825
    We cant help you optimise your heavy task if we dont know what it is.
    Also OnValidate could get called multiple times adding the method again no? So you might be running that function multiple times.
     
  6. EnriqueGato

    EnriqueGato

    Joined:
    Sep 3, 2016
    Posts:
    81
    Oh, I'm not trying to optimize those heavy tasks. I have to do them anyway. I'm just trying to do them only if UNDO operation changed the object with the script, not every time UNDO is performed.

    Again, I changed the very first post to explain the whole thing better.
     
  7. EnriqueGato

    EnriqueGato

    Joined:
    Sep 3, 2016
    Posts:
    81
    One last try. I'm really stuck on this one. Up again!
     
  8. Siccity

    Siccity

    Joined:
    Dec 7, 2013
    Posts:
    255
    You're not the only one. Up
     
  9. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Gonna revive this old thread. I'm trying to work around it with version numbers and stuff, but this would be very helpful
     
  10. FishToast

    FishToast

    Joined:
    Dec 17, 2012
    Posts:
    4
    encountered this thread while solving it for my case.
    in my case I used Time.frameCount, OnValidate, and Undo.undoRedoPerformed to match the undo to the target object.
    because it appeared that OnValidate and Undo.undoRedoPerfomed happened on the same editor frame. I will update you here if this does not hold up.

    as a side note, it appears that in Unity 2022 there is some progress made to make this easier but this appears to not directly address which object had Redo/Undo done on but just give you slightly more information as in groups and the name of the record but I am currently using 2021 so I did not inspect it further.
     
    CodeSmile likes this.
  11. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,975
    @FishToast A code example would be appreciated!

    Though on first thought I think this solution may be very brittle because Time.frameCount will be identical for multiple (dozens) EditorApplication.update calls, so when a user is quickly hitting undo/redo you may actually stack multiple Undo/Redo operations onto the same frameCount number.

    The improvement in Unity 2022 relates to undoRedoEvent which gives you UndoRedoInfo as a parameter and that ends up giving you the group (a number), the name (what's seen in the menu) and whether it is a redo operation. Unfortunately no info about the affected objects.

    It should be possible with the Undo postprocessing event. This gives you access to a target property but you risk generally slowing down undo/redo operations if you're not super-careful.
     
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    You ever find yourself working on something, then run into a wall, so you google it and find a thread that you were part of years prior???

    Well that was me today.

    Thank you @CodeSmile for pointing out the new version of underRedoEvent that includes the groupid and groupname (the thing I specifically was looking for), so not exactly OP's needs, but close enough.

    But also I'm unfortunately in Unity 2021, and we're not moving off of it, especially not for this little thing.

    So I wrote up my own version that emulates it, and did so in a way that will just automatically transition to the 2022.2 way if/when we move to that version for future projects.

    It can be found here:
    https://github.com/lordofduct/space...work/com.spacepuppy.core/Editor/src/SPUndo.cs

    Or the current state of it as of today:
    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using System.Reflection;
    6.  
    7. namespace com.spacepuppyeditor
    8. {
    9.  
    10.     [InitializeOnLoad]
    11.     public static class SPUndo
    12.     {
    13.  
    14.         public struct UndoRedoInfo
    15.         {
    16.             public string groupName;
    17.             public int groupId;
    18.             public bool isRedo;
    19.         }
    20.  
    21.         public static event System.Action<UndoRedoInfo> undoRedoEvent;
    22.  
    23. #if UNITY_2022_2
    24.         static SPUndo()
    25.         {
    26.             Undo.undoRedoEvent += UndoRedoEventHandler;
    27.         }
    28.  
    29.         static void UndoRedoEventHandler(in UnityEditor.UndoRedoInfo info)
    30.         {
    31.             undoRedoEvent?.Invoke(new UndoRedoInfo()
    32.             {
    33.                 groupId = info.undoGroup,
    34.                 groupName = info.undoName,
    35.                 isRedo = info.isRedo,
    36.             });
    37.         }
    38. #else
    39.         delegate void GetRecordsDelegate(List<string> records, out int cursor);
    40.  
    41.         static GroupInfo _lastGroup;
    42.  
    43.         static List<string> _records = new List<string>();
    44.         static int _cursorPos;
    45.         static GetRecordsDelegate _getRecordsCallback;
    46.  
    47.         static SPUndo()
    48.         {
    49.             unsafe
    50.             {
    51.                 var tp = typeof(Undo);
    52.                 var methinfo = tp.GetMethods(BindingFlags.Static | BindingFlags.NonPublic).Where(o => o.Name == "GetRecords").FirstOrDefault(o =>
    53.                 {
    54.                     var parr = o.GetParameters();
    55.                     return parr.Length == 2 && parr[0].ParameterType == typeof(List<string>) && parr[1].IsOut;
    56.                 });
    57.                 if (methinfo != null)
    58.                 {
    59.                     _getRecordsCallback = methinfo.CreateDelegate(typeof(GetRecordsDelegate)) as GetRecordsDelegate;
    60.                 }
    61.                 if (_getRecordsCallback == null)
    62.                 {
    63.                     Debug.Log("SPUndo - failed to locate Undo.GetRecords, this version of Unity is not supported.");
    64.                 }
    65.             }
    66.             if (_getRecordsCallback == null) return;
    67.  
    68.             Undo.undoRedoPerformed += UndoRedoPerformed;
    69.             Undo.willFlushUndoRecord += WillFlushUndoRecord;
    70.            
    71.             //initialization in editor sometimes happens outside of the main loop and 'GetRecords' call only works on main loop
    72.             EditorHelper.Invoke(() =>
    73.             {
    74.                 _lastGroup = GroupInfo.GetCurrent();
    75.             }, 0f);
    76.         }
    77.  
    78.         static void UndoRedoPerformed()
    79.         {
    80.             if (_getRecordsCallback == null) return;
    81.  
    82.             var cur = GroupInfo.GetCurrent();
    83.  
    84.             UndoRedoInfo outinfo;
    85.             if (cur.cursor > _lastGroup.cursor)
    86.             {
    87.                 //redo
    88.                 outinfo = new UndoRedoInfo()
    89.                 {
    90.                     groupName = cur.name,
    91.                     groupId = cur.id,
    92.                     isRedo = true,
    93.                 };
    94.             }
    95.             else
    96.             {
    97.                 //undo
    98.                 outinfo = new UndoRedoInfo()
    99.                 {
    100.                     groupName = _lastGroup.name,
    101.                     groupId = _lastGroup.id,
    102.                     isRedo = false,
    103.                 };
    104.             }
    105.  
    106.             _lastGroup = cur;
    107.             undoRedoEvent?.Invoke(outinfo);
    108.         }
    109.  
    110.         static void WillFlushUndoRecord()
    111.         {
    112.             if (_getRecordsCallback == null) return;
    113.  
    114.             var cur = GroupInfo.GetCurrent();
    115.             if (cur.id != _lastGroup.id)
    116.             {
    117.                 _lastGroup = cur;
    118.             }
    119.         }
    120.  
    121.  
    122.         struct GroupInfo
    123.         {
    124.             public string name;
    125.             public int id;
    126.             public int cursor;
    127.  
    128.             public static GroupInfo GetCurrent()
    129.             {
    130.                 _getRecordsCallback?.Invoke(_records, out _cursorPos);
    131.                 return new GroupInfo()
    132.                 {
    133.                     name = Undo.GetCurrentGroupName(),
    134.                     id = Undo.GetCurrentGroup(),
    135.                     cursor = _cursorPos,
    136.                 };
    137.             }
    138.  
    139.         }
    140.  
    141. #endif
    142.  
    143.     }
    144.  
    145. }
    This is "mostly" self-contained aside from lines 72-75:
    Code (csharp):
    1.             //initialization in editor sometimes happens outside of the main loop and 'GetRecords' call only works on main loop
    2.             EditorHelper.Invoke(() =>
    3.             {
    4.                 _lastGroup = GroupInfo.GetCurrent();
    5.             }, 0f);
    I need to get the "initial" state on initialize, but because that may not always occur on the main loop in editor we need to ensure it does so. This is what my custom 'EditorHelper.Invoke' does, it just calls the delegate the next time EditorApplication.update fires.

    If someone wants this you could replace it with an 'EditorCoroutine' (unity package that can be added through the package manager), or just inline the update callback:
    Code (csharp):
    1.             EditorApplication.CallbackFunction callback = null;
    2.             callback = () =>
    3.             {
    4.                 EditorApplication.update -= callback;
    5.                 _lastGroup = GroupInfo.GetCurrent();
    6.             };
    7.             EditorApplication.update += callback;
     
    CodeSmile likes this.