Pools at last…

Pools at last…Pools at last! THANK GOD ALMIGHTY….OK, I might be overplaying this.

splashI’ve written many times of my desire to add a native pooling capability to Strange, and at last that feature is primed for release as part of v0.7.

To recap the purpose in brief: pooling saves memory, and therefore improves performance, particularly on mobile. This post explains our implementation of pools, and details how to use them. If you’re interested, a prior post goes into  the question of “why?” in more detail.

A pool, as I see it, consists of three parts:

  • Poolable (recyclable) items. That is, items capable of being reused instead of thrown away. While you can pretty much pool anything, we’ve created an IPoolable interface which is useful for establishing the important behaviors of a pooled instance.
  • The pool, which is at heart just a collection of recyclable instances. We have a Pool class, of course.
  • A client. The agency that wants to use the pooled instances, and is responsible for returning them. The client typically thinks in terms of IPool<T>.

That last bullet is important, and differentiates a pool from a simple instance provider. The client of a provider simply takes instances. The pool’s client knows it has to put things back. Don’t forget this point, lest all the virtues of pooling be lost on you.

There are three main uses for pools in Strange. We’ll go through them from the simplest to the most complex.

Use Case 1: Event Pooling

Event pooling is by far the simplest form, especially as you needn’t even know it’s happening. Quite simply, the EventDispatcher now implements a pool, recycling its events instead of throwing them away. Obviously if you use Signals this has no particular impact on your life.

Since Event pooling is happening under-the-hood, you don’t see any of the three parts mentioned above.

Use Case 2: Command Pooling

Commands (whether for Events or for Signals) may now be pooled at your discretion. In general, there is no need to do so, BUT (notice that it’s a very big ‘but’) it can be highly advantageous to do so for Commands that fire a lot, as with Commands fired as part of your main game loop. This was the key reason for implementing pools: it is now possible to mark a Command for pooling, so that Strange will be more friendly for use in game loops!

Marking a Command for pooling is exceedingly simple, and looks like this:

commandBinder.Bind(MyEvents.SOME_EVENT).To<SomeCommand>().Pooled();

There’s a Signals version, of course. I hope it won’t disappoint if it looks almost exactly the same:

commandBinder.Bind(mySignal).To<SomeCommand>().Pooled();

The result of marking the binding this way is that the Command will fire and get recycled when it’s done. A retained Command can also be recycled, but there are a couple important caveats.

First, remember that instances returned to a pool need to be restored to their pristine state for future use. Leaving uncleared references could lead to memory leaks or other problems. Therefore, if you Retain() a pooled Command, make sure you clear any variables in the new Restore() method, the express purpose of which is to clear data immediately before recycling. You don’t need to worry about cleaning up anything injected; these properties will be cleaned up automagically by Strange. And you don’t need to call Restore() yourself as it’s called as a normal part of the Release() cycle.

Second, note that even an empty Command takes some memory, so don’t mark commands Pooled() willy-nilly. Consider whether or not pooling each specific Command gives you an advantage.

Use Case 3: Everything Else

That headline might sound a bit glib, but it pretty much describes the case of you using Pools when you need them. It’s not automated at this level, but pools have been designed to be both simple and powerful, so that you can easily recycle most anything you like.

Here’s a mapping for a typical pool:

injectionBinder.Bind<Starship>().To<Starship>();
injectionBinder.Bind<IPool<Starship>>().To<Pool<Starship>>().ToSingleton();

Note first that we tell the Pool what the Type of its instances will be. A pool’s instances must all be of the same concrete type.

But there’s something more important to note here: we’re not mapping a pool of type Starship to an interface of, say, IShip. This is profoundly disappointing to me, but it’s a limitation of the version of .Net that Unity uses. While you can map IPool<SomeClass>, you can’t map IPool<ISomeInterface>, or even IPool<SomeSuperClass>. You’ll get an error.

Finally, note that we have to bind the class that the pool provides. This is because the pool by default uses the Injector to instantiate its instances. This is useful, since most of the object creation can be automated in the usual IoC form. But note that if we want we can override this behavior (see below).

To use the pool we just inject it:

[Inject]
public IPool<Starship> shipPool { get; set; }

// used somewhere in your class
Starship shipInstance = shipPool.GetInstance();

// and when you're done with it
shipPool.ReturnInstance(shipInstance);

So that’s all pretty straightforward, but we’ve added a number of configurable little details that allow you to customize how your pool works.

Size

pool.size = 16;

Your pool’s default size is 0, which is a special case meaning ‘infinite’. A pool with a size of 0 will expand as the need arises, so as long as you return the checked-out instances, all the rest of the management is taken care of for you. (NB: ‘size’ refers to the allowable number of instances. There are other properties — instanceCount, available — for inspecting the current state of the pool.)

If you fix the size, then other behaviors governing overflow will kick in.

Inflation Type

pool.inflationType = PoolInflationType.INCREMENT;

If your pool size is 0 it will inflate when necessary, and you can control the type of inflation with the PoolInflationType constants. Your options are INCREMENT, which will add one more instance to the pool whenever you need one, and DOUBLE (the default), which will double the pool size.

Overflow Behavior

pool.overflowBehavior = PoolOverflowBehavior.IGNORE;

If your pool is set to a fixed size, then overflowing the pool (i.e., asking for more instances than the pool has available) will result in one of the following behaviors, depending on the PoolOverflowBehavior. By default, an overflowing pool with throw a PoolException. If you would rather get a warning or ignore it altogether, got can set this value to WARNING or IGNORE. Both these options result in returning null instead of an instance.

Instance Provider

As noted above, the pool by default goes straight to the Injector in order to instantiate its instances, but this isn’t always desirable. Sometimes you want to control the instance creation just so. You can do this by simply writing your own provider, then setting the instanceProvider property of the pool to your custom implementation. Here’s an example. This provider creates an instance and sets a property before returning it.

class ShipInstanceProvider : IInstanceProvider
{
     public T GetInstance<T>()
     {
         object instance = GetInstance (typeof (T));
         T retv = (T) instance;
         return retv;
     }

     public object GetInstance(Type key)
     {
         Starship retv = new Starship();
         retv.phasers = 100;
         return retv;
     }
}

pool.instanceProvider = new ShipInstanceProvider();

Your instance provider just needs to instantiate…it doesn’t need to worry about the details of maintaining those instances. That’s all being taken care of by the pool itself.

I think that covers it. I haven’t actually drunk even a sip of whisky while writing this post, which feels wrong. But at least we have pools, and they most assuredly feel right.

Advertisements

6 thoughts on “Pools at last…

    • Pooled items know nothing of their pools, so the short answer is no. My question is “why?” The Pool already maintains a list of its contents, so it knows the items it contains. If your point is to have the pooled item get rid of itself, the correct pattern would probably be to fire a Signal and have a Command do the work (thereby maintaining separation between the Pool and the IPoolable).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s