3

I am at what I believe to be the last big logical leap before completing my component based game engine in C# using XNA. I have my Entity class and abstract components defined. My issue is arising in my EntityFactory.

When I want to create a new entity I pass in an EntityType enum to a static method in the factory, and it goes through a switch/case statement finding which components to put together. The problem is, I am trying to create a way that the components can share fields with other components in the same Entity without them having access to everything. For instance, if two components have Vector2 fields that represents position, they should both point to the same Vector2.

I can do this by initializing all fields in the entity factory and requiring that they be passed into the constructor of the component (and using ref for the primitives), but this would be extremely hard to maintain, since any time I expanded or changed a component, I would have to rewrite code in the factory in every place that component was used. I really want to avoid this solution, but if I can't find a better way, I will put up with it.

My current solution is to create a wrapper class called Attribute. It contains two fields:

private AttributeType type;
private Object data;

Attribute Type is an enum, representing the purpose of the attribute. So there are entries in the enum of Position, Rotation, Texture, etc.

The EntityFactory creates an empty list of attributes, and passes it to each component constructor. The setField method would be called by the component's constructor rather than initializing the fields. Here is the Attribute class and the setField method.

 public class Attribute
{
    private AttributeType type;
    private Object data;

    public AttributeType Type
    {
        get { return this.type; }
    }
    public Object Data
    {
        get { return this.data; }
    }

    public Attribute(AttributeType type, Object data)
    {
        this.type = type;
        this.data = data;
    }

    public static void setField<T>(List<Attribute> attributeList, AttributeType type, out T field, T defaultValue)
    {
        bool attributeFound = false;
        field = defaultValue;

        foreach (Attribute attribute in attributeList)
        {
            if (attribute.Type == type)
            {
                field = (T)attribute.Data;
                attributeFound = true;
                break;
            }
        }

        if (!attributeFound)
        {
            attributeList.Add(new Attribute(type, field));
        }
    }
}

My problem is when the attribute contains data that is of a primitive type. I considered writing a method in the Attribute class of

public void getData<T>(out T field) { field = this.data; }

however I can't seem to pass in data to the Attribute constructor using ref. I can't make Attribute generic since it wouldn't go into a list. I am just wondering if there is a way to handle value type as well as reference type data, or am I making a logical mistake somewhere in this whole thing.

Godric Seer
  • 359
  • 4
  • 16

1 Answers1

10

Snarky version: Congratulations you've reinvented the variable. Badly. Or, at best, a property on an interface.

Useful version:

I can see a few issues with your design.

The first issue is simply that it's complicated. Complication is best avoided unless you have a compelling and existent reason for it (ie: not a "maybe in the future" need). Otherwise YAGNI. You should always try to express concepts directly in code before resorting to creating systems to express those concepts in data (like what I said about reinventing the variable; also consider this).

But let's assume that you do have a good reason for a component based design...

Second issue is boxing. Boxing happens anywhere you store a value type (eg: int, float, Vector2, any struct) directly as a reference type (eg: object, IEquatable). Boxed objects are immutable - so every time your position changes, a new boxed object is created. Boxing a variable is (relatively) slow. Boxed objects are stored on the heap - so they are considered during, and can cause, a garbage collection. So the design you propose in your question is going to perform horribly.

I assume your idea for a component based design is similar to the one explained in this article. Here's a helpful diagram:


(source: cowboyprogramming.com)

And that brings me to the third issue: you shouldn't have more than one component that holds a position anyway! (You seem to be going way more granular than you need to, in your design.)

Basically a component-based design is about reinventing the class, rather than the variable. In a normal design, you might have a "Render" function like this:

public void Draw()
{
    spriteBatch.Draw(texture, this.Position, Color.White);
}

But in a component based design you will have Draw and Position in different classes. Which, by the way, I would have implementing interfaces like:

interface IRenderComponent { void Draw(); }
interface IPositionComponent { Vector2 Position { get; set; } }

So how does Draw access Position? Well, you need a way to express this (if you're going to reinvent classes, this is perhaps the most important concept you need to include).

How would you do this? Here's a rough design idea for you:

I would make each component class inherit from a Component class with a property Self. I would make Self return some kind of ComposedObject with a mechanism for accessing any of the other components that make up the composed object by interface. So perhaps your render component might look like:

class SimpleRenderer : Component, IRenderComponent
{
    public void Draw()
    {
        sb.Draw(texture, Self.Get<IPositionComponent>().Position, Color.White);
    }
}

(This works similarly to GameServiceContainer (ie: the Game.Services property). The idea here is that no ComposedObject should have more than one instance of each interface. If your number of interfaces is small, ComposedObject need not even use a list - simply store each directly. You could have components that implement more than one interface, though.)

Now if that is too verbose for you, perhaps you could add a few convenience properties on ComposedObject (or use extension methods) for common pieces of data, like Position, like this:

public Vector2 Position { get { return Get<IPositionComponent>().Position; } }

Then your draw function could simply be this:

spriteBatch.Draw(texture, Self.Position, Color.White);
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Andrew Russell
  • 26,924
  • 7
  • 58
  • 104
  • As far as the issue with boxing, I understand there is considerable overhead with wrapping all the fields in another class. That is why I attempted to have the field reference the data inside Attribute. Once the Entity was created, none of the Attributes or the List would be referenced, so I thought the extra layer would be garbage collected, leaving only the data underneath. I feel like your suggestion of interfaces will lead to coupling between component implementations that I was trying to avoid, but maybe the separation I was looking for isn't possible. – Godric Seer Jul 16 '11 at 18:35
  • @Zak: I'm not sure I fully understand your idea about boxing - but I'm not seeing anything in there that would reduce the amount of boxing required. As far as coupling goes - you have to have *some* level of coupling - eg: to draw something at a position you *have to have a position*. The design I've given couples `SimpleRenderer` to `IPositionComponent` (both in code and in your composed object definitions). It avoids the bad kind of coupling - where `SimpleRenderer` might depend on specific position providers like `StaticPosition` and `PhysicsPosition`. – Andrew Russell Jul 18 '11 at 02:10
  • My idea was that these attributes would be made during Entity creation, but all of the fields in the components would be references to actual data, not to one of these attributes. That way, when the Entity was fully constructed, there wouldnt be any references to any attributes or the list of attributes, so the wrapper class would get garbage collected leaving the data they contained behind. As far as the coupling, I always saw components as actions, but you seem to include states and data as well. I just have to think about it a bit. – Godric Seer Jul 18 '11 at 21:58
  • Am I misunderstanding your design? Because it sounds to me like you intend to store references to value types (eg: `Vector2`). But you can't do that. Value types are always copied, and when they're boxed into a reference type they're immutable. The only way to have a "reference" to a mutable value type is if it's a member of a `class` (like your `Attribute`, or kind of like my `IPositionComponent`) and you always access it via that class. (`ref` parameters are an all together different story.) – Andrew Russell Jul 19 '11 at 00:57
  • Ah, okay. I understand now. Yes that was my mistake, thinking i could create references to the value types. I was trying to avoid the overhead of holding on to the containing class, but maybe there isnt a simple way to avoid it. Thank you for the help. – Godric Seer Jul 19 '11 at 01:37