天天看点

12 对象池

先新建如下脚本

12 对象池

ObjectBase是对象基类,如果某个对象想要被对象池管理,那就必须继承自这个类,实际上真正的对象被封装在这个类内部

IObjectPool是对象池接口,OBjectPool是对象池,和引用池的结构很像

ObjectPoolManager就是核心管理器了

ObjectBase

namespace SimpleGameFramework.ObjectPool
{
    /// <summary>
    /// 池对象基类
    /// </summary>
    public abstract class ObjectBase 
    {
        #region Property

            /// <summary>
            /// 对象名称
            /// </summary>
            public string Name { get; set; }
     
            /// <summary>
            /// 对象(实际使用到的对象放到这里)
            /// </summary>
            public object Target { get; set; }
     
            /// <summary>
            /// 对象上次使用时间
            /// </summary>
            public DateTime LastUseTime { get; private set; }
     
            /// <summary>
            /// 对象的获取计数
            /// </summary>
            public int SpawnCount { get; set; }
     
            /// <summary>
            /// 对象是否正在使用
            /// </summary>
            public bool IsInUse
            {
                get
                {
                    return SpawnCount > 0;
                }
            }
     
            public ObjectBase(object target, string name = "")
            {
                Name = name;
                Target = target;
            }

        #endregion

        #region 生命周期

            /// <summary>
            /// 获取对象时
            /// </summary>
            protected virtual void OnSpawn()
            {
     
            }
     
            /// <summary>
            /// 回收对象时
            /// </summary>
            protected virtual void OnUnspawn()
            {
     
            }
     
            /// <summary>
            /// 释放对象时
            /// </summary>
            public abstract void Release();

        #endregion

        #region 接口

            /// <summary>
            /// 获取对象
            /// </summary>
            public ObjectBase Spawn()
            {
                SpawnCount++;
                LastUseTime = DateTime.Now;
                OnSpawn();
                return this;
            }
     
            /// <summary>
            /// 回收对象
            /// </summary>
            public void Unspawn()
            {
                OnUnspawn();
                LastUseTime = DateTime.Now;
                SpawnCount--;
            }

        #endregion
    }
}
           

这里的关键是接口里的两个方法,我们注意到,不管是生成对象还是归还对象,我们都只是对引用计数进行了操作,哪怕归还后引用计数为0,我们也没有释放他,注意,我们的ObjectBase,也就是被管理的对象,它自身是不会自动释放的, 单纯只是维护了一个引用计数,和生成销毁时调用的方法!而是否释放,应该由我们的对象池来进行控制。

IObjectPool

namespace SimpleGameFramework.ObjectPool
{
    public interface IObjectPool
    {
        #region Property

            /// <summary>
            /// 对象池名称
            /// </summary>
            string Name { get; }
     
            /// <summary>
            /// 对象池对象类型
            /// </summary>
            Type ObjectType { get; }
     
     
            /// <summary>
            /// 对象池中对象的数量。
            /// </summary>
            int Count { get; }
     
     
            /// <summary>
            /// 对象池中能被释放的对象的数量。
            /// </summary>
            int CanReleaseCount { get; }
     
            /// <summary>
            /// 对象池自动释放可释放对象的间隔秒数(隔几秒进行一次自动释放)
            /// </summary>
            float AutoReleaseInterval { get; set; }
     
            /// <summary>
            /// 对象池的容量。
            /// </summary>
            int Capacity { get; set; }
     
     
            /// <summary>
            /// 对象池对象过期秒数(被回收几秒钟视为过期,需要被释放)
            /// </summary>
            float ExpireTime { get; set; }

        #endregion

        #region 释放

            /// <summary>
            /// 释放超出对象池容量的可释放对象
            /// </summary>
            void Release();
     
            /// <summary>
            /// 释放指定数量的可释放对象
            /// </summary>
            /// <param name="toReleaseCount">尝试释放对象数量。</param>
            void Release(int toReleaseCount);
     
            /// <summary>
            /// 释放对象池中的所有未使用对象
            /// </summary>
            void ReleaseAllUnused();

