- Home /
Does Destroy remove all instances?
I want to know if Destroy(obj) removes all references to that object (garbage collection permitted).
I have a Manager class that keeps a List[GameObjects]
(note: [ = < ) which each of those objects holds a toBeRemoved:bool
so on the Manager's Update() the GameObjects can all be removed at once.
public void Update(){
...
foreach(GameObject obj in entityList){
if(obj.getComponent<EntityScript>().toBeRemoved){
Destroy(obj);
}
}
...
}
do I need to do additional bookkeeping to remove references to this object from the list I am stepping through?
will this cause a segmentation/fragmentation of the list?
if a reference to the object is held on other scripts will those references also be removed, or do I need to do bookkeeping to remove those manually?
Answer by Bunny83 · Jan 09, 2013 at 11:59 PM
You have to understand how Unity works behind the scenes. Mono / .NET is a garbage collected language and it works exactly the same in Unity. In most managed languages / environments there is no manual way to destroy objects. Objects get destroyed by the GC when you "clear" all references to the object.
However a lot objects in Unity, to be more precisely all that are inherited from UnityEngine.Object, consists of two parts. A managed object and a native code c++ class in the engine itself. In the managed environment you only "see" the managed object that is "linked" with the native part. This is where two totally different worlds collide.
When you call destroy on the managed object, Unity will destroy the native code part of the object immediately (well DestroyImmediate will since Destroy is delayed one frame). The managed part of the class is still there! Unity will flag the class internally as "destroyed". Unity has implemented a custom == operator and Equals function. In those functions unity will pretend the reference is null even when it isn't. This makes it basically impossible to do anything with the reference at all once it's marked as destroyed.
The managed part will be destroyed by the GC when there is no reference to the object left.
You can simply test this with the following two classes:
// Test.cs
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour
{
private static int m_UniqueCounter = 0;
private int m_MyID = m_UniqueCounter++;
~Test()
{
Debug.Log("Test destroyed (" + m_MyID + ")" );
}
void Awake()
{
Debug.Log("Test created (" + m_MyID + ")");
}
}
And this one:
// TestManager.cs
using UnityEngine;
using System.Collections;
public class TestManager : MonoBehaviour
{
private Test otherScript;
private Test secondRef;
void Update()
{
if (Input.GetKeyDown(KeyCode.C))
{
otherScript = (new GameObject("TestObject")).AddComponent<Test>();
}
else if (Input.GetKeyDown(KeyCode.D))
{
Destroy(otherScript.gameObject);
}
else if (Input.GetKeyDown(KeyCode.N))
{
otherScript = null;
}
else if (Input.GetKeyDown(KeyCode.S))
{
secondRef = otherScript;
}
else if (Input.GetKeyDown(KeyCode.M))
{
secondRef = null;
}
}
void OnGUI()
{
GUILayout.BeginVertical();
GUILayout.Label("C : otherScript = 'new Test'");
GUILayout.Label("D : Destroy(otherScript.gameObject);");
GUILayout.Label("N : otherScript = null;");
GUILayout.Label("S : secondRef = otherScript;");
GUILayout.Label("M : secondRef = null;");
GUILayout.Label("---");
GUILayout.Label("Is otherScript == null? " + (otherScript == null));
GUILayout.Label("Is secondRef == null? " + (secondRef == null));
GUILayout.EndVertical();
}
}
Just add the TestManager to an GameObject and start the game.
If you press "C" you will see that a Test script has been created on a new GameObject
If you press now "D" the GameObject and the Test script will be removed but the destructor of Test will not be executed.
If you press now "N" you will see that the destructor will get executed after a short time (when the GC collects it)
Now press again "C" and then "D". Now we have again a ghost reference which seems to be null but it can even be copied.
Press now "S" to copy the reference.
Press "N" to wipe out first reference --> nothing will happen
Press "M" to wipe the second reference --> the object will be collected
This "pretending null" behaviour allows a strange looking piece of code which will actually have an effect:
if (otherScript == null)
otherScript = null;
Usually this makes no sense but in the case of Unity it will effectively wipe the reference.
To answer your question about storing referenced in Lists or Array:
The values stored in an array or List behave the same as any other variable. Nobody will remove anything from an array (which isn't even possible) or a List. The reference will remain the same but will evaluate to "null" once you used Destroy on the object.
Where can we get this kind of info?? Unity docs seems to be more about the face of the engine, how did you figure out the backend functioning?
Well, i started using C# along with Unity but used it alot beside Unity. Every day you dig deeper how C# / .NET / $$anonymous$$ono works. I've played myself with including a scripting environment (lua) into my own application. I even found a simple implementation which is written in pure C# so it can be used in Unity :D
Well basically i tried a lot things in Unity. $$anonymous$$y test project contains about 50+ scenes with different tests. $$anonymous$$ost have been created because of a question here ;)
I also use ILSpy a lot. You can see how some things are implemented in the UnityEngine.dll (which is mostly a managed code wrapper for native code functions). $$anonymous$$ost functions map directly to native code but some (almost the whole GUI system for example besides the very low-level things) are written in managed code.
This answer is really useful to know! Thank you for explaining it. Another way I confirmed what you have written is I tried to call "object.GetType()" on a Destroyed object and it worked. The same would cause a Null Reference Exception on a truly null object.
Answer by MibZ · Jan 09, 2013 at 04:30 PM
No, Destroy only removes the instance it is called on.
That said, instead of iterating through the entire list each update you could move objects that need to be destroyed into a separate list and only iterate through the list if it has length - not that what you're doing now is wrong, but if you have a large amount of objects to be managed you would see slight performance improvement.
EDIT: Sorry, I read your question quickly and thought you said instances, not references. My answer is accidentally irrelevant.
kind of yes. this object that the update snip is given in will hold instances through
GameObject newObj = (GameObject) Instantiate(thing);
EntityList.Add(newObj);
(but mainly as interaction handles?). while other scripts will be holding references to these same GameObjects (probably just unique strings used to search the $$anonymous$$anager's list, but that is only a thought).
I was just wondering if the references would go away too, or if they would stick around after the fact.
on the point of your answer I will actually need to remove them (and I will be prefising what is to be destroyed as Destroy(X)
will result in whatever x is being destroyed?) I can see what you mean by hold them separately, but where I will most likely not be able to reuse the object later in the scene I want it gone.
Answer by fafase · Jan 09, 2013 at 05:01 PM
It seems Destroy() actually destroys all references of the object since this:
GameObject obj;
GameObject objI;
GameObject objII;
void Start()
{
obj = GameObject.Find("Cube");
objI = GameObject.Find("Cube");
objII = obj;
}
void Update(){
if (Input.GetKeyDown(KeyCode.Space)) {
Destroy(obj);
}
if (Input.GetKeyDown(KeyCode.A))
{
print("obj "+ obj);
print("objI " + objI);
print("objII " + objII);
}
}
prints null for all objects. So even though obj is destroyed, objI and objII are null. As a result, the actual object is good for GC. Not the reference though as they are on the stack and will get destroyed at the end of the program.
Edit: I just noticed the other question.
So any reference gets null. If you have a reference on another script on another object, it will be null as well. Destroy just cut all bridges to the stack.
For defragmentation, lists are arrays that get automatically reallocated by the compiler. So, you do not need to bother about that part.
so my bookkeeping concerns are not necessary, but what about this reallocation? are null references automatically removed, or do I need entityList.Remove(obj)
?
Despite the realloaction, you still need to remove the items
Best would be to use a for loop backwards so that you have an index and you do not miss the last one:
for (int i = list.Count - 1; i >= 0; i-- )
{
var script = list[i].GetComponent<EntityScript>();
if (script.toBeRemoved)
{
Destroy(list[i]);
list.RemoveAt(i);
}
}
With this, you destroy the object and remove it from the list.
Answer by Loius · Jan 09, 2013 at 06:13 PM
Destroy just sets a flag on the gameObject. From now on, Unity will treat it as if that object was null, and it'll be cleaned up when it can.
Your arrays containing that object will now (essentially) contain null references there instead.
Nothing ever removes itself from arrays, you always have to do that yourself (because if the engine does it then you've lost some control of your program, and that's bad)
so I will need to do bookkeeping of the array through entityList.Remove(obj), or would walking through finding all null references be easier?
then I will still need to track down all references, and remove those as well?
Why would you do that? If your game logic is good, you don't have to worry about NullReferenceExceptions. If you destroy an object, it's gone from your perspective, and it will be garbage-collected, but that isn't under your control from then.
Btw. an object only has one instance, because the object is itself the instance.
If you had to remove all references to an object manually so you can destroy it, nobody would use Unity... at all. And as far as I know, the .NET runtime has no capability of warning you about additional references to an object you're about to change.. it would be a huge resource-hog, and doesn't really make sense. It's your responsibility as the developer to write code that has $$anonymous$$imal chance of hitting an unhandled null reference.
Just issue the Destroy()
if toBeRemoved
is true, then remove it from the list, then you don't have a reference to it anymore, at least in the list. If you previously set references to the object and those references are still in scope and alive, you should know that everytime you call something on them, there can be a NullReferenceException, so either write your code differently, or based on this information check for this condition before you access the variable, or encapsulate the call in a try-catch.
Your answer
