// ===================================================================== // Copyright 2013-2017 Fluffy Underware // All rights reserved // // http://www.fluffyunderware.com // ===================================================================== using System; using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Linq; using FluffyUnderware.DevTools.Extensions; #if CURVY_SANITY_CHECKS_PRIVATE using UnityEngine.Assertions; #endif using UnityEngine.SceneManagement; namespace FluffyUnderware.DevTools { /// /// A pool of instances of the UnityEngine.Component class /// [HelpURL(DTUtility.HelpUrlBase + "dtcomponentpool")] public class ComponentPool : DTVersionedMonoBehaviour, IPool, ISerializationCallbackReceiver { [SerializeField, HideInInspector] private string m_Identifier; [Inline] [SerializeField] private PoolSettings m_Settings; public PoolSettings Settings { get { return m_Settings; } set { if (m_Settings != value) m_Settings = value; if (m_Settings != null) m_Settings.OnValidate(); } } private PoolManager mManager; public PoolManager Manager { get { if (mManager == null) mManager = GetComponent(); return mManager; } } /// /// Due to bad design decisions, Identifier is used to store the type of the pooled objects. And the setter does nothing /// public string Identifier { get { #if CURVY_SANITY_CHECKS_PRIVATE Assert.IsNotNull(m_Identifier); #endif return m_Identifier; } set { throw new InvalidOperationException("Component pool's identifier should always indicate the pooled type's assembly qualified name"); /*Here is why: In the Initialize method, m_Identifier is set as a fully qualified type name. The Type getter uses m_Identifier as a fully qualified type name. If needed, add a field that contains the pooled type name, and use it instead of Identifier when you need to find the right pool for the right component type*/ } } /// /// The type of the pooled objects /// public Type Type { get { Type type = null; if (Identifier != null) type = Type.GetType(Identifier); if (type == null) DTLog.LogWarning("[DevTools] ComponentPool's Type is an unknown type " + m_Identifier); return type; } } public int Count { get { return mObjects.Count; } } private List mObjects = new List(); private double mLastTime; private double mDeltaTime; public void Initialize(Type type, PoolSettings settings) { m_Identifier = type.AssemblyQualifiedName; #if CURVY_SANITY_CHECKS_PRIVATE Assert.IsNotNull(m_Identifier); #endif m_Settings = settings; mLastTime = DTTime.TimeSinceStartup + UnityEngine.Random.Range(0, Settings.Speed); if (Settings.Prewarm) Reset(); } private void Start() { if (Settings.Prewarm) Reset(); } #if UNITY_EDITOR private void OnValidate() { Settings = m_Settings; } #endif private void OnEnable() { SceneManager.sceneLoaded += OnSceneLoaded; } public void Update() { if (Application.isPlaying) { mDeltaTime += DTTime.TimeSinceStartup - mLastTime; mLastTime = DTTime.TimeSinceStartup; if (Settings.Speed > 0) { int c = (int)(mDeltaTime / Settings.Speed); mDeltaTime -= c; if (Count > Settings.Threshold) { c = Mathf.Min(c, Count - Settings.Threshold); while (c-- > 0) { if (Settings.Debug) log("Threshold exceeded: Deleting item"); destroy(mObjects[0]); mObjects.RemoveAt(0); } } else if (Count < Settings.MinItems) { c = Mathf.Min(c, Settings.MinItems - Count); while (c-- > 0) { if (Settings.Debug) log("Below MinItems: Adding item"); mObjects.Add(create()); } } } else mDeltaTime = 0; } } public void Reset() { if (Application.isPlaying) { while (Count < Settings.MinItems) { mObjects.Add(create()); } while (Count > Settings.Threshold) { destroy(mObjects[0]); mObjects.RemoveAt(0); } if (Settings.Debug) log("Prewarm/Reset"); } } public void OnSceneLoaded(Scene scn, LoadSceneMode mode) { for (int i = mObjects.Count - 1; i >= 0; i--) if (mObjects[i] == null) mObjects.RemoveAt(i); } public void Clear() { if (Settings.Debug) log("Clear"); for (int i = 0; i < Count; i++) destroy(mObjects[i]); mObjects.Clear(); } public void Push(Component item) { sendBeforePush(item); #if UNITY_EDITOR if (!Application.isPlaying) { item.gameObject.Destroy(false, true); } else #endif if (item != null) { mObjects.Add(item); item.transform.parent = Manager.transform; item.gameObject.hideFlags = (Settings.Debug) ? HideFlags.DontSave : HideFlags.HideAndDontSave; if (Settings.AutoEnableDisable) item.gameObject.SetActive(false); } } public Component Pop(Transform parent = null) { Component item = null; if (Count > 0) { item = mObjects[0]; mObjects.RemoveAt(0); } else { if (Settings.AutoCreate || !Application.isPlaying) { if (Settings.Debug) log("Auto create item"); item = create(); } } if (item) { item.gameObject.hideFlags = HideFlags.None; item.transform.parent = parent; if (Settings.AutoEnableDisable) item.gameObject.SetActive(true); sendAfterPop(item); if (Settings.Debug) log("Pop " + item); } return item; } public T Pop(Transform parent) where T : Component { return Pop(parent) as T; } private Component create() { GameObject go = new GameObject(); go.name = Identifier; go.transform.parent = Manager.transform; if (Settings.AutoEnableDisable) go.SetActive(false); Type componentType = Type; Component component = null; if (componentType != null) component = go.AddComponent(componentType); else DTLog.LogError(String.Format("[DevTools] ComponentPool {0} could not create component because the associated type is null", m_Identifier)); return component; } private void destroy(Component item) { if (item != null) item.gameObject.Destroy(false, true); } private void setParent(Component item, Transform parent) { if (item != null) item.transform.parent = parent; } private void sendAfterPop(Component item) { GameObject itemGameObject = item.gameObject; if (itemGameObject.activeSelf && itemGameObject.activeInHierarchy) //Send message works only for active game objects. Source: https://docs.unity3d.com/ScriptReference/GameObject.SendMessage.html itemGameObject.SendMessage(nameof(IPoolable.OnAfterPop), SendMessageOptions.DontRequireReceiver); else { if (item is IPoolable) ((IPoolable)item).OnAfterPop(); else DTLog.LogWarning("[Curvy] sendAfterPop could not send message because the receiver " + item.name + " is not active"); } } private void sendBeforePush(Component item) { GameObject itemGameObject = item.gameObject; if (itemGameObject.activeSelf && itemGameObject.activeInHierarchy) //Send message works only for active game objects. Source: https://docs.unity3d.com/ScriptReference/GameObject.SendMessage.html itemGameObject.SendMessage(nameof(IPoolable.OnBeforePush), SendMessageOptions.DontRequireReceiver); else { if (item is IPoolable) ((IPoolable)item).OnBeforePush(); else DTLog.LogWarning("[Curvy] sendBeforePush could not send message because the receiver " + item.name + " is not active"); } } private void log(string msg) { Debug.Log(string.Format("[{0}] ({1} items) {2}", Identifier, Count, msg)); } #region ISerializationCallbackReceiver /*! \cond PRIVATE */ /// /// Implementation of UnityEngine.ISerializationCallbackReceiver /// Called automatically by Unity, is not meant to be called by Curvy's users /// public void OnBeforeSerialize() { } /// /// Implementation of UnityEngine.ISerializationCallbackReceiver /// Called automatically by Unity, is not meant to be called by Curvy's users /// public void OnAfterDeserialize() { if (Type.GetType(m_Identifier) == null) { //Handles cases where the component is migrated to another assembly (for example if using Unity's Assembly Definitions feature const char separator = ','; const string separatorString = ","; string[] splittedAssemblyQualifiedName = m_Identifier.Split(separator); if (splittedAssemblyQualifiedName.Length >= 5) { string typeName = String.Join(separatorString, splittedAssemblyQualifiedName.SubArray(0, splittedAssemblyQualifiedName.Length - 4)); //As you can see with this example: //"FluffyUnderware.Curvy.CurvySplineSegment, ToolBuddy.Curvy, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" //the 4 last elements do not contain the type name. Keep in mind that a type name can include a ',' like Dictionary> #if UNITY_EDITOR var knownTypes = UnityEditor.TypeCache.GetTypesDerivedFrom(typeof(System.Object)); #else #if NETFX_CORE Type[] knownTypes = this.GetType().GetAllTypes(); #else Type[] knownTypes = TypeExt.GetLoadedTypes(); #endif #endif Type type = knownTypes.FirstOrDefault(t => t.FullName == typeName); if (type != null) { m_Identifier = type.AssemblyQualifiedName; #if CURVY_SANITY_CHECKS_PRIVATE Assert.IsNotNull(m_Identifier); #endif } } } } /*! \endcond */ #endregion } }