        #endregion

        #region Update与ShutDown

            /// <summary>
            /// 轮询对象池
            /// </summary>
            void Update(float time);
     
            /// <summary>
            /// 清理并关闭对象池
            /// </summary>
            void Shutdown();

        #endregion
    }
}
           

对象池接口,定义了我们的对象池必须实现的方法和属性

ObjectPool

首先咱们给对象池加上一个泛型,代表它管理的对象的类型,注意一个对象池只管理一种类型的对象,然后由管理器管理很多不同类型的对象池,和引用池的设计思路是一样的

namespace SimpleGameFramework.ObjectPool
{
    public class ObjectPool<T> : IObjectPool where T : ObjectBase
    {
        
    }
}
           

实现接口并添加一些字段

namespace SimpleGameFramework.ObjectPool
{
    public class ObjectPool<T> : IObjectPool where T : ObjectBase
    {
        #region Implement

            public string Name { get; private set; }
            
            public Type ObjectType
            {
                get { return typeof(T); }
            }
            
            public int Count
            {
                get { return m_Objects.Count; }
            }
            
            public int CanReleaseCount {
                get
                {
                    // 这个方法定义在了后面
                    return GetCanReleaseObjects().Count;
                }
            }
            
            public float AutoReleaseInterval { get; set; }

            public int Capacity
            {
                get { return m_Capacity; }
                set
                {
                    if (value < 0)
                        Debug.LogError("对象池容量必须大于0");

                    if (m_Capacity == value) 
                        return;

                    m_Capacity = value;
                }
            }

            public float ExpireTime
            {
                get { return m_ExpireTime; }

                set
                {
                    if (value < 0)
                        Debug.LogError("对象过期秒数必须大于0");

                    if (m_ExpireTime == value)
                        return;

                    m_ExpireTime = value;
                }
            }

            public void Release()
            {
            }
 
            public void Release(int toReleaseCount)
            {
            }
 
            public void ReleaseAllUnused()
            {
            }

            public void Update(float time)
            {
            }
 
            public void Shutdown()
            {
            }

        #endregion

        #region Field

            /// <summary>
            /// 对象池容量
            /// </summary>
            private int m_Capacity;
         
            /// <summary>
            /// 对象池对象过期秒数
            /// </summary>
            private float m_ExpireTime;
             
            /// <summary>
            /// 对象链表
            /// </summary>
            private LinkedList<ObjectBase> m_Objects;

        #endregion
        
        #region Property
         
            /// <summary>
            /// 池对象是否可被多次获取
            /// </summary>
            public bool AllowMultiSpawn { get; private set; }
         
            /// <summary>
            /// 对象池自动释放可释放对象计时
            /// </summary>
            public float AutoReleaseTime { get; private set; }

        #endregion
    }
}
           

这里比较关键的属性是对象池的对象过期时间,这是对象池自动管理的关键,之后会解释。

构造方法

namespace SimpleGameFramework.ObjectPool
{
    public class ObjectPool<T> : IObjectPool where T : ObjectBase
    {
       #region 生命周期

            public ObjectPool(string name, int capacity, float expireTime, bool allowMultiSpawn)
            {
                Name = name;
                m_Objects = new LinkedList<ObjectBase>();
     
                Capacity = capacity;
                AutoReleaseInterval = expireTime; 
                ExpireTime = expireTime;
                AutoReleaseTime = 0f;
                AllowMultiSpawn = allowMultiSpawn;
            }

        #endregion
    }
}
           

可以看到,我们需要初始化的一共有4个值,依次是,对象池的名字、对象池的容量、对象池内对象的过期时间、单一对象是否允许多重引用

对象的注册,获取,归还

