Service Dependencies in Game Components

Update : See this post for a sample project!

Last summer I worked with Microsoft patterns & practicesGuidance Automation Toolkit, one of their software factories used for convenient Visual Studio 2005 extensibility. It imposed a strict but well-made service/component model not unlike XNA, but had some more stuff that I thought could become necessary in a big XNA game project; two of those being service dependencies and injection in components and services. So I went ahead an implemented them in an XNA context.

The principle is an extension to the current service/component model of XNA :

  • Components should be “pluggable” and react properly when removed from or appended to a project;
  • The only communication points between a component and its environment are the services it obtains from the game context.

This already works in a typical XNA project. My problem was that if a component needs a service to behave properly, it has no direct means of telling the game, so if the game can’t provide it, the component will throw a NullReferenceException when using the null service that the service collection returned.

Now I had seen in p&p’s service factories the [ServiceDependency] attribute and their “Injection System”. (read more about it here, near the end)
Basically, whenever a service is needed in a component, you do the following :

  • Add a public setter (mostly to avoid compiler warnings that the variable is never set);
  • Tag it with the [ServiceDependency] custom attribute.

Here’s an example :

public class MyComponent : GameComponent
{
	IMyService myService;

	[ServiceDependency]
	public IMyService MyService
	{
		set { myService = value; }
	}
}

The component itself never fetches the service instance from anywhere.

In the game’s initialization, instead of adding components directly to Game.Components, some helper class (might call it ComponentHelper) is used. Its logic goes like this :

  • Use reflection on the component’s type to find all the public setters tagged with [ServiceDependency];
  • For each of these,
    • Try to find the service associated the setter’s interface type :
    • If it exists, “inject” it into the component by invoking the setter.
    • If not, raise an exception – I called it MissingServiceException.

It might look a lot of trouble for something that already works, and not to mention adding all those setters to your components. I just find that it makes the components more pluggable and umm… contract-ful.
Each component tells directly to the framework : “I need those services, they’re not just random fields. I don’t really know if they exist or where to get them and I don’t have to know that anyway, I just need their functionality.”
If the framework can’t provide it with the right services, a meaningful exception is raised before even initializing the component.

I’m about to make a proper sample of all this. It’s written and working, but kind of tightly knit with my game project’s code. So give me a couple of days… :)

5 thoughts on “Service Dependencies in Game Components”

  1. Nice. With the extra bit of work of creating your own attribute that targets the class instead of a property you could reduce the required code in the component to a simple class level attribute listing the service(s) it requires, e.g. your sample would look like:

    [XnaServiceDependency(typeof(IMyService))]
    public class MyComponent : GameComponent
    {
    }

    Maybe I am just too tired right now to see any shortcomings in the class based solution; the P&P team must have had a good reason to choose the property attribute approach instead.

  2. @Björn : Actually I do create my own attribute, I don’t import the class from some p&p package. And a couple of things with the approach you mentioned :
    – I think typeof’s are ugly… and generic custom attributes are impossible in C# 2.0 :(
    – Setters would have to be created anyway, unless you don’t mind the compiler to complain about “private variable x is never set and its value will always be null” for every service.
    Otherwise it’s valid variant… you can adapt the code I’ll release to your liking :)

  3. Why would there be a never assigned to private variable for the service for the class attribute approach?

    As for the typeof: It could become
    XnaServiceDependency(“Foo.Bar.IMyService”)]
    Et voila, no typeof in the declaration :]

    The only pro for the Attribute-on-Setter is that the loader will pass a reference to the required service(s), thought that might not be too good (memory wise) for all components, e.g. when the service is only used in GameComponent.Initialize()…

  4. Ah! I thought you meant that the component still had services in its fields but no setters, and they were injected via private field reflection (which is a bit evil).
    In that case yes, it works, but you lose injection… Which I considered was a cool feature and a part of the “independant component contract”. Whatever works best for you.
    And I don’t think an object reference (which is just a pointer, right?) per service is a very heavy memory load, even if you have lots of them.

    Uh… fully-qualified type names in a string? I prefer typeof’s in that case. :o

  5. Oh, yeah, well, I might need to add that I am not a friend of such evilness like injection anything via reflection, there’s always an elegant way which is also self-documnetating – or at least more obvious =]

    The injection as part of the “independant component contract” is rather mood as GameComponents hold a reference to the Game class which provides access to the Services already. Anyway, I guess this hair splitting is a bit too academic to have a place in the real world.

    As for the fully qualified string: For some reason I couldn’t get a simple typeof inside a ConverterAttribute to work across assembly bounderies, the string worked. Ok, I didn’t really spent to much time investigating that issue…

Leave a Reply to Björn Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.