www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Meta-programming: iterating over a container with different types

reply Claude <no no.no> writes:
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
   }
}
Sep 23 2016
parent reply deed <none none.none> writes:
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); }
Sep 23 2016
parent Claude <no no.no> writes:
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(); }
Oct 20 2016