Search code examples
c++compiler-errorscomponentsgame-engineentity-component-system

My GetComponent<>() function in my game engine's Entity-Component-System returns Compiler Error C2440


This question is regarding a compiler error from Visual Studio 2019 in a C++ application operating within a Windows 10 OS. It is probably an intermediate-level or higher question.

Summary

I have this function:

template<typename T> T* Scene::GetComponent(EntityID entityID) {
    CompTypeID typeID = TypeToID<T>();
    if (HasComponent<T>(entityID)) {
        CompIndex index = componentIndexes[typeID][entityID];
        return components[typeID][index];
    }
    else return nullptr;
}

This function results in compiler error C2440:

'return': cannot convert from '_Ty' to 'T*' with [_Ty=Component*] and [T=Name]

This error also has these two messages attached to it:

message : Cast from base to derived requires dynamic_cast or static_cast

message : see reference to function template instantiation 'T *Scene::GetComponent(EntityID)' being compiled with [T=Name]

This error appears 3 times, each with T equaling Name, Transform, & Sprite (each of the components so far in my game engine)

Compiler Error C2440 on MSDN: https://learn.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/compiler-error-c2440?view=vs-2019

Function Explanation

ECS Design

My Entity Component System stores entities and components via two arrays of arrays.

The first is componentIndexes, which is used to index by entity into the second array, components which will return based upon index, a component. Essentially, if you've heard of condensing arrays using a "sparse" array and a 'dense' array, componentIndexes is my sparse array and components is my dense array to store components in my scene.

In short:

componentIndexes stores an index to components of type CompIndex, provided: [ComponentType][entityID] | See Graphic 1A

components stores component data of type Component*, provided: [ComponentType][index] | See Graphic 1B

// An array of arrays of indexes into the component arrays, acquired via Entities and Component Types
// Y - std::array of sets with each element position being representative of a Component Type ID
// & each element value being X
// X - std::vector of indexes with each element position being representative of an Entity ID
// & each element value being representative of the position of the component in the component containter(components)
/****EXAMPLE:****
  _E0_E1_E2_E3_   E = Entity ID | C = Component ID | i = Index
C0|i0|i1|~~|i2|     **A ~~ IS REPRESENTATIVE OF A NULL/NAN VALUE (of actual value equal to -1)**
  |--+--+--+--+-    Entity 0 has only Component 0 at index 0
C1|~~|~~|i0|i1|     Entity 1 has Components 0(indx1) and 2(indx0)
  |--+--+--+--+-    Entity 2 has only Component 1(indx0)
C2|~~|i0|~~|i1|     Entity 3 has Components 0(indx2), 1(indx1), and 2(indx1)
****************/
std::array<std::vector<CompIndex>, TOTAL_COMPONENT_TYPES> componentIndexes;

Graphic 1A | componentIndexes

// An array of vectors of Component*s, holding each components' data
// Y - std::array of vectors with each element position being representative of a Component Type
// & each element value being X
// X - std::vector of Component*s with each element position being indexed into via Entity IDs
// & each element value holding the data concerning a certain component
/****EXAMPLE:****
  _i0_i1_i2_i3_   CD = Component Data | C = Component ID | i = Index
C0|CD|CD|CD|~~|     Component 0 has 3 components
  |--+--+--+--+-
C1|CD|CD|~~|~~|     Component 1 has 2 components
  |--+--+--+--+-
C2|CD|CD|~~|~~|     Component 2 has 2 components
****************/
std::array<std::vector<Component*>, TOTAL_COMPONENT_TYPES> components;

Graphic 1B | components

Line-by-Line

CompTypeID typeID = TypeToID<T>();

This function works as expected, and returns an enum CompTypeID that serves as a numerical representation of component types. This can be used to index into my componentIndexes and components arrays, based upon the component type I'm targeting.

if (HasComponent<T>(entityID)) {
    . . .
}
else return nullptr;

This function works as expected, and returns a bool whether or not an entity has a specific component. It will return false if the provided type T is not a component or if the entity does not have that component. Else return nullptr is the self-explanatory fail-state of this function.

CompIndex index = componentIndexes[typeID][entityID];
return components[typeID][index];

