Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question In OnValidate since using Destroy and DestroyImmediate can result in errors

Discussion in 'Scripting' started by sokar92, Jul 30, 2023.

  1. sokar92

    sokar92

    Joined:
    Feb 3, 2017
    Posts:
    2
    I'm currently facing an issue while trying to destroy an object inside the OnValidate() function in Unity. It seems that using either Destroy or DestroyImmediate to remove the object during OnValidate() triggers errors or unwanted behavior.

    Is there a recommended approach for correctly destroying objects in OnValidate() without running into issues? I've tried both methods, but neither seems to work as intended.

    Any insights or suggestions on how to properly handle object destruction during OnValidate() would be greatly appreciated. Thank you in advance for your help!

    Best regards
     
  2. sokar92

    sokar92

    Joined:
    Feb 3, 2017
    Posts:
    2
  3. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,468
    Yes, don't do it. It's for validation, not destruction. If you absolutely need to do it then you need to defer the destruction until later or change your design. Add it to a list of things to destroy or do it in a coroutine or some other method, just not there.
     
    Bunny83 and sokar92 like this.
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    The solution is to use a custom editor/proper editor tooling if you require this kind of functionality. OnValidate is only for very basic validation within the same object.
     
    sokar92 likes this.
  5. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
    I haven't tested or debugged it on my end but this should do the job:
    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityObject = UnityEngine.Object;
    4.  
    5. [InitializeOnLoad]
    6. public static class UnityObjectDestroyer
    7. {
    8.     private static Queue<UnityObject> queue;
    9.  
    10.     static UnityObjectDestroyer()
    11.     {
    12.         queue = new Queue<UnityObject>();
    13.  
    14.         EditorApplication.update += OnUpdate;
    15.     }
    16.  
    17.     private void OnUpdate()
    18.     {
    19.         while (queue.TryDequeue(out var obj))
    20.         {
    21.             Destroy(obj);
    22.         }
    23.     }
    24.  
    25.     public static void Add(UnityObject obj)
    26.     {
    27.         queue.Enqueue(obj);
    28.     }
    29. }
    Example:
    Code (csharp):
    1. void OnValidate()
    2. {
    3.     // do something
    4.  
    5.     UnityObjectDestroy.Add(an_object_to_destroy);
    6. }
     
    Last edited: Jul 30, 2023
    SisusCo and sokar92 like this.
  6. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,899
    Why so complicated? ;)

    Destroy is just one thing, and OnValidate just one event where Destroy and others aren't allowed. Thus I call code that potentially does any of the "no, you can't do that here" stuff this way:
    Code (CSharp):
    1. public void OnValidate()
    2. {
    3.     this.InvokeNextFrame(() => Build());
    4. }
    And the extension class and methods for this is:
    Code (CSharp):
    1. public static class MonoBehaviourExt
    2. {
    3.     public static void InvokeNextFrame(this MonoBehaviour self, Action callback)
    4.     {
    5.         if (self.gameObject.activeInHierarchy && self.enabled)
    6.             self.StartCoroutine(DelayedCall(callback));
    7.     }
    8.  
    9.     private static IEnumerator DelayedCall(Action callback)
    10.     {
    11.         yield return null;
    12.         callback?.Invoke();
    13.     }
    14. }
    Not sure if this can be moved further down the class hierarchy (Component? Object?) so it will also work with ScriptableObject. But even so, just duplicating that extension for ScriptableObject I'd be fine with if I ever need it.
     
    Last edited: Jul 30, 2023
    sokar92 likes this.
  7. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,104
    EditorApplication.delayCall is also handy for deferring something to be executed later on the main thread.
    Code (CSharp):
    1. void OnValidate()
    2. {
    3.     if(ShouldDestroySelf())
    4.     {
    5.         EditorApplication.delayCall += DestroySelf;
    6.  
    7.         void DestroySelf()
    8.         {
    9.             // Handle case where object was already destroyed by something else during the delay
    10.             if(this != null)
    11.             {
    12.                 DestroyImmediate(this);
    13.             }
    14.         }
    15.     }
    16. }
     
    sokar92, Bunny83 and Ryiah like this.