using System; using System.Collections.Generic; using Unity.Profiling; using UnityEngine; using UnityEngine.Perception.Randomization.Samplers; namespace UnityEngine.Perception.Randomization.Randomizers.Utilities { /// /// Facilitates object pooling for a pre-specified collection of prefabs with the caveat that objects can be fetched /// from the cache but not returned. Every frame, the cache needs to be reset, which will return all objects to the pool /// public class GameObjectOneWayCache { static ProfilerMarker s_ResetAllObjectsMarker = new ProfilerMarker("ResetAllObjects"); GameObject[] m_GameObjects; UniformSampler m_Sampler = new UniformSampler(); Transform m_CacheParent; Dictionary m_InstanceIdToIndex; List[] m_InstantiatedObjects; int[] m_NumObjectsActive; int NumObjectsInCache { get; set; } /// /// The number of active cache objects in the scene /// public int NumObjectsActive { get; private set; } /// /// Creates a new GameObjectOneWayCache /// /// The parent object all cached instances will be parented under /// The gameObjects to cache public GameObjectOneWayCache(Transform parent, GameObject[] gameObjects) { if (gameObjects.Length == 0) throw new ArgumentException( "A non-empty array of GameObjects is required to initialize this GameObject cache"); m_GameObjects = gameObjects; m_CacheParent = parent; m_InstanceIdToIndex = new Dictionary(); m_InstantiatedObjects = new List[gameObjects.Length]; m_NumObjectsActive = new int[gameObjects.Length]; var index = 0; foreach (var obj in gameObjects) { if (!IsPrefab(obj)) { obj.transform.parent = parent; obj.SetActive(false); } var instanceId = obj.GetInstanceID(); m_InstanceIdToIndex.Add(instanceId, index); m_InstantiatedObjects[index] = new List(); m_NumObjectsActive[index] = 0; ++index; } } /// /// Retrieves an existing instance of the given gameObject from the cache if available. /// Otherwise, instantiate a new instance of the given gameObject. /// /// /// /// public GameObject GetOrInstantiate(GameObject gameObject) { if (!m_InstanceIdToIndex.TryGetValue(gameObject.GetInstanceID(), out var index)) throw new ArgumentException($"GameObject {gameObject.name} (ID: {gameObject.GetInstanceID()}) is not in cache."); ++NumObjectsActive; if (m_NumObjectsActive[index] < m_InstantiatedObjects[index].Count) { var nextInCache = m_InstantiatedObjects[index][m_NumObjectsActive[index]]; ++m_NumObjectsActive[index]; foreach (var tag in nextInCache.randomizerTags) tag.Register(); return nextInCache.instance; } ++NumObjectsInCache; var newObject = Object.Instantiate(gameObject, m_CacheParent); newObject.SetActive(true); ++m_NumObjectsActive[index]; m_InstantiatedObjects[index].Add(new CachedObjectData(newObject)); return newObject; } /// /// Retrieves an existing instance of the given gameObject from the cache if available. /// Otherwise, instantiate a new instance of the given gameObject. /// /// The index of the gameObject to instantiate /// /// public GameObject GetOrInstantiate(int index) { var gameObject = m_GameObjects[index]; return GetOrInstantiate(gameObject); } /// /// Retrieves an existing instance of a random gameObject from the cache if available. /// Otherwise, instantiate a new instance of the random gameObject. /// /// A random cached GameObject public GameObject GetOrInstantiateRandomCachedObject() { return GetOrInstantiate(m_GameObjects[(int)(m_Sampler.Sample() * m_GameObjects.Length)]); } /// /// Return all active cache objects back to an inactive state /// public void ResetAllObjects() { using (s_ResetAllObjectsMarker.Auto()) { NumObjectsActive = 0; for (var i = 0; i < m_InstantiatedObjects.Length; ++i) { m_NumObjectsActive[i] = 0; foreach (var cachedObjectData in m_InstantiatedObjects[i]) { ResetObjectState(cachedObjectData); } } } } /// /// Returns the given cache object back to an inactive state /// /// The object to make inactive /// Thrown when gameObject is not an active cached object. public void ResetObject(GameObject gameObject) { for (var i = 0; i < m_InstantiatedObjects.Length; ++i) { var instantiatedObjectList = m_InstantiatedObjects[i]; int indexFound = -1; for (var j = 0; j < instantiatedObjectList.Count && indexFound < 0; j++) { if (instantiatedObjectList[j].instance == gameObject) indexFound = j; } if (indexFound >= 0) { ResetObjectState(instantiatedObjectList[indexFound]); m_NumObjectsActive[i]--; return; } } throw new ArgumentException("Passed GameObject is not an active object in the cache."); } private static void ResetObjectState(CachedObjectData cachedObjectData) { // Position outside the frame cachedObjectData.instance.transform.localPosition = new Vector3(10000, 0, 0); foreach (var tag in cachedObjectData.randomizerTags) tag.Unregister(); } static bool IsPrefab(GameObject obj) { return obj.scene.rootCount == 0; } struct CachedObjectData { public GameObject instance; public RandomizerTag[] randomizerTags; public CachedObjectData(GameObject instance) { this.instance = instance; randomizerTags = instance.GetComponents(); } } } }