Tuesday, July 29, 2008

Entity System (Part 4)

As I said in part 2, the core of this entity system is how messages are dispatched to entities (technically, to their message handlers). Since handlers have a state and a priority flag, it'd make sense to create an ordered list of handlers, and shoot the message down this list. Of course, upon component installation, the order must be kept and it depends on the above flags.

At this point, I don't care if the "message pipe" must be a list, a tree, an array, whatever; I'm worried about the performance of the system, to be precise about allocations.

In a game, especially such as ours, it is common to have many similar enemies which share the same type of components (to keep the es terminology) - we know that all of them, at least, have a Health component. Since I mentioned allocations, the attentive reader might have already understood that I'm about to talk about object pooling.

Why pooling? I find it logical to reuse some objects over and over, since I'm not sure how long they'll live. They might be short-termed (entity insta-kill), or long-lived, or anything in between. We know that GCs kick in post allocations, and that short-lived objects are fast to collect. We also know that caches need a policy, or they're just disguised memory leaks (and if Rico Mariani says so...).
However, in our case scenario, I cannot predict the life of the objects composing an entity (plus the entity itself). Should I just allocate whatever I want and focus my attention on, say, a funny youtube video? Or should I consider creating a pool of reusable objects, instead of allocating without a specific order? Still, allocations are very fast in the CLR.

And the story isn't over, yet. A message pipe contains many references to handlers. And there will be x message pipes equal to the number of entities times the number of system messages, each of which will contain y references to y different handlers. It's a lot of references to be tracked by the garbage collection, and I'm scared to just think what'd happen if these references kept some objects alive instead of letting the GC collect them.

My solution (and it's far from being the right one, I suppose) would be to pool stuff, even if I have just advised against it in a recent gamedev.net post. With pooling, I have collections of objects ready to be used or already used, and I can access them via an integer "handle" (namely an index). All my message pipes would contain is a list of these integers - thus no actual pointers but indices. Would my garbage collector be happy? Am I wasting my time? I tend to think the latter, since I haven't managed to make up my mind about this matter yet.

I gotta say I find many .net perf advices a bit obscure.

There's also something else which has kept my mind a bit busy between a general ledger entry table and a C/AL codeunit this morning (brrrrr!). Should I suggest the GC to perform a collection when, say, the user reloads the level or changes map? I'd say so, but it isn't necessarily right. I must remember to test this as well at some point...

Premature optimization, anyone?

No comments: