Skip to content

Instantly share code, notes, and snippets.

@Ming-Tang
Created February 16, 2015 08:21
Show Gist options
  • Save Ming-Tang/bb0835d14a99016ba0e5 to your computer and use it in GitHub Desktop.
Save Ming-Tang/bb0835d14a99016ba0e5 to your computer and use it in GitHub Desktop.
Generic object pooling script with custom allocator
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