1

In Entity Component Systems, an entity holds a relationship to the data that is in components, and then each component is operated on by what can be multiple systems. Each system can depend on many components.

Currently I'm implementing the systems in a base class and doing much of the heavy lifting of finding and coupling the components together to the same entity. A lot of this is done using Generics, and it works quite well.

public abstract class System<T1>: System
    where T1: Component
{        

    public override void Update( long delta )
    {
        ComponentStorage<Component> componentStorage1 = GetComponentStorageByType<T1>( );

        List<Entity> entities = GetEntities( );

        if ( componentStorage1 == null )
            return;

        entities.ForEach( e =>
        {
            int index = entities.IndexOf( e );

            if ( componentStorage1[ index ] == null )
                return;

            Update( (T1) componentStorage1[ index ], delta );
        } );
    }

    protected abstract void Update( T1 component1, long delta );
}

The inherited classes overrides the method called Update, which I pass instantiated components to through the update method from the storages.

class TestSystem1: System<TestComponent1>
{
    protected override void Update( TestComponent1 component1, long delta )
    {
        Console.WriteLine( $"{component1.Count++}" );
    }
}

This works well for systems with only one component. If the system has more than one components, I would have to add another generic Tn for however many components, which means implementing an up to unlimited number of classes.

I've looked into variable number of generic arguments, C++11 has it but C# does not.

I can probably fit reflection to work magic, but until I've exhausted all other options I'd rather not.

Is there a design that can satisfy my needs? I would like it to leave the inherited class the most intact - call an override, protected Update method ( or similar ) and have an already cast components handed to it.

mstehula
  • 38
  • 6

1 Answers1

1

Some possible options are:

  • use a T4 template to generate a set of System<> base classes with support for e.g. 1 to 16 parameters. This is a compile-time solution.
  • have System<T> accept only one parameter but in case T is not a component itself, treat it as a composite. IMO, it's simpler both to implement and to use. For example:
    class ComponentsRequired
    {
       Component1 First { get; set; }
       Component2 Second { get; set; }
    }

    class TestSystem : System<ComponentsRequired>
    {
        protected override void Update( ComponentsRequired components, long delta )
        {
            Console.WriteLine( $"{components.First}" );
            Console.WriteLine( $"{components.Second}" );
        }
    }

Reflection can be used to get types of properties in ComponentsRequired once and cached to speed it up next time.

pbalaga
  • 2,018
  • 1
  • 21
  • 33
  • I do like the the idea of a composite, but then you'd still have to unpack it somehow in the inherited class, which means user implementation. The T4 templates look very useful for exactly my problem, putting in the code changes and hopefully be able to duplicate the code more than a few times. – mstehula Mar 09 '19 at 23:06
  • @mstehula, why would you need to unpack? The composite can be passed around as it is. Using it in the derived class is as simple as getting properties from the composite object. I extended the code sample to make it clearer. The `System` base class can be implemented once for all use cases. – pbalaga Mar 10 '19 at 07:36
  • I guess by unpacking I also mean casting. Whether it's inside the composite or not, we don't know the type of First or Second from your example. In my test case, TestSystem relies on knowing which components it uses to allow it access to the components data, which is not inherited from the base class Component. @pbalaga – mstehula Mar 11 '19 at 16:12
  • @mstehula: casting is still not necessary. The `ComponentsRequired` is created for every distinct set of parameters you want to operate on in classes deriving from `System`. You know exact types you need to use when writing code, otherwise you couldn't make use of it in `TestComponent` class. The difference is that in the base class `System` you have to get types of properties defined in `T` *dynamically* and then call `GetComponentStorageByType(propertyType)` for every such type. There should be no single explicit cast required. Do you find this clearer now? – pbalaga Mar 16 '19 at 12:06
  • Yes it does. Create the composite alongside the system, in my case it's called TestSystem1Components. Everything on the inherited side of things is fine and dandy, I was having some struggles figuring out what to do in the base system. Knowing the type of the update method was the same as before, passing the generic type through to the base system. Casting is not necessary because of the reflection type lookup. @pbalaga – mstehula Mar 18 '19 at 17:16