UGUI 中使用的对象池
在 C:\Files\Unity\Projects\UGUITest\PackageSource\com.unity.ugui\Runtime\UI\Core\Utility\ObjectPool.cs
中,UGUI 实现了一个对象池,代码如下:
using System.Collections.Generic;
using UnityEngine.Events;
namespace UnityEngine.UI
{
internal class ObjectPool<T> where T : new()
{
private readonly Stack<T> m_Stack = new Stack<T>();
private readonly UnityAction<T> m_ActionOnGet;
private readonly UnityAction<T> m_ActionOnRelease;
public int countAll { get; private set; }
public int countActive { get { return countAll - countInactive; } }
public int countInactive { get { return m_Stack.Count; } }
public ObjectPool(UnityAction<T> actionOnGet, UnityAction<T> actionOnRelease)
{
m_ActionOnGet = actionOnGet;
m_ActionOnRelease = actionOnRelease;
}
public T Get()
{
T element;
if (m_Stack.Count == 0)
{
element = new T();
countAll++;
}
else
{
element = m_Stack.Pop();
}
if (m_ActionOnGet != null)
m_ActionOnGet(element);
return element;
}
public void Release(T element)
{
if (m_Stack.Count > 0 && ReferenceEquals(m_Stack.Peek(), element))
Debug.LogError("Internal error. Trying to destroy object that is already released to pool.");
if (m_ActionOnRelease != null)
m_ActionOnRelease(element);
m_Stack.Push(element);
}
}
}
然后我认为里面是存在一些问题的:
- 会存在栈里面有重复元素的情况,虽然在
Release
的时候有判断是否和栈顶元素相同,但是如果出现 A 元素先被 Release 一次,然后 B 元素又被 Release,再 Release A,那么依然不能排除这种重复 Release 的情况。 - 当一个元素被 Release 之后,依然可以被使用。所以可能需要在 Get 的时候完全的再初始化一遍,相对而言在 Release 的时候所传的方法就没有那么重要了。
下面是使用了 HashSet 进行仿写的一段代码:
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using UnityEngine;
using UnityEngine.Events;
namespace ZhangqrTools
{
/// <summary>
/// 对象池
/// T:需要有无参的构造函数
/// </summary>
public class ObjectPool<T> where T : new()
{
private HashSet<T> m_HashSet; // 使用 HashSet 是为了保证里面的元素不重复,避免多个指针指向同一个对象
// 在调用 Get 和 Release 方法的时候对 T 元素的初始化和销毁自定义操作
private UnityAction<T> m_OnGet;
private UnityAction<T> m_OnRelease;
public int AllCount
{
private set;
get;
}
public int InactiveCount {
// private set { InactiveCount = value; } // 不写就是只读的意思,类内外都不可以更改其值,如果类内部需要更改,可以将注释去掉
get{ return m_HashSet.Count; }
}
public int ActiveCount
{
// private set { ActiveCount = value; }
get { return AllCount - InactiveCount; }
}
public ObjectPool(UnityAction<T> onGetFunction, UnityAction<T> onReleaseFunction)
{
m_HashSet = new HashSet<T>();
m_OnGet = onGetFunction;
m_OnRelease = onReleaseFunction;
}
public T Get()
{
T ret;
if (m_HashSet.Count == 0)
{
ret = new T();
AllCount++;
}
else
{
ret = m_HashSet.ElementAt(0);
m_HashSet.Remove(ret);
}
m_OnGet?.Invoke(ret);
return ret;
}
public void Release(T element)
{
if (m_HashSet.Contains(element))
{
Debug.LogError("The element is double pushed in object pool");
return;
}
m_OnRelease?.Invoke(element);
m_HashSet.Add(element);
}
}
}
然后下面是测试代码:
using System.Collections.Generic;
using UnityEngine;
using ZhangqrTools;
/// <summary>
/// 一个记录轨迹的类,维护一个数组,这个数组可能很大,所以希望在进入对象池的时候都能够清空。
/// 并且在出对象池的时候,数组第一个元素是 (0,0,0)
/// </summary>
class Track
{
private List<Vector3> m_Points;
public Track()
{
m_Points = new List<Vector3>();
}
public void Add(Vector3 p)
{
m_Points.Add(p);
}
public void Clear()
{
m_Points.Clear();
}
public Vector3 this[int index]
{
get
{
return m_Points[index];
}
}
}
class Test:MonoBehaviour
{
private ObjectPool<Track> m_TrackPool;
private void Awake()
{
m_TrackPool = new ObjectPool<Track>(TrackGet, TrackRelease);
}
private void TrackGet(Track track)
{
track.Add(Vector3.zero);
}
public void TrackRelease(Track track)
{
track.Clear();
}
private void Start()
{
Track track = m_TrackPool.Get();
m_TrackPool.Release(track);
track = m_TrackPool.Get(); // 不加的话会报错,因为元素被重复 Release 了。
m_TrackPool.Release(track);
}
}
TODO:通过 HashSet 来控制集合内不会有重复元素,但是依然不能解决元素被 Release 之后依然可以在外部被使用的情况,那么就可能出现像下面的情况:
private void Start()
{
Track track = m_TrackPool.Get();
m_TrackPool.Release(track);
track = m_TrackPool.Get(); // 不加的话会报错,因为元素被重复 Release 了。
m_TrackPool.Release(track);
Track track2 = m_TrackPool.Get();
m_TrackPool.Release(track);
m_TrackPool.Release(track2); // 报错,因为元素被重复 Release 了,(track2 表示很无辜,是谁!占了我的坑)
}
网友评论