These functions get and return the component, of type Component*. How this does this is explained in ECS Design. each element in components should be stored components - these are structs and are inherited from an empty struct Component (ex. struct Name : Component {};). Because they're inherited from the parent class Component, I should be able to store each child "component" in components because it stores types of Component*.

What I Think The Problem Is

The type T of GetComponent<T>(EntityID) is going to be equal to Name, Transform, or Sprite. The storage type of components is a Component*. While Name*s, Transform*s, & Sprite*s can all be stored in Component*, it appears that the compiler won't let me assign data of type Component* to data of type Name* (or other components) - so while theoretically I should be able to fetch the Name data from my array of Component*s, I have no way to do so due to that I can't send it away from the GetComponent function as a Name*.

I fear that this issue may be caused by a fundamental misunderstanding of how storing children in an array of parents works - maybe this only stores the parent part of the child?

I only can imagine this being fixed one of two ways.

  1. There may be a way for me to get the child's information, given the parent's pointer's location - maybe by offsetting the pointer in memory based upon the size of the parent and child pointer if they're contiguous with one another? I don't think C++ exposes memory references in that capacity, maybe that kind-of approach would only be possible using Assembly due to pointer information being hidden behind typenames (like int* or Component*).
  2. The other way could be some magic using smart pointers or (as the message attached to the error said) explicit dynamic_cast-ing or static_cast-ing; but I have no clue how any of these 3 things work, what they do, how to use them, or when they should be used.

Those are my best guesses - they're really shots in the dark from me. If you have a better idea, please share it and I'll do my best to look into it. This is a compiler error that I found little documentation or talk about in my Google searches, so I figured it would be appropriate to contribute with this.

Disclaimer

Sometimes I find that I've missed some basic information that most people pick up early due to structured class curriculum. I'm guessing that this is where I will be learning about smart pointers and dynamic pointers.

Resources

Scene.h

#ifndef SCENE_H
#define SCENE_H
#pragma once

#include "Components.h"
#include "Systems.h"
#include "Entity.h"
#include <array>
#include <vector>
#include <set>
#include <queue>

class Scene {
private:
    // An array of arrays of indexes into the component arrays, acquired via Entities and Component Types
    // Y - std::array of sets with each element position being representative of a Component Type ID
    // & each element value being X
    // X - std::vector of indexes with each element position being representative of an Entity ID
    // & each element value being representative of the position of the component in the component container(components)
    /****EXAMPLE:****
      _E0_E1_E2_E3_   E = Entity ID | C = Component ID | i = Index
    C0|i0|i1|~~|i2|     **A ~~ IS REPRESENTATIVE OF A NULL/NAN VALUE (of actual value equal to -1)**
      |--+--+--+--+-    Entity 0 has only Component 0 at index 0
    C1|~~|~~|i0|i1|     Entity 1 has Components 0(indx1) and 2(indx0)
      |--+--+--+--+-    Entity 2 has only Component 1(indx0)
    C2|~~|i0|~~|i1|     Entity 3 has Components 0(indx2), 1(indx1), and 2(indx1)
    ****************/
    std::array<std::vector<CompIndex>, TOTAL_COMPONENT_TYPES> componentIndexes;

    // An array of vectors of Component*s, holding each components' data, acquired via Index & Component Type
    // Y - std::array of vectors with each element position being representative of a Component Type
    // & each element value being X
    // X - std::vector of Component*s with each element position being indexed into via Entity IDs
    // & each element value holding the data concerning a certain component
    /****EXAMPLE:****
      _i0_i1_i2_i3_   CD = Component Data | C = Component ID | i = Index
    C0|CD|CD|CD|~~|     Component 0 has 3 components
      |--+--+--+--+-
    C1|CD|CD|~~|~~|     Component 1 has 2 components
      |--+--+--+--+-
    C2|CD|CD|~~|~~|     Component 2 has 2 components
    ****************/
    std::array<std::vector<Component*>, TOTAL_COMPONENT_TYPES> components;

    std::queue<EntityID> oldEntityIDs;
    std::array<std::queue<CompIndex>, TOTAL_COMPONENT_TYPES> oldCompIndexes;
    EntityID GenerateNewEntityID();
    CompIndex GenerateNewCompIndex(CompTypeID compType);
public:
    Scene();

