using System; using System.Collections.Generic; using UnityEngine.Assertions; using UnityEngine.SceneManagement; namespace UnityEngine.Rendering.HighDefinition { class SceneObjectIDMap { public static bool TryGetSceneObjectID(GameObject gameObject, out int index, out TCategory category) where TCategory : struct, IConvertible { if (!typeof(TCategory).IsEnum) throw new ArgumentException("'TCategory' must be an Enum type."); if (gameObject == null) throw new ArgumentNullException("gameObject"); index = default; category = default; return TryGetOrCreateSceneIDMapFor(gameObject.scene, out SceneObjectIDMapSceneAsset map) && map.TryGetSceneIDFor(gameObject, out index, out category); } public static int GetOrCreateSceneObjectID(GameObject gameObject, TCategory category) where TCategory : struct, IConvertible { if (!typeof(TCategory).IsEnum) throw new ArgumentException("'TCategory' must be an Enum type."); if (gameObject == null) throw new ArgumentNullException("gameObject"); if (!TryGetOrCreateSceneIDMapFor(gameObject.scene, out SceneObjectIDMapSceneAsset map)) throw new ArgumentException($"Provided GameObject {gameObject} does not belong to a loaded scene."); if (!map.TryGetSceneIDFor(gameObject, out int index, out TCategory registeredCategory)) { var insertion = map.TryInsert(gameObject, category, out index); Assert.IsTrue(insertion); } return index; } public static void GetAllIDsForAllScenes( TCategory category, List outGameObjects, List outIndices, List outScenes ) where TCategory : struct, IConvertible { if (outGameObjects == null) throw new ArgumentNullException("outGameObjects"); if (outIndices == null) throw new ArgumentNullException("outIndices"); if (outIndices == null) throw new ArgumentNullException("outScenes"); var lastCount = outGameObjects.Count; for (int i = 0; i < SceneManager.sceneCount; ++i) { var scene = SceneManager.GetSceneAt(i); GetAllIDsFor(category, scene, outGameObjects, outIndices); for (int j = 0, c = outGameObjects.Count - lastCount; j < c; ++j) outScenes.Add(scene); } } public static void GetAllIDsFor( TCategory category, Scene scene, List outGameObjects, List outIndices ) where TCategory : struct, IConvertible { if (outGameObjects == null) throw new ArgumentNullException("outGameObjects"); if (outIndices == null) throw new ArgumentNullException("outIndices"); if (TryGetSceneIDMapFor(scene, out SceneObjectIDMapSceneAsset map)) map.GetALLIDsFor(category, outGameObjects, outIndices); } static bool TryGetSceneIDMapFor(Scene scene, out SceneObjectIDMapSceneAsset map) { if (!scene.isLoaded) { map = default; return false; } var roots = scene.GetRootGameObjects(); for (int i = 0; i < roots.Length; ++i) { if (roots[i].name == SceneObjectIDMapSceneAsset.k_GameObjectName && (map = roots[i].GetComponent()) != null && !map.Equals(null)) return true; } map = null; return false; } static SceneObjectIDMapSceneAsset CreateSceneIDMapFor(Scene scene) { var gameObject = new GameObject(SceneObjectIDMapSceneAsset.k_GameObjectName) { hideFlags = HideFlags.DontSaveInBuild | HideFlags.HideInHierarchy | HideFlags.HideInInspector }; var result = gameObject.AddComponent(); SceneManager.MoveGameObjectToScene(gameObject, scene); return result; } static bool TryGetOrCreateSceneIDMapFor(Scene scene, out SceneObjectIDMapSceneAsset map) { if (!scene.isLoaded) { map = default; return false; } if (!TryGetSceneIDMapFor(scene, out map)) map = CreateSceneIDMapFor(scene); return true; } } class SceneObjectIDMapSceneAsset : MonoBehaviour, ISerializationCallbackReceiver { internal const string k_GameObjectName = "SceneIDMap"; [Serializable] struct Entry { public int id; public int category; public GameObject gameObject; } [SerializeField] List m_Entries = new List(); Dictionary m_IndexByGameObject = new Dictionary(); public void GetALLIDsFor( TCategory category, List outGameObjects, List outIndices ) where TCategory : struct, IConvertible { if (outGameObjects == null) throw new ArgumentNullException("outGameObjects"); if (outIndices == null) throw new ArgumentNullException("outIndices"); CleanDestroyedGameObjects(); var intCategory = Convert.ToInt32(category); for (int i = m_Entries.Count - 1; i >= 0; --i) { if (m_Entries[i].category != intCategory) continue; outIndices.Add(m_Entries[i].id); outGameObjects.Add(m_Entries[i].gameObject); } } internal bool TryGetSceneIDFor(GameObject gameObject, out int index, out TCategory category) where TCategory : struct, IConvertible { if (!typeof(TCategory).IsEnum) throw new ArgumentException("'TCategory' must be an Enum type."); if (gameObject == null) throw new ArgumentNullException("gameObject"); int entryIndex; if (m_IndexByGameObject.TryGetValue(gameObject, out entryIndex)) { if (entryIndex < m_Entries.Count) { category = (TCategory)(object)m_Entries[entryIndex].category; index = m_Entries[entryIndex].id; return true; } else m_IndexByGameObject.Remove(gameObject); } category = default(TCategory); index = -1; return false; } internal bool TryInsert(GameObject gameObject, TCategory category, out int index) where TCategory : struct, IConvertible { if (!typeof(TCategory).IsEnum) throw new ArgumentException("'TCategory' must be an Enum type."); if (gameObject == null) throw new ArgumentNullException("gameObject"); if (gameObject.scene != this.gameObject.scene) { index = -1; return false; } TCategory registeredCategory; if (TryGetSceneIDFor(gameObject, out index, out registeredCategory)) return false; index = Insert(gameObject, category); return true; } int Insert(GameObject gameObject, TCategory category) where TCategory : struct, IConvertible { Assert.IsFalse(m_IndexByGameObject.ContainsKey(gameObject)); Assert.AreEqual(gameObject.scene, this.gameObject.scene); var entry = new Entry { gameObject = gameObject, category = Convert.ToInt32(category) }; // Sorted insert // Insert where there is room between to indices var index = -1; if (m_Entries.Count > 0 && m_Entries[0].id != 0) { index = 0; entry.id = 0; } else { for (int i = 0; i < m_Entries.Count - 1; ++i) { if (m_Entries[i].id + 1 == m_Entries[i + 1].id) continue; index = i + 1; entry.id = m_Entries[i].id + 1; break; } } if (index == -1) { index = m_Entries.Count; // entries are full, so the id is the number of entries entry.id = m_Entries.Count; } m_IndexByGameObject.Add(gameObject, index); m_Entries.Insert(index, entry); for (int i = index + 1; i < m_Entries.Count; ++i) { // Upon insertion, all index by game object entries after the insertion point need their index updated. m_IndexByGameObject[m_Entries[i].gameObject] = i; } return m_Entries[index].id; } void ISerializationCallbackReceiver.OnAfterDeserialize() { BuildIndex(); } void ISerializationCallbackReceiver.OnBeforeSerialize() { CleanDestroyedGameObjects(); } void CleanDestroyedGameObjects() { var rebuildIndex = false; for (int i = m_Entries.Count - 1; i >= 0; --i) { // Clean destroyed game object in if (m_Entries[i].gameObject == null) { m_Entries.RemoveAt(i); rebuildIndex = true; } } if (rebuildIndex) BuildIndex(); } void BuildIndex() { m_IndexByGameObject.Clear(); for (int i = 0; i < m_Entries.Count; ++i) m_IndexByGameObject[m_Entries[i].gameObject] = i; } } }