using System; using System.Collections.Generic; namespace Photon.Voice { /// /// Generic Pool to re-use objects of a certain type (TType) that optionally match a certain property or set of properties (TInfo). /// /// Object type. /// Type of parameter used to check 2 objects identity (like integral length of array). public abstract class ObjectPool : ObjectFactory { protected int capacity; protected TInfo info; private TType[] freeObj = new TType[0]; protected int pos; protected string name; private bool inited; abstract protected TType createObject(TInfo info); abstract protected void destroyObject(TType obj); abstract protected bool infosMatch(TInfo i0, TInfo i1); internal string LogPrefix { get { return "[ObjectPool] [" + name + "]"; } } /// Create a new ObjectPool instance with the given info structure. /// Capacity (size) of the object pool. /// Name of the object pool. /// Info about this Pool's objects. public ObjectPool(int capacity, string name, TInfo info) { this.capacity = capacity; this.name = name; init(info); } /// (Re-)Initializes this ObjectPool. /// If there are objects available in this Pool, they will be destroyed. /// Allocates (Capacity) new Objects. /// Info about this Pool's objects. private void init(TInfo info) { lock (this) { while (pos > 0) { destroyObject(freeObj[--pos]); } this.info = info; this.freeObj = new TType[capacity]; inited = true; } } /// The property (info) that objects in this Pool must match. public TInfo Info { get { return info; } } /// Acquire an existing object, or create a new one if none are available. /// If it fails to get one from the pool, this will create from the info given in this pool's constructor. public TType New() { lock (this) { if (pos > 0) { return freeObj[--pos]; } if (!inited) { throw new Exception(LogPrefix + " not initialized"); } } return createObject(this.info); } /// Acquire an existing object (if info matches), or create a new one from the passed info. /// Info structure to match, or create a new object with. public TType New(TInfo info) { // TODO: this.info thread safety if (!infosMatch(this.info, info)) { init(info); } return New(); } /// Returns object to pool. /// The object to return to the pool. /// The info structure about obj. /// obj is returned to the pool only if objInfo matches this pool's info. Else, it is destroyed. virtual public bool Free(TType obj, TInfo objInfo) { // TODO: this.info thread safety if (infosMatch(this.info, objInfo)) { lock (this) { if (pos < freeObj.Length) { freeObj[pos++] = obj; return true; } } } // destroy if can't reuse //UnityEngine.Debug.Log(LogPrefix + " Free(Info) destroy"); destroyObject(obj); // TODO: log warning return false; } /// Returns object to pool, or destroys it if the pool is full. /// The object to return to the pool. virtual public bool Free(TType obj) { lock (this) { if (pos < freeObj.Length) { freeObj[pos++] = obj; return true; } } // destroy if can't reuse //UnityEngine.Debug.Log(LogPrefix + " Free destroy " + pos); destroyObject(obj); // TODO: log warning return false; } /// Free resources assoicated with this ObjectPool public void Dispose() { lock (this) { while (pos > 0) { destroyObject(freeObj[--pos]); } freeObj = new TType[0]; } } } /// /// Pool of Arrays with components of type T, with ObjectPool info being the array's size. /// /// Array element type. public class ArrayPool : ObjectPool { public ArrayPool(int capacity, string name, int info) : base(capacity, name, info) { } protected override T[] createObject(int info) { //UnityEngine.Debug.Log(LogPrefix + " Create " + info); return new T[info]; } protected override void destroyObject(T[] obj) { //UnityEngine.Debug.Log(LogPrefix + " Dispose " + pos + " " + obj.GetHashCode()); } protected override bool infosMatch(int i0, int i1) { return i0 == i1; } } /// /// ArrayPool set of limited size. /// /// Array element type. public class ArrayPoolSet : ObjectFactory { Dictionary> pools; int capacity; string name; int defaultInfo; int setSize; public ArrayPoolSet(int capacity, string name, int defaultInfo, int setSize) { this.capacity = capacity; this.name = name; this.defaultInfo = defaultInfo; this.setSize = setSize; this.pools = new Dictionary>(setSize); } public T[] New() { return New(defaultInfo); } public bool Free(T[] obj) { return Free(obj, defaultInfo); } public T[] New(int info) { ArrayPool pool; lock (pools) { if (!pools.TryGetValue(info, out pool)) { if (pools.Count < setSize) { pool = new ArrayPool(capacity, name + " [" + info + "]", info); pools[info] = pool; } } } return pool != null ? pool.New() : new T[info]; } public bool Free(T[] obj, int info) { ArrayPool pool; lock (pools) { pools.TryGetValue(info, out pool); } return pool != null && pool.Free(obj, info); } public void Dispose() { lock (pools) { foreach (var p in pools) { p.Value.Dispose(); } } } } /// /// Set of ArrayPool's of N^2-sized arrays used to produce an ArraySegment with the requested size. /// /// Array element type. public class ArraySegmentPool : ObjectFactory, int> { const int SLOT_0_SIZE_LOG2 = 6; // 1 << 6 = 64 const int MAX_SIZE_LOG2 = 16; // 1 << 16 = 256*256 // counts leading 0's static uint nlz(uint x) { uint y; uint n = 32; y = x >> 16; if (y != 0) { n = n - 16; x = y; } y = x >> 8; if (y != 0) { n = n - 8; x = y; } y = x >> 4; if (y != 0) { n = n - 4; x = y; } y = x >> 2; if (y != 0) { n = n - 2; x = y; } y = x >> 1; if (y != 0) return n - 2; return n - x; } static int slot(uint x) { x--; // 100000 -> 011111 to put round sizes to the correct slot int leftmost1 = 32 - (int)nlz(x); return Math.Max(0, leftmost1 - SLOT_0_SIZE_LOG2 /* to start with 64 */ ); } ArrayPool[] pools = new ArrayPool[MAX_SIZE_LOG2 - SLOT_0_SIZE_LOG2 + 1]; int capacity; string name; int defaultInfo; public ArraySegmentPool(int capacity, string name, int defaultInfo) { this.capacity = capacity; this.name = name; this.defaultInfo = defaultInfo; } public ArraySegment New() { return New(defaultInfo); } public ArraySegment New(int info) { if (info == 0) { return new ArraySegment(Array.Empty()); } int s = slot((uint)info); if (s < pools.Length) { ArrayPool pool; lock (pools) { pool = pools[s]; if (pool == null) { pool = new ArrayPool(capacity, name + " [" + s + "]", (1 << SLOT_0_SIZE_LOG2) << (s)); pools[s] = pool; } } return new ArraySegment(pool.New(), 0, info); } else { throw new ArgumentException("ArraySegmentPool New size is too large: " + info); } } public bool Free(ArraySegment obj, int info) { return Free(obj); } public bool Free(ArraySegment obj) { if (obj.Count == 0) { return false; } int s = slot((uint)obj.Array.Length); if (s < pools.Length) { var pool = pools[s]; return pool != null && pool.Free(obj.Array); } return false; } public void Dispose() { foreach (var p in pools) { if (p != null) { p.Dispose(); } } } static public void test() { ObjectFactory, int> bufferFactory = new ArraySegmentPool(100, " =======", 12345); Queue> test = new Queue>(); for (int i = 0; i < 5; i++) { new System.Threading.Thread(() => { int cnt = 0; while (true) { lock (test) if (test.Count < 100) { var s = cnt++; var b = bufferFactory.New(s); if (s > 0) { b.Array[b.Offset + s - 1] = default(T); } test.Enqueue(b); } } }).Start(); new System.Threading.Thread(() => { while (true) { lock (test) if (test.Count > 10) bufferFactory.Free(test.Dequeue()); } }).Start(); } } } public class ImageBufferNativePool : ObjectPool where T : ImageBufferNative { public delegate T Factory(ImageBufferNativePool pool, ImageBufferInfo info); Factory factory; public ImageBufferNativePool(int capacity, Factory factory, string name, ImageBufferInfo info) : base(capacity, name, info) { this.factory = factory; } protected override T createObject(ImageBufferInfo info) { //UnityEngine.Debug.Log(LogPrefix + " Create " + pos); return factory(this, info); } protected override void destroyObject(T obj) { //UnityEngine.Debug.Log(LogPrefix + " Dispose " + pos + " " + obj.GetHashCode()); obj.Dispose(); } // only height and stride compared, other parameters do not affect native buffers and can be simple overwritten protected override bool infosMatch(ImageBufferInfo i0, ImageBufferInfo i1) { if (i0.Height != i1.Height) { return false; } var s0 = i0.Stride; var s1 = i1.Stride; if (s0.Length != s1.Length) { return false; } switch (i0.Stride.Length) { // most common case are 1 and 3 planes case 1: return s0[0] == s1[0]; case 2: return s0[0] == s1[0] && s0[1] == s1[1]; case 3: return s0[0] == s1[0] && s0[1] == s1[1] && s0[2] == s1[2]; default: for (int i = 0; i < s0.Length; i++) { if (s0[i] != s1[i]) { return false; } } return true; } } } }