    void Update();

    EntityID NewEntity();
    EntityID GetEntityID(std::string entityName);
    Signature GetEntityComponentSignature(EntityID entityID);
    std::vector<EntityID> GetEntitiesWithComponents(Signature components);
    void DeleteEntity(EntityID entityID);

    // template<typename T> T* AddComponent(EntityID entityID); // Same issue as GetComponent
    template<typename T> T* GetComponent(EntityID entityID);
    template<typename T> bool HasComponent(EntityID entityID);
    bool HasComponents(Signature signComponents, EntityID entityID);
    template<typename T> void DeleteComponent(EntityID entityID);
    void DeleteComponents(Signature signComponents, EntityID entityID);
};

#endif // SCENE_H

Scene.cpp

#include "Scene.h"

Scene::Scene() {

}

void Scene::Update() {
    // RENDER SYSTEM
    for (EntityID entityID : GetEntitiesWithComponents(RenderSystem::required)) {
        RenderSystem::Update(GetComponent<Transform>(entityID), GetComponent<Sprite>(entityID));
    }
}

EntityID Scene::GenerateNewEntityID() {
    EntityID newID;
    if (oldEntityIDs.empty()) {
        newID = (EntityID)componentIndexes[0].size();
    }
    else {
        newID = oldEntityIDs.front();
        oldEntityIDs.pop();
    }
    return newID;
}

CompIndex Scene::GenerateNewCompIndex(CompTypeID typeID) {
    CompIndex newIndex;
    if (oldCompIndexes[typeID].empty()) {
        newIndex = components[typeID].size();
    }
    else {
        newIndex = oldCompIndexes[typeID].front();
        oldCompIndexes[typeID].pop();
    }
    return newIndex;
}

EntityID Scene::NewEntity() {
    // generate entity ID
    EntityID entityID = GenerateNewEntityID();
    // populate entity index
    for (std::vector<CompIndex> componentSparse : componentIndexes) {
        componentSparse[entityID] = NO_COMPONENT;
    }
    // add default entity components
    //AddComponent<Name>(entityID);
    //AddComponent<Transform>(entityID);
    return entityID;
}

EntityID Scene::GetEntityID(std::string entityName) {
    for (EntityID entityID : GetEntitiesWithComponents(IDName)) {
        if (GetComponent<Name>(entityID)->name == entityName) {
            return entityID;
        }
    }
    return 0; // TODO: resolve "NO_ENTITY" case
}

Signature Scene::GetEntityComponentSignature(EntityID entityID) {
    Signature signature;
    // for each component type
    for (CompTypeID i = (CompTypeID)0; i < TOTAL_COMPONENT_TYPES; i = (CompTypeID)(i + (CompTypeID)1)) {
        // if component type is on entity
        if (componentIndexes[i][entityID] != NO_COMPONENT) {
            // mark that bit of the signature to true
            signature.set(i);
        }
    }
    return signature;
}

std::vector<EntityID> Scene::GetEntitiesWithComponents(Signature components) {
    std::vector<EntityID> entities;
    // TODO: write GetEntittiesWithComponents
    return entities;
}

void Scene::DeleteEntity(EntityID entityID) {
    // delete entity Components
    DeleteComponents(GetEntityComponentSignature(entityID), entityID);
    // delete entity indexes
    for (std::vector<CompIndex> componentSparse : componentIndexes) {
        componentSparse.erase(componentSparse.begin() + entityID);
    }
    // add ID to list of avaliable IDs
    oldEntityIDs.push(entityID);
    return;
}

// Same issue as GetComponent()
/*template<typename T> T* Scene::AddComponent(EntityID entityID) {
    CompTypeID typeID = TypeToID<T>();
    if (typeID == IDNotAComponent || componentsSparse[typeID][entityID] != NO_COMPONENT) {
        return nullptr;
    }
    else {
        CompIndex index = GenerateNewCompIndex(typeID);
        componentsSparse[typeID][entityID] = index;
        components[typeID][index] = new T; // TODO Ensure, does this work?? (T if not pointer could have issue?)
        return &components[typeID][index];
    }
}*/

