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

Problems with accessing just DESTROYED objects

Discussion in 'Editor & General Support' started by JeffersonTD, Oct 5, 2014.

  1. JeffersonTD

    JeffersonTD

    Joined:
    Feb 5, 2013
    Posts:
    267
    The behaviour I'm experiencing with just created objects results in a lot of extra trouble. It seems that accessing items created within the same update results in either getting a reference to an item that only exists in some temporary limbo or that after accessing and making modifications to it, it will in any case be reconstructed during the next update.

    Problem 1 (Transform):


    If I create an object and set it as a parent like this:

    GameObject obj = new GameObject();
    obj.name = "foobar";
    anotherObj.transform.parent = obj.transform;​

    things work fine. But if I refer to this already created object during the same update like this:

    Transform newPar = Edges.Find("foobar");
    if(newPar != null) {
    anotherObj.transform.parent = newPar;
    }​

    The object "anotherObj" just disappears somewhere. This behaviour would be easier to understand if it happened without the null check. Then it would say that Transforms need one update round to be properly founded in the hierarchy. But the Transform is not null, and still things just disappear.

    I have a feeling that I've once earlier also experienced a case where a Transform was not null even though it wasn't really a proper reference to that transform either. What's up with this, and is there a way around the problem?

    Problem 2 (Components):

    Similarly to problem 1, if I add a component and modify it by accessing it separately like this:
    TargetGameObject.AddComponent<MeshFilter>();
    TargetGameObject.GetComponent<MeshFilter>();​
    The modifications I do to the component don't persist and next frame it's as if nothing happened to the component (mesh filter in this case). I know I can store the component reference as I add the component, but if I'd like to access that script through somewhere else, that won't do or is highly impractical.


    Summary:
    So things seem to work fine if I wait for one frame, but if I don't, I have to either build more direct references or do some other workarounds. For my current need I've worked around problem 1 by simply not creating the GameObjects through code. It's more ugly that way, but as it's only a limited set I need, it works as a solution. For problem 2 I've made a workaround that waits for one frame if the situation requires that.

    That being said, my questions are more general: what exactly happens when I access these items? Is the limbo description or the reconstruction description closer to the truth or something else? What's the best workaround to avoid these problems? Would using LateUpdate help with this? Whether it does or not, is there an easier more general solution to avoid these issues?
     
    Last edited: Oct 7, 2014
  2. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,490
    Should this "anotherObj.transform.parent = Find("foobar");" be "anotherObj.transform.parent = newPar;" ?

    Since you mention wanting to accessing this object through another script, I'm guessing the 1st code sample is run in one MonoBehavior and the 2nd code sample is running on a different MonoBehavior in its Update loop. If that's the case, you cannot know the order of execution of those MonoBehaviors. Unity will update each MB one by one based on an internal order you do not have access to. You could modify this with ScriptExecutionOrder, but that's not a good solution if you do this sort of thing a lot or if you have multiple of the same MB trying to access components on each other.

    Also, you say the object disappears somewhere. Is it gone from the hierarchy or might possibly it have been scaled or its position moved because of the parenting.

    Also, just to verify, you are creating these components in Update and not Awake, Start, OnEnable, etc? Perhaps a test sample file would help us see the problem better.
     
    JeffersonTD likes this.
  3. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,490
    This is a simple recreation of your scenario #1, but not using two different MB's in case that's not what you meant. There are no issues with this, so your example must have been more involved.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class ZZZ : MonoBehaviour {
    6.     public GameObject anotherObj;
    7.     bool done = false;
    8.  
    9.     void Update () {
    10.         if(!done) { // just do this once
    11.        
    12.             GameObject obj = newGameObject();
    13.             obj.name ="foobar";
    14.             anotherObj.transform.parent = obj.transform;
    15.             Transform newPar = GameObject.Find("foobar").transform;
    16.  
    17.             if(newPar != null) {
    18.                 anotherObj.transform.parent = newPar;
    19.             } elseDebug.LogWarning("newPar was null!");
    20.  
    21.             done = true;
    22.         }
    23.     }
    24. }
    25.  
    By the way, generally performing Find and GetComponent operations are quite expensive, so you do want to store their results if you have to use them often. It's easy enough to create a property on an object and cache the result the first time if you have a lot of objects accessing the components.
     
    JeffersonTD likes this.
  4. JeffersonTD

    JeffersonTD

    Joined:
    Feb 5, 2013
    Posts:
    267
    Ah, right. Sorry for the faulty example. And this is awkward, as that wasn't the only mistake: I meant that the object that disappears is "anotherObj" not "newPar". I should really make sure the description is as precise as possible, so again, sorry for that. It's fixed now.

    Good point, but I had considered that too. The reason why the 2nd MonoBehaviour can't be run before the first one is that it's created during this update within the 1st MonoBehaviour's Update.

    Ok, this I didn't actually know. Even though probably not a very common thing to do or for many cases not the best practice, this is good to know. Thanks for the tip.

    It's entirely gone from the hierarchy (I checked the root and did a search). Just the existence of that "anotherObj.transform.parent = newPar" makes it disappear.

    Yes, this I'm aware of. I'm making these calls only once when it's needed (triggered by player opening the game menu), so definitely not every frame.

    At some point it was actually called both on Start and once during Update, which caused a different problem, as the call in Start() wasn't really needed. But I think that's not the problem here as this issue exists even without the start call. I'll try to reproduce this in a minimum implementation class.

    In any case, thanks for you answers!
     
    Last edited: Oct 7, 2014
  5. JeffersonTD

    JeffersonTD

    Joined:
    Feb 5, 2013
    Posts:
    267
    Ok. Tried to reproduce it from scratch (which I maybe should have done from the let go) and couldn't. And now through reverting to an earlier version and doing some tweaking I got to do my original thing the way I initially wanted to do it, and it works.

    It's probably so that the Problem 2 was caused by the call in the Start and Problem 1 actually lay at least partially somewhere else: I was combining meshes and this parenting might have caused them to merge so that they sort of vanished. I saw similarity between the two problems and interpreted it as a common problem which in fact doesn't seem to exist. So all in all the main troublemaker which made me not see the real problem with problem 1 was probably the call in start. I just didn't realize it early enough.

    Again, thanks for the answers!
     
  6. JeffersonTD

    JeffersonTD

    Joined:
    Feb 5, 2013
    Posts:
    267
    Ah, guavaman (or anyone else for that matter), now I found out what probably was the real issue here! I found out that the problem was at the other end of things. So, instead of there being a problem when creating new things, it's when you Destroy things that the things aren't really destroyed during that Update(), but after it. So, here's the ZZZ class with the problem reproduced:

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class ZZZ : MonoBehaviour {
    5.     GameObject anotherObj;
    6.     public bool done = false;
    7.      
    8.     void Update () {
    9.         if(!done) { // just do this once
    10.             RedoItems();
    11.             done = true;
    12.         }
    13.     }
    14.  
    15.     void RedoItems() {
    16.         Debug.LogWarning("redoing items");
    17.         Transform oldPar = transform.Find("parent");
    18.         if(oldPar != null) {
    19.             //oldPar.name = "this is deleted";
    20.             Destroy (oldPar.gameObject);
    21.         }
    22.         GameObject obj = new GameObject();
    23.         obj.name ="parent";
    24.         obj.transform.parent = transform;
    25.         for(int i = 0; i < 5; i++) {
    26.             AddAnother();
    27.         }
    28.     }
    29.  
    30.     void AddAnother() {
    31.         GameObject anotherObj = GameObject.CreatePrimitive(PrimitiveType.Cube);
    32.         anotherObj.name ="anotherObj";
    33.         anotherObj.transform.parent = transform;
    34.         Transform newPar = transform.Find("parent");
    35.         if(newPar != null) {
    36.             anotherObj.transform.parent = newPar;
    37.         } else Debug.LogWarning("newPar was null!");
    38.     }
    39. }
    So to reproduce, just mark the "done" as false in the inspector so that it tries to recreate the stuff and now the object "parent" doesn't have any children any more.

    But luckily there is an easy way to go around this, which is renaming the object before destroying it (the commented oldPar.name = "this is deleted";).

    (ps. I renamed the title to match the issue)
     
  7. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,613
    Note that there's a second Destroy function, DestroyImmediate, which will not wait until the end of the frame but will actually destroy the object at the time you call.
     
    JeffersonTD likes this.
  8. JeffersonTD

    JeffersonTD

    Joined:
    Feb 5, 2013
    Posts:
    267
    Ah, true, alright. The documentation says though that "You are strongly recommended to use Destroy instead." Usually destroy will work just fine, so maybe I'll just do as the recommendation says and if I have situations like this, I just do the renaming before destroying.
     
  9. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,490
    Thanks for the clarifications! Glad you figured it out.

    The main issue with the destroy example is that you're doing a Find by name, which would get confused if you have two objects with the same name because of the (intentional) delayed destruction. In my view, the real solution is to not find things by name after instantiation but have a manager responsible for doing the instantiations that keeps an array of all the objects its created so you can access them as needed. Using names for identification isn't very robust.

    Be careful with DestroyImmediate and don't use it on any assets like Prefabs or textures that are stored in your project, only instances, otherwise it will delete the files.
     
  10. JeffersonTD

    JeffersonTD

    Joined:
    Feb 5, 2013
    Posts:
    267
    Thanks for the further comment. In this case the need is very specific so I know there aren't multiple items with the same name (when the duplicate is properly destroyed that is), so not much to manage here.

    Yeah, I won't be using that for now at least.