Thread overview
Meta-programming: iterating over a container with different types
Sep 23, 2016
Claude
Sep 23, 2016
deed
Oct 20, 2016
Claude
September 23, 2016
It's more a general meta-programming question than a specific D stuff.

For an entity-component engine, I am trying to do some run-time composition: registering a certain type (component) to a structure (entity).

I would like to know how I can iterate an entity and get the different type instances registered to it.

Here is a simple example to clarify:

class Entity
{
  void register!Component(Component val);
  void unregister!Component();
  Component getComponent!Component();

  //iterating over the components (?!??)
  void opApply(blabla);
}

unittest
{
  // registering
  auto e = new Entity;
  e.register!int(42);
  e.register!string("loire");
  e.register!float(3.14);

  assert(e.getComponent!float() == 3.14); // that is OK

  // the code below is wrong, but how can I make that work??
  foreach (c; e)
  {
    writeln(c); // it would display magically 42, "loire" and 3.14
                // and c would have the correct type at each step
  }
}
September 23, 2016
On Friday, 23 September 2016 at 09:21:56 UTC, Claude wrote:
> ...

// Maybe you can try using std.variant?

import std.variant;
alias Component = Variant;

class Entity
{
    void register (Component v) { components ~= v; }
    void unregister (T) () {
        foreach (i, c; components) if (c.type == typeid(T))
            components = components[0..i] ~ components[i+1 .. $];
    }
    Component getComponent (T) () {
        foreach (i, c; components) if (c.type == typeid(T))
            return components[i];
    }
    Component[] components;
    // iterating over the components
    alias components this;

    // or you can provide an inputRange interface by implementing
    //
    // bool empty () {}
    // Component front () {}
    // void popFront () {}
    //
    // to support whatever type of backing data structure you have.
    // (see http://ddili.org/ders/d.en/ranges.html)
}

unittest
{
    import std.stdio;
    Entity e = new Entity();
    e.register(Component(42));
    e.register(Component("loire"));
    e.register(Component(3.14));
    foreach (c; e) write(c, " "); writeln;  // Prints 42 "loire" 3.14
    e.unregister!string;
    foreach (c; e) write(c, " "); writeln;  // Prints 42 3.14
    e.unregister!double;
    foreach (c; e) write(c, " "); writeln;  // Prints 42
    e.unregister!int;
    assert(e.components.length == 0);
}
October 20, 2016
On Friday, 23 September 2016 at 12:55:42 UTC, deed wrote:
> // Maybe you can try using std.variant?


Thanks for your answer.
However I cannot use variants, as I have to store the components natively in a void[] array (for cache coherency reasons).

So I found a way to solve that problem: delegate callbacks.
There may be more elegant solutions but well, it works.

Basically I register some kind of accessor delegate of the form:

void accessor(Entity e, Component* c)
{
  // Do stuff, like save the component struct for that entity in a file
}

And it is stored in the entity class in an array of delegates:

  void delegate(Entity e, void* c);


Here's a very basic implementation:

class Entity
{
public:
  void register!Component(Component val);
  void unregister!Component();
  Component getComponent!Component();

  alias CompDg = void delegate(Entity e, void* c);

  void accessor!Component(void delegate(Entity e, Component* c) dg) @property
  {
    auto compId = getComponentId!Component;
    mCompDg[compId] = cast(CompDg)dg;
  }


  // Iterating over the components
  void iterate()
  {
    // For every possible components
    foreach (compId; 0..mMaxNbComponents)
      if (isRegistered(compId))
        if (mCompDg[compId] !is null)
          mCompDg[compId](this, getComponentStoragePtr(compId));
  }

private:
  void* getComponentStoragePtr(uint compId);
  bool isRegistered(uint compId);

  void[]   mComponentStorage;  // Flat contiguous storage of all components
  CompDg[] mCompDg;
  // ...
}

unittest
{
  // registering
  auto e = new Entity;
  e.register!int(42);
  e.register!string("loire");
  e.register!float(3.14);

  assert(e.getComponent!float() == 3.14); // that is OK

  e.accessor!int    = (Entity et, int i)    { writefln("%d", i); };
  e.accessor!string = (Entity et, string s) { writefln("%s", s); };
  e.accessor!float  = (Entity et, float f)  { writefln("%f", f); };

  // Print the values
  e.iterate();
}