namespace SimpleGameFramework.ObjectPool
{
    public class ObjectPool<T> : IObjectPool where T : ObjectBase
    {
        #region 对象的注册、获取、归还
            /// <summary>
            /// 注册对象
            /// </summary>
            /// <param name="obj">对象</param>
            /// <param name="spawned">对象是否已被获取</param>
            public void Register(T obj, bool spawned = false)
            {
                if (obj == null)
                {
                    Debug.LogError("要放入对象池的对象为空:" + typeof(T).FullName);
                    return;
                }
                // 已被获取就让计数+1
                if (spawned)
                {
                    obj.SpawnCount++;
                }
                m_Objects.AddLast(obj);
            }
 
            /// <summary>
            /// 获取对象
            /// </summary>
            /// <param name="name">对象名称</param>
            /// <returns>要获取的对象</returns>
            public T Spawn(string name = "")
            {
                foreach (ObjectBase obj in m_Objects)
                {
                    if (obj.Name != name)
                    {
                        continue;
                    }
 
                    if (AllowMultiSpawn || !obj.IsInUse)
                    {
                        Debug.Log("获取了对象:" + typeof(T).FullName + "/" + obj.Name);
                        return obj.Spawn() as T;
                    }
                }
                return null;
            }
 
            /// <summary>
            /// 回收对象
            /// </summary>
            public void Unspawn(ObjectBase obj)
            {
                Unspawn(obj.Target);
            }
 
            /// <summary>
            /// 回收对象
            /// </summary>
            public void Unspawn(object target)
            {
                if (target == null)
                    Debug.LogError("要回收的对象为空:" + typeof(object).FullName);
 
                foreach (ObjectBase obj in m_Objects)
                {
                    if (obj.Target == target)
                    {
                        obj.Unspawn();
                        Debug.Log("对象被回收了:" + typeof(T).FullName + "/" + obj.Name);
                        return;
                    }
                }
 
                Debug.LogError("找不到要回收的对象:" + typeof(object).FullName);
            }
          
        #endregion
    }
}
           

通过代码可以知道,所谓注册,就是把某一个对象放到对象池的对象链表中,只有这样,这个对象才能够被对象池所管理。获取与归还就很好理解了,需要注意的是,这里就能够体现变量AllowMultiSpawn的作用了,在获取的时候我们发现,如果一个对象允许多重引用,那么不管它是否正在使用中,我们都可以获取到它。当然,一般来说是不应该支持这种方式的...

对象的释放

namespace SimpleGameFramework.ObjectPool
{
    public class ObjectPool<T> : IObjectPool where T : ObjectBase
    {
       #region 对象的释放
        	/// <summary>
            /// 获取所有可以释放的对象
            /// </summary>
            private LinkedList<T> GetCanReleaseObjects()
            {
                LinkedList<T> canReleaseObjects = new LinkedList<T>();
 
                foreach (ObjectBase obj in m_Objects)
                {
                    if (obj.IsInUse)
                        continue;
 
                    canReleaseObjects.AddLast(obj as T);
                }
 
                return canReleaseObjects;
            }
        #endregion
    }
}
           

对象的释放分为两部分,首先咱们获取到所有可以释放的对象,所谓可以释放,就是指对象链表里那些没有被使用的对象,但是,并不是说我们会释放所有没有被使用的对象,这个释放需要满足一定的规则,那么这个规则如何制定呢?额...是自定义的,大家可以根据自己的需求制定自己的规则...那么如何能够让我们的释放可以满足不同的自定义规则呢?那可以利用委托机制...本质就函数指针...

定义一个委托,并写好默认的匹配规则

namespace SimpleGameFramework.ObjectPool
{
    public class ObjectPool<T> : IObjectPool where T : ObjectBase
    {
       #region Delegate

        /// <summary>
        /// 释放对象筛选方法
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="candidateObjects">要筛选的对象集合</param>
        /// <param name="toReleaseCount">需要释放的对象数量</param>
        /// <param name="expireTime">对象过期参考时间</param>
        /// <returns>经筛选需要释放的对象集合</returns>
        public delegate LinkedList<T> ReleaseObjectFilterCallback<T>(LinkedList<T> candidateObjects, int toReleaseCount, DateTime expireTime) where T : ObjectBase;

