Komplexer verschachtelter Objektpool (3) – ein Objektpool, der mehrere Instanzobjekte verwaltet

[Der Unterschied zwischen Objektpools, die einzelne und mehrere Instanzobjekte verwalten]

Für einen Objektpool, der ein einzelnes Instanzobjekt verwaltet, muss er nur die kontinuierliche Ausleihe und Wiederverwendung seiner Instanzobjekte berücksichtigen. Wenn die Wiederverwendungsgeschwindigkeit größer als die Ausleihgeschwindigkeit ist, bedeutet dies, dass die zwischengespeicherten Objekte ausreichend sind. Wenn nicht genug, Erweitern Sie einfach die Kapazität. Wir müssen uns keine Sorgen machen, dass der Speicher knapp wird, Dutzende, höchstens Hunderte von Objekten reichen aus.

Für einen Objektpool, der mehrere Instanzobjekte verwaltet, beträgt der verwaltete Gesamtspeicher Tausende oder sogar Zehntausende. Einige Cache-Objekte werden möglicherweise nicht mehr benötigt, befinden sich jedoch immer noch im Speicher, was zu einer erheblichen Speicherverschwendung führt. Daher müssen wir diese Objekte, die längere Zeit nicht verwendet wurden, entfernen, um Speicherplatz zu sparen. Dabei handelt es sich um die Strategie der Cache-Eliminierung.

Zu den gängigen Cache-Eliminierungsstrategien gehören: FIFO, LRU, LFU usw. Was bedeuten diese? Es gibt viele Informationen im Internet, daher werde ich hier nicht auf Details eingehen. Hier werden wir LRU verwenden, einfach implementieren.

[Implementierung von LRU]

Definieren Sie zunächst die Schnittstelle des Caches und die Aufzählung der Cache-Eliminierung. Dieses Get wird nicht wie eine Warteschlange entfernt, und es muss eine Remove-Methode hinzugefügt werden.

    public interface ICacheWeedOut<K,V>: ICache
    {
        bool Get(K key,out V value);
        bool Put(K key,V value);
        bool Remove(K key);
    }

    /// <summary>
    /// 缓存淘汰策略
    /// </summary>
    public enum CacheWeedOutStrategy
    {
        NONE = 0,
        FIFO,
        LRU,
        LFU,
    }

Die Implementierung von LRU muss Hashmap verwenden, um schnelle Abfragedaten mit einer Zeitkomplexität von O(1) zu erhalten, und eine doppelt verknüpfte Liste verwenden, um neue und gelöschte Daten mit einer Zeitkomplexität von O(1) zu erhalten. Wenn Sie es nicht wissen, können Sie sich auf Folgendes beziehen: Prinzip des LRU-Algorithmus

Das Folgende ist die C#-Implementierung von LRU

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Cache
{
    public class LRU<K,V> : ICacheWeedOut<K,V>
    {
        private LinkedList<K> lru;//这里放Key,V的会更新
        private Dictionary<K,LinkedListNode<K>> nodeCache;//key对应的LinkedListNode结构
        private Dictionary<K,V> keyCache;//键值对

        private int capacity;
        private WeedOutDelegate<V> weedOutDelegate;
        private bool disposed;
        
        public LRU(int capacity,WeedOutDelegate<V> weedOutDelegate = null)
        {
            this.capacity = capacity;
            this.weedOutDelegate = weedOutDelegate;

            lru = new LinkedList<K>();
            nodeCache = new Dictionary<K,LinkedListNode<K>>();
            keyCache = new Dictionary<K,V>();

        }
        public int Capacity()
        {
            return capacity;
        }

        public int Count()
        {
            return lru.Count;
        }

        public bool Get(K key, out V value)
        {
            LinkedListNode<K> node;
            if (nodeCache.TryGetValue(key, out node))
            {
                lru.Remove(node);
                lru.AddFirst(node);//访问了,放到最前面
                value = keyCache[key];
                return true;
            }
            value = default(V);
            return false;
        }

        public bool Put(K key, V value)
        {
            if(!value.GetType().IsValueType && default(V) == null)
                return false;

            LinkedListNode<K> node;
            if(nodeCache.TryGetValue(key, out node))//如果有Key,放到最前面
            {
                lru.Remove(node);
                lru.AddFirst(node);
            }
            else
            {
                node = lru.AddFirst(key);//没有Key,则添加
            }
            if (!keyCache.ContainsKey(key))
            {
                if(lru.Count == capacity)//如果这个Key是新添加的,要检查是否到容量了
                {
                    if(!WeedOut())
                    {
                        return false;
                    }
                }                  
            }
            keyCache[key] = value;
            nodeCache[key] = node;
            return true;

        }
        private bool WeedOut()//将链表尾部淘汰
        {
            if (lru.Count == 0)//做下保护
                return false;

            K key = lru.Last.Value;
            nodeCache.Remove(key);
            lru.RemoveLast();//移除末尾的
            V value = keyCache[key];
            keyCache.Remove(key);
            weedOutDelegate?.Invoke(value);
            return true;
        }

        public bool Remove(K key)
        {
            LinkedListNode<K> node;
            if (nodeCache.TryGetValue(key, out node))
            {
                V value = keyCache[key];
                keyCache.Remove(key);
                nodeCache.Remove(key);
                lru.Remove(node);
                weedOutDelegate?.Invoke(value);//主动移除某个Key,淘汰
                return true;
            }
            return false;
        }

        public bool Shrink(ShrinkStrategy shrinkStrategy,  float percent = 0f, int count = 0, bool weedout = true)
        {
            bool result = true;
            switch (shrinkStrategy)
            {
                case ShrinkStrategy.NONE: break;
                case ShrinkStrategy.FIXEDCOUNT:
                    {
                        int residualCapacity = capacity - count;
                        if (residualCapacity < 0)//容量不能小于0
                        {
                            result = false;
                            break;
                        }
                        if (residualCapacity >= lru.Count)
                        {
                            capacity = residualCapacity;
                        }
                        else
                        {
                            if (weedout)//淘汰
                            {
                                int num = lru.Count - residualCapacity;
                                while (num > 0)
                                {
                                    result = WeedOut();
                                    num--;
                                }
                                capacity = residualCapacity;
                            }
                            else
                            {
                                capacity = lru.Count;
                            }
                        }
                        break;
                    }
                case ShrinkStrategy.DOUBLE:
                    count = capacity / 2;
                    Shrink(ShrinkStrategy.FIXEDCOUNT, percent, count, weedout);
                    break;
                case ShrinkStrategy.PERCENT:
                    count = (int)(capacity * percent);
                    Shrink(ShrinkStrategy.FIXEDCOUNT, percent, count, weedout);
                    break;
            }
            return result;
        }


        ~LRU()
        {
            Dispose(false);
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(true);
        }

        protected virtual void Dispose(bool dispose)
        {
            if (disposed)
            {
                return;
            }

            if (dispose)//true表示主动调用
            {
                //这个不用处理非托管资源,用委托传递出去
            }

            foreach (var item in keyCache.Values)
            {
                weedOutDelegate?.Invoke(item);
            }
            lru.Clear();
            nodeCache.Clear();
            keyCache.Clear();   
            disposed = true;
        }
    }

}

