Моя функция GetComponent <> () в Entity-Component-System моего игрового движка возвращает ошибку компилятора C2440 - PullRequest
2 голосов
/ 05 августа 2020

Этот вопрос касается ошибки компилятора Visual Studio 2019 в приложении C ++, работающем в ОС Windows 10. Вероятно, это вопрос среднего или более высокого уровня.

Резюме

У меня есть эта функция:

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;
}

Эта функция приводит к ошибке компилятора C2440:

'return': невозможно преобразовать из '_Ty' в 'T *' с [_Ty = Component *] и [T = Name]

К этой ошибке также прикреплены эти два сообщения к нему:

сообщение: преобразование из базы в производное требует dynamic_cast или static_cast

сообщение: см. ссылку на создание экземпляра шаблона функции 'T * Scene :: GetComponent (EntityID)' в процессе компиляции with [T = Name]

Эта ошибка появляется 3 раза, каждый с T, равным Name, Transform и Sprite (каждый из компонентов, которые пока что были в моем игровом движке)

Compiler Ошибка C2440 в MSDN: https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/compiler-error-c2440?view=vs-2019

Описание функций

ECS Design

My Entity Component System хранит сущности и компоненты через два массива массивов.

Первый - componentIndexes, который используется для индексации по объекту i. n ко второму массиву components, который будет возвращен на основе индекса, компонента. По сути, если вы слышали о сжатых массивах, использующих «разреженный» массив и «плотный» массив, componentIndexes - это мой разреженный массив, а components - мой плотный массив для хранения компонентов в моей сцене.

Вкратце:

componentIndexes сохраняет индекс для компонентов типа CompIndex, при условии: [ComponentType] [entityID] | См. График c 1A

components хранит данные компонента типа Component *, при условии: [ComponentType] [index] | См. График c 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

Построчно

CompTypeID typeID = TypeToID<T>();

Эта функция работает должным образом и возвращает перечисление CompTypeID, которое служит числовым представлением типов компонентов. Это можно использовать для индексации в мои массивы componentIndexes и components в зависимости от типа компонента, на который я нацелен.

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

Эта функция работает должным образом и возвращает логическое значение независимо от того, является ли объект имеет определенный c компонент. Он вернет false, если предоставленный тип T не является компонентом или если объект не имеет этого компонента. Else return nullptr - это самоочевидное состояние отказа этой функции.

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

Эти функции получают и возвращают компонент типа Component*. Как это происходит, объясняется в ECS Design . каждый элемент в components должен быть сохраненным компонентом - это структуры, унаследованные от пустой структуры Component (например, struct Name : Component {};). Поскольку они унаследованы от родительского класса Component, я должен иметь возможность хранить каждый дочерний «компонент» в components, потому что он хранит типы Component*.

В чем, на мой взгляд, проблема

Тип T для GetComponent<T>(EntityID) будет равен Name, Transform или Sprite. Тип хранения components - Component*. Хотя Name * s, Transform* s, & Sprite* s могут быть сохранены в Component*, похоже, что компилятор не позволит мне присвоить данные типа Component* данным типа Name* (или другие компоненты) - поэтому, хотя теоретически я должен иметь возможность получить данные Name из моего массива Component* s, у меня нет возможности сделать это из-за того, что я не могу отправить его из функции GetComponent как Name*.

Я боюсь, что эта проблема может быть вызвана фундаментальным непониманием того, как работает хранение детей в массиве родителей - может быть, это сохраняет только родительскую часть ребенка?

Только я Можете представить, как это можно исправить одним из двух способов.

  1. Может быть, у меня есть способ получить информацию о дочернем элементе, учитывая местоположение указателя родителя - возможно, путем смещения указателя в памяти в зависимости от размера указателя родительского и дочернего, если они смежны друг с другом? Я не думаю, что C ++ предоставляет ссылки на память в этом качестве, возможно, такой подход был бы возможен только с использованием Assembly из-за того, что информация указателя скрыта за типами (например, int* или Component*).
  2. Другой способ может заключаться в использовании c умных указателей или (как сказано в сообщении, прикрепленном к ошибке) явного dynamic_cast-ing или static_cast-ing; но я понятия не имею, как работает любая из этих трех вещей, что они делают, как их использовать или когда их следует использовать.

Это мои лучшие предположения - они действительно выстрелы в тьма от меня. Если у вас есть идея получше, поделитесь ею, и я постараюсь разобраться в ней. Это ошибка компилятора, о которой я нашел мало документации или о которой говорил в своих поисках в Google, поэтому я решил, что было бы целесообразно внести свой вклад в это.

Заявление об ограничении ответственности

Иногда я обнаруживаю, что пропустил некоторую c основную информацию, которую большинство людей усваивает рано из-за структурированного учебного плана. Я предполагаю, что именно здесь я буду изучать интеллектуальные указатели и динамические c указатели.

Ресурсы

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

Сцена. 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

Имя.h

#ifndef NAME_H
#define NAME_H
#pragma once

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

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

#endif // NAME_H

Компонент.h

#ifndef COMPONENT_H
#define COMPONENT_H
#pragma once

struct Component {
};

#endif // COMPONENT_H

1 Ответ

5 голосов
/ 05 августа 2020
• 1000 *
return static_cast<T*>(components[typeID][index]);

Насколько я могу судить, это просто обычный случай наличия указателя на базовый класс, который, как вы знаете, на самом деле указывает на производный класс. static_cast - решение этой проблемы.

Впечатляющее кодирование для самоучки, кстати,

...