Создать эффективный пул компонентов для системных циклов

Я устанавливаю очень маленький пример Entity-Component-System и имею некоторые проблемы с пулами компонентов.

В настоящее время мои сущности - это просто идентификаторы (GUID), и это нормально.

Каждая система должна реализовывать ISystem интерфейс

internal interface ISystem
{
    void Run();
}

и хранится в KeyedByTypeCollection, Эта коллекция гарантирует, что каждая система уникальна.

Каждый компонент должен реализовать IComponent интерфейс.

internal interface IComponent
{
}

Таким образом я могу сохранить все различные типы компонентов в их пуле соответствующих компонентов. Каждый бассейн Dictionary<Guid, IComponent>, Ключ представляет собой идентификатор объекта, а значение является компонентом этого объекта. Каждый пул хранится в KeyedByTypeCollection чтобы убедиться, что пул компонентов уникален.

В настоящее время мой EntityManager класс содержит ядро ​​логики. Я не знаю, требуется ли, чтобы он был статичным, но в настоящее время мои системы должны получать от него некоторую информацию.

Важными основными методами работы с пулами компонентов являются:

internal static class EntityManager
{
    public static List<Guid> activeEntities = new List<Guid>();

    private static KeyedByTypeCollection<Dictionary<Guid, IComponent>> componentPools = new KeyedByTypeCollection<Dictionary<Guid, IComponent>>();

    public static KeyedByTypeCollection<ISystem> systems = new KeyedByTypeCollection<ISystem>(); // Hold unique Systems

    public static Guid CreateEntity() // Add a new GUID and return it
    {
        Guid entityId = Guid.NewGuid();
        activeEntities.Add(entityId);
        return entityId;
    }

    public static void DestroyEntity(Guid entityId) // Remove the entity from every component pool
    {
        activeEntities.Remove(entityId);

        for (int i = 0; i < componentPools.Count; i++)
        {
            componentPools[i].Remove(entityId);
        }
    }

    public static Dictionary<Guid, TComponent> GetComponentPool<TComponent>() where TComponent : IComponent // get a component pool by its component type
    {
        return componentPools[typeof(TComponent)];
    }

    public static void AddComponentPool<TComponent>() where TComponent : IComponent // add a new pool by its component type
    {
        componentPools.Add(new Dictionary<Guid, TComponent>());
    }

    public static void RemoveComponentPool<TComponent>() where TComponent : IComponent // remove a pool by its component type
    {
        componentPools.Remove(typeof(TComponent));
    }

    public static void AddComponentToEntity(Guid entityId, IComponent component) // add a component to an entity by the component type
    {
        componentPools[component.GetType()].Add(entityId, component);
    }

    public static void RemoveComponentFromEntity<TComponent>(Guid entityId) where TComponent : IComponent // remove a component from an entity by the component type
    {
        componentPools[typeof(TComponent)].Remove(entityId);
    }
}

Я создал небольшую систему движений для целей тестирования:

internal class Movement : ISystem
{
    public void Run()
    {
        for (int i = 0; i < EntityManager.activeEntities.Count; i++)
        {
            Guid entityId = EntityManager.activeEntities[i];

            if (EntityManager.GetComponentPool<Position>().TryGetValue(entityId, out Position positionComponent)) // Get the position component
            {
                positionComponent.X++; // Move one to the right and one up ...
                positionComponent.Y++;
            }
        }
    }
}

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

И вот проблема:

Я не умею пользоваться KeyedByTypeCollection<Dictionary<Guid, IComponent>> потому что мне нужно что-то вроде

KeyedByTypeCollection<Dictionary<Guid, Type where Type : IComponent>>

мой GetComponentPool а также AddComponentPool методы выдают ошибки.

GetComponentPool: невозможно неявное преобразование IComponent в TComponent

При звонке GetComponentPool Я должен был бы привести значения словаря из IComponent в TComponent,

AddComponentPool: невозможно преобразовать TComponent в IComponent

При звонке AddComponentPool Я должен был бы бросить TComponent в IComponent,

Я не думаю, что кастинг это вариант, потому что это, кажется, снижает производительность.

Может ли кто-нибудь помочь мне решить проблему типа?


Обновить:

При отладке я использую этот код для тестирования всего ECS

internal class Program
{
    private static void Main(string[] args)
    {
        EntityManager.AddComponentPool<Position>();
        EntityManager.AddComponentPool<MovementSpeed>();

        EntityManager.Systems.Add(new Movement());

        Guid playerId = EntityManager.CreateEntity();
        EntityManager.AddComponentToEntity(playerId, new Position());
        EntityManager.AddComponentToEntity(playerId, new MovementSpeed(1));

        foreach (ISystem system in EntityManager.Systems)
        {
            system.Run();
        }
    }
}

Я уже показал Movement Система, вот недостающие компоненты

internal class Position : IComponent
{
    public Position(float x = 0, float y = 0)
    {
        X = x;
        Y = y;
    }

    public float X { get; set; }
    public float Y { get; set; }
}

internal class MovementSpeed : IComponent
{
    public MovementSpeed(float value = 0)
    {
        Value = value;
    }

    public float Value { get; set; }
}

1 ответ

Решение

Суть проблемы в том, что Dictionary<Guid, TComponent> а также Dictionary<Guid, IComponent> совершенно не связанные типы. Они не конвертируемы друг в друга. Это потому, что словарь можно читать и писать. Если бы вы могли преобразовать List<Giraffe> к List<Animal> Вы могли бы затем сохранить Tiger что нарушит безопасность типа. Если бы вы могли конвертировать наоборот, вы могли бы прочитать Tiger экземпляры и относиться к ним как Giraffe что снова нарушило бы безопасность типов. Вы можете искать в Интернете для .net covariance contravariance чтобы найти больше информации.


Простое решение состоит в том, чтобы изменить

KeyedByTypeCollection<Dictionary<Guid, IComponent>> componentPools

в

Dictionary<Type, object> componentPools

,

Это позволяет вам хранить Dictionary<Guid, TComponent> в коллекции, которая была невозможна раньше. Недостатком является то, что вам нужно сыграть сейчас. Но вам нужно только привести экземпляр словаря, который довольно дешев. Вам не нужно конвертировать весь словарь в O(N) времени.

Аксессоры:

public static Dictionary<Guid, TComponent> GetComponentPool<TComponent>() where TComponent : IComponent // get a component pool by its component type
{
    return (Dictionary<Guid, TComponent>)componentPools[typeof(TComponent)]; //cast inserted
}

public static void AddComponentPool<TComponent>() where TComponent : IComponent // add a new pool by its component type
{
    componentPools.Add(typeof(TComponent), (object)new Dictionary<Guid, TComponent>()); //unneeded cast inserted for clarity
}

Может быть, вам не нравится, что нам нужно сыграть на всех. Я не вижу возможности обойти это. Практический опыт показывает, что, когда вы начинаете разрабатывать систему типов (обобщения, дисперсии), вы получаете худшее решение. Так что не беспокойся об этом. Делай то, что работает.

Другие вопросы по тегам