前言
对象池和引用池的功能是一样的,也是为了防止反复销毁和创建,偏向于游戏实例中的使用,添加了释放事件和更新机制,引用池是偏向代码中反复用到的对象,比如各种任务,事件参数,各种模块信息。
对象池不紧不慢的看了挺多天的,终于慢慢理解作者当时设计GF框架时为什么要这样去编写代码了,所以现在和大家分享一下对象池模块是如何慢慢架构起来的,由于对象池的代码比较多,可以先看看作者提供的GF游戏例是如何使用对象池模块的,于是找到了游戏中血条显示就是使用对象池的,接下来就给截图:
四个地方:创建对象池(CreateSingleSpawnObjectPool)、获取对象 (Spawn)、注册对象(Register)、释放对象(hpBarItem),就先从这四个接口入手,慢慢的小鸡炖蘑菇,这样就可以成功的把对象池的实现原理和使用都理解的明明白白,创建对象池函数连着跳啊跳,找到了以下实现的代码:
private IObjectPool<T> InternalCreateObjectPool<T>(string name, bool allowMultiSpawn,
float autoReleaseInterval, int capacity, float expireTime, int priority) where T : ObjectBase
{
TypeNamePair typeNamePair = new TypeNamePair(typeof(T), name);
if (HasObjectPool<T>(name))
{
throw new GameFrameworkException(Utility.Text.Format("Already exist object pool '{0}'.", typeNamePair.ToString()));
}
ObjectPool<T> objectPool = new ObjectPool<T>(name, allowMultiSpawn, autoReleaseInterval, capacity, expireTime, priority);
m_ObjectPools.Add(typeNamePair, objectPool);
return objectPool;
}
首先把名字和对象类型结合起来作为字典的Key,字典的Value是这个类型的对象池,好像突然明白了什么?我们创建对象池都统一对放到m_ObjectPools总对象池里,参数倒是挺多的,接下来给出表格让大家明白一下这些参数的用处是什么?
allowMultiSpawn | 是否允许对象被多次获取,如果设置为true,导致对象池中只有一个对象,用于对象参数不变的情况。 |
autoReleaseInterval | 对象池自动释放可释放对象的间隔秒数。 |
capacity | 对象池的容量,如果超过容量会按照上面参数间隙自动释放。 |
expireTime | 对象池对象过期秒数,到时间会强制释放。 |
priority | 对象池的优先级。 |
ObjectPool<T>里有还有多键值对的字典,数据结构具体的实现之后会说明,接下来看一下Spawn函数的作用,这里就是获取到多键值对字典,一个Key有对应着一个Value列表,然后循环查询对象是否使用,如果没有占用就将其返回,具体代码如下:
public T Spawn(string name)
{
GameFrameworkLinkedListRange<Object<T>> objectRange = default(GameFrameworkLinkedListRange<Object<T>>);
if (m_Objects.TryGetValue(name, out objectRange))
{
foreach (Object<T> internalObject in objectRange)
{
if (m_AllowMultiSpawn || !internalObject.IsInUse)
{
return internalObject.Spawn();
}
}
}
return null;
}
这里的IsInUse并不是获取对象(Spwn)成功以后把某值设置成true,而是将m_SpawnCount的数里加1,这个参数的意思就是表示对象被占用的次数,这样设计主要是也兼容了m_AllowMultiSpawn参数,知道对象被占用的次数和是否被占用,如果全部对象使用中怎么办呢?导致对象会返回null,这样我们就可以调用Register函数,为其多添加一个对象,具体代码如下:
public void Register(T obj, bool spawned)
{
if (obj == null)
{
throw new GameFrameworkException("Object is invalid.");
}
Object<T> internalObject = Object<T>.Create(obj, spawned);
m_Objects.Add(obj.Name, internalObject);
m_ObjectMap.Add(obj.Target, internalObject);
if (Count > m_Capacity)
{
Release();
}
}
这里也可以看到如果超过了设置的容量,就会自动释放所有对象,最后一个就是归还函数(UnSpawn),大家记得哦!有借有还再借不难,虽然这里你不还对象也没有什么问题,此对象非彼对象。因为设置了对象池容量,作者已经帮我们考虑到了这个问题,如果只获取对象但不归还,对象池将会进行一次清理(这样做并不好,合理设置对象池容量,对象池容量的设计部分原因是为了内存容量考虑,但是不归还对象相当于没有使用对象池优化一样,所以用完对象时必须要进行释放),具体代码如下:
public void Unspawn(object target)
{
if (target == null)
{
throw new GameFrameworkException("Target is invalid.");
}
Object<T> internalObject = GetObject(target);
if (internalObject != null)
{
internalObject.Unspawn();
if (Count > m_Capacity && internalObject.SpawnCount <= 0)
{
Release();
}
}
else
{
throw new GameFrameworkException(Utility.Text.Format("Can not find target in object pool '{0}', target type is '{1}', target value is '{2}'.", new TypeNamePair(typeof(T), Name).ToString(), target.GetType().FullName, target.ToString()));
}
}
经过上述讲解各位一定知道对象池的使用方式了,接下来我们看一下多键值对字典是如何实现的,数据结构继承了.Net的迭代器接口,可以看到这里的Key对应着一个GF框架自身实现的列表,具体代码如下:
using System.Collections;
using System.Collections.Generic;
namespace GameFramework
{
/// <summary>
/// 游戏框架多值字典类。
/// </summary>
/// <typeparam name="TKey">指定多值字典的主键类型。</typeparam>
/// <typeparam name="TValue">指定多值字典的值类型。</typeparam>
public sealed class GameFrameworkMultiDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, GameFrameworkLinkedListRange<TValue>>>, IEnumerable
{
private readonly GameFrameworkLinkedList<TValue> m_LinkedList;
private readonly Dictionary<TKey, GameFrameworkLinkedListRange<TValue>> m_Dictionary;
/// <summary>
/// 初始化游戏框架多值字典类的新实例。
/// </summary>
public GameFrameworkMultiDictionary()
{
m_LinkedList = new GameFrameworkLinkedList<TValue>();
m_Dictionary = new Dictionary<TKey, GameFrameworkLinkedListRange<TValue>>();
}
/// <summary>
/// 获取多值字典中实际包含的主键数量。
/// </summary>
public int Count
{
get
{
return m_Dictionary.Count;
}
}
/// <summary>
/// 获取多值字典中指定主键的范围。
/// </summary>
/// <param name="key">指定的主键。</param>
/// <returns>指定主键的范围。</returns>
public GameFrameworkLinkedListRange<TValue> this[TKey key]
{
get
{
GameFrameworkLinkedListRange<TValue> range = default(GameFrameworkLinkedListRange<TValue>);
m_Dictionary.TryGetValue(key, out range);
return range;
}
}
/// <summary>
/// 清理多值字典。
/// </summary>
public void Clear()
{
m_Dictionary.Clear();
m_LinkedList.Clear();
}
/// <summary>
/// 检查多值字典中是否包含指定主键。
/// </summary>
/// <param name="key">要检查的主键。</param>
/// <returns>多值字典中是否包含指定主键。</returns>
public bool Contains(TKey key)
{
return m_Dictionary.ContainsKey(key);
}
/// <summary>
/// 检查多值字典中是否包含指定值。
/// </summary>
/// <param name="key">要检查的主键。</param>
/// <param name="value">要检查的值。</param>
/// <returns>多值字典中是否包含指定值。</returns>
public bool Contains(TKey key, TValue value)
{
GameFrameworkLinkedListRange<TValue> range = default(GameFrameworkLinkedListRange<TValue>);
if (m_Dictionary.TryGetValue(key, out range))
{
return range.Contains(value);
}
return false;
}
/// <summary>
/// 尝试获取多值字典中指定主键的范围。
/// </summary>
/// <param name="key">指定的主键。</param>
/// <param name="range">指定主键的范围。</param>
/// <returns>是否获取成功。</returns>
public bool TryGetValue(TKey key, out GameFrameworkLinkedListRange<TValue> range)
{
return m_Dictionary.TryGetValue(key, out range);
}
/// <summary>
/// 向指定的主键增加指定的值。
/// </summary>
/// <param name="key">指定的主键。</param>
/// <param name="value">指定的值。</param>
public void Add(TKey key, TValue value)
{
GameFrameworkLinkedListRange<TValue> range = default(GameFrameworkLinkedListRange<TValue>);
if (m_Dictionary.TryGetValue(key, out range))
{
m_LinkedList.AddBefore(range.Terminal, value);
}
else
{
LinkedListNode<TValue> first = m_LinkedList.AddLast(value);
LinkedListNode<TValue> terminal = m_LinkedList.AddLast(default(TValue));
m_Dictionary.Add(key, new GameFrameworkLinkedListRange<TValue>(first, terminal));
}
}
/// <summary>
/// 从指定的主键中移除指定的值。
/// </summary>
/// <param name="key">指定的主键。</param>
/// <param name="value">指定的值。</param>
/// <returns>是否移除成功。</returns>
public bool Remove(TKey key, TValue value)
{
GameFrameworkLinkedListRange<TValue> range = default(GameFrameworkLinkedListRange<TValue>);
if (m_Dictionary.TryGetValue(key, out range))
{
for (LinkedListNode<TValue> current = range.First; current != null && current != range.Terminal; current = current.Next)
{
if (current.Value.Equals(value))
{
if (current == range.First)
{
LinkedListNode<TValue> next = current.Next;
if (next == range.Terminal)
{
m_LinkedList.Remove(next);
m_Dictionary.Remove(key);
}
else
{
m_Dictionary[key] = new GameFrameworkLinkedListRange<TValue>(next, range.Terminal);
}
}
m_LinkedList.Remove(current);
return true;
}
}
}
return false;
}
/// <summary>
/// 从指定的主键中移除所有的值。
/// </summary>
/// <param name="key">指定的主键。</param>
/// <returns>是否移除成功。</returns>
public bool RemoveAll(TKey key)
{
GameFrameworkLinkedListRange<TValue> range = default(GameFrameworkLinkedListRange<TValue>);
if (m_Dictionary.TryGetValue(key, out range))
{
m_Dictionary.Remove(key);
LinkedListNode<TValue> current = range.First;
while (current != null)
{
LinkedListNode<TValue> next = current != range.Terminal ? current.Next : null;
m_LinkedList.Remove(current);
current = next;
}
return true;
}
return false;
}
/// <summary>
/// 返回循环访问集合的枚举数。
/// </summary>
/// <returns>循环访问集合的枚举数。</returns>
public Enumerator GetEnumerator()
{
return new Enumerator(m_Dictionary);
}
IEnumerator<KeyValuePair<TKey, GameFrameworkLinkedListRange<TValue>>> IEnumerable<KeyValuePair<TKey, GameFrameworkLinkedListRange<TValue>>>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// 循环访问集合的枚举数。
/// </summary>
public struct Enumerator : IEnumerator<KeyValuePair<TKey, GameFrameworkLinkedListRange<TValue>>>, IEnumerator
{
private Dictionary<TKey, GameFrameworkLinkedListRange<TValue>>.Enumerator m_Enumerator;
internal Enumerator(Dictionary<TKey, GameFrameworkLinkedListRange<TValue>> dictionary)
{
if (dictionary == null)
{
throw new GameFrameworkException("Dictionary is invalid.");
}
m_Enumerator = dictionary.GetEnumerator();
}
/// <summary>
/// 获取当前结点。
/// </summary>
public KeyValuePair<TKey, GameFrameworkLinkedListRange<TValue>> Current
{
get
{
return m_Enumerator.Current;
}
}
/// <summary>
/// 获取当前的枚举数。
/// </summary>
object IEnumerator.Current
{
get
{
return m_Enumerator.Current;
}
}
/// <summary>
/// 清理枚举数。
/// </summary>
public void Dispose()
{
m_Enumerator.Dispose();
}
/// <summary>
/// 获取下一个结点。
/// </summary>
/// <returns>返回下一个结点。</returns>
public bool MoveNext()
{
return m_Enumerator.MoveNext();
}
/// <summary>
/// 重置枚举数。
/// </summary>
void IEnumerator.Reset()
{
((IEnumerator<KeyValuePair<TKey, GameFrameworkLinkedListRange<TValue>>>)m_Enumerator).Reset();
}
}
}
}
如果不太清楚迭代器为何物的,具体传送门如下:
https://blog.csdn.net/m0_37920739/article/details/104478904
今天感觉是不是感觉又学到新的东西了(^U^)ノ~YO,如果文章那里讲解不好的,希望留言讨论一下~