Um das Anrufen komfortabler zu gestalten, erstellen wir eine Kapselungsschicht:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Cache
{
    public class Cache<K,V> : ICacheWeedOut<K, V>
    {
        protected ICacheWeedOut<K, V> cache;

        public Cache(CacheWeedOutStrategy cacheWeedOutStrategy, int capacity, WeedOutDelegate<V> weedOutDelegate)
        {
            switch(cacheWeedOutStrategy)
            {
                case CacheWeedOutStrategy.LRU: cache = new LRU<K,V>(capacity,weedOutDelegate); break;
                case CacheWeedOutStrategy.LFU: cache = new LFU<K,V>(capacity,weedOutDelegate); break;
            }
        }
        public int Capacity()
        {
            return cache.Capacity();
        }

        public int Count()
        {
            return cache.Count();
        }

        public bool Get(K key, out V value)
        {
            return cache.Get(key, out value);
        }

        public bool Put(K key, V value)
        {
            return cache.Put(key, value);
        }

        public bool Shrink(ShrinkStrategy shrinkStrategy,  float percent, int count, bool weedout)
        {
            return cache.Shrink(shrinkStrategy, percent, count, weedout);
        }

        public void Dispose()
        {
            cache.Dispose();
        }

        public bool Remove(K key)
        {
            return cache.Remove(key);
        }
    }
}

【Konfiguration des Objektpools】

Jeder Unterobjektpool im Objektpool, der mehrere Instanzen verwaltet, muss über ScriptableObject über eigene Initialisierungsparameter, also eine eigene Konfiguration, verfügen

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

namespace Cache
{
    public enum PoolType
    {
        NONE = 0,
        Single,
        Multi,
        ObjectType,
        StageType
    }

    public class ObjectPoolAsset : ScriptableObject
    {
        public List<PoolInfo> poolInfos = new List<PoolInfo>();            
    }

    [Serializable]
    public class PoolInfo
    {
        /// <summary>
        /// 对象池类型
        /// </summary>
        public PoolType poolType;
        /// <summary>
        /// 对象池名字
        /// </summary>
        public string poolName;
        /// <summary>
        /// 对象池容量
        /// </summary>
        public int capacity;
        /// <summary>
        /// 缓存淘汰的策略
        /// </summary>
        public CacheWeedOutStrategy cacheWeedOutStrategy;
        /// <summary>
        /// 扩容策略
        /// </summary>
        public ExpansionStrategy expansionStrategy; 
        /// <summary>
        /// 子对象池数量
        /// </summary>
        public int subPoolCount;
        /// <summary>
        /// 默认子对象池设置
        /// </summary>
        public PoolInfo defaultSubPoolInfo;
        /// <summary>
        /// 子对象池的设置
        /// </summary>
        public List<PoolInfo> subPoolInfo;        
    }
}

[Implementierung eines Objektpools, der mehrere Instanzpaare verwaltet]

 Hier müssen wir mithilfe von PoolInfo einen Konstruktor hinzufügen. Die anderen sind ähnlich. Weitere Informationen finden Sie im Kommentarcode

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Cache
{
    public class MultiObjectPool:ObjectPoolBase
    {
        //需要知道所需要的对象Object从哪个singleObjectPool里产出
        private Cache<string, SingleObjectPool> _poolCache;
        //需要知道借出的实例对象是从哪个Object实例出来的
        private Dictionary<int, string> lentInstance2Object = new Dictionary<int, string>();

        public MultiObjectPool(int capacity, string poolName, CacheWeedOutStrategy cacheWeedOutStrategy, Action<bool> onlendObjectOut = null,Action<bool> onRecycleObject = null):base(capacity, poolName,onlendObjectOut, onRecycleObject)
        {            
            poolCache = new Cache<string, SingleObjectPool>( cacheWeedOutStrategy, capacity, OnWeedOut);
            _poolCache = poolCache as Cache<string, SingleObjectPool>;
        }

        //新的构造函数,用PoolInfo
        public MultiObjectPool(PoolInfo poolInfo, Action<bool> onlendObjectOut = null, Action<bool> onRecycleObject = null):base(poolInfo, onlendObjectOut, onRecycleObject)
        {
            if(poolInfo != null)
            {
                if(poolInfo.poolType == PoolType.Multi)
                {
                    poolCache = new Cache<string, SingleObjectPool>( poolInfo.cacheWeedOutStrategy, poolInfo.capacity, OnWeedOut);
                    _poolCache = poolCache as Cache<string, SingleObjectPool>;

                    //初始化子对象池
                    foreach (var item in poolInfo.subPoolInfo)
                    {
                        var subPool = new SingleObjectPool(item.capacity,item.poolName, item.expansionStrategy, OnLendObjectOut, OnRecycleObject);
                        _poolCache.Put(subPool.poolName, subPool);
                        TotalCapcity += subPool.Capacity();
                    }
                    //使用默认的池子设置
                    for (int i = poolInfo.subPoolInfo.Count; i < poolInfo.subPoolCount; i++)
                    {
                        var subPool = new SingleObjectPool(poolInfo.defaultSubPoolInfo.capacity,poolInfo.defaultSubPoolInfo.poolName,poolInfo.defaultSubPoolInfo.expansionStrategy,OnLendObjectOut, OnRecycleObject);
                        _poolCache.Put(subPool.poolName, subPool);
                        TotalCapcity += subPool.Capacity();
                    }
                }
            }
        }

        public override GameObject LendObjectOut(string objectName,Transform parent = null, params object[] extradata)
        {           
            if (string.IsNullOrEmpty(objectName))
                return null;

            GameObject go = null;
            SingleObjectPool singleObjectPool = null;
            if(_poolCache.Get(objectName, out singleObjectPool))//找到想要的实例对象应该从哪个singleObjectPool里借出
            {
                go = singleObjectPool.LendObjectOut(objectName,parent);
            }
            else 
            {
                //没有的话应该不做处理,但在刚开始的时候子对象池数量很多,不可能自己一一配置
                //singleObjectPool = new SingleObjectPool(10,objectName, ExpansionStrategy.NONE, OnLendObjectOut, OnRecycleObject);                
                //_poolCache.Put(objectName, singleObjectPool);
                //go = singleObjectPool.LendObjectOut(objectName, parent);
                //TotalCapcity += singleObjectPool.Capacity();
            }

            if(go)
                lentInstance2Object[go.GetInstanceID()] = objectName;
            return go;
        }

        public override bool RecycleObject(GameObject go)
        {
            if (go == null)
                return false;

            if (!lentInstance2Object.ContainsKey(go.GetInstanceID()))//表示不是从该对象池中借出的
                return false;

            SingleObjectPool pool;
            if(_poolCache.Get(lentInstance2Object[go.GetInstanceID()],out pool))
            {
                if (pool.RecycleObject(go))
                {
                    lentInstance2Object.Remove(go.GetInstanceID());
                    return true;
                }             
            }
            return false;
        }

        private void OnWeedOut(SingleObjectPool singleObjectPool)
        {
            TotalCount -= singleObjectPool.Count();//生成的实例总数减少,对象池中缓存的对象都是先生成,随后再放进去的,生成总数-缓存总数= 借出总数
            CachedCount -= singleObjectPool.Count();//缓存的实例总数减少
            TotalCapcity -= singleObjectPool.Capacity();//可缓存容量减少
            singleObjectPool.Dispose();
        }

        private void OnLendObjectOut(bool instantiated)
        {
            if (instantiated)
                TotalCount++;
            else
                CachedCount--;
            onlendObjectOut?.Invoke(instantiated);
        }

        private void OnRecycleObject(bool destroyed)
        {
            if (destroyed)
            { 
                TotalCount--; 
                CachedCount--;
            }
            else
                CachedCount++;
            onRecycleObject?.Invoke(destroyed); 
        }
    }
}

Guess you like

Origin blog.csdn.net/enternalstar/article/details/125272814