        /// <summary>
        /// 默认的释放对象筛选方法(未被使用且过期的对象)
        /// </summary>
        private LinkedList<T> DefaultReleaseObjectFilterCallBack(LinkedList<T> candidateObjects, int toReleaseCount, DateTime expireTime)
        {
            LinkedList<T> toReleaseObjects = new LinkedList<T>();
 
           	// 必须保证预计过期时间是有效的
            if (expireTime > DateTime.MinValue)
            {
                // 从第一个可释放对象开始
                LinkedListNode<T> current = candidateObjects.First;
                while (current != null)
                {
                    // 对象最后使用时间 <= 过期参考时间,就需要释放
                    if (current.Value.LastUseTime <= expireTime)
                    {
                        toReleaseObjects.AddLast(current.Value);
                        LinkedListNode<T> next = current.Next;
                        candidateObjects.Remove(current);
                        // 这里限制了我们能够释放的对象数量
                        toReleaseCount--;
                        if (toReleaseCount <= 0)
                        {
                            return toReleaseObjects;
                        }
                        current = next;
                        continue;
                    }
 
                    current = current.Next;
                }
 
            }
 
            return toReleaseObjects;
        }
        
        #endregion
    }
}
           

首先定义一个委托,用来筛选对象,所谓筛选对象,就是从可以释放的对象中,找到哪些我们想要释放掉的对象。

第一个参数就是是可以释放的对象链表,通过上一步定义的函数可以得到。第二个参数是我们想要释放的数量。第三个参数是对象的过期时间,这个参数在上面我强调过,所谓过期时间,举个例子来说,如果我们对象的过期时间是10:30:40(十点三十零四十秒),而我们调用这个筛选方法的时间是10:30:20(十点三四零二十秒),那么肯定对象还没有过期啊!我们自然就不能释放!否则就发生了错误!

另外需要强调的是,首先我们定义了一个筛选对象的委托,这个委托是作为一个模板的,也就是筛选规则必须满足这个委托指定的参数和返回值,但是其具体内容是自定义的,比如说我们给出了我们默认的筛选规则,但只要符合这个委托的参数和返回值,其具体内容完全是可以由大家自己定义的!

最后是真正的释放对象

namespace SimpleGameFramework.ObjectPool
{
    public class ObjectPool<T> : IObjectPool where T : ObjectBase
    {
       #region 对象的释放          
            /// <summary>
            /// 释放对象池中的可释放对象
            /// </summary>
            /// <param name="toReleaseCount">尝试释放对象数量</param>
            /// <param name="releaseObjectFilterCallback">释放对象筛选方法</param>
            public void Release(int toReleaseCount, ReleaseObjectFilterCallback<T> releaseObjectFilterCallback)
            {
                // 重置计时
                AutoReleaseTime = 0;
 
                if (toReleaseCount <= 0)
                    return;
 
                // 计算对象过期参考时间
                DateTime expireTime = DateTime.MinValue;
                if (m_ExpireTime < float.MaxValue)
                    // 当前时间 - 过期秒数 = 过期参考时间
                    expireTime = DateTime.Now.AddSeconds(-m_ExpireTime);
 
                // 获取可释放的对象和实际要释放的对象
                LinkedList<T> canReleaseObjects = GetCanReleaseObjects();
                LinkedList<T> toReleaseObjects = releaseObjectFilterCallback(canReleaseObjects, toReleaseCount, expireTime);
                if (toReleaseObjects == null || toReleaseObjects.Count <= 0)
                    return;
 
                // 遍历实际要释放的对象
                foreach (ObjectBase toReleaseObject in toReleaseObjects)
                {
                    if (toReleaseObject == null)
                        Debug.LogError("无法释放空对象");
 
                    foreach (ObjectBase obj in m_Objects)
                    {
                        if (obj != toReleaseObject)
                            continue;
 
                        // 释放对象
                        m_Objects.Remove(obj);
                        obj.Release();
                        Debug.Log("对象被释放了:" + obj.Name);
                        break;
                    }
                }
 
            }

        #endregion
    }
}
           

这里的关键在于,我们如何定义我们对象的过期时间,想理解这个问题,那我们必须知道Release这个方法会在什么时候被调用,接着往下看

接口方法实现

Update和ShutDown

namespace SimpleGameFramework.ObjectPool
{
    public class ObjectPool<T> : IObjectPool where T : ObjectBase
    {
       #region Implement
        	/// <summary>
            /// 对象池的定时释放
            /// </summary>
            public void Update(float time)
            {
                AutoReleaseTime += time;
                if (AutoReleaseTime < AutoReleaseInterval)
                    return;
 
                Release();
            }
 
            /// <summary>
            /// 清理对象池
            /// </summary>
            public void Shutdown()
            {
                LinkedListNode<ObjectBase> current = m_Objects.First;
                while (current != null)
                {
 
                    LinkedListNode<ObjectBase> next = current.Next;
                    m_Objects.Remove(current);
                    current.Value.Release();
                    Debug.Log("对象被释放了:" + current.Value.Name);
                    current = next;
                }
            }
        #endregion
    }
}
           

咱们看Update,首先,Update是一个固定时间的检查,假设AutoReleaseInterval是10,那就是每10秒会执行一次Release(),当然,只是尝试释放,并不是说每10秒就会释放一次对象。

而Release(),最终还是调用的我们上面所写的那个方法。那我们假设我们会在15秒的时候归还一个对象,并且对象的过期时间是3秒。

好,时间从0开始,0 1 2 ...10,第一次尝试释放,但是这时候我们还没有归还对象,所以啥也释放不掉

然后继续 11 12 13 14 15 UnSpawn() 16 17 18 19 20 ,UnSpawn()会标记对象的最后使用时间为15

(注意,UnSpawn()并不是直接归还对象,它只是把对象的引用计数-1,如果引用计数=0了,那么这个对象就会被标记为未使用,即可以释放)

好了,20秒的时候我们第二次尝试释放对象,因为对象的过期时间为3秒,那么20秒的时候,我们只会释放最后使用时间在17秒之前的对象(着重理解这句话)

而我们释放的对象,被标记为15秒,那么他自然就会被释放了!

最后,实现我们的另外一些接口

namespace SimpleGameFramework.ObjectPool
{
    public class ObjectPool<T> : IObjectPool where T : ObjectBase
    {
       #region Implement
        	/// <summary>
            /// 释放超出对象池容量的可释放对象
            /// </summary>
            public void Release()
            {
                Release(m_Objects.Count - m_Capacity, DefaultReleaseObjectFilterCallBack);
            }
 
            /// <summary>
            /// 释放指定数量的可释放对象
            /// </summary>
            /// <param name="toReleaseCount"></param>
            public void Release(int toReleaseCount)
            {
                Release(toReleaseCount, DefaultReleaseObjectFilterCallBack);
            }
 
            /// <summary>
            /// 释放对象池中所有未使用对象
            /// </summary>
            public void ReleaseAllUnused()
            {
                LinkedListNode<ObjectBase> current = m_Objects.First;
                while (current != null)
                {
                    if (current.Value.IsInUse)
                    {
                        current = current.Next;
                        continue;
                    }
 
                    LinkedListNode<ObjectBase> next = current.Next;
                    m_Objects.Remove(current);
                    current.Value.Release();
                    Debug.Log("对象被释放了:" + current.Value.Name);
                    current = next;
                }
            }
        #endregion
    }
}
           

对象池到这里就基本结束了,然后是管理器

ObjectPoolManager

继承ManagerBase,实现一些基本内容

namespace SimpleGameFramework.ObjectPool
{
    public class ObjectPoolManager : ManagerBase
    {
        #region Implement
            public override int Priority 
            {
                get
                {
                    return ManagerPriority.ObjectPoolManager.GetHashCode();
                } 
            }
            
            public override void Init()
            {
                m_ObjectPools = new Dictionary<string, IObjectPool>();
            }

            public override void Update(float time)
            {
                foreach (IObjectPool objectPool in m_ObjectPools.Values)
                {
                    objectPool.Update(time);
                }

            }

            public override void ShutDown()
            {
                foreach (IObjectPool objectPool in m_ObjectPools.Values)
                {
                    objectPool.Shutdown();
                }
                m_ObjectPools.Clear();
            }

        #endregion
    }
}
           

添加一些属性

namespace SimpleGameFramework.ObjectPool
{
    public class ObjectPoolManager : ManagerBase
    {
        #region Field

            /// <summary>
            /// 默认对象池容量
            /// </summary>
            private const int DefaultCapacity = int.MaxValue;
     
            /// <summary>
            /// 默认对象过期秒数
            /// </summary>
            private const float DefaultExpireTime = float.MaxValue;
            
            /// <summary>
            /// 对象池字典
            /// </summary>
            private Dictionary<string, IObjectPool> m_ObjectPools;

        #endregion

        #region Property

        /// <summary>
        /// 对象池数量
        /// </summary>
        public int Count
        {
            get { return m_ObjectPools.Count; }
        }

        #endregion
    }
}
           
namespace SimpleGameFramework.ObjectPool
{
    public class ObjectPoolManager : ManagerBase
    {
        #region 对象池管理

            /// <summary>
            /// 检查对象池
            /// </summary>
            public bool HasObjectPool<T>() where T : ObjectBase
            {
                return m_ObjectPools.ContainsKey(typeof(T).FullName);
            }
            
            /// <summary>
            /// 创建对象池
            /// </summary>
            public ObjectPool<T> CreateObjectPool<T>(int capacity = DefaultCapacity, float exprireTime = DefaultExpireTime, bool allowMultiSpawn = false) where T : ObjectBase
            {
                string name = typeof(T).FullName;
                if (HasObjectPool<T>())
                {
                    Debug.LogError("要创建的对象池已存在");
                    return null;
                }
                ObjectPool<T> objectPool = new ObjectPool<T>(name, 0, 2, allowMultiSpawn);
                m_ObjectPools.Add(name, objectPool);
                return objectPool;
            }
 
            /// <summary>
            /// 获取对象池
            /// </summary>
            public ObjectPool<T> GetObjectPool<T>() where T : ObjectBase
            {
                IObjectPool objectPool = null;
                m_ObjectPools.TryGetValue(typeof(T).FullName, out objectPool);
                return objectPool as ObjectPool<T>;
            }
 
            /// <summary>
            /// 销毁对象池
            /// </summary>
            public bool DestroyObjectPool<T>()
            {
                IObjectPool objectPool = null;
                if (m_ObjectPools.TryGetValue(typeof(T).FullName, out objectPool))
                {
                    objectPool.Shutdown();
                    return m_ObjectPools.Remove(typeof(T).FullName);
                }
 
                return false;
            }
            
            /// <summary>
            /// 释放所有对象池中的可释放对象。
            /// </summary>
            public void Release()
            {
                foreach (IObjectPool objectPool in m_ObjectPools.Values)
                {
                    objectPool.Release();
                }
            }
 
            /// <summary>
            /// 释放所有对象池中的未使用对象。
            /// </summary>
            public void ReleaseAllUnused()
            {
                foreach (IObjectPool objectPool in m_ObjectPools.Values)
                {
                    objectPool.ReleaseAllUnused();
                }
            }

        #endregion
    }
}
           
public class TestObject : ObjectBase
{
    public TestObject(object target, string name = "") : base(target, name)
    {
    }

    public override void Release()
    {
        
    }
}

public class test : MonoBehaviour
{
    private ObjectPoolManager objectPoolManager;
    private ObjectPool<TestObject> testPool;
    void Start()
    {
        objectPoolManager = SGFEntry.Instance.GetManager<ObjectPoolManager>();
        testPool = objectPoolManager.CreateObjectPool<TestObject>();

        TestObject temp = new TestObject("HelloWorld","test1");
        testPool.Register(temp);
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            TestObject testObject = testPool.Spawn("test1");
            Debug.Log(testObject.Target);
            testPool.Unspawn(testObject.Target);
        }
    }
}