summaryrefslogtreecommitdiff
path: root/Runtime/Misc/GarbageCollectSharedAssets.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Misc/GarbageCollectSharedAssets.cpp')
-rw-r--r--Runtime/Misc/GarbageCollectSharedAssets.cpp1188
1 files changed, 1188 insertions, 0 deletions
diff --git a/Runtime/Misc/GarbageCollectSharedAssets.cpp b/Runtime/Misc/GarbageCollectSharedAssets.cpp
new file mode 100644
index 0000000..9b75d14
--- /dev/null
+++ b/Runtime/Misc/GarbageCollectSharedAssets.cpp
@@ -0,0 +1,1188 @@
+#include "UnityPrefix.h"
+#if !ENABLE_OLD_GARBAGE_COLLECT_SHARED_ASSETS
+#include "Runtime/BaseClasses/BaseObject.h"
+#include "Runtime/Serialize/TransferFunctions/RemapPPtrTransfer.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "Runtime/Mono/Coroutine.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "GameObjectUtility.h"
+#include "Runtime/Mono/MonoManager.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Filters/Mesh/MeshRenderer.h"
+#include "Runtime/Dynamics/MeshCollider.h"
+#include "Runtime/Filters/Mesh/LodMeshFilter.h"
+#include "Runtime/Serialize/PersistentManager.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Profiler/CollectProfilerStats.h"
+#include "Runtime/Profiler/TimeHelper.h"
+#include "BatchDeleteObjects.h"
+#include "Runtime/Interfaces/IPhysics.h"
+
+/// All these includes are used by AddManagerRoots
+#if UNITY_EDITOR
+#include "Editor/Src/AssetPipeline/AssetDatabase.h"
+#include "Editor/Src/AssetPipeline/AssetImporter.h"
+#include "Editor/Src/GUIDPersistentManager.h"
+#include "Editor/Src/AssetServer/ASCache.h"
+#include "Editor/Src/EditorBuildSettings.h"
+#include "Editor/Src/EditorUserBuildSettings.h"
+#include "Editor/Src/EditorSettings.h"
+#include "Editor/Src/EditorUserSettings.h"
+#include "Editor/Src/AssetPipeline/AssetInterface.h"
+#include "Editor/Src/GUIDPersistentManager.h"
+#include "Editor/Src/AssetPipeline/AssetPathUtilities.h"
+#include "Editor/Src/Application.h"
+#include "Editor/Src/HierarchyState.h"
+#include "Editor/Src/InspectorExpandedState.h"
+#include "Editor/Src/AnnotationManager.h"
+#include "Editor/Src/EditorExtensionImpl.h"
+#include "Runtime/Serialize/SerializedFile.h"
+#include "Editor/Src/EditorAssetGarbageCollectManager.h"
+#endif
+#include "Runtime/Allocator/MemoryManager.h"
+
+#include "Runtime/Scripting/Scripting.h"
+
+PROFILER_INFORMATION(gGarbageCollectSharedAssetsProfile, "GarbageCollectAssetsProfile", kProfilerLoading)
+PROFILER_INFORMATION(gGCFindLiveObjects, "GC.FindLiveObjects", kProfilerLoading)
+PROFILER_INFORMATION(gGCBuildLiveObjectMaps, "GC.BuildLiveObjectMaps", kProfilerLoading)
+PROFILER_INFORMATION(gGCMarkDependencies, "GC.MarkDependencies", kProfilerLoading)
+PROFILER_INFORMATION(gGCDeletedUnusedAssets, "GC.DeleteUnusedAssets", kProfilerLoading)
+PROFILER_INFORMATION(gGCOnDestroyCallback, "ScriptableObject.OnDestroy", kProfilerLoading)
+
+#if UNITY_EDITOR
+static int gPreventGarbageCollectionOfInstanceID = 0;
+#endif
+
+struct ObjectState
+{
+ Object* object;
+ UInt32 classID : 29;
+ UInt32 marked : 1;
+ UInt32 isPersistent : 1;
+};
+
+
+struct ObjectHashFunctor
+{
+ inline size_t operator()(const Object* x) const
+ {
+ return (size_t)x / 32;
+ }
+};
+
+
+#define ASSET_REMAP_TABLE 1
+#define USE_MONO_LIVENESS ENABLE_MONO
+
+// Cap on how much memory we can use for the dense remap table for assets
+enum { kMaximumAssetRemapTableSize = 250 * 1024 };
+
+struct GarbageCollectorState;
+
+struct GenericSlowGarbageCollector : public GenerateIDFunctor
+{
+ GarbageCollectorState* gcState;
+
+ GenericSlowGarbageCollector () { }
+ virtual ~GenericSlowGarbageCollector () {}
+
+ inline void ProcessReference (SInt32 oldInstanceID);
+
+ // General purpose GarbageCollector callback
+ virtual SInt32 GenerateInstanceID (SInt32 oldInstanceID, TransferMetaFlags metaFlag);
+};
+
+struct GarbageCollectorState
+{
+ typedef pair<const int, int> InstanceIDToIndexPair;
+
+#if ASSET_REMAP_TABLE
+ typedef dense_hash_map<int, int, InstanceIDHashFunctor, std::equal_to<int>, STL_ALLOCATOR( kMemTempAlloc, InstanceIDToIndexPair )> InstanceIDToIndex;
+#else
+ typedef map<int, int> InstanceIDToIndex;
+#endif
+ InstanceIDToIndex instanceIDToIndex;
+ dynamic_array<UInt32> assetRemapTable;
+
+ dynamic_array<ObjectState> liveObjects;
+ dynamic_array<UInt32> needsProcessing;
+
+ int originalObjectCount;
+
+ void* livenessState;
+
+ bool followMonoReferences;
+
+ #if ENABLE_MEM_PROFILER
+ bool alwaysAddToNeedsProcessing;
+ #endif
+
+ RemapPPtrTransfer genericSlowTransfer;
+ GenericSlowGarbageCollector genericCollector;
+
+ GarbageCollectorState ()
+ : assetRemapTable (kMemTempAlloc),
+ liveObjects (kMemTempAlloc),
+ needsProcessing (kMemTempAlloc),
+ genericSlowTransfer (kDontRequireAllMetaFlags | kPerformUnloadDependencyTracking, false)
+ {
+ genericCollector.gcState = this;
+#if ENABLE_MEM_PROFILER
+ alwaysAddToNeedsProcessing = false;
+#endif
+ genericSlowTransfer.SetGenerateIDFunctor (&genericCollector);
+ }
+};
+
+
+///@TODO: Make a function that can let us find all classes that have references
+static bool DoesClassIDHaveReferences (int classID)
+{
+#if UNITY_EDITOR
+
+ bool classHasNoReferences =
+ classID == ClassID(AssetDatabase) ||
+ classID == ClassID(HierarchyState) ||
+ classID == ClassID(GUIDSerializer) ||
+ classID == ClassID(AssetServerCache);
+
+ if(classHasNoReferences)
+ return false;
+
+#endif
+
+ return
+ classID != ClassID (ScriptMapper) &&
+ classID != ClassID (MonoScript) &&
+ classID != ClassID (NetworkManager) &&
+ classID != ClassID (ResourceManager) &&
+ classID != ClassID (PreloadData) &&
+ classID != ClassID (Texture) &&
+ classID != ClassID (Texture2D) &&
+ classID != ClassID (Texture3D) &&
+ classID != ClassID (Cubemap) &&
+ classID != ClassID (WebCamTexture) &&
+ classID != ClassID (RenderTexture) &&
+ classID != ClassID (AssetBundle) &&
+ classID != ClassID (Mesh) &&
+ classID != ClassID (TagManager);
+}
+
+static void MarkManagedStaticVariableRoots (GarbageCollectorState& gcState);
+static void FindAllLiveObjects (GarbageCollectorState& gcState);
+static void MarkSceneRootsAndReduceLiveObjects (GarbageCollectorState& gcState);
+static void MarkManagerRoots (GarbageCollectorState& gcState);
+static void MarkSelectedObjectsAsRoots (GarbageCollectorState& gcState);
+static void MarkAllDependencies (GarbageCollectorState& gcState);
+static void CleanupUnusedObjects (GarbageCollectorState& gcState);
+static void ValidateNoObjectsWereLoaded (GarbageCollectorState& state);
+static void CreateObjectToIndexMappingFromNonRootObjects (GarbageCollectorState& gcState);
+static void CleanupOtherUnusedMemory (GarbageCollectorState& gcState);
+static void BeginLivenessChecking( GarbageCollectorState& gcState);
+static void EndLivenessChecking( GarbageCollectorState& gcState);
+
+
+// Rename
+void GarbageCollectSharedAssets (bool monoReferences)
+{
+ PROFILER_AUTO(gGarbageCollectSharedAssetsProfile, NULL);
+
+ ABSOLUTE_TIME totalTime;
+ ABSOLUTE_TIME findAllLiveObjectsTime;
+ ABSOLUTE_TIME buildLiveObjectMapTime;
+ ABSOLUTE_TIME markTime;
+ ABSOLUTE_TIME unloadTime;
+
+#if ENABLE_SERIALIZATION_BY_CODEGENERATION
+ // Gab: ManagedLivenessAnalysis needs to be reset every time before executing the liveness check.
+ static ScriptingInvocation resetManagedAnalysis(GetScriptingMethodRegistry().GetMethod("UnityEngine.Serialization","ManagedLivenessAnalysis","ResetState"));
+ resetManagedAnalysis.Invoke();
+#endif
+
+ totalTime = START_TIME;
+ int originalObjectCount = Object::GetLoadedObjectCount();
+#if ENABLE_PROFILER
+ printf_console("System memory in use before: %s.\n", FormatBytes(GetUsedHeapSize()).c_str());
+#endif
+ // This is essentially a Mark & Sweep Garbage Collector.
+
+ GarbageCollectorState state;
+
+ state.followMonoReferences = monoReferences;
+ state.originalObjectCount = originalObjectCount;
+
+ findAllLiveObjectsTime = START_TIME;
+
+ //@TODO: This stage can easily be jobified
+ // Fills all live objects and extracts their classID's for use by the marking algorithm
+ FindAllLiveObjects(state);
+ MarkSceneRootsAndReduceLiveObjects (state);
+
+ findAllLiveObjectsTime = ELAPSED_TIME(findAllLiveObjectsTime);
+
+ buildLiveObjectMapTime = START_TIME;
+ // Create mapping to quickly look up all object references
+ CreateObjectToIndexMappingFromNonRootObjects(state);
+ buildLiveObjectMapTime = ELAPSED_TIME(buildLiveObjectMapTime);
+
+ markTime = START_TIME;
+
+ MarkManagerRoots (state);
+ MarkSelectedObjectsAsRoots (state);
+
+ ValidateNoObjectsWereLoaded (state);
+
+ {
+ // Once we call BeginLivenessChecking we can not use the profiler because it might allocate memory.
+ PROFILER_AUTO(gGCMarkDependencies, NULL);
+
+ BeginLivenessChecking(state);
+
+ // Mark static variables
+ MarkManagedStaticVariableRoots (state);
+ // Process roots to find all referenced objects
+ MarkAllDependencies (state);
+
+ EndLivenessChecking(state);
+ }
+ markTime = ELAPSED_TIME(markTime);
+
+ ValidateNoObjectsWereLoaded (state);
+
+ if (state.originalObjectCount != Object::GetLoadedObjectCount())
+ {
+ ErrorString("UnloadUnusedAssets incorrect caused some assets to load. This can easily cause deadlocks or crashes.");
+ }
+
+ unloadTime = START_TIME;
+
+ // Cleanup all objects not marked as dependencies or roots
+ CleanupUnusedObjects (state);
+
+ unloadTime = ELAPSED_TIME(unloadTime);
+
+ int unloadedObjects = originalObjectCount - Object::GetLoadedObjectCount();
+
+ // Unload PersistentManager memory that is not needed
+ CleanupOtherUnusedMemory (state);
+
+ totalTime = ELAPSED_TIME(totalTime);
+
+#if ENABLE_PROFILER
+ printf_console("System memory in use after: %s.\n", FormatBytes(GetUsedHeapSize()).c_str());
+#endif
+ printf_console("\nUnloading %d unused Assets to reduce memory usage. Loaded Objects now: %d.\n", unloadedObjects, (int)Object::GetLoadedObjectCount());
+ printf_console("Total: %f ms (FindLiveObjects: %f ms CreateObjectMapping: %f ms MarkObjects: %f ms DeleteObjects: %f ms)\n\n",
+ AbsoluteTimeToMilliseconds(totalTime), AbsoluteTimeToMilliseconds(findAllLiveObjectsTime), AbsoluteTimeToMilliseconds(buildLiveObjectMapTime), AbsoluteTimeToMilliseconds(markTime), AbsoluteTimeToMilliseconds(unloadTime) );
+
+ #if UNITY_EDITOR
+ EditorAssetGarbageCollectManager::Get()->SetPostCollectMemoryUsage();
+ #endif
+}
+
+
+#if ASSET_REMAP_TABLE
+static void CreateObjectToIndexMappingFromNonRootObjects (GarbageCollectorState& gcState)
+{
+ PROFILER_AUTO(gGCBuildLiveObjectMaps, NULL);
+
+ gcState.instanceIDToIndex.set_empty_key (-1);
+ gcState.instanceIDToIndex.set_deleted_key (-2);
+
+ int largestInstanceID = 0;
+
+ for (int i=0;i<gcState.liveObjects.size();++i)
+ {
+ ObjectState& state = gcState.liveObjects[i];
+ if (state.marked == 0)
+ {
+ int instanceID = state.object->GetInstanceID();
+
+ if (instanceID > 0)
+ {
+ largestInstanceID = std::max<SInt32>(largestInstanceID, instanceID);
+ }
+ else
+ {
+ gcState.instanceIDToIndex.insert(std::make_pair(instanceID, i));
+ }
+ }
+ }
+
+ // Cap the memory of dense assetRemapTable
+ if (largestInstanceID < (kMaximumAssetRemapTableSize / sizeof(UInt32)))
+ {
+ gcState.assetRemapTable.resize_initialized(largestInstanceID + 1, -1);
+ for (int i=0;i<gcState.liveObjects.size();++i)
+ {
+ ObjectState& state = gcState.liveObjects[i];
+ if (state.marked == 0)
+ {
+ int instanceID = state.object->GetInstanceID();
+
+ if (instanceID > 0)
+ gcState.assetRemapTable[instanceID] = i;
+ }
+ }
+ }
+ // Use hashtable for all instance ids as a fallback
+ else
+ {
+ for (int i=0;i<gcState.liveObjects.size();++i)
+ {
+ ObjectState& state = gcState.liveObjects[i];
+ if (state.marked == 0)
+ {
+ int instanceID = state.object->GetInstanceID();
+
+ if (instanceID > 0)
+ gcState.instanceIDToIndex.insert(std::make_pair(instanceID, i));
+ }
+ }
+ }
+}
+
+#else
+static void CreateObjectToIndexMappingFromNonRootObjects (GarbageCollectorState& gcState)
+{
+ ///@TODO: Should we recreate gcState.liveObjects here? There is no reason to ever visit objects that are already marked at this point...
+ //// Try it...
+
+ for (int i=0;i<gcState.liveObjects.size();++i)
+ {
+ ObjectState& state = gcState.liveObjects[i];
+ if (state.marked == 0)
+ {
+ int instanceID = state.object->GetInstanceID();
+
+ gcState.instanceIDToIndex.insert(make_pair(instanceID, i));
+ }
+ }
+}
+#endif
+
+
+static inline int LookupInstanceIDIndex (int instanceID, GarbageCollectorState& gcState)
+{
+ if (instanceID == 0)
+ return -1;
+
+#if ASSET_REMAP_TABLE
+
+ size_t size = gcState.assetRemapTable.size();
+ if (instanceID > 0 && size != 0)
+ {
+ if (instanceID < size)
+ return gcState.assetRemapTable[instanceID];
+ else
+ return -1;
+ }
+#endif
+ else
+ {
+ GarbageCollectorState::InstanceIDToIndex::const_iterator found = gcState.instanceIDToIndex.find(instanceID);
+ if (found == gcState.instanceIDToIndex.end())
+ return -1;
+ else
+ return found->second;
+ }
+}
+
+static inline int LookupObjectIndex (const Object& object, GarbageCollectorState& gcState)
+{
+ return LookupInstanceIDIndex (object.GetInstanceID(), gcState);
+}
+
+
+static void MarkIndexAsRoot (int index, GarbageCollectorState& gcState)
+{
+ ObjectState& state = gcState.liveObjects[index];
+
+ Assert(state.marked == 0);
+ state.marked = 1;
+
+ // If we have references to other objects in the marked object -> then we need to process it
+ // No processing is needed if the class has no references
+ // (Eg. a texture has no references to other objects, thus it does not have to be processed)
+ bool addToNeedsProcessing = DoesClassIDHaveReferences (state.classID);
+ Assert(state.object->ShouldIgnoreInGarbageDependencyTracking() == !addToNeedsProcessing);
+
+ #if ENABLE_MEM_PROFILER
+ // When extracting references for the memory profiler we use the needsProcessing array as the object which have been marked.
+ // Thus we can not use the fastpath for objects that have no references to other objects
+ addToNeedsProcessing |= gcState.alwaysAddToNeedsProcessing;
+ #endif
+
+ if (addToNeedsProcessing)
+ {
+ gcState.needsProcessing.push_back(index);
+ }
+}
+
+static void MarkObjectAsRoot (const Object& object, GarbageCollectorState& gcState)
+{
+ int index = LookupObjectIndex (object, gcState);
+ if (index != -1)
+ MarkIndexAsRoot(index, gcState);
+}
+
+static void MarkInstanceIDAsRoot (int instanceID, GarbageCollectorState& gcState)
+{
+ int index = LookupInstanceIDIndex (instanceID, gcState);
+ if (index != -1)
+ {
+ if (gcState.liveObjects[index].marked == 0)
+ MarkIndexAsRoot(index, gcState);
+ }
+}
+
+static void MarkObjectAsRootUnknownMarkState (const Object& object, GarbageCollectorState& gcState)
+{
+ int index = LookupObjectIndex (object, gcState);
+ if (index != -1 && gcState.liveObjects[index].marked == 0)
+ MarkIndexAsRoot(index, gcState);
+}
+
+static void MarkObjectAsRootCheckNull (const Object* object, GarbageCollectorState& gcState)
+{
+ if (object != NULL)
+ MarkObjectAsRootUnknownMarkState (*object, gcState);
+}
+
+#if USE_MONO_LIVENESS
+static void RegisterFilteredObjectCallback(gpointer* arr, int count, void* userdata)
+{
+ GarbageCollectorState* gcStatePtr = (GarbageCollectorState*)userdata;
+ for (int i = 0; i < count ;i++)
+ {
+ SInt32 instanceID = Scripting::GetInstanceIDFromScriptingWrapper((MonoObject*)arr[i]);
+ MarkInstanceIDAsRoot(instanceID, *gcStatePtr);
+ }
+}
+#endif
+
+static void BeginLivenessChecking( GarbageCollectorState& gcState)
+{
+ if(!gcState.followMonoReferences)
+ return;
+#if USE_MONO_LIVENESS
+ #if ENABLE_MEMORY_MANAGER
+ GetMemoryManager().DisallowAllocationsOnThisThread();
+ #endif
+ gcState.livenessState = mono_unity_liveness_calculation_begin(ScriptingClassFor(Object), gcState.liveObjects.size(), RegisterFilteredObjectCallback, (void*)&gcState);
+#endif
+}
+
+static void EndLivenessChecking( GarbageCollectorState& gcState)
+{
+ if(!gcState.followMonoReferences)
+ return;
+#if USE_MONO_LIVENESS
+ mono_unity_liveness_calculation_end(gcState.livenessState);
+ #if ENABLE_MEMORY_MANAGER
+ GetMemoryManager().ReallowAllocationsOnThisThread();
+ #endif
+#endif
+}
+
+static void MarkManagedStaticVariableRoots (GarbageCollectorState& gcState)
+{
+ if(!gcState.followMonoReferences)
+ return;
+
+#if USE_MONO_LIVENESS
+ ValidateNoObjectsWereLoaded (gcState);
+ mono_unity_liveness_calculation_from_statics (gcState.livenessState);
+#endif
+}
+
+#if UNITY_EDITOR
+void SetPreventGarbageCollectionOfAsset (int instanceID)
+{
+ Assert(gPreventGarbageCollectionOfInstanceID == 0);
+ gPreventGarbageCollectionOfInstanceID = instanceID;
+}
+
+void ClearPreventGarbageCollectionOfAsset (int instanceID)
+{
+ Assert(gPreventGarbageCollectionOfInstanceID == instanceID);
+ gPreventGarbageCollectionOfInstanceID = 0;
+}
+#endif
+
+
+static void MarkSelectedObjectsAsRoots (GarbageCollectorState& gcState)
+{
+#if UNITY_EDITOR
+ // Add all selected objects as GC Roots because they are being shown in the inspector.
+ set<int> selection = GetSceneTracker().GetSelectionID ();
+ for (set<int>::iterator i=selection.begin();i!=selection.end();i++)
+ MarkInstanceIDAsRoot(*i, gcState);
+#endif
+}
+
+
+static void MarkManagerRoots (GarbageCollectorState& gcState)
+{
+ // All managers are roots (except script mapper)
+ for (int i=0;i<ManagerContext::kManagerCount;i++)
+ {
+ if (GetManagerPtrFromContext(i) != NULL)
+ MarkObjectAsRootUnknownMarkState (*GetManagerPtrFromContext(i), gcState);
+ }
+
+ #if UNITY_EDITOR
+ MarkObjectAsRootUnknownMarkState (AssetDatabase::Get(), gcState);
+ MarkObjectAsRootUnknownMarkState (AssetServerCache::Get(), gcState);
+ MarkObjectAsRootUnknownMarkState (GetEditorBuildSettings(), gcState);
+ MarkObjectAsRootUnknownMarkState (GetEditorUserBuildSettings(), gcState);
+ MarkObjectAsRootUnknownMarkState (GetEditorSettings(), gcState);
+ MarkObjectAsRootUnknownMarkState (GetEditorUserSettings(), gcState);
+ MarkObjectAsRootCheckNull (AssetInterface::Get().GetGUIDSerializer(), gcState);
+ MarkObjectAsRootCheckNull (GetProjectWindowHierarchyStateIfLoaded (), gcState);
+ MarkObjectAsRootUnknownMarkState (GetInspectorExpandedState (), gcState);
+ MarkObjectAsRootUnknownMarkState (GetAnnotationManager (), gcState);
+
+ if (gPreventGarbageCollectionOfInstanceID != 0)
+ MarkInstanceIDAsRoot(gPreventGarbageCollectionOfInstanceID, gcState);
+
+ #endif
+
+ ValidateNoObjectsWereLoaded (gcState);
+}
+
+#if UNITY_EDITOR
+bool ShouldPersistentDirtyObjectBeKeptAlive (int instanceID)
+{
+ // The Object is not mapped to disk
+ SerializedObjectIdentifier identifier;
+ if (!GetPersistentManager().InstanceIDToSerializedObjectIdentifier(instanceID, identifier))
+ {
+ // #if !UNITY_RELEASE
+ // WarningString("Persistent object is known in persistent manager will unload Might happen due to Assetbundle.Unload(false)");
+ // #endif
+ return false;
+ }
+
+ // Cached asset file assets should just be unloaded. They contain nothing that can not be reloaded.
+ // Eg a texture in an imported .psd file, while scripts might call SetDirty on it, it doesn't actually ever get serialized back.
+ // Because the only thing that save an texture is the asset importer. Any modifications done in memory will never be saved.
+ // -> Thus a texture that is marked dirty can be unloaded
+ // -> But a MetaData object or AssetImporter settings object (eg. class TextureImporter) should not be unloaded if it is marked dirty
+ FileIdentifier file = GetGUIDPersistentManager().PathIDToFileIdentifierInternal(identifier.serializedFileIndex);
+ if (file.type == FileIdentifier::kMetaAssetType)
+ {
+ if (identifier.localIdentifierInFile == kAssetMetaDataFileID)
+ {
+ // We can't have this warning since changing an import setting and then starting a long import operation will trigger it.
+ //WarningString(Format("ImportSettings '%s' has been modified but AssetDatabase.ImportAsset has not been called. Please fix the scripts code or Import the asset manually.", GetAssetPathFromInstanceID(instanceID).c_str()));
+ return true;
+ }
+ else if (identifier.localIdentifierInFile == kAssetImporterFileID)
+ {
+ // We can't have this warning since changing an import setting and then starting a long import operation will trigger it.
+ //WarningString(Format("ImportSettings '%s' has been modified but AssetDatabase.ImportAsset has not been called. Please fix the scripts code or Import the asset manually.", GetAssetPathFromInstanceID(instanceID).c_str()));
+ return true;
+ }
+
+ return false;
+ }
+ // Any other assets are will not be unloaded when dirtied.
+ // Eg. scriptmapper, guidmapper, builtin resources...
+ else
+ {
+ return true;
+ }
+}
+#endif
+
+enum GCRootType
+{
+ // The object is not a GC root (might be referenced during the marking stage)
+ kNoGCRoot = 0,
+ // The object is a GC root and is guaranteed to not have any references to non-root objects (Things that are already guaranteed to be marked)
+ // For example, game objects and transforms in a scene don't need to be processed, because they can only reference components that are already in the scene and those are already marked as roots.
+ kGCRootReferencesOnly = 1,
+
+ // a root for the GC marking, must be processed and it's dependencies must be found
+ kGCRootWithReferences = 2
+};
+
+#if UNITY_EDITOR
+#define DoesNotHavePrefabAttached(liveObject) (static_cast<EditorExtension*> (liveObject.object)->GetPrefab().GetInstanceID() == 0)
+#else
+#define DoesNotHavePrefabAttached(liveObject) true
+#endif
+
+inline bool HasValidGameObject (const ObjectState& liveObject)
+{
+ const Unity::Component* component = static_cast<const Unity::Component*> (liveObject.object);
+ return component->GetGameObjectPtr() != NULL;
+}
+
+inline bool IsScriptableObject (const ObjectState& liveObject)
+{
+ const MonoBehaviour* monoBehaviour = static_cast<const MonoBehaviour*> (liveObject.object);
+ return monoBehaviour->GetGameObjectPtr() == NULL;
+}
+
+static bool IsSceneObject (const ObjectState& liveObject)
+{
+ if (liveObject.isPersistent)
+ return false;
+
+ if (liveObject.classID == ClassID(MonoBehaviour) && IsScriptableObject(liveObject))
+ return false;
+
+ return liveObject.classID == ClassID(GameObject) || Object::IsDerivedFromClassID(liveObject.classID, ClassID(Component));
+}
+
+static bool IsAssetNotYetSavedToDisk (const ObjectState& liveObject)
+{
+#if UNITY_EDITOR
+ if (liveObject.isPersistent && liveObject.object->IsPersistentDirty())
+ {
+ if (ShouldPersistentDirtyObjectBeKeptAlive (liveObject.object->GetInstanceID()))
+ return true;
+ }
+#endif
+ return false;
+}
+
+
+static GCRootType IsObjectAGCRoot (const ObjectState& liveObject)
+{
+ int classID = liveObject.classID;
+ if (!liveObject.isPersistent)
+ {
+ // Game Objects & Transforms in the scene are roots and have no references beyond other objects that are also in the scene
+ // Except for prefabs which we specifically check for in the editor
+ if (classID == ClassID(GameObject) && DoesNotHavePrefabAttached(liveObject))
+ return kGCRootReferencesOnly;
+ else if (classID == ClassID(Transform) && DoesNotHavePrefabAttached(liveObject))
+ return kGCRootReferencesOnly;
+ // Only MonoBehaviour not ScriptableObject are treated as roots
+ else if (classID == ClassID(MonoBehaviour))
+ {
+ if (!IsScriptableObject(liveObject))
+ return kGCRootWithReferences;
+ }
+ else if (Object::IsDerivedFromClassID(classID, ClassID(Component)))
+ {
+ Assert(HasValidGameObject (liveObject));
+ return kGCRootWithReferences;
+ }
+ }
+
+ // Asset bundles are always explicitly unloaded
+ if (classID == ClassID(AssetBundle))
+ return kGCRootWithReferences;
+ // Objects marked as hide and dont save are treated as roots
+ // else if (liveObject.object->TestHideFlag (Object::kDontSave))
+ // return kGCRootWithReferences;
+ else if (liveObject.object->TestHideFlag (Object::kDontSave))
+ return kGCRootWithReferences;
+#if UNITY_EDITOR
+ else if (liveObject.isPersistent && liveObject.object->IsPersistentDirty())
+ {
+ if (ShouldPersistentDirtyObjectBeKeptAlive (liveObject.object->GetInstanceID()))
+ return kGCRootWithReferences;
+ }
+#endif
+
+ return kNoGCRoot;
+}
+
+static void FindAllLiveObjects (GarbageCollectorState& gcState)
+{
+ PROFILER_AUTO(gGCFindLiveObjects, NULL);
+ gcState.originalObjectCount = Object::GetLoadedObjectCount();
+
+ Object::IDToPointerMap& pointerMap = Object::GetIDToPointerMapInternal ();
+ gcState.liveObjects.resize_uninitialized(pointerMap.size());
+ gcState.needsProcessing.reserve(pointerMap.size());
+
+ Object::IDToPointerMap::const_iterator end = pointerMap.end();
+ Object::IDToPointerMap::const_iterator i=pointerMap.begin();
+
+ int index = 0;
+
+ ObjectState* liveObjects = gcState.liveObjects.begin();
+
+ // Extract All live objects
+ while (i != end)
+ {
+ Object& object = *i->second;
+
+ ObjectState& state = liveObjects[index];
+
+ state.object = &object;
+ state.classID = object.GetClassID();
+ state.marked = 0;
+ state.isPersistent = object.IsPersistent();
+
+ index++;
+ i++;
+ }
+
+ ValidateNoObjectsWereLoaded (gcState);
+}
+
+static void MarkSceneRootsAndReduceLiveObjects (GarbageCollectorState& gcState)
+{
+ int size = gcState.liveObjects.size();
+ ObjectState* liveObjects = gcState.liveObjects.begin();
+
+ // Mark scene roots
+ for (int index=0;index<size;)
+ {
+ ObjectState& state = liveObjects[index];
+
+ GCRootType type = IsObjectAGCRoot (state);
+
+ // It is a root and is guaranteed to have no references to non-roots
+ // We dont need it in any maps or even the liveObjects list.
+ if (type == kGCRootReferencesOnly)
+ {
+ // Reducing the size of this array early on saved around 3% on angrybots so not a significant optimization
+ size--;
+ liveObjects[index] = liveObjects[size];
+ }
+ // Root that might have references to other objects, thus must be processed
+ else if (type == kGCRootWithReferences )
+ {
+ MarkIndexAsRoot (index, gcState);
+ index++;
+ }
+ else
+ {
+ index++;
+ }
+ }
+
+ gcState.liveObjects.resize_uninitialized(size);
+
+ ValidateNoObjectsWereLoaded (gcState);
+}
+
+#if UNITY_EDITOR
+static void ProcessEditorExtension (GarbageCollectorState& gcState, EditorExtension& object)
+{
+ MarkInstanceIDAsRoot (object.GetPrefab().GetInstanceID(), gcState);
+}
+#else
+#define ProcessEditorExtension(x,y)
+#endif
+
+
+static void ProcessGameObject (GarbageCollectorState& gcState, ObjectState& state)
+{
+ GameObject& go = *static_cast<Unity::GameObject*> (state.object);
+
+ ProcessEditorExtension(gcState, go);
+
+ GameObject::Container& container = go.GetComponentContainerInternal ();
+ GameObject::Container::iterator end = container.end();
+ for (GameObject::Container::iterator i = container.begin();i != end;++i)
+ {
+ MarkObjectAsRootUnknownMarkState (*i->second, gcState);
+ }
+}
+
+static void ProcessValidComponent (GarbageCollectorState& gcState, ObjectState& state)
+{
+ Unity::Component& com = *static_cast<Unity::Component*> (state.object);
+
+ ProcessEditorExtension(gcState, com);
+
+ GameObject& go = com.GetGameObject ();
+ MarkObjectAsRootUnknownMarkState (go, gcState);
+}
+
+
+static void ProcessTransform (GarbageCollectorState& gcState, ObjectState& state)
+{
+ ProcessValidComponent(gcState, state);
+
+ Transform& transform = *static_cast<Transform*> (state.object);
+
+ const Transform::TransformComList& children = transform.GetChildrenInternal ();
+ Transform::TransformComList::const_iterator end = children.end();
+ for (Transform::TransformComList::const_iterator i = children.begin();i != end;++i)
+ {
+ MarkObjectAsRootUnknownMarkState (**i, gcState);
+ }
+
+ MarkObjectAsRootCheckNull (transform.GetParentPtrInternal (), gcState);
+}
+
+static void ProcessMeshFilter (GarbageCollectorState& gcState, ObjectState& state)
+{
+ ProcessValidComponent (gcState, state);
+
+ MeshFilter& filter = *static_cast<MeshFilter*> (state.object);
+ MarkInstanceIDAsRoot (filter.GetSharedMesh().GetInstanceID(), gcState);
+}
+
+static void ProcessMeshRenderer (GarbageCollectorState& gcState, ObjectState& state)
+{
+ ProcessValidComponent (gcState, state);
+
+ Renderer& renderer = *static_cast<Renderer*> (state.object);
+
+ const Renderer::MaterialArray& materials = renderer.GetMaterialArray ();
+ Renderer::MaterialArray::const_iterator end = materials.end();
+ for (Renderer::MaterialArray::const_iterator i=materials.begin();i != end;++i)
+ MarkInstanceIDAsRoot(i->GetInstanceID(), gcState);
+
+ // Make sure that the static batching root has a game object...
+// MarkInstanceIDAsRoot (renderer.GetStaticBatchRoot ().GetInstanceID(), gcState);
+
+
+ MarkInstanceIDAsRoot (renderer.GetLightProbeAnchor ().GetInstanceID(), gcState);
+}
+
+#if ENABLE_PHYSICS
+static void ProcessCollider (GarbageCollectorState& gcState, ObjectState& state)
+{
+ ProcessValidComponent (gcState, state);
+
+ Collider& collider = *static_cast<Collider*> (state.object);
+ MarkInstanceIDAsRoot(collider.GetMaterial().GetInstanceID(), gcState);
+}
+
+static void ProcessPrimitiveCollider (GarbageCollectorState& gcState, ObjectState& state)
+{
+ ProcessCollider (gcState, state);
+}
+
+static void ProcessMeshCollider (GarbageCollectorState& gcState, ObjectState& state)
+{
+ ProcessCollider (gcState, state);
+
+ MeshCollider& collider = *static_cast<MeshCollider*> (state.object);
+ MarkInstanceIDAsRoot(collider.GetSharedMesh().GetInstanceID(), gcState);
+}
+#else
+
+static void ProcessPrimitiveCollider (GarbageCollectorState& gcState, ObjectState& state)
+{
+ AssertString("Not supported");
+}
+static void ProcessMeshCollider (GarbageCollectorState& gcState, ObjectState& state)
+{
+ AssertString("Not supported");
+}
+#endif
+
+#if USE_MONO_LIVENESS
+
+static void ProcessLivenessFromScriptingObject (GarbageCollectorState& gcState, ScriptingObjectPtr object)
+{
+ mono_unity_liveness_calculation_from_root(object, gcState.livenessState);
+}
+
+static void ProcessMonoBehaviour (GarbageCollectorState& gcState, ObjectState& state)
+{
+ MonoBehaviour& behaviour = *static_cast<MonoBehaviour*> (state.object);
+
+ ProcessEditorExtension(gcState, behaviour);
+
+ // Not all MonoBehaviours are attached to a game object.
+ // Handle that situation specifically here.
+ GameObject* go = behaviour.GetGameObjectPtr ();
+ if (go != NULL)
+ MarkObjectAsRootUnknownMarkState (*go, gcState);
+
+ MarkInstanceIDAsRoot (behaviour.GetScript().GetInstanceID(), gcState);
+
+ if(!gcState.followMonoReferences)
+ return;
+
+ // MonoBehaviour managed instance
+ MonoObject* instance = Scripting::ScriptingWrapperFor(state.object);
+ if (instance != NULL)
+ ProcessLivenessFromScriptingObject(gcState, instance);
+
+ // Coroutine managed instances
+ List<Coroutine>& coroutine = behaviour.GetActiveCoroutines();
+ ListIterator<Coroutine> end = coroutine.end();
+ for (ListIterator<Coroutine> i=coroutine.begin();i != end;++i)
+ {
+ Coroutine& coroutine = *i;
+ ProcessLivenessFromScriptingObject(gcState, coroutine.m_CoroutineEnumerator);
+ }
+}
+
+#else
+
+static void ProcessMonoBehaviour (GarbageCollectorState& gcState, ObjectState& state)
+{
+ AssertString("Not supported");
+}
+#endif
+
+inline void GenericSlowGarbageCollector::ProcessReference (SInt32 oldInstanceID)
+{
+ int index = LookupInstanceIDIndex (oldInstanceID, *gcState);
+ if (index == -1)
+ return;
+
+ ObjectState& referencedState = gcState->liveObjects[index];
+ if (referencedState.marked == 0)
+ MarkIndexAsRoot(index, *gcState);
+ }
+
+ // General purpose GarbageCollector callback
+SInt32 GenericSlowGarbageCollector::GenerateInstanceID (SInt32 oldInstanceID, TransferMetaFlags metaFlag)
+ {
+ ProcessReference(oldInstanceID);
+ return oldInstanceID;
+ }
+
+void MarkDependencies (GarbageCollectorState& gcState, UInt32 index)
+{
+ ObjectState& state = gcState.liveObjects[index];
+
+ #if ENABLE_MEM_PROFILER
+ Assert(!state.object->ShouldIgnoreInGarbageDependencyTracking() || gcState.alwaysAddToNeedsProcessing);
+ #else
+ Assert(!state.object->ShouldIgnoreInGarbageDependencyTracking());
+ #endif
+
+ Assert((bool)state.marked);
+
+ if (state.classID == ClassID (GameObject))
+ ProcessGameObject(gcState, state);
+ else if (state.classID == ClassID (Transform))
+ ProcessTransform(gcState, state);
+ else if (state.classID == ClassID (MeshRenderer))
+ ProcessMeshRenderer(gcState, state);
+ else if (state.classID == ClassID (MeshFilter))
+ ProcessMeshFilter(gcState, state);
+ // Specialized MonoBehaviour code path for mono based platforms, otherwise falls back to the generic RemapPPtrTransfer
+ else if ((USE_MONO_LIVENESS) && gcState.followMonoReferences && state.classID == ClassID (MonoBehaviour))
+ ProcessMonoBehaviour(gcState, state);
+ else if ((ENABLE_PHYSICS) && state.classID == ClassID (MeshCollider))
+ ProcessMeshCollider(gcState, state);
+ else if ((ENABLE_PHYSICS) && state.classID == ClassID (BoxCollider))
+ ProcessPrimitiveCollider(gcState, state);
+#if ENABLE_SERIALIZATION_BY_CODEGENERATION
+ else if (state.classID == ClassID (MonoBehaviour))
+ state.object->DoLivenessCheck(gcState.genericSlowTransfer);
+#endif
+ else
+ state.object->VirtualRedirectTransfer (gcState.genericSlowTransfer);
+}
+
+static void MarkAllDependencies (GarbageCollectorState& gcState)
+{
+ //@TODO: This stage can easily be multi-threaded
+ /// The out of this stage is gcState.liveObjects[i].marked = 1;
+ // Find some efficient way multithread that without expensive thread synchronization
+
+ dynamic_array<UInt32>& needsProcessing = gcState.needsProcessing;
+ while (!needsProcessing.empty ())
+ {
+ int index = needsProcessing.back();
+ needsProcessing.pop_back();
+
+ MarkDependencies (gcState, index);
+ }
+}
+
+#if ENABLE_MEM_PROFILER
+static void ExtractObjectArray (GarbageCollectorState& gcState, dynamic_array<Object*>& objects)
+{
+ objects.resize_uninitialized(gcState.liveObjects.size());
+ for (int i=0;i<objects.size();i++)
+ objects[i] = gcState.liveObjects[i].object;
+}
+
+static void ResetMarkedAndNeedsProcessing (GarbageCollectorState& state, dynamic_array<UInt32>& referencedObjectCount, dynamic_array<UInt32>& referencedObjectIndices)
+{
+ referencedObjectCount.push_back(state.needsProcessing.size());
+ referencedObjectIndices.insert(referencedObjectIndices.end(), state.needsProcessing.begin(), state.needsProcessing.end());
+ for (int i=0;i<state.needsProcessing.size();i++)
+ state.liveObjects[state.needsProcessing[i]].marked = 0;
+ state.needsProcessing.resize_uninitialized(0);
+}
+
+static void ClassifyRootObjects (GarbageCollectorState& state, dynamic_array<const char*>& additionalCategories, dynamic_array<UInt32>& referencedObjectCount, dynamic_array<UInt32>& referencedObjectIndices)
+{
+ // Calculate references for all scene roots
+ dynamic_array<UInt32> sceneRootIndices;
+ dynamic_array<UInt32> otherRootIndices;
+ dynamic_array<UInt32> dirtyAssetIndices;
+
+ for (int i=0;i<state.liveObjects.size();i++)
+ {
+ ObjectState& liveObject = state.liveObjects[i];
+
+ if (IsSceneObject (liveObject))
+ sceneRootIndices.push_back(i);
+ else if (IsAssetNotYetSavedToDisk (liveObject))
+ dirtyAssetIndices.push_back(i);
+ else if (IsObjectAGCRoot (liveObject) != kNoGCRoot)
+ otherRootIndices.push_back(i);
+ }
+
+ additionalCategories.push_back("Scene Object");
+ referencedObjectCount.push_back(sceneRootIndices.size());
+ referencedObjectIndices.insert(referencedObjectIndices.end(), sceneRootIndices.begin(), sceneRootIndices.end());
+
+ additionalCategories.push_back("HideAndDontSave, Manager or AssetBundle");
+ referencedObjectCount.push_back(otherRootIndices.size());
+ referencedObjectIndices.insert(referencedObjectIndices.end(), otherRootIndices.begin(), otherRootIndices.end());
+
+ additionalCategories.push_back("Asset has been edited and not yet saved to disk");
+ referencedObjectCount.push_back(dirtyAssetIndices.size());
+ referencedObjectIndices.insert(referencedObjectIndices.end(), dirtyAssetIndices.begin(), dirtyAssetIndices.end());
+}
+
+void CalculateAllObjectReferences (dynamic_array<Object*>& loadedObjects, dynamic_array<const char*>& additionalCategories, dynamic_array<UInt32>& referencedObjectCount, dynamic_array<UInt32>& referencedObjectIndices)
+{
+ enum { kNbAdditionalCategoriesReserve = 20 };
+
+ GarbageCollectorState state;
+ state.alwaysAddToNeedsProcessing = true;
+ state.followMonoReferences = true;
+
+ // Setup live object mapping
+ FindAllLiveObjects(state);
+ CreateObjectToIndexMappingFromNonRootObjects (state);
+
+ // Build loadedObjects output
+ ExtractObjectArray (state, loadedObjects);
+
+ referencedObjectIndices.reserve (loadedObjects.size() * 2);
+ referencedObjectCount.reserve(loadedObjects.size() + kNbAdditionalCategoriesReserve);
+
+ // Calculate references for all loaded objects
+ for (int i=0;i<loadedObjects.size();i++)
+ {
+ ObjectState& liveObject = state.liveObjects[i];
+ if (liveObject.classID == ClassID(MonoBehaviour))
+ BeginLivenessChecking(state);
+
+ Assert(!liveObject.marked);
+ if (DoesClassIDHaveReferences (liveObject.classID))
+ {
+ liveObject.marked = 1;
+ MarkDependencies (state, i);
+ liveObject.marked = 0;
+ }
+
+ if (liveObject.classID == ClassID(MonoBehaviour))
+ EndLivenessChecking(state);
+
+ ResetMarkedAndNeedsProcessing (state, referencedObjectCount, referencedObjectIndices);
+ }
+
+ // Calculate references for managed static references
+ additionalCategories.push_back("ManagedStaticReferences");
+ BeginLivenessChecking(state);
+ MarkManagedStaticVariableRoots (state);
+ EndLivenessChecking(state);
+ ResetMarkedAndNeedsProcessing (state, referencedObjectCount, referencedObjectIndices);
+
+ // Calculate references from any managers
+ additionalCategories.push_back("Managers");
+ MarkManagerRoots(state);
+ ResetMarkedAndNeedsProcessing (state, referencedObjectCount, referencedObjectIndices);
+
+ // Calculate references from any managers
+ additionalCategories.push_back("Selection");
+ MarkSelectedObjectsAsRoots(state);
+ ResetMarkedAndNeedsProcessing (state, referencedObjectCount, referencedObjectIndices);
+
+ // Classify root objects (Scene objects, hide and dontsave etc)
+ ClassifyRootObjects (state, additionalCategories, referencedObjectCount, referencedObjectIndices);
+}
+#endif
+
+static void InvokeScriptableObjectUnloadCallbacks (const SInt32* scriptableObjectCallbacks, int size)
+{
+ PROFILER_AUTO(gGCOnDestroyCallback, NULL);
+
+#if ENABLE_SCRIPTING
+ for (int i=0;i<size;i++)
+ {
+ MonoBehaviour* behaviour = reinterpret_cast<MonoBehaviour*>(Object::IDToPointer(scriptableObjectCallbacks[i]));
+ if (behaviour)
+ {
+ behaviour->WillUnloadScriptableObject();
+ }
+ }
+#endif
+}
+
+static void CleanupUnusedObjects (GarbageCollectorState& gcState)
+{
+ PROFILER_AUTO(gGCDeletedUnusedAssets, NULL);
+
+ dynamic_array<SInt32> unloadObjects;
+ dynamic_array<SInt32> scriptableObjectUnloadCallbacks;
+ unloadObjects.reserve(gcState.liveObjects.size());
+ scriptableObjectUnloadCallbacks.reserve(gcState.liveObjects.size());
+
+ // Classify
+ for (int i=0;i<gcState.liveObjects.size();i++)
+ {
+ const ObjectState& objectState = gcState.liveObjects[i];
+
+ if (objectState.marked == 0)
+ {
+ SInt32 instanceID = objectState.object->GetInstanceID();
+ unloadObjects.push_back(instanceID);
+
+ if (objectState.classID == ClassID(MonoBehaviour))
+ scriptableObjectUnloadCallbacks.push_back(instanceID);
+ }
+ }
+
+ // ScriptableObject.OnDestroy
+ InvokeScriptableObjectUnloadCallbacks (scriptableObjectUnloadCallbacks.begin(), scriptableObjectUnloadCallbacks.size());
+
+ // Delete objects
+ BatchDeleteObjectInternal (unloadObjects.begin(), unloadObjects.size());
+}
+
+static void CleanupOtherUnusedMemory (GarbageCollectorState& gcState)
+{
+ // Unload streams that are not dirty
+ GetPersistentManager().UnloadNonDirtyStreams ();
+}
+
+
+static void ValidateNoObjectsWereLoaded (GarbageCollectorState& state)
+{
+ Assert(state.originalObjectCount == Object::GetLoadedObjectCount());
+}
+
+
+/*
+
+TODO:
+
+Tests:
+ - ScriptableObject referenced from static variable and no longer referenced from static variable
+ - Dirty assets are not unloaded
+ - Get rid of Validation of bound animation targets on every frame again. Profiler new event system for performance of deleting characters in that case.
+
+Important:
+-- When unloading make sure we dont unload other objects during destruction. Maybe we can use it to get rid of Object* lookups this way???
+ @TODO: Try if we can enable this safely.
+ #if DEBUGMODE
+ int count = Object::GetLoadedObjectCount ();
+#endif
+ Do deletion
+ #if DEBUGMODE
+ if (count-1 != Object::GetLoadedObjectCount ())
+ {
+ AssertString("Unloading this object deleted more than the object alone. This is not allowed.");
+ }
+#endif
+*/
+#endif