template<typename T> T* Scene::GetComponent(EntityID entityID) {
    CompTypeID typeID = TypeToID<T>();
    if (HasComponent<T>(entityID)) {
        CompIndex index = componentIndexes[typeID][entityID];
        return components[typeID][index];
    }
    else return nullptr;
}

template<typename T> bool Scene::HasComponent(EntityID entityID) {
    CompTypeID typeID = TypeToID<T>();
    if (typeID == IDNotAComponent) {
        return false;
    }
    else if (componentIndexes[typeID][entityID] == NO_COMPONENT) {
        return false;
    }
    else return true;
}

bool Scene::HasComponents(Signature signComponents, EntityID entityID) {
    // for each component type
    for (CompTypeID i = (CompTypeID)0; i < TOTAL_COMPONENT_TYPES; i = (CompTypeID)(i + (CompTypeID)1)) {
        // if component type is in component signature
        if (signComponents.test(i)) {
            // if component type is not on entity
            if (componentIndexes[i][entityID] == NO_COMPONENT) {
                return false;
            }
        }
    }
    return true;
}

template<typename T> void Scene::DeleteComponent(EntityID entityID) {
    CompTypeID typeID = TypeToID(T);
    if (typeID == nullptr || componentIndexes[typeID][entityID] != NO_COMPONENT) {
        return;
    }
    else {
        CompIndex index = componentIndexes[typeID][entityID];
        // delete component
        delete components[typeID][index];
        components[typeID][index] = nullptr;
        // delete component index
        componentIndexes[typeID][entityID] = NO_COMPONENT;
        oldCompIndexes[typeID].push(index);
        return;
    }
}

void Scene::DeleteComponents(Signature signComponents, EntityID entityID) {
    // for each component type
    for (CompTypeID i = (CompTypeID)0; i < TOTAL_COMPONENT_TYPES; i = (CompTypeID)(i + (CompTypeID)1)) {
        // if component type is in component signature
        if (signComponents.test(i)) {
            // if component type is on entity
            if (componentIndexes[i][entityID] != NO_COMPONENT) {
                CompIndex index = componentIndexes[i][entityID];
                // delete component
                delete components[i][index];
                components[i][index] = nullptr;
                // delete component index
                componentIndexes[i][entityID] = NO_COMPONENT;
                oldCompIndexes[i].push(index);
            }
        }
    }
    return;
}

Entity.h

#ifndef ENTITY_H
#define ENTITY_H
#pragma once

#include <limits>
#include <cstdint>

typedef std::uint16_t EntityID;

// Maximum number of entities allowed in a scene
const EntityID MAX_ENTITIES = std::numeric_limits<EntityID>::max(); // 65535

#endif // ENTITY_H

Components.h

#ifndef COMPONENTS_H
#define COMPONENTS_H
#pragma once

#include <bitset>

#include "Name.h"
#include "Transform.h"
#include "Sprite.h"

enum CompTypeID {
    IDName,
    IDTransform,
    IDSprite,
    TOTAL_COMPONENT_TYPES,
    IDNotAComponent
};

template<typename T> CompTypeID TypeToID() {
    if (std::is_same<T, Name>()) {
        return IDName;
    }
    else if (std::is_same<T, Transform>()) {
        return IDTransform;
    }
    else if (std::is_same<T, Sprite>()) {
        return IDSprite;
    }
    else return IDNotAComponent;
}

// Component Signature
typedef std::bitset<TOTAL_COMPONENT_TYPES> Signature;

// Component Index
typedef int CompIndex;

const CompIndex NO_COMPONENT = -1;

#endif // COMPONENTS_H

Name.h

#ifndef NAME_H
#define NAME_H
#pragma once

#include "Component.h"
#include <string>

struct Name : Component {
public:
    std::string name;
};

#endif // NAME_H

Component.h

#ifndef COMPONENT_H
#define COMPONENT_H
#pragma once

struct Component {
};

#endif // COMPONENT_H

Solution

  • I'm possibly missing something as your post is rather long and I didn't read all of it, but can't you do what the error message says and replace

    return components[typeID][index];
    

    with

    return static_cast<T*>(components[typeID][index]);
    

    As far as I could tell this is just the common case of having a pointer to a base class which you know is actually pointing to a derived class. static_cast is the solution to that problem.

    Impressive coding for someone who is self taught BTW.