Created
February 16, 2015 08:21
-
-
Save Ming-Tang/bb0835d14a99016ba0e5 to your computer and use it in GitHub Desktop.
Generic object pooling script with custom allocator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEngine; | |
using System.Linq; | |
using System.Collections.Generic; | |
using Random = UnityEngine.Random; | |
using Object = UnityEngine.Object; | |
// Class Diagram | |
// | |
// MonoBehaviour vertical arrow = is-a | |
// ^ horizontal arrow = has-a | |
// | | |
// | | |
// | 1 1 <<interface>> | |
// ObjectPool<TObj> ------> ObjectAllocator<TObj> | |
// ^ ^ | |
// | : | |
// | : | |
// | : | |
// | <<abstract>> | |
// | GameObjectAllocator | |
// | ^ | |
// | | | |
// | | | |
// | | | |
// ObjectPool ------------- ObjectPool.Allocator | |
// 1 1 | |
/// <summary> | |
/// An ObjectAllocator allocates and deallocates objects through the object pool. | |
/// An object has three states and has the following transitions: | |
/// <code> | |
/// (Destroyed) | |
/// | ^ | |
/// Allocate | | Free | |
/// v | | |
/// (Dormant) | |
/// | ^ | |
/// Activate | | Deactivate | |
/// v | | |
/// (Active) | |
/// </code> | |
/// </summary> | |
interface ObjectAllocator<TObj> where TObj : class { | |
/// Set the Transform attached to the ObjectPool | |
void SetParent(Transform t); | |
/// Allocate a new object in the dormant state | |
TObj Allocate(); | |
/// Destroy an object already in dormant state | |
void Free(TObj obj); | |
/// Make a dormant object active | |
void Activate(TObj obj); | |
/// Make an active dormant | |
void Deactivate(TObj obj); | |
} | |
abstract class GameObjectAllocator : ObjectAllocator<GameObject> { | |
private int _id = 0; | |
private Transform _parent; | |
protected virtual GameObject NewGameObject() { | |
return new GameObject("Pooled Object " + _id); | |
} | |
public void SetParent(Transform t) { | |
_parent = t; | |
} | |
public GameObject Allocate() { | |
var go = NewGameObject(); | |
go.name = "Pooled Object " + _id; | |
Debug.Log("Allocate " + go.name, go); | |
go.transform.parent = _parent; | |
go.SetActive(false); | |
_id++; | |
return go; | |
} | |
public void Free(GameObject go) { | |
Debug.Log("Free " + go.name, go); | |
GameObject.Destroy(go); | |
} | |
public void Activate(GameObject go) { | |
Debug.Log("Activate " + go.name, go); | |
go.SetActive(true); | |
} | |
public void Deactivate(GameObject go) { | |
Debug.Log("Deactivate " + go.name, go); | |
go.SetActive(false); | |
} | |
} | |
/// A generic object pool with objects managed by a custom allocator. | |
class ObjectPool<TObj> : MonoBehaviour where TObj : class { | |
public int initialCapacity = 16; | |
private ICollection<TObj> _active = null; | |
private IList<TObj> _free = null; | |
private ObjectAllocator<TObj> _alloc = null; | |
public int Count { get { return _active.Count; } } | |
public int Capacity { get { return _active.Count + _free.Count; } } | |
public bool Initialized { get; private set; } | |
protected void SetAllocator(ObjectAllocator<TObj> alloc) { | |
_alloc = alloc; | |
if (Initialized) _alloc.SetParent(transform); | |
} | |
#region Lifecycle Methode | |
private void OnDestroy() { | |
foreach (var o in _active) { | |
_alloc.Deactivate(o); | |
_alloc.Free(o); | |
} | |
_active.Clear(); | |
_active = null; | |
foreach (var f in _free) { | |
_alloc.Free(f); | |
} | |
_free.Clear(); | |
_free = null; | |
Initialized = false; | |
} | |
private void Initialize() { | |
if (_alloc == null) throw new System.Exception("allocator is null"); | |
_active = new HashSet<TObj>(); | |
_free = new List<TObj>(); | |
_alloc.SetParent(transform); | |
// allocate dormant objects | |
for (int i = 0; i < initialCapacity; i++) | |
_free.Add(_alloc.Allocate()); | |
Initialized = true; | |
} | |
#endregion | |
#region Private Methods | |
private void EnsureInitialized() { | |
if (!Initialized) Initialize(); | |
} | |
private void AddFree() { | |
var o = _alloc.Allocate(); | |
_free.Add(o); | |
} | |
private void RemoveFree() { | |
var o = TakeFree(); | |
_alloc.Free(o); | |
} | |
private TObj TakeFree() { | |
TObj o = _free[_free.Count - 1]; | |
_free.RemoveAt(_free.Count - 1); | |
return o; | |
} | |
private void AddFree(TObj o) { | |
_free.Add(o); | |
} | |
#endregion | |
#region Public Methods | |
/// Allocate a new active object from the pool. | |
public TObj Allocate() { | |
EnsureInitialized(); | |
if (_free.Count == 0) Expand(Mathf.NextPowerOfTwo(_active.Count)); | |
var o = TakeFree(); | |
_active.Add(o); | |
_alloc.Activate(o); | |
return o; | |
} | |
/// Free an existing object. | |
public void Free(TObj obj) { | |
EnsureInitialized(); | |
_alloc.Deactivate(obj); | |
AddFree(obj); | |
_active.Remove(obj); | |
} | |
/// Expand the capacity of the object pool. | |
public void Expand(int capacity) { | |
EnsureInitialized(); | |
while (Capacity < capacity) | |
AddFree(); | |
} | |
/// Shrink the capacity of the object pool. | |
public void Shrink(int capacity) { | |
EnsureInitialized(); | |
while (Capacity > capacity) | |
RemoveFree(); | |
} | |
/// Free all active objects into the object pool. | |
public void FreeAll() { | |
EnsureInitialized(); | |
while (_active.Count > 0) | |
Free(_active.First()); | |
} | |
#endregion | |
} | |
class ObjectPool : ObjectPool<GameObject> { | |
class Allocator : GameObjectAllocator { | |
private ObjectPool _objectPool; | |
protected override GameObject NewGameObject() { | |
return _objectPool.NewGameObject(); | |
} | |
public Allocator(ObjectPool objectPool) { | |
_objectPool = objectPool; | |
} | |
} | |
public GameObject prefab = null; | |
public PrimitiveType primitiveType = PrimitiveType.Cube; | |
public GameObject NewGameObject() { | |
if (prefab) return (GameObject) Instantiate(prefab); | |
else return GameObject.CreatePrimitive(primitiveType); | |
} | |
public ObjectPool() { | |
SetAllocator(new Allocator(this)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment