原文地址:C# Memory Management for Unity Developers (part 3 of 3),php
其實從原文標題能夠看出,這是一系列文章中的第三篇,前兩篇講解了從C#語言自己優化內存和Unity3D Profiler的使用,都很精彩,有興趣的童鞋能夠參考一下。數據庫
C# Memory Management for Unity Developers (part 1 of 3)設計模式
C# Memory Management for Unity Developers (part 2 of 3)安全
對象池背後的理念實際上是很是簡單的。咱們將對象存儲在一個池子中,當須要時在再次使用,而不是每次都實例化一個新的對象。池的最重要的特性,也就是對象池設計模式的本質是容許咱們獲取一個「新的」對象而無論它真的是一個新的對象仍是循環使用的對象。該模式能夠用如下簡單的幾行代碼實現:多線程
public class ObjectPool<T> where T : class, new() { private Stack<T> m_objectStack = new Stack<T>(); public T New() { return (m_objectStack.Count == 0) ? new T() : m_objectStack.Pop(); } public void Store(T t) { m_objectStack.Push(t); } }
很簡單,也很好地體現了該模式的核心。若是你不太理解」where T」,不要緊,稍後會解釋的。如何使用呢?很簡單,你只須要找到以前使用new操做符的表達式,例如:閉包
void Update() { MyClass m = new MyClass(); }
而後將其替換爲New()和Store()。函數
ObjectPool<MyClass> poolOfMyClass = new ObjectPool<MyClass>(); void Update() { MyClass m = poolOfMyClass.New(); // do stuff... poolOfMyClass.Store(m); }
我是簡潔主義的忠實信徒,但就目前而言ObjectPool類或許過於簡單了。若是你搜索下用C#實現的對象池類庫,你會發現其中不少是至關複雜的。咱們先暫停一下,仔細想一想在一個通用的對象池中到底哪些是咱們須要的,哪些是不須要的:優化
那麼其中那些是必需的呢?你的答案或許和個人不同,但請容許我闡述個人觀點:spa
修訂後的版本以下:插件
public class ObjectPool<T> where T : class, new() { private Stack<T> m_objectStack; private Action<T> m_resetAction; private Action<T> m_onetimeInitAction; public ObjectPool(int initialBufferSize, Action<T> ResetAction = null, Action<T> OnetimeInitAction = null) { m_objectStack = new Stack<T>(initialBufferSize); m_resetAction = ResetAction; m_onetimeInitAction = OnetimeInitAction; } public T New() { if (m_objectStack.Count > 0) { T t = m_objectStack.Pop(); if (m_resetAction != null) m_resetAction(t); return t; } else { T t = new T(); if (m_onetimeInitAction != null) m_onetimeInitAction(t); return t; } } public void Store(T obj) { m_objectStack.Push(obj); } }
該實現很是簡單直白。參數T被指明爲」where T:class,new()」,意味着有兩個限制。首先,T必須爲一個類(畢竟,只有引用類型須要被obejct-pool);其次,它必需要有一個無參構造函數。
構造函數將池可能的最大值做爲第一個參數。另外兩個是可選的閉包,若是傳入值,第一個閉包將用來重置池,第二個初始化一個新的對象。除了構造函數外,ObjectPool<T>只有兩個方法:New()和Store()。由於池使用了延遲策略,主要的工做在於New()。其中,新的和循環使用的對象要麼被實例化,要麼被重置,這兩個操做經過傳入的閉包實現。如下是池的使用方法:
class SomeClass : MonoBehaviour { private ObjectPool<List<Vector3>> m_poolOfListOfVector3 = //32爲假設的最大數量 new ObjectPool<List<Vector3>>(32, (list) => { list.Clear(); }, (list) => { //初始化容量爲1024 list.Capacity = 1024; }); void Update() { List<Vector3> listVector3 = m_poolOfListOfVector3.New(); // do stuff m_poolOfListOfVector3.Store(listVector3); } }
上述的對象池實現了基本功能,但仍是有瑕疵。它將初始化和重置對象在對象定義中分開了,在必定程度了違反了封裝原則。致使了緊耦合,這是須要儘量避免的。在上述SomeClass中,咱們是沒有真正的替代方案的,由於咱們不能修改List<T>的定義。然而,當你用自定義類時,你能夠實現IResetable接口做爲代替。對應的ObjectPoolWithReset<T>也能夠不須要指明兩個閉包了(請注意,爲了靈活性我仍是留下了)。
public interface IResetable { void Reset(); } public class ObjectPoolWithReset<T> where T : class, IResetable, new() { private Stack<T> m_objectStack; private Action<T> m_resetAction; private Action<T> m_onetimeInitAction; public ObjectPoolWithReset(int initialBufferSize, Action<T> ResetAction = null, Action<T> OnetimeInitAction = null) { m_objectStack = new Stack<T>(initialBufferSize); m_resetAction = ResetAction; m_onetimeInitAction = OnetimeInitAction; } public T New() { if (m_objectStack.Count > 0) { T t = m_objectStack.Pop(); //自行重置 t.Reset(); if (m_resetAction != null) m_resetAction(t); return t; } else { T t = new T(); if (m_onetimeInitAction != null) m_onetimeInitAction(t); return t; } } public void Store(T obj) { m_objectStack.Push(obj); } }
有一些類型不須要在一系列幀中存留,僅在幀結束前就失效了。在這種狀況下,咱們能夠在一個合適的時機將全部已經池化的對象(pooled objects)再次存儲於池中。如今,咱們重寫該池使之更加簡單高效。
public class ObjectPoolWithCollectiveReset<T> where T : class, new() { private List<T> m_objectList; private int m_nextAvailableIndex = 0; private Action<T> m_resetAction; private Action<T> m_onetimeInitAction; public ObjectPoolWithCollectiveReset(int initialBufferSize, Action<T> ResetAction = null, Action<T> OnetimeInitAction = null) { m_objectList = new List<T>(initialBufferSize); m_resetAction = ResetAction; m_onetimeInitAction = OnetimeInitAction; } public T New() { if (m_nextAvailableIndex < m_objectList.Count) { // an allocated object is already available; just reset it T t = m_objectList[m_nextAvailableIndex]; m_nextAvailableIndex++; if (m_resetAction != null) m_resetAction(t); return t; } else { // no allocated object is available T t = new T(); m_objectList.Add(t); m_nextAvailableIndex++; if (m_onetimeInitAction != null) m_onetimeInitAction(t); return t; } } public void ResetAll() { //重置索引 m_nextAvailableIndex = 0; } }
相比於原始的ObjectPool<T>,改動仍是蠻大的。先無論類的簽名,能夠看到,Store()已經被ResetAll()代替了,且僅在全部已經分配的對象須要被放入池中時調用一次。在類內部,Stack<T>被List<T>代替,其中保存了全部已分配的對象(包括正在使用的對象)的引用。咱們也能夠跟蹤最近建立或釋放的對象在list中索引,由此,New()即可以知道是建立一個新的對象仍是重置一個已有的對象。
上文講解了ObjectPool的基本原理及其實現。下面推薦一個更加成熟的插件——PoolManager,該插件功能十分強大,因此纔敢賣那麼貴,30美刀...國內有位牛人已經寫了一份不錯的教程,有興趣的童鞋能夠參考下:Unity3D研究院之初探PoolManager插件。