I’d like to know if I can expect Unity to reliably destroy resources that are no longer referenced by anything.
My main concern is textures and materials instantiated by “Resources.Load” command.
I’d like to know what is the proper way to handle those objects once they’re loaded, when it is not possible to precisely track their lifetime (object loaded, then cloned multiple times - will result in material/texture being shared by multiple clones).
Online questions I found (on unity answers) basically said that I should let garbage collector handle it.
Is that the right way to do it? Or should I just spam “UnloadUnusedAssets” at every opportunity I get?
You can manually Invoke the garbage collector using System.GC.Collect(); followed by
GC.WaitForPendingFinalizers(); I do this when switching scenes. I have a LoadingManager that switches scenes so I always know what scene I am in. When I call LoadingManager.LoadScene(scene) in the method body it will do some background stuff but the important part is it invokes the Garbage Collector and Resources.UnloadUnusedAssets().
You can get performance issues when calling the garbage collector so do it sparingly like when you switch a scene. Since I load my scenes asynchronously, I give the engine time to load the scene and have the garbage collector do its thing while the player watches a loading bar fill.
I’m less interested in micromanaging resources and more interested in how to properly do cleanup for textures and materials. Because in situation when you load a mesh with material, THEN make clones of the mesh, THEN modify material on one mesh (which will “un-share” the material, making it unique) then start destroying stuff, it is going to be very hard to keep track of everything.
Leaked being the keyword here.
I suspected that loading a new scene would cause leaked assets to be cleaned, which was true.
using UnityEngine;
using System.Collections;
public class NewMeshAndReloadLevel : MonoBehaviour {
void Start () {
Debug.Log ("Start");
Invoke ("CreateMeshes", 2f);
}
private void CreateMeshes() {
Debug.Log ("CreateMeshes");
for (int i = 0; i < 10; i++) {
Mesh m = new Mesh();
m.name = "NewMesh" + i.ToString();
}
Invoke ("Collect", 2f);
}
private void Collect() {
Debug.Log("Collect");
System.GC.Collect();
Invoke ("ReloadScene", 2f);
}
private void ReloadScene() {
Debug.Log ("ReloadScene");
Application.LoadLevel(Application.loadedLevel);
}
}
Using the above script, the profiler will tell us that System.GC.Collect does nothing to the unreferenced meshes, while reloading the scene will clean them. If you however never load a new scene, I’m not sure this ever happens.
using UnityEngine;
using System.Collections;
public class UnloadInstantiated : MonoBehaviour {
public Shader materialShader;
void Start () {
Debug.Log ("Start");
Invoke ("LoadMaterial", 2f);
}
private void LoadMaterial() {
Debug.Log ("LoadMaterial");
Material source = Resources.Load<Material>("LoadMaterial");
for (int i = 0; i < 10; i++) {
Material m = Instantiate<Material>(source);
Material p = new Material(materialShader);
}
Invoke ("Unload", 2f);
}
private void Unload() {
Debug.Log("Unload");
Resources.UnloadUnusedAssets();
Invoke ("LoadMaterial", 2f);
}
}
Profiling this tells us that each of the 21 materials created are properly unloaded using Resources.UnloadUnusedAssets();
You shouldn’t need to spam UnloadUnusedAssets. If you create/load enough assets at run time to fill up the RAM then yes call it once, so you can free up to make more room, IF needed.
Assets are not managed by the GC, as @ThermalFusion pointed out. Garbage Collection is more the killer in terms of performance though.