// ===================================================================== // Copyright 2013-2017 Fluffy Underware // All rights reserved // // http://www.fluffyunderware.com // ===================================================================== using System; using UnityEngine; using System.Collections; using System.Collections.Generic; using Random = UnityEngine.Random; namespace FluffyUnderware.DevTools { [System.Serializable] public struct FloatRegion : IEquatable { public float From; public float To; public bool SimpleValue; public FloatRegion(float value) { From = value; To = value; SimpleValue = true; } public FloatRegion(float A, float B) { this.From = A; this.To = B; SimpleValue = false; } public static FloatRegion ZeroOne { get { return new FloatRegion(0, 1); } } public void MakePositive() { if (To < From) { float temp = To; To = From; From = temp; } } public void Clamp(float low, float high) { Low = Mathf.Clamp(Low, low, high); High = Mathf.Clamp(High, low, high); } public bool Positive { get { return From <= To; } } public float Low { get { return (Positive) ? From : To; } set { if (Positive) From = value; else To = value; } } public float High { get { return (Positive) ? To : From; } set { if (Positive) To = value; else From = value; } } public float Random { get { return UnityEngine.Random.Range(From, To); } } /// /// Gets the next value in the range /// Depending on the value of , this call will or will not make the Random generator's seed progress /// public float Next { get { if (SimpleValue) return From; else return Random; } } public float Length { get { return To - From; } } public float LengthPositive { get { return (Positive) ? To - From : From - To; } } public override string ToString() { return string.Format("({0:F2}-{1:F2})", From, To); } public override int GetHashCode() { return From.GetHashCode() ^ To.GetHashCode() << 2; } public bool Equals(FloatRegion other) { return From.Equals(other.From) && To.Equals(other.To); } public override bool Equals(object other) { if (!(other is FloatRegion)) { return false; } FloatRegion r = (FloatRegion)other; return From.Equals(r.From) && To.Equals(r.To); } public static FloatRegion operator +(FloatRegion a, FloatRegion b) { return new FloatRegion(a.From + b.From, a.To + b.To); } public static FloatRegion operator -(FloatRegion a, FloatRegion b) { return new FloatRegion(a.From - b.From, a.To - b.To); } public static FloatRegion operator -(FloatRegion a) { return new FloatRegion(-a.From, -a.To); } public static FloatRegion operator *(FloatRegion a, float v) { return new FloatRegion(a.From * v, a.To * v); } public static FloatRegion operator *(float v, FloatRegion a) { return new FloatRegion(a.From * v, a.To * v); } public static FloatRegion operator /(FloatRegion a, float v) { return new FloatRegion(a.From / v, a.To / v); } public static bool operator ==(FloatRegion lhs, FloatRegion rhs) { return lhs.SimpleValue == rhs.SimpleValue && Mathf.Approximately(lhs.From, rhs.From) && Mathf.Approximately(lhs.To, rhs.To); } public static bool operator !=(FloatRegion lhs, FloatRegion rhs) { return lhs.SimpleValue != rhs.SimpleValue || !Mathf.Approximately(lhs.From, rhs.From) || !Mathf.Approximately(lhs.To, rhs.To); } } [System.Serializable] public struct IntRegion : IEquatable { public int From; public int To; public bool SimpleValue; public IntRegion(int value) { From = value; To = value; SimpleValue = true; } public IntRegion(int A, int B) { this.From = A; this.To = B; SimpleValue = false; } public static IntRegion ZeroOne { get { return new IntRegion(0, 1); } } public void MakePositive() { if (To < From) { int temp = To; To = From; From = temp; } } public void Clamp(int low, int high) { Low = Mathf.Clamp(Low, low, high); High = Mathf.Clamp(High, low, high); } public bool Positive { get { return From <= To; } } public int Low { get { return (Positive) ? From : To; } set { if (Positive) From = value; else To = value; } } public int High { get { return (Positive) ? To : From; } set { if (Positive) To = value; else From = value; } } public int Random { get { return UnityEngine.Random.Range(From, To); } } public int Length { get { return To - From; } } public int LengthPositive { get { return (Positive) ? To - From : From - To; } } public override string ToString() { return string.Format("({0}-{1})", From, To); } public override int GetHashCode() { return From.GetHashCode() ^ To.GetHashCode() << 2; } public bool Equals(IntRegion other) { return From.Equals(other.From) && To.Equals(other.To); } public override bool Equals(object other) { if (!(other is IntRegion)) { return false; } IntRegion r = (IntRegion)other; return From.Equals(r.From) && To.Equals(r.To); } public static IntRegion operator +(IntRegion a, IntRegion b) { return new IntRegion(a.From + b.From, a.To + b.To); } public static IntRegion operator -(IntRegion a, IntRegion b) { return new IntRegion(a.From - b.From, a.To - b.To); } public static IntRegion operator -(IntRegion a) { return new IntRegion(-a.From, -a.To); } public static IntRegion operator *(IntRegion a, int v) { return new IntRegion(a.From * v, a.To * v); } public static IntRegion operator *(int v, IntRegion a) { return new IntRegion(a.From * v, a.To * v); } public static IntRegion operator /(IntRegion a, int v) { return new IntRegion(a.From / v, a.To / v); } public static bool operator ==(IntRegion lhs, IntRegion rhs) { return lhs.From == rhs.From && lhs.To == rhs.To && lhs.SimpleValue != rhs.SimpleValue; } public static bool operator !=(IntRegion lhs, IntRegion rhs) { return lhs.From != rhs.From || lhs.To != rhs.To || lhs.SimpleValue != rhs.SimpleValue; } } /// /// Simple but effective ShuffleBag implementation /// public class WeightedRandom { List mData; int mCurrentPosition = -1; T mCurrentItem; public int Seed { get; set; } public bool RandomizeSeed { get; set; } private int Capacity { get { return mData.Capacity; } } public int Size { get { return mData.Count; } } public WeightedRandom(int initCapacity = 0) { mData = new List(initCapacity); } public WeightedRandom(int initCapacity, int seed):this(initCapacity) { Seed = seed; } /// /// Adds items to the bag /// /// the item /// number of times this item should be added public void Add(T item, int amount) { for (int i = 0; i < amount; i++) mData.Add(item); mCurrentPosition = Size - 1; } /// /// Gets a random item from the bag /// /// an item public T Next() { if (mCurrentPosition < 1) { mCurrentPosition = Size - 1; mCurrentItem = mData[0]; return mCurrentItem; } Random.State s = Random.state; if (RandomizeSeed) Seed = Random.Range(0, int.MaxValue); Random.InitState(Seed); int idx = Random.Range(0, mCurrentPosition); Random.state = s; mCurrentItem = mData[idx]; mData[idx] = mData[mCurrentPosition]; mData[mCurrentPosition] = mCurrentItem; mCurrentPosition--; return mCurrentItem; } /// /// Refill the bag /// public void Reset() { mCurrentPosition = Size - 1; } /// /// Clear the bag /// public void Clear() { mData.Clear(); mCurrentPosition = -1; } } public class Ring : IList { List mList; public int Size { get; private set; } int mIndex; public Ring(int size) { mList = new List(size); Size = size; } public void Add(T item) { if (mList.Count == Size) { mList[mIndex++] = item; if (mIndex == mList.Count) mIndex = 0; } else mList.Add(item); } public void Clear() { mList.Clear(); mIndex = 0; } public int IndexOf(T item) { return mList.IndexOf(item); } public void Insert(int index, T item) { throw new System.NotSupportedException(); } public void RemoveAt(int index) { throw new System.NotSupportedException(); } public T this[int index] { get { return mList[index]; } set { mList[index] = value; } } public IEnumerator GetEnumerator() { return mList.GetEnumerator(); } public bool Contains(T item) { return mList.Contains(item); } public void CopyTo(T[] array, int arrayIndex) { mList.CopyTo(array, arrayIndex); } public int Count { get { return mList.Count; } } public bool IsReadOnly { get { throw new System.NotSupportedException(); } } public bool Remove(T item) { return mList.Remove(item); } IEnumerator IEnumerable.GetEnumerator() { throw new System.NotImplementedException(); } } public class Pool : IPool { List mObjects = new List(); public string Identifier { get; set; } public PoolSettings Settings { get; protected set; } public System.Type Type { get { return typeof(T); } } double mLastTime; double mDeltaTime; public Pool(PoolSettings settings = null) { Settings = settings ?? new PoolSettings(); Identifier = typeof(T).FullName; mLastTime = DTTime.TimeSinceStartup + UnityEngine.Random.Range(0, Settings.Speed); if (Settings.Prewarm) Reset(); } public void Update() { 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) { destroy(mObjects[0]); mObjects.RemoveAt(0); log("Threshold exceeded: Deleting item"); } } else if (Count < Settings.MinItems) { c = Mathf.Min(c, Settings.MinItems - Count); while (c-- > 0) { mObjects.Add(create()); log("Below MinItems: Adding item"); } } } 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); } log("Prewarm/Reset"); } } public void Clear() { log("Clear"); for (int i = 0; i < Count; i++) destroy(mObjects[i]); mObjects.Clear(); } public int Count { get { return mObjects.Count; } } public virtual T Pop(Transform parent = null) { T item = default(T); if (Count > 0) { item = mObjects[0]; mObjects.RemoveAt(0); } else { if (Settings.AutoCreate || !Application.isPlaying) { log("Auto create item"); item = create(); } } if (item != null) { sendAfterPop(item); setParent(item, parent); log("Pop " + item); } return item; } public virtual void Push(T item) { log("Push " + item); if (Application.isPlaying && item != null) { sendBeforePush(item); mObjects.Add(item); } } protected virtual void sendBeforePush(T item) { if (item is IPoolable) ((IPoolable)item).OnBeforePush(); } protected virtual void sendAfterPop(T item) { if (item is IPoolable) ((IPoolable)item).OnAfterPop(); } protected virtual void setParent(T item, Transform parent) { } protected virtual T create() { return System.Activator.CreateInstance(); } protected virtual void destroy(T item) { } void log(string msg) { if (Settings.Debug) Debug.Log(string.Format("[{0}] ({1} items) {2}", Identifier, Count, msg)); } } [System.Serializable] public class PoolSettings { [SerializeField] bool m_Prewarm = false; [SerializeField] bool m_AutoCreate = true; [SerializeField] bool m_AutoEnableDisable = true; [Positive] [SerializeField] int m_MinItems = 0; [Positive] [SerializeField] int m_Threshold = 0; [Positive] [SerializeField] float m_Speed = 1; public bool Debug; public PoolSettings() { } public PoolSettings(PoolSettings src) { Prewarm = src.Prewarm; AutoCreate = src.AutoCreate; MinItems = src.MinItems; Threshold = src.Threshold; Speed = src.Speed; Debug = src.Debug; } public bool Prewarm { get { return m_Prewarm; } set { if (m_Prewarm != value) m_Prewarm = value; } } public bool AutoCreate { get { return m_AutoCreate; } set { if (m_AutoCreate != value) m_AutoCreate = value; } } public bool AutoEnableDisable { get { return m_AutoEnableDisable; } set { if (m_AutoEnableDisable != value) m_AutoEnableDisable = value; } } public int MinItems { get { return m_MinItems; } set { int v = Mathf.Max(0, value); if (m_MinItems != v) m_MinItems = v; } } public int Threshold { get { return m_Threshold; } set { int v = Mathf.Max(MinItems, value); if (m_Threshold != v) m_Threshold = v; } } public float Speed { get { return m_Speed; } set { float v = Mathf.Max(0, value); if (m_Speed != v) m_Speed = v; } } public void OnValidate() { MinItems = m_MinItems; Threshold = m_Threshold; Speed = m_Speed; } } public interface IPool { string Identifier { get; set; } PoolSettings Settings { get; } void Clear(); void Reset(); void Update(); int Count { get; } } public interface IPoolable { void OnBeforePush(); void OnAfterPop(); } }