There are a couple keys to making object pooling work:
- All objects being pooled should have an empty constructor
- The pooled objects should all have some "initialize" or "activate" method
- Be sure to clean up the object after you are done using it... you don't want any residual properties left over from the previous usage.
class ObjectPool<t> where T : IPoolableObject { #region Private Fields private List<t> activeItems; private Queue<t> inactiveItems; private int resizeAmount; #endregion #region Public Properties public T this[int index] { get { return this.activeItems[index]; } } public int Count { get { return activeItems.Count; } } public List<t> InactiveObjects { get { return inactiveItems.ToList(); } } #endregion public ObjectPool(int Capacity, int ResizeAmount = 1) { if (Capacity < 1) throw new ArgumentOutOfRangeException("The capacity must be 1 or greater"); if (ResizeAmount < 1) throw new ArgumentOutOfRangeException("The resize amount must be 1 or greater"); activeItems = new List<t>(); inactiveItems = new Queue<t>(); resizeAmount = ResizeAmount; //Fill up the inactive items list System.Reflection.ConstructorInfo constructor = typeof(T).GetConstructor(new Type[0]); for (int i = 0; i < Capacity; i++) { inactiveItems.Enqueue((T)constructor.Invoke((object[])null)); } } public T ActivateItem(string ObjectModelName, Vector2 StartingPosition, float StartingOrientation, Vector2? StartingVelocity = null, float StartingAngularVelocity = 0) { if (inactiveItems.Count == 0) //enlarge the queue if we've run out of objects! { System.Reflection.ConstructorInfo constructor = typeof(T).GetConstructor(new Type[0]); for (int i = 0; i < resizeAmount; i++) { inactiveItems.Enqueue((T)constructor.Invoke((object[])null)); } } T newItem = inactiveItems.Dequeue(); activeItems.Add(newItem); newItem.ActivateObject(ObjectModelName, StartingPosition, StartingOrientation, StartingVelocity, StartingAngularVelocity); return newItem; } public void DeactivateItem(T item) { item.DeactivateObject(); inactiveItems.Enqueue(item); activeItems.Remove(item); } // The stuff below just makes the ObjectPool fancy, allowing you to call a "foreach" loop on the pool or simply get a list of all the active objects: public IEnumerator<t> GetEnumerator() { return (IEnumerator<t>)this.activeItems.GetEnumerator(); } public List<t> ToList() { return activeItems; } }
IPoolableObject, here is a really simple interface inherited by your pooled objects. It's everything that's needed for a "new" bullet and everything it needs to clean up after itself (dirty object..). The ObjectModelName is used as a lookup value for both a pre-loaded texture and the statistics library entry (ex: health, speed, armor, ect..., remember? See the previous post if you don't). StartingPosition, StartingOrientation, StartingVelocity, and StartingAngularVelocity are all relavent quantities for the physics engine. As for cleanup, everything of relevance gets overwritten during the ActivateObject call, so there really isn't anything to the DeactivateObject method. Your objects don't have to have these parameters (or lack-there-of), I'm just throwing them out there as an example.
interface IPoolableObject { void ActivateObject(string ObjectModelName, Vector2 StartingPosition, float StartingOrientation, Vector2? StartingVelocity = null, float StartingAngularVelocity = 0); void DeactivateObject(); }
Also, the bit about the "ResizeAmount" isn't really necessary. I just wanted something to fall back on if the initial capacity guess was too small and isn't the app barfing by throwing a "queue empty" exception. You could just have the pool return null; make sure your code knows how to interpret a null object, though.
As for how it's implemented in the larger game, it's super effective!
// Create and construct the pool: public ObjectPool<bullet> BulletPool; BulletPool = new ObjectPool<bullet>(100); // Activate a new bullet: Bullet newBullet = bulletPool.ActivateItem(BulletTypeFired, StartingLocation, StartingOrientation, StartingVelocity); // Insert a bunch of code about how your app uses the newBullet // and finally, deactivate it when you are finished: BulletPool.DeactivateItem(newBullet);
So there you have it. I'd like to give some credit to a couple other sources that I kinda picked and chose my favorite parts from: Jason Mitchell, Thomas Aylesworth, and the XNA Wiki: Generic Resource Pool.
No comments:
Post a Comment