首页 > 技术文章 > UniRx-unirx中的对象池

Firepad-magic 2020-10-27 22:10 原文

UniRx-unirx中的对象池

一、对象池模式

《游戏设计模式-对象池模式》

1.概念

定义一个池对象,其包含了一组可重用对象。 其中每个可重用对象都支持查询“使用中”状态,说明它是不是“正在使用”。 池被初始化时,它就创建了整个对象集合(通常使用一次连续的分配),然后初始化所有对象到“不在使用中”状态。

当你需要新对象,向池子要一个。 它找到一个可用对象,初始化为“使用中”然后返回。 当对象不再被需要,它被设置回“不在使用中”。 通过这种方式,可以轻易地创建和销毁对象而不必分配内存或其他资源。

2.优点

游戏中会出现诸如子弹,粒子特效等大量的单一资源。频繁的创建和销毁会浪费很多性能并且产生大量的内存碎片。对象池模式放弃单独地分配和释放对象,从固定的池中重用对象,以提高性能和内存使用率。

enter description here
enter description here

3.何时使用

  • 需要频繁创建和销毁对象。
  • 对象大小相仿。
  • 在堆上进行对象内存分配十分缓慢或者会导致内存碎片。
  • 每个对象都封装了像数据库或者网络连接这样很昂贵又可以重用的资源。

4.注意

  • 可以将创建对象和销毁对象平缓的放到多个帧处理
  • 动态的维护池子的大小

UniRx中的对象池使用

1.案例

1.1 prefab上脚本

using System;
using UniRx;
using UnityEngine;
using Random = UnityEngine.Random;

public class PoolObject : MonoBehaviour
{
   public IObservable<Unit> AsyncAction()
   {
       var colorstream = Observable.Timer(TimeSpan.FromSeconds(0.2f));
       colorstream.Subscribe(_ =>
       {
           GetComponent<Renderer>().material.color = Color.Lerp(Color.red, Color.blue, Random.Range(0.0f,1.0f));
       });
       var positiontream = Observable.Timer(TimeSpan.FromSeconds(0.2f));
       positiontream.Subscribe(_ =>
       {
           transform.localPosition=Vector3.one*Random.Range(-5f,5f);
       });

       var unit = Observable.ReturnUnit();
       var allStream = Observable.WhenAll(unit);
       allStream.Subscribe(_ =>
       {
           Debug.Log("QAQ");
       });

       return Observable.ReturnUnit();
   }
}

2.自定义对象池

using System;
using UniRx;
using UniRx.Toolkit;
using UnityEngine;
using Object = UnityEngine.Object;
using Random = UnityEngine.Random;

public class UniRxObjectPool : ObjectPool<PoolObject>
{

   private GameObject _prefab;

   public UniRxObjectPool(GameObject prefab)
   {
       this._prefab = prefab;
   }

   protected override PoolObject CreateInstance()
   {
       var gameObj = Object.Instantiate(_prefab);
       return gameObj.GetComponent<PoolObject>();
   }

   protected override void OnBeforeRent(PoolObject instance)
   {
       base.OnBeforeRent(instance);
       Debug.Log($"从池子中取出:{instance.name}");
   }

   // 在对象返回到池子里面之前回调
   protected override void OnBeforeReturn(PoolObject instance)
   {
       base.OnBeforeReturn(instance);
       Debug.Log($" 返回 {instance} 到池子里面");
   }

   // 在对象从池子里面移除回调
   protected override void OnClear(PoolObject instance)
   {
       base.OnClear(instance);
       Debug.Log($"从池子里面移除 {instance}");
   }
}

3.对象池调用

using System;
using UniRx;
using UnityEngine;
using UnityEngine.UI;

public class No11_ObjectPool : MonoBehaviour
{
    [SerializeField]
    private GameObject mPoolPrefab;

    [SerializeField]
    private Button mBtnSpawn;

    [SerializeField]
    private Button mBtnShrink;

    [SerializeField]
    private Button mBtnClear;

    void Start()
    {
        UniRxObjectPool pool = new UniRxObjectPool(mPoolPrefab);

        mBtnSpawn.OnClickAsObservable().Subscribe(_ =>
        {
            for (int i = 0; i < 5; i++)
            {
                //从池中获取实例,出栈
                var poolObj = pool.Rent();
                poolObj.AsyncAction().Subscribe(next =>
                {
                    Debug.Log("mult thread run");
                });
                //根据对象创建的频率来扩张池子大小
                Observable.TimerFrame(300).Subscribe(f =>
                {
                    //回收对象到池子
                    pool.Return(poolObj);
                }); 
            }

        });

        mBtnShrink.OnClickAsObservable().Subscribe(_ =>
        {
            // 手动回收对象池子, 第一个参数是回收比例,第二个参数是保存最小数量
            //pool.Shrink(0.6f, 1);

            // 自动回收对象池子,3秒检查一次进行回收,销毁实例,缩小池子大小,保持最小数
            pool.StartShrinkTimer(TimeSpan.FromSeconds(3f), 0.6f, 2);
        });

        mBtnClear.OnClickAsObservable().Subscribe(_ =>
        {
            //清理对象池,destroy所有对象
            pool.Clear();
        });
    }
}

2.源码

1.回收实例到对象池

将实例入栈,可以在OnBeforeReturn中对将要回收的实例进行以下操作,比如隐藏。

  1. /// <summary> 
  2. /// Return instance to pool. 
  3. /// </summary> 
  4. public void Return(T instance) 
  5. { 
  6. if (isDisposed) throw new ObjectDisposedException("ObjectPool was already disposed."); 
  7. if (instance == null) throw new ArgumentNullException("instance"); 
  8.  
  9. if (q == null) q = new Queue<T>(); 
  10.  
  11. if ((q.Count + 1) == MaxPoolCount) 
  12. { 
  13. throw new InvalidOperationException("Reached Max PoolSize"); 
  14. } 
  15.  
  16. OnBeforeReturn(instance); 
  17. q.Enqueue(instance); 
  18. } 
  19.  

2.缩减对象池

设置一个对象池保有对象实例的最小值,然后不断按照一定的比例系数出栈操作来缩减池子大小

  1. /// <summary> 
  2. /// Trim pool instances.  
  3. /// </summary> 
  4. /// <param name="instanceCountRatio">0.0f = clear all ~ 1.0f = live all.</param> 
  5. /// <param name="minSize">Min pool count.</param> 
  6. /// <param name="callOnBeforeRent">If true, call OnBeforeRent before OnClear.</param> 
  7. public void Shrink(float instanceCountRatio, int minSize, bool callOnBeforeRent = false) 
  8. { 
  9. if (q == null) return; 
  10.  
  11. if (instanceCountRatio <= 0) instanceCountRatio = 0; 
  12. if (instanceCountRatio >= 1.0f) instanceCountRatio = 1.0f; 
  13.  
  14. var size = (int)(q.Count * instanceCountRatio); 
  15. size = Math.Max(minSize, size); 
  16.  
  17. while (q.Count > size) 
  18. { 
  19. var instance = q.Dequeue(); 
  20. if (callOnBeforeRent) 
  21. { 
  22. OnBeforeRent(instance); 
  23. } 
  24. OnClear(instance); 
  25. } 
  26. } 
  1. /// <summary> 
  2. /// If needs shrink pool frequently, start check timer. 
  3. /// </summary> 
  4. /// <param name="checkInterval">Interval of call Shrink.</param> 
  5. /// <param name="instanceCountRatio">0.0f = clearAll ~ 1.0f = live all.</param> 
  6. /// <param name="minSize">Min pool count.</param> 
  7. /// <param name="callOnBeforeRent">If true, call OnBeforeRent before OnClear.</param> 
  8. public IDisposable StartShrinkTimer(TimeSpan checkInterval, float instanceCountRatio, int minSize, bool callOnBeforeRent = false) 
  9. { 
  10. //UniRx Interval定间隔调用 
  11. return Observable.Interval(checkInterval) 
  12. .TakeWhile(_ => !isDisposed)//TakeWhile(condition) conditon为false时终止,且不包含临界的item 
  13. .Subscribe(_ => 
  14. { 
  15. Shrink(instanceCountRatio, minSize, callOnBeforeRent); 
  16. }); 
  17. } 

二、小结

对象池的好处就是提高性能和内存使用率,适合诸如子弹,弹幕,粒子效果(另一种优化技术GPU Intance),小怪群等。它和享元模式的区别类似于享元模式接头缝合怪,很多人用一个头,它则是可量产回收的机械克隆人。

推荐阅读