Насколько хорошо эта реализация ECS следует этим принципам - PullRequest
0 голосов
/ 09 ноября 2019

В настоящее время я изучаю архитектуру Entity-Component-System. Проектирование на основе данных как способ противодействия неправильному проектированию ООП и оптимизации извлечения данных как операции, дружественной к процессору.

Я пытаюсь написатьпростая система ECS, которая пытается следовать этим принципам:

  • ЦП дружествен к процессам за счет хранения сущностей / компонентов в непрерывном порции памяти и минимизации пропадания кэша
  • Не устанавливать верхнюю границу для числа сущностей/ компоненты / системы в системе

Моя базовая реализация на данный момент выглядит следующим образом:

Entity класс с подсчетом m_ID члена, увеличиваемого для каждого вновь создаваемого объекта. std::vector<Entity*> для содержания дочерних объектов. std::vector<bool> для хранения подписи компонентов, которые использует объект. Я выбрал std::vector<bool> вместо std::bitset<S>, поскольку хотел бы иметь возможность добавлять / удалять компоненты во время выполнения и не хочу устанавливать верхнюю границу для размера компонентов, которые есть у сущности, поэтому я не могу знатьво время компиляции, поэтому я решил использовать специализацию std::vector<bool>, в которой каждый bool хранится как 1 бит, что экономит место и позволяет расти. std::unordered_map<unsigned int, unsigned int>, где ключ - это идентификатор компонента, а значение - это индекс в std::vector<unsigned int, std::vector<ComponentBase>> сопоставлении идентификатора компонента с вектором компонента, находящимся в классе ECSManager.

Component класс с уникальным идентификаторомдля каждого типа компонента с использованием шаблонов для генерации уникального Component<T>::ID для каждого типа компонента.

System класс, содержащий std::vector<bool>, отвечающий за хранение подписи тех объектов, над которыми система хочет работать, зависит от того, имеют ли ониподпись достаточного количества компонентов.

Теперь я уверен, что моя реализация не совсем соответствует моим требованиям, например, я не совсем уверен, что она дружественна к ЦП, как должна быть ECS, поскольку я сильно полагаюсь наstd::unordered_map, который реализован в виде связанного списка. Но, если честно, я совсем не уверен, поэтому я публикую здесь свою реализацию в надежде получить отзыв о том, что хорошо, а что нет, и как я могу улучшить его.

ComponentBase.h:

#pragma once

#include <atomic>

static std::atomic<unsigned int> s_CurrentId;

class ComponentBase
{
public:
    inline static unsigned GetID() { return ++s_CurrentId; }
};

Component.h:

#pragma once

#include "ComponentBase.h"

template <typename T>
class Component : public ComponentBase
{
public:
    static const unsigned int ID = ComponentBase::GetID();
};

Entity.h:

#pragma once

#include <vector>
#include <unordered_map>

class Entity
{
private:
    unsigned int m_Id;
    std::vector<Entity*> m_Children;
    std::vector<bool> m_Signature;
    std::unordered_map<unsigned int, unsigned int> m_ComponentsIndex;
protected:
    Entity();
    friend class ComponentManager;
    friend class Scene;
public:
    Entity(const Entity& other) = delete;
    virtual ~Entity();

    Entity& operator=(const Entity& other) = delete;

    inline unsigned int GetID() { return m_Id; }
    void AddChild(Entity* entity);
    inline const std::vector<Entity*>& GetChildren() const { return m_Children; }
    void AddComponent(unsigned int componentID, unsigned int index);
    void RemoveComponent(unsigned int componentID);
    inline const std::vector<bool>& GetSignature() const { return m_Signature; }
    inline const std::unordered_map<unsigned int, unsigned int>& GetComponentsIndex() const { return m_ComponentsIndex; }
};

Entity.cpp:

#include "Entity.h"

#include <atomic>

static std::atomic<unsigned int> s_CurrentId;

Entity::Entity()
    : m_Id(s_CurrentId++)
{
}

Entity::~Entity()
{
    for (const auto& child : m_Children)
        delete child;
}

void Entity::AddChild(Entity* entity)
{
    m_Children.push_back(entity);
}

void Entity::AddComponent(unsigned int componentID, unsigned int index)
{
    m_Signature[componentID] = true;
    m_ComponentsIndex[componentID] = index;
}

void Entity::RemoveComponent(unsigned int componentID)
{
    m_Signature[componentID] = false;
    m_ComponentsIndex.erase(componentID);
}

System.h:

#pragma once

#include <vector>
#include "Entity.h"

class System
{
private:
    std::vector<bool> m_ComponentIDs;
protected:
    System(std::vector<unsigned int>& componentIDs);
public:
    virtual ~System();

    virtual void Update() = 0;
};

System.cpp:

#include "System.h"

System::System(std::vector<unsigned int>& componentIDs)
{
    for (const auto& componentID : componentIDs)
        m_ComponentIDs[componentID] = true;
}

System::~System()
{
}

ECSManager.h:

#pragma once

#include <vector>
#include <unordered_map>
#include "../Entity.h"
#include "ComponentBase.h"

class ECSManager
{
private:
    std::unordered_map<unsigned int, std::vector<ComponentBase>> m_Components;
    std::unordered_map<unsigned int, Entity> m_Entities;
private:
    ECSManager();
public:

    const Entity& CreateEntity()
    {
        Entity e;
        m_Entities.emplace(e.GetID(), std::move(e));
        return m_Entities[e.GetID()];
    }

    template <typename TComponent, typename... Args>
    void AddComponent(unsigned int entityId, Args&&... args)
    {
        unsigned int componentID = TComponent::ID;
        if (m_Components[componentID] == m_Components.end())
            m_Components[componentID] = std::vector<TComponent>();
        m_Components[componentID].push_back(T(std::forward<Args>(args)...));

        m_Entities[entityId].AddComponent(componentID, m_Components[componentID].size() - 1);

        if (m_Signatures[m_Entities[entityId].GetSignature()] == m_Signatures.end())
            m_Signatures[m_Entities[entityId].GetSignature()] = std::vector<unsigned int>();
        m_Signatures[m_Entities[entityId].GetSignature()].push_back(entityId);
    }

    template <typename TComponent>
    TComponent& GetComponent(Entity& entity)
    {
        return m_Components[TComponent::ID][entity.GetComponentsIndex()[TComponent::ID]];
    }

    template <typename TComponent>
    std::vector<TComponent>& GetComponents()
    {
        unsigned int componentID = TComponent::ID;
        return m_Components[componentID];
    }

    std::vector<Entity&> GetEntities(std::vector<bool> componentIDs)
    {
        std::vector<Entity&> entities;
        for (auto& entity : m_Entities)
        {
            const auto& entitySignature = entity.second.GetSignature();
            if (componentIDs.size() > entitySignature.size())
                continue;

            bool equal = true;
            for (std::size_t i = 0; i != componentIDs.size(); i++)
            {
                if (componentIDs[i] & entitySignature[i] == 0)
                {
                    equal = false;
                    break;
                }
            }
            if (!equal)
                continue;

            entities.push_back(entity.second);
        }
        return entities;
    }
};
...