I have some problems understanding what exactly “unused” means for Resources.UnloadUnusedAssets. From the docs:
An asset is deemed to be unused if it isn’t reached after walking the whole game object hierarchy, including script components. Static variables are also examined.
The script execution stack, however, is not examined so an asset referenced only from within the script stack will be unloaded and, if necessary, loaded back in the next time one of its properties or methods is used. This requires extra care for assets which have been modified in memory. Make sure to call EditorUtility.SetDirty before an asset garbage collection is triggered.
I’m using C#, and my questions are:
static variables: does it mean ALL static variables of ANY C# class in ANY loaded assembly? (public/private/protected)?
what is meant by the “script stack”? is it the execution stack of c#?
what about fields of normal C# instances (non MonoBehaviour) that reference C# instances corresponding to assets, and themselves are references from a MonoBehaviour on an existing GameObject. E.g. MyMonoBehaviour has a private field of type MyClass which has a field myTexture of type Texture2D. Is this deemed as “used” or “unused”?
------ Edit 1 ------
I mainly load resources dynamically using WWW, so at many times they are not (yet) referenced anywhere in the scene.
Yet still it’s hard to construct a simple case where, based on my understanding of the docs, I could say in advance if the resource will be still valid at some point or not. Here is an example:
public class UnloadUnusedAssetsTest : MonoBehaviour {
class MyClass {
private Texture tex;
public MyClass(Texture tex) { this.tex = tex; }
public Texture Get() { return tex; }
}
private MyClass myClass;
IEnumerator Start () {
var path = "file://" + Application.dataPath + "/../../TestConfig/Images/ImageSmall_01.png";
var www = new WWW(path);
yield return www;
myClass = new MyClass (www.texture);
Debug.Log(myClass.Get().width);
www.Dispose();
www = null;
GC.Collect();
var unloader = Resources.UnloadUnusedAssets();
while (!unloader.isDone) {
yield return null;
}
Debug.Log(myClass.Get().width);
}
}
In the above example, I expect the texture referenced by myClass.tex to have been unloaded at the last line in the method, but it is not the case, even if:
myClass.tex is not referenced anywhere in the scene
myClass.tex is not directly available from any MonoBehaviour in the scene
After several experiments (I used Unity 4.6.1) involving Resources.UnloadUnusedAssets() and references to a Texture instance kept as: a field in the same class, in different classes, in the same or different assemblies, in a closure/lambda, all in an instance or static field, trying both private and public access specifiers, the observation is that:
Unity finds (over reflection?) and deems as “used” all strong-references to C# instances that can be reached by C# code via fields/properties – not just stuff that Unity serializes;
if as mentioned in the docs the search is initiated from static variables and MonoBehaviours, given that MonoBehviours are the entry-points into client code, this in effect means that in a single-threaded scenario this search will eventually visit any instance/reference that is stored in a field (or property) in your code;
Unity does not follow still active WeakReferences – if a Texture is only referenced in a WeakReference, it will destroy it;
Unity does not perform a GC.Collect() in UnloadUnusedAssets() – the WeakReference is still available after UnloadUnusedAssets, and a GC.Collect() after it is needed to release it;
Unity does keep an own reference to the Texture – calling GC.Collect() before UnloadUnusedAssets() does not clear the WeakReference, as does a call to GC.Collect() after UnloadUnusedAssets().
I could not verify the part about the “script execution stack”, which I interpreted to be the case of a resource instance being stored only in a local variable in a method, because:
UnloadUnusedAssets() runs asynchronously, so I need to sync with it to know when it’s finished;
to sync with it, in a single threaded context, I had to use an IEnumerator, which however creates a class in the background where any local variable is transformed into a field of the instance;
local variables in lambdas also become instance-variables for the compiler generated classes.
In other words, I did not find a way to store the resource in a method-local variable only and, in the same method, sync with UnloadUnusedAssets(), to afterwards test the resource instance.
Here some simple code to test some of the things mentioned above:
IEnumerator Start () {
var path = "file://" + Application.dataPath + "/../../TestConfig/Images/ImageSmall_01.png";
var www = new WWW(path);
yield return www;
var wr = new WeakReference(www.texture);
var unloader = Resources.UnloadUnusedAssets();
while (!unloader.isDone) {
yield return null;
}
//GC.Collect();
var newTex = wr.Target;
if (newTex == null)
Debug.Log("GC collected");
else {
Debug.Log("Reference still here: " + newTex.GetType());
if (newTex as UnityEngine.Object)
Debug.Log((newTex as Texture).width);
else
Debug.Log("Texture was destroyed");
}
}
Reading about the Resources folder explains this more.
More-or-less, anything linked in the Inspector is USED, for example public Material m1; with goatMat1 dragged into it. That’s the part about “script components.” Stuff you can see in the Inspector for that script, when the game isn’t running.
The stuff to be careful about are things you reference in scripts using various Load commands. That’s the “Script execution” part. The wording is saying that it doesn’t look through the code in your scripts. If you read the Resource folder Q’s, they explain the problem and the fix.
Statics, well, you can’t Inspector assign to them (can you? Maybe in a scriptable object?)