Archive

Archive for August, 2007

Context objects

August 24th, 2007

Finally I’ve found some free time, and got my lazy ass to type in my first article. I hope you’ll enjoy it, and have something useful for takeaway. :-)

Many moons ago I came across a concept of “Context Objects”, and found it quite interesting. I didn’t had any opportunity to test it until recently, and now I’m really happy I have found it. So what it’s all about…

When programming higher layer classes, you end up using services of lower layers, or need some other global or local state information. For such services as logging, rendering, input, it is very tempting to write singletons, and it’s really easy to put necessary state information like game’s update delta time in a global scope variables. But any more experienced programmer will tell you that making your objects depend on a global state is bad. Global state can be unexpectedly modified from anywhere, you don’t see when it is initialized or destroyed, it ties classes to it, code is harder to read, modify or extend, writing unit tests for such classes is hard or even impossible, and so on. So how do you solve this mess?

It’s obvious that you need to pass necessary service objects or state to constructors or as parameters to methods. But this can be a bit cumbersome when you need a lot of information in a particular method. It’s common for methods such as Update(), Load(), Render(). In such cases it is more practical to use context objects. Context object is no more than a simple container for required services and state. As you already guessed, they are called context objects because they carry only information that is potentially useful in a given context (like update or rendering).

For example, in game’s update loop you usually need services like input, time and other game state information, so these can be grouped and passed along to game object’s Update() method in an UpdateContext class. In game’s rendering loop you need stuff like renderer, camera, frustum, so you would group these in RenderContext class and pass to Render() methods. Here’s a sample code to give an idea of how to use it:

struct UpdateContext { public GameTime Time; public GameState State; public Input Input; } struct RenderContext { public Renderer Renderer; public Camera Camera; public Frustum Frustum; } class CoolObject { public void Update(UpdateContext update) { if (update.State.IsPaused()) { return; } if (update.Input.KeyDown("forward")) { velocity.x += acceleration * update.Time.GetDeltaTime(); } position += velocity * update.Time.GetDeltaTime(); } public void Render(RenderContext render) { if (boundingBox.IsInside(render.Frustum)) { render.Renderer.RenderMesh(objectMesh); } } }

Note that it’s a good practice to add constructors for context objects, to ensure that user is forced to properly initialize them. If they don’t – you at least have someone to blame. ;-) Do not try to extend this class with features – keep it as simple dumb object container. And always make sure that you pass initialized and valid data. This will tighten possible causes for bugs.

When using context objects you get quite a few useful things:

  • Class is not tied to global variables. It’s easier to read it, reuse, modify and extend.
  • You’re not tied to single instances of services or same global state. For different objects you can pass different contexts. Can be very handy when debugging.
  • It’s easier to write unit tests by simply passing mock objects in context variables.
  • Code is safer as you hide stuff that could be meaningless and provide wrong information in other contexts.
  • It’s easy to extend the context class if you suddenly need to pass more information for objects.
  • You always know that incoming context data will be initialized and ready for use. Of course it doesn’t protect if someone (un)intentionally forget to initialize context object properly. ;-)

As for drawbacks, I can only mention that it requires a bit more typing, which isn’t an issue at all. Also, sometimes larger code blocks can get a bit messier due longer lines. But it can be easily fixed by accessing objects, held in a context object, through locally declared references or pointers.

Smilediver Coding