前言
Unity的基础功能残缺得令人发指,官方对象池也是近年才补上的。
概览
使用UnityEngine.Pool引入对象池。
重点在于三个关键类:CollectionPool<TCollection,TItem>,IObjectPool<T>,PooledObject<T>。需要注意的是,官方提供的所有池子都不是线程安全的。
CollectionPool<TCollection,TItem>
这是一个用于缓存ICollection的池子,不需要创建实例,只需要使用静态方法Get和Release来获取和释放ICollection对象。以此可以减少频繁的容器创建带来的压力。
例如,若我暂时使用到了List<int>,无需手动创建List<int>对象,仅需要如此做:
//使用静态方法获取List<int>实例
var listInt = CollectionPool<List<int>, int>.Get();
//使用
...
//使用静态方法释放
CollectionPool<List<int>, int>.Release(listInt);
当然,对于常见的容器,Unity也给出了相应的变种,省去了手打类型名的麻烦。
例如,ListPool<T>继承了CollectionPool<List<T>, T>,所以上面代码可以简化成:
//使用静态方法获取List<int>实例
var listInt = ListPool<int>.Get();
//使用
...
//使用静态方法释放
ListPool<int>.Release(listInt);
类似的,还有DictionaryPool<TKey,TValue>,HashSetPool<T>可用于缓存字典和哈希散列。
IObjectPool<T>
对象池的接口,定义如下:
public interface IObjectPool<T> where T : class
{
//池中有多少个对象
int CountInactive { get; }
//清除
void Clear();
//获取
T Get();
//便于释放的获取方式
PooledObject<T> Get(out T v);
//释放
void Release(T element);
}
其更接近于传统意义上的对象池,需要手动创建对象池的实例,用于缓存引用类型(class)。
官方提供了两个实现类,分别是ObjectPool<T>和LinkedPool<T>。其区别在于ObjectPool内部使用栈(Stack)实现,而LinkedPool内部使用LinkedList实现。
两者实际使用区别不大,LinkedPool虽然使用LinkedList实现,但不能自由插入和删除,同样以Get和Release进行类似于栈的操作。
在构造函数方面,因为采用的存储结构不同,在容量参数上有一点区别,ObjectPool多了一个默认容量的参数。
public ObjectPool(Func<T> createFunc, Action<T> actionOnGet = null, Action<T> actionOnRelease = null, Action<T> actionOnDestroy = null, bool collectionCheck = true, int defaultCapacity = 10, int maxSize = 10000);
public LinkedPool(Func<T> createFunc, Action<T> actionOnGet = null, Action<T> actionOnRelease = null, Action<T> actionOnDestroy = null, bool collectionCheck = true, int maxSize = 10000);
这里比较重要的参数有:
Func<T> createFunc,指示池子如何创建一个实例。当使用Get从池子里获取,但池子里可用的实例数量不足时,就会执行此委托来创建一个新的实例。例如对于一般对象,可以使用()=>new T()创建,而对于预制体对象,可以使用Instantiate()创建。
Action<T> actionOnGet,当使用Get获取实例时,对实例进行的操作。例如对于GameObject而言,可以设为在获取时令其活跃(SetActive(true))。
Action<T> actionOnRelease,当使用Release释放实例时,对实例进行的操作。例如对于GameObject而言,可以设为在获取时令其隐藏(SetActive(false))。
Action<T> actionOnDestroy,当对象池被清除(Clear)或释放(Dispose)时,对实例进行的操作。例如使用Destroy销毁Mono实例。
bool collectionCheck,在释放实例入池时,检查其是否已经在池子里。开启时会有一定的性能开销(用于查询和比较实例是否相同),但可以避免同一实例被释放两次引发的未知错误。
如果不想手动创建对象池,官方也贴心地提供了类似于前面CollectionPool的静态实现版本:GenericPool<T>和
UnsafeGenericPool<T>,使用静态方法Get和Release来创建和释放实例对象。
//截取自官方文档
class MyClass
{
public int someValue;
public string someString;
}
void GetPooled()
{
//获取实例
var instance = UnsafeGenericPool<MyClass>.Get();
//释放实例
UnsafeGenericPool<MyClass>.Release(instance);
}
两者区别在于,GenericPool开启了collectionCheck重复检查,会在检查过程中不可避免产生一些垃圾;而UnsafeGenericPool关闭了重复检查,不产生垃圾。
PooledObject<T>
若从对象池里获取一个实例,使用后应当释放回相应对象池。前面所有提到的池子,都有一个PooledObject<T> Get(out T value)的重载,PooledObject便是用来辅助释放的结构。
PooledObject实现了IDisposable接口,当调用其Dispose方法时,会自动完成对应实例到对应池子的回收。
因此,可以使用如下的结构来完成创建和释放:
//此处重载后的Get返回类型是PooledObject<List<int>>
//因为其实现了IDisposable接口,可以使用using语法释放
using(ListPool<int>.Get(out var list))
{
list.AddRange(new int[] { 1, 2, 3, 4 });
list.ForEach(i => Debug.Log(i));
}
总结
尽管省去了一些造轮子的功夫,但实际使用感觉还是差点东西。
例如回调必须也只能在对象池构造时完成,缺少带有配置参数的Get方法,以及缺少可能用到的遍历对象池的方法。
而官方也是一如既往地喜欢把路堵死,重要的字段全是private,例如继承ObjectPool写一个子类,子类的权限和外界竟然别无二致。还是得自己造点轮子,能够适应项目才是最重要的。
文章版权归作者所有





- 最新
- 最热
查看全部