0

I'm writing an engine with ECS and the data-oriented model. I'm trying to avoid inheritance and dynamic dispatch to avoid trashing the cache with every update() call. What I came up with was this:

struct transformComponent {
    const unsigned short id;
    vecShort p;

    transformComponent(unsigned short id, short x, short y): p(x, y), id(id) {}
    transformComponent(unsigned short id): p(0, 0), id(id) {}
};

struct physicsComponent {
    const unsigned short id;
    unsigned short mass;
    double invmass;
    vecShort v, a, f;

    physicsComponent(unsigned short id, unsigned short mass):
        id(id), mass(mass), invmass(1/mass) {}
    physicsComponent(unsigned short id): id(id) {}
};

//...

struct component {
    enum compType {transform, physics, behavior, collision, rendering};

    union {
        transformComponent  tra;
        physicsComponent    phy;
        //...
    };

    template<typename ...argtypes>
    component(compType type, unsigned short id, argtypes... args) {
        switch(type) {
            case transform:
                tra(id, std::forward<argtypes>(args)...);
                break;
            case physics:
                phy(id, std::forward<argtypes>(args)...);
                break;
            //...
        }
    }
};

Then I have a room class (the manager) that holds all the systems and components, through memory pools. The problem is the

template<typename ...argtypes>
    component(compType type, unsigned short id, argtypes... args) {
        switch(type) {
            case transform:
                tra(id, std::forward<argtypes>(args)...);
                break;

part, which the compiler complains about:

src\inc/components.h: In instantiation of 'component::component(component::compType, short unsigned 
int, argtypes ...) [with argtypes = {}]':
src\inc/components.h:94:71:   required from here
src\inc/components.h:57:5: error: no match for call to '(transformComponent) (short unsigned int&)'
   57 |     tra(id, std::forward<argtypes>(args)...);
      |     ^~~

I tried moving the ellipses around, to no effect. What I'm essentially trying to do is avoid the use of both templates and dynamic dispatch on every frame, but I need templates for initialization. Am I getting some basic syntax wrong? Am I completely missing something? I'm new to the data-oriented paradigm, so I'll gladly take any advice. Minimal reproducible example: https://godbolt.org/z/zGAfXS

lenerdv
  • 177
  • 13
  • Is the argument `compType type` known at compile time? – Nelfeal May 11 '20 at 11:09
  • @Nelfeal I'm not sure it is, but it shouldn't matter, since [this](https://godbolt.org/z/keu4H9) compiles. – lenerdv May 11 '20 at 11:27
  • Sure it does, but `tra` here is a member you want to initialize (same for `phy`). You can only initialize member in the constructor's [member initializer list](https://en.cppreference.com/w/cpp/language/initializer_list). If you want to initialize it later, you need to create it later, meaning your union should be a pointer or something (I guess you could use a `std::variant` but that's complicated). The other thing you can do is default-initialize `tra` and assign it the proper value in your switch (kind of delaying initialization). I'm not a fan of that. – Nelfeal May 11 '20 at 11:41
  • In any case, you should make an [MCVE](https://stackoverflow.com/help/minimal-reproducible-example). – Nelfeal May 11 '20 at 11:41
  • @Nelfeal I get why the compiler might need to know which template to create, but when it has a defined set of options it should be able to compile without problems, as demonstrated by my example, right? – lenerdv May 11 '20 at 12:56
  • @leonardovegetti: All branches of `switch` should be valid, even if not taken. – Jarod42 May 11 '20 at 13:08
  • @Jarod42 which one isn't? – lenerdv May 11 '20 at 13:10
  • In `component(transform, id, x, y)` call, you have invalid `phy(id, x, y)`. – Jarod42 May 11 '20 at 13:16
  • I'm not sure what you mean by "which template to create". The only template here is the constructor of `component`. My point is that you are trying to call the constructor of `tra` (or `phy`) after its initialization (which happens at the very beginning of the initialization of `component`). Here is what it looks like with a variant: [https://godbolt.org/z/gKsge7](https://godbolt.org/z/gKsge7) – Nelfeal May 11 '20 at 13:16
  • @Nelfeal the problem is, I'm trying to please the cache by storing, for example, a lot of `transformComponent` contiguously. `std::variant` does the opposite, having an instance of every type it is given, which makes matters even worse than dynamic dispatch would, completely blowing the cache. – lenerdv May 11 '20 at 14:27
  • A variant only takes as much space as its largest "alternative" type, plus an indicator of which one is current, just like a "tagged" union. It is meant to replace unions, only adding type-safety. That said, I'm not sure I understand why you would use a union in the first place. Don't you just want an array of `transformComponent`, and then an array of `physicsComponent`, and so on? – Nelfeal May 11 '20 at 14:46
  • @Nelfeal https://godbolt.org/z/turLva – lenerdv May 11 '20 at 15:27
  • @Nelfeal also, I do want an array (mempool) of each type, but I also want to pack them in a vector, so templates aren't a viable choice – lenerdv May 11 '20 at 15:29
  • @Nelfeal sorry, I misinterpreted my tests. `std::variant` is actually the way to go – lenerdv May 11 '20 at 15:41
  • @Nelfeal i'll be happy to accept it as an answer – lenerdv May 11 '20 at 15:42
  • Do you mean that you want a single big array (or vector) with n `transformComponent`, then m `physicsComponent`, and so on? If so, you could do better than an array of variant by just knowing where each type is (from the values of n, m, etc.) and re-interpreting these elements accordingly. It will take some writing code though. – Nelfeal May 11 '20 at 15:49
  • @Nelfeal the thing is, I want a vector of pools, otherwise, when I get to, say, 50 component types, I'll have to maintain 50 different individual pools, making it unclean at best – lenerdv May 12 '20 at 07:42
  • Then how about a tuple of vectors of different types? Or a vector of `std::any`s, each containing a vector of a different type? – Nelfeal May 12 '20 at 11:30

0 Answers0