Friday, December 24, 2010

Entity Systems Part 2: Components

Entities are essentially disconnected collections of components. Or as disconnected as possible. Some components depend on others to mean anything in game, but can be processed individually. We should start looking at how components are stored and associated with entities.

It's useful to think of each component type having it's own database table. Each row then is an instance of that component type. The primary key is the entity key.

Components are just data containers so there's no standard functionality across all of them. The only functionality they should contain are things like operator overloads, type conversions, or internal data manipulation (getters, setters). A few will have events to indicate that a value has crossed a threshold for example an OnDied event when int Health == 0

I've found it useful for the component to have on small bit of common functionality - the owner. My original design was an empty interface, but this didn't provide a link back to the owning entity unless the component type specified it. I added an abstract base that implemented this.

IEntityComponent

public interface IEntityComponent
{
    ulong Owner { get; set; }
}

EntityComponent

public abstract class EntityComponent : IEntityComponent
{
    private ulong _owner;

    public virtual ulong Owner
    {
        get
        {
            return _owner;
        }
        set
        {
            if (_owner == 0)
                _owner = value;
            else
                throw new FieldAccessException("Cannot reset component to different entity.");
        }
    }
}

And that's it. Just an interface so our component managers can enforce some semblance of type control.

IComponentManager

This is really the meat of components - holding and manipulating typed collections. I use a base interface to make it easier to swap out different implementations.

interface IComponentManager : IEnumerable<Type>
{
    void Add<T>(ulong entityId, T o) where T : EntityComponent;
    void Add(ulong id, IEnumerable components);

    void Remove(ulong id);
    bool Remove<T>(ulong id) where T : EntityComponent;

    T Get<T>(ulong id) where T : EntityComponent;

    bool Contains(ulong id, Type t);
}

The interface specifies the functionality to add components singly or as a list. It can remove a whole entity or a single type from an entity. Finally, it can retrieve components.

This is not meant to be a forward facing class. It's designed to be internal to the EntityManager. It provides the mechanisms for the entities to report about themselves.

Default ComponentManager

The default ComponentManager is mostly just a wrapper for a dictionary that conforms to the interface.

internal class ComponentManager : IComponentManager
{
    ...

    Dictionary<Type, Dictionary<ulong, EntityComponent>> _components;

    ...
}

Conclusion

Everything up till now has been fairly straightforward. Things get more complicated when we start creating systems that require multiple component types. To put it in SQL terms again, how do we write a JOIN query? Well, that's for part 3.

Entity Systems Part 1: Entity and EntityManager

The current state can be found in changeset e73319ab32fd.

Entity

First things first, an entity is essentially an id number. There are a few useful utility functions that can be encapsulated inside of it, but embedding too many will cause problems down the line. Most of this is duplicated in C# from Adam Martin's post here.

Entities know where to find the component store to add and retrieve associated components. Arguments can be made about storing basic things like position and orientation in the entity, but as will be shown later, systems don't hold references to entities, just lists of components to operate on.

/// <summary>
/// Basic entity class.  Consists of an id and a list of components.  Ideally it
/// should only be an id, but it's useful to be able to know what components it
/// is built of and to find them quickly.
/// </summary>
public class Entity
{
    readonly ulong _id;

    public ulong Id
    {
        get
        {
            return _id;
        }
    }

    /// <summary>
    /// Static instance of the entity manager.  Entities should be able to add 
    /// themselves to the manager on creation - saves having to remember to do this.
    /// </summary>
    public static EntityManager EntityManager { get; set; }
        
    public Entity(ulong id)
    {
        _id = id;

        if (EntityManager == null)
            throw new ArgumentNullException("Entity manager cannot be null.");

        EntityManager.Add(this);
    }

    /// <summary>
    /// Add a new component to the entity
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="component"></param>
    /// <returns></returns>
    public Entity Add<T>(T component) where T : IComponent
    {
        EntityManager.Components.Add(Id, component);
        return this;
    }

    /// <summary>
    /// Return this as a component
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public T As<T>() where T : IComponent
    {
        return EntityManager.Components.Get<T>(Id);
    }
}

EntityManager

The manager is responsible for creating and tracking loaded entities and assigning them ids. Entity ids are transient - rebuilt every load. Template ids persist between game runs. Currently I have two Get<T> functions that aren't implemented - still thinking if those are actually needed or not. Leaning towards no. Not show here are the details of the ComponentManager - that's another long post.

public class EntityManager
{
    static ulong _nextAvailableId;

    Dictionary<ulong, Entity> _entities;

    /// <summary>
    /// Store of all loaded components, indexed by type and entity id
    /// </summary>
    public ComponentManager Components { get; set; }

    public Entity this[ulong index]
    {
        get
        {
            // TODO: Should we be doing this?
            // If we're looking for a non-existant entity, go ahead and add it
            if (!_entities.ContainsKey(index))
            {
                Add(new Entity(index));
            }

            return _entities[index]; 
        }
    }

    public EntityManager()
    {
        _entities = new Dictionary<ulong, Entity>();
        _nextAvailableId = 0;
        Components = new ComponentManager();

        // Check that entities know this is the manager
        if (Entity.EntityManager == null)
        {
            Entity.EntityManager = this;
        }
    }

    /// <summary>
    /// Return all entities containing a particular type of component
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public IEnumerable<Entity> Get<T>() where T : IComponent
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// Return all entities containing all of the parameter types
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public IEnumerable<Entity> Get(params Type[] componentTypes)
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// Add an existing entity to this
    /// </summary>
    /// <param name="entity"></param>
    public void Add(Entity entity)
    {
        if (!_entities.ContainsKey(entity.Id))
        {
            _entities.Add(entity.Id, entity);
        }
    }

    /// <summary>
    /// Create and return a new entity
    /// </summary>
    /// <returns></returns>
    public Entity Create()
    {
        _nextAvailableId++;

        // Ensure that the next available id is, in fact, available
        while (_entities.ContainsKey(_nextAvailableId))
        {
            _nextAvailableId++;
        }

        return new Entity(_nextAvailableId);
    }

    /// <summary>
    /// Remove an entity
    /// </summary>
    /// <param name="entity"></param>
    public void Remove(Entity entity)
    {
        Components.Remove(entity.Id);
        _entities.Remove(entity.Id);
    }

    /// <summary>
    /// Remove an entity
    /// </summary>
    /// <param name="entity"></param>
    public void Remove(ulong id)
    {
        Components.Remove(id);
        _entities.Remove(id);
    }
}

Currently entity id's are just ulong's incremented for every entity. Theoretically it's possible to reach the max, but even creating one new entity every millisecond, it would take over 500,000,000 years to reach the max. ulong's are really big. Even just a plain int would give us 2,147,483,647 possibilities (positive ints only).

Sample Usage

_manager = new EntityManager();

_manager.Create()
    .Add<Transform>(new Transform() { })
    .Add<Renderable>(new Renderable() { });

var transform = _manager[1L].As<Transform>();

Nothing really special here - most of the work is done in the ComponentManager. That's coming up next.

Saturday, December 18, 2010

Entity System Test Project

Entity Systems are a great way to build up game objects, but a significant thought shift from traditional OOP inheritance trees. Simply getting started is one of the bigger challenges as well as being aware of, and avoiding, functionality creep into entity and component objects.

Much has been written on entity systems, although most of it is at a very high level overview of what they are. There are some examples in a few books, but I'm cheap. Browsing the net though, here's a quick list of some of the best examples and most helpful collections:

Step 1

I plan on stepping away from OTD for a little while here. It has a ton of code behind it, a lot which has been sitting there for over a year without refactoring. I'm starting a much smaller project to focus on entity systems for a while as well as clean up some of my engine code.

Amoebal

Source code is up on BitBucket. It's still very, very early, but a decent foundation to build on. Further posts will detail the process of creating an entity system in XNA and C# as well as try to explain my reasoning behind a few things.

Questions to deal with

  • How will lists of components be sent to systems? Big dictionaries?
  • How will component types be cached and then pruned? Entities can register components on add

Saturday, December 11, 2010

Mesh Levels

Got some various debugging info in place and finally decided to get some stats on the various levels of the map build.

Build Data
Level Cells Avg. Distance Time (ms)
0 12 1.051462 56
2 92 0.396275 243
4 252 0.2403322 1264
6 492 0.1721796 3594
8 812 0.1340831 11187
10 1212 0.1097718 19649
12 1692 0.09291888 34545

The cells increase at a slight exponential rate while the time at a much steeper rate. The generating code could be a lot more optimal, and the vast majority of this time is spent in the mesh generate. Each cell is locally subdivided down three levels. There are probably a lot of optimizations that could be run, and a couple of the loops could be consolidated. Over time I've managed to optimize most loops to just n * n.neighbors. I've opted more for understandable code (as much as possible) vs highly optimized loops. Considering that all of this is done once at compile time, it's not a big deal really.

Graphs

Cells

Time

Distance

Friday, December 3, 2010

IEnumerable.ToString()

One useful thing is to be able to concatenate IEnumerable's into a string for printing. A couple extension methods take care of this.

public static string ToString<T>(this IEnumerable<T> collection)
{
    return collection.ToString(",");
}

public static string ToString<T>(this IEnumerable<T> collection, string separater)
{
    return String.Join(",", collection);
}

public static string ToString<T>(this IEnumerable<T> collection, Func<T, object> field) where T : class
{
    return collection.ToString(",", field);
}

public static string ToString<T>(this IEnumerable<T> collection, string separater, Func<T, object> field) where T : class
{
    return String.Join(separater, collection.Select(field));
}

But couldn't we just use String.Join as the extension methods are? Sure, but this is more fun and provides a more logical response to ToString() as well as more control over objects with anonymous functions.

var testList = new List<SimpleObject>()
{
    new SimpleObject() { Id = 1, Name = "asdf" },
    new SimpleObject() { Id = 2, Name = "qwer" },
    new SimpleObject() { Id = 3, Name = "zxcv" },
};
var s = testList.ToString<SimpleObject>(",", c => c.Id);

One caveat, the base IEnumerable.ToString() is still there, and simple prints out something like System.Collections.Generic.... Not what we expect. To call the basic extension method, we have to specify it with the type parameter:

var fail = testList.ToString();  // Ruh-roh!
var works = testList.ToString<SimpleObject>();  // Woot!