Search Unity

OnValidate and destroying objects

Discussion in 'Scripting' started by StarManta, Jul 24, 2014.

  1. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    I'm using OnValidate (first time I've used that function, incidentally), and I've run into a significant and nonsensical problem with it: there's no way to destroy objects. Destroying (and recreating) child obejcts is like 99% of what I would ever want to use OnValidate for.

    Let me elaborate. So I tried to use DestroyImmediate, because this is an editor function and that's what you do in the editor. It gives me this:
    Code (csharp):
    1.  
    2. Destroying GameObjects immediately is not permitted during physics trigger/contact, animation event callbacks or OnValidate. You must use Destroy instead.
    3. UnityEngine.Object:DestroyImmediate(Object)
    4. BranchGenerator:OnValidate() (at Assets/BranchGenerator.cs:19)
    5. UnityEditor.DockArea:OnGUI()
    6.  
    Weird, I think. Why would it want me to use Destroy in OnValidate, which can only ever be run outside play mode? I give it a shot, and as expected:
    Code (csharp):
    1.  
    2. Destroy may not be called from edit mode! Use DestroyImmediate instead.
    3. Also think twice if you really want to destroy something in edit mode. Since this will destroy objects permanently.
    4. UnityEngine.Object:Destroy(Object)
    5. BranchGenerator:OnValidate() (at Assets/BranchGenerator.cs:19)
    6.  
    So, let me reiterate: Why would it want me to use Destroy in OnValidate, which can only ever be run outside play mode? Also, how the hell am I supposed to destroy objects in OnValidate?

    (And on a related note: I've never understood the reason for having Destroy and DestroyImmediate be two different functions. Just have Destroy see if we're in Editor mode, and if appropriate call DestroyImmediate.)
     
  2. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    There is a more fundamental difference to those functions. DestroyImmediate() really does immediately destroy the data right away whereas Destroy() waits until script execution has finished and only then destroys the object(s). This is much safer as it won't destroy objects that are actively being worked on.

    Also, it's actually the reverse concerning play mode, i.e. you can only call Destroy() while *in* play mode.

    However, the situation you are describing with one referring you to the other and producing a stalemate where you can call neither is a rather unfortunate one. That warrants deeper investigation...
     
    phobos2077, angrypenguin and eelstork like this.
  3. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    I understand they behave differently, but why two different functions? Why not simply note the different behavior in the docs - "When called from outside Play Mode, Destroy will destroy the object immediately; when called from inside play mode, it will wait until the end of script execution." There is never a situation where you can actually choose which one to use, right? It's always one or the other.

    In the current state of things, I have to do this ridiculousness anytime I have a generator function that may be called from either play mode or the editor:
    Code (csharp):
    1.  if (Application.isPlaying) Destroy(thing);
    2. else DestroyImmediate(thing);
     
    Ghosthowl likes this.
  4. PrefabEvolution

    PrefabEvolution

    Joined:
    Mar 27, 2014
    Posts:
    225
    You can't destroy object from OnInvalidate(), but you can:
    Code (csharp):
    1.        
    2. void OnValidate()
    3. {
    4.      UnityEditor.EditorApplication.delayCall+=()=>
    5.      {
    6.           DestroyImmediate(objectToDestroy);
    7.      };
    8. }
    9.  
     
  5. eelstork

    eelstork

    Joined:
    Jun 15, 2014
    Posts:
    221
    I know this is 6 months old so, in case this is still of any interest to you...

    In edit mode, DestroyImmediate remains unsafe. I'm encountering cases where the editor crashes on me, or hangs, because it's not happy about when I invoke this function. Yes, these are often the cases when it turns out to be most useful.

    You can postpone destruction in a variety of ways as PrefabEvolution already suggested.

    Even so, DestroyImmediate has the tricky advantage that it does what it says, and does it right away. Having unwanted objects lingering inside the runtime often ends up with code doing things with an object that shouldn't be there.
    Although its use is strongly discouraged, calling DestroyImmediate() at runtime isn't technically forbidden (unless that changed recently?); you do it at your own risk - a risk no greater than deleting Objects in C++.

    Since I'm strongly against GC style object management (because I don't want objects to exceed their welcome in my runtime) I would rather these two functions continue to exist side by side.

    Destroy() is still very useful and reliable enough in 99% of cases. Would be nice to be able to use it in edit mode; I understand edit mode would never honour the destroy request in the current implementation. Does it mean it cannot be made to work?
     
    Danny-vda likes this.
  6. voldemarz

    voldemarz

    Joined:
    Sep 19, 2011
    Posts:
    25
    The issue is still there in Unity 4.6.3, but now attempting to use DestroyImmediate() results in slightly different message:
    Destroying components immediately is not permitted during physics trigger/contact, animation event callbacks or OnValidate. You must use Destroy instead.​
     
  7. Hyp-X

    Hyp-X

    Joined:
    Jun 24, 2015
    Posts:
    438
    Still displaying this confusing (and ultimately invalid) message in 5.3.4p3
    It would be even better to include a link to this thread in the error message...
     
    Laiken likes this.
  8. Deni35

    Deni35

    Joined:
    Jul 10, 2012
    Posts:
    43
    If you will not make scene dirty, changes will not be saved.
     
  9. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    This just saved me. <3
     
  10. atomicjoe

    atomicjoe

    Joined:
    Apr 10, 2013
    Posts:
    1,869
    2019 and still saving asses.
    You're a genius! :D
     
  11. Kokowolo

    Kokowolo

    Joined:
    Mar 26, 2020
    Posts:
    60
    Yea, this worked perfectly for me. Huge thanks for this crazy piece of code.
     
  12. unity_BDp5fSWQbqegRA

    unity_BDp5fSWQbqegRA

    Joined:
    May 7, 2018
    Posts:
    4
    Thank you! For some reason I was able to run:

    IEnumerator Destroy(List<GameObject> list)
    {
    yield return new WaitForEndOfFrame();
    foreach (GameObject go in list) {
    DestroyImmediate(go);
    }

    until today when I changed some references from Scene objects to Scriptable objects. Baffles me why this worked before, haha.
     
  13. Graph

    Graph

    Joined:
    Jun 8, 2014
    Posts:
    154
    This ridiculousness is still alive and well in 2020.2.7, 8 years later
     
    mirage3d and atomicjoe like this.
  14. atomicjoe

    atomicjoe

    Joined:
    Apr 10, 2013
    Posts:
    1,869
    Friendly advice here!
    Instead of DestroyInmediate, use the Undo version for it: otherwise you can't undo the destruction!
    This code will take care of the destruction in editor + make it undoable :)
    Code (CSharp):
    1. void OnValidate()
    2. {
    3.      UnityEditor.EditorApplication.delayCall+=()=>
    4.      {
    5.           UnityEditor.Undo.DestroyObjectImmediate(objectToDestroy);
    6.      };
    7. }
    Oh and by the way, you can use it for multiple objects (I use it to destroy several components that require each other) and it will still record a single UNDO step, which is nice :)

    Code (CSharp):
    1. void OnValidate()
    2. {
    3.      UnityEditor.EditorApplication.delayCall+=()=>
    4.      {
    5.           UnityEditor.Undo.DestroyObjectImmediate(objectToDestroy_1);
    6.           UnityEditor.Undo.DestroyObjectImmediate(objectToDestroy_2);
    7.           UnityEditor.Undo.DestroyObjectImmediate(objectToDestroy_3);
    8.           UnityEditor.Undo.DestroyObjectImmediate(objectToDestroy_4);
    9.      };
    10. }
     
    dawidk1, Xrank and Kokowolo like this.
  15. maewionn

    maewionn

    Joined:
    Jan 18, 2016
    Posts:
    38
    Ahahaha, this is till a thing. Unity's great 5-years of doing nothing for basic functionality hits once more.
     
  16. atomicjoe

    atomicjoe

    Joined:
    Apr 10, 2013
    Posts:
    1,869
    To be honest, this issue is the least of my concerns about Unity.
     
    TorbenDK and maewionn like this.
  17. maewionn

    maewionn

    Joined:
    Jan 18, 2016
    Posts:
    38
    For all those in future, oddly enough, it works if you go via a coroutine:
    Code (CSharp):
    1. void OnValdidate(){
    2.             foreach (Transform child in transform) {
    3.                 StartCoroutine(DestroyGO(child.gameObject));
    4.             }
    5. }
    6.     IEnumerator DestroyGO(GameObject go) {
    7.         yield return new WaitForSeconds(0);
    8.         DestroyImmediate(go);
    9.     }
     
    ILonion, atifuae22, AlienOP and 2 others like this.
  18. Forrest-Trepte

    Forrest-Trepte

    Joined:
    Jan 22, 2013
    Posts:
    8
    Thanks @maewionn! Using Unity 2020.3 this Coroutine solution was the only approach that I was able to get working when running in the editor.
     
    maewionn likes this.
  19. kevwills

    kevwills

    Joined:
    Feb 9, 2018
    Posts:
    5
    Also here in 2021, wondering why DestroyImmediate is blocked from being used in OnValidate. Thank you for the Coroutine solution
     
  20. Xanasavin

    Xanasavin

    Joined:
    May 3, 2021
    Posts:
    1
    I know this is an old thread but another approach would be to implement your own Destroy() Method in the MonoBehaviour you want to destroy and call it with Invoke()
     
  21. jakesee

    jakesee

    Joined:
    Jan 7, 2020
    Posts:
    79
    Still the same "feature" in 2023 :)

    I now use the following as advised above.

    Code (CSharp):
    1. void DeinitializeMesh() {
    2.             if (terrain != null && meshObject != null) {
    3.                 UnityEditor.EditorApplication.delayCall += () =>
    4.                 {
    5.                     meshObject.SetActive(false);
    6.                     DestroyImmediate(meshObject);
    7.                     meshObject = null;
    8.                 };
    9.             }
    10. }
     
  22. Yunasawa

    Yunasawa

    Joined:
    Nov 7, 2022
    Posts:
    2
    Made it into an extension method so you can put this in any static class and call it more easily
    Code (csharp):
    1.  
    2.     public static void DestroyOnValidate(this Object component)
    3.     {
    4.         UnityEditor.EditorApplication.delayCall += () =>
    5.         {
    6.             MonoBehaviour.DestroyImmediate(component);
    7.         };
    8.     }
    9.  
     
  23. Just for clarification for beginners, if you take away one thing from this thread, you should take this:

    You aren't supposed to change anything in
    OnValidate
    method.

    As the name of the method says it is for validation, check if your component's properties and references are ok. You should not change anything inside. Officially. You obviously can ignore this and do change stuff on your own risk and preferably not blaming Unity if your change doesn't work inside
    OnValidate
    like some did above.
     
  24. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,935
    Modifying values within the same Unity asset is fine with
    OnValidate()
    . As I understand, it happens during re-serialisation of an asset. As is noted in the docs for ISerializationCallbackReciever, you shouldn't reach outside of a Unity object during serialisation. Which is why you get warnings when trying to do a number of things in OnValidate, as you're breaking Unity's single-threadedness.

    This also means OnValidate runs a whole lot more than you'd expect. During every inspector repaint, during domain reloads, during a build, etc etc. Doing too much stuff in OnValidate is a good way to slow your project down.

    It's really only meant for very basic data validation as you note. But you can at least modify values within the same object safely.

    However for anything beyond the absolute basic level validation, custom editor tools should be used. Though a lot of folks seems pretty adverse to ever touching editor tooling.