Группировка объектов одного и того же набора компонентов в линейную память

Мы начинаем с базовых системных компонентов - сущности подходят .

Давайте создадим сборки (термин, полученный из эта статья ) просто из информации о типах компонентов . Это выполняется динамически во время выполнения, так же, как мы добавляем /удаляем компоненты в объект один за другим, но давайте просто назовите его более точно, поскольку речь идет только о типе информации.

Затем мы создаем сущности , определяя сборку для каждого из них. Как только мы создаем объект, его сборка является неизменной, что означает, что мы не можем напрямую модифицировать ее на месте, но все же мы можем получить подпись существующего объекта в локальной копии (вместе с содержимым), внести в нее правильные изменения и создать новую сущность его.

Теперь для ключевой концепции: всякий раз, когда создается сущность, ей присваивается объект с именем сборка bucket , что означает, что все объекты одной и той же подписи будут в том же контейнере (например, в std :: vector).

Теперь системы просто перебирают каждое ведро своего интереса и выполняют свою работу.

Этот подход имеет некоторые преимущества:

    Компоненты
  • хранятся в нескольких (точно: количество ковшей) смежных фрагментов памяти - это улучшает удобство использования памяти, и легче сбрасывать состояние всей игры.
  • система обрабатывает компоненты линейным образом, что означает улучшенную когерентность кэша, до свидания и скачки случайной памяти.
  • создание новой сущности так же просто, как сопоставление сборки с ковшом и отбрасывание необходимых компонентов в вектор
  • удаление объекта так же просто, как один вызов std :: move для замены последнего элемента с удаленным, потому что порядок не имеет значения в данный момент

введите описание изображения здесь

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

Также существует проблема с недействительностью указателя после перераспределения векторов - это можно решить, введя такую ​​структуру, как:

struct assemblage_bucket {
    struct entity_watcher {
        assemblage_bucket* owner;
        entity_id real_index_in_vector;
    };

    std::unordered_map<entity_id, std::vector<entity_watcher*>> subscribers;

    //...
};

Поэтому, когда по какой-то причине в нашей логике игры мы хотим отслеживать вновь созданный объект, внутри ведра мы регистрируем entity_watcher , и как только объект должен быть std :: move ' d во время удаления, мы просматриваем его наблюдателей и обновляем их real_index_in_vector до новых значений. В большинстве случаев это накладывает только один поиск в словаре для каждого удаления сущности.

Есть ли еще недостатки этого подхода?

Почему решение нигде не упоминается, несмотря на то, что оно довольно очевидно?

EDIT : я редактирую вопрос, чтобы «отвечать на ответы», поскольку комментариев недостаточно.

  

вы теряете динамический характер подключаемых компонентов, который был создан   чтобы уйти от статического класса.

Я этого не делаю. Возможно, я не объяснил это достаточно ясно:

auto signature = world.get_signature(entity_id); // this would just return entity_id.bucket_owner->bucket_signature or so
signature.add(foo_component);
signature.remove(bar_component);
world.delete_entity(entity_id); // entity_id would hold information about its bucket owner
world.create_entity(signature); // automatically assigns new entity to an existing or a new bucket

Это так же просто, как просто подписание существующей сущности, ее изменение и повторная загрузка в качестве нового объекта. Вставная динамическая природа ? Конечно. Здесь я хотел бы подчеркнуть, что существует только один «сборка» и один класс «ведро». Ведра управляются данными и создаются во время выполнения в оптимальном количестве.

  

вам нужно будет пройти через все ведра, которые могут содержать действительные   цель. Без внешней структуры данных обнаружение столкновений могло бы   быть столь же трудным.

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

10 голосов | спросил Patryk Czachurski 6 J000000Saturday13 2013, 21:01:48

3 ответа


6

В основном вы создали статическую объектную систему с распределителем пула и динамическими классами.

Я написал объектную систему, которая почти идентична работе вашей системы «сборки» в мои школьные дни, хотя я всегда склоняюсь к «сборкам» либо «чертежей», либо «архетипов» в своих собственных проектах. Архитектура была скорее болью в прикладах, чем с наивными объектными системами, и не имела никаких измеримых преимуществ по сравнению с некоторыми из более гибких конструкций, которые я сравнивал с ними. Возможность динамически изменять объект без необходимости его реанимировать или перераспределять его чрезвычайно важна, когда вы работаете над редактором игры. Дизайнеры захотят перетащить компоненты n-drop в определения объектов. Код исполнения может даже потребоваться для эффективного изменения компонентов в некоторых проектах, хотя лично мне это не нравится. В зависимости от того, как вы связываете ссылки на объекты в своем редакторе, вам просто пригодится возможность добавления и удаления компонентов с существующими объектами с нулевыми дополнительными махинациями.

У вас будет хуже когерентность кэша, чем вы думаете в большинстве нетривиальных случаев. Например, ваша система AI не заботится о компонентах Render, но заканчивается тем, что они застревают над ними как часть каждого объекта. Итерируемые объекты больше, а запросы кешлин заканчиваются тем, что вытаскивают ненужные данные, и с каждым запросом возвращается меньше всего объектов). Он по-прежнему будет лучше, чем наивный метод, а композиция объектов наивного метода используется даже в больших механизмах AAA, поэтому вы, вероятно, не будете нуждаться лучше, но, по крайней мере, не думайте, что вы не может улучшить его далее.

Ваш подход действительно имеет смысл для некоторых компонентов, но не для всех. Мне очень не нравится ECS, потому что он всегда защищает каждый компонент в отдельном контейнере, что имеет смысл для физики или графики или чего-то еще, но не имеет никакого смысла, если вы разрешаете несколько компонентов сценария или составной AI. Если вы позволяете системе компонентов использоваться не только как встроенные объекты, но и как разработчики и разработчики игрового процесса для компоновки поведения объектов, имеет смысл объединить все компоненты AI (которые часто будут взаимодействовать) или весь скрипт компонентов (поскольку вы хотите обновить их все в одной партии). Если вам нужна самая эффективная система, тогда вам понадобится сочетание схем распределения и хранения компонентов и поместите время, чтобы выяснить окончательно, что лучше для каждого конкретного типа компонента.

ответил Sean Middleditch 7 J000000Sunday13 2013, 00:27:15
4

Что вы сделали, это перепроектированные объекты C ++. Причина, по которой это кажется очевидным, заключается в том, что если вы замените слово «сущность» на «класс» и «компонент» на «член», это стандартный проект ООП с использованием mixins.

1) вы теряете динамический характер подключаемых компонентов, который был создан специально, чтобы уйти от статической конструкции класса.

2) Когерентность памяти наиболее важна в типе данных, а не в объекте, объединяющем несколько типов данных в одном месте. Это одна из причин создания компонентов + системы, чтобы избежать фрагментации памяти класса +.

3) эта конструкция также возвращается к стилю класса C ++, потому что вы думаете о сущности как о когерентном объекте, когда в компоненте + системный дизайн объект является просто тегом /идентификатором, чтобы сделать внутреннюю работу понятной для людей.

4), так же просто, чтобы компонент сериализовал себя, чем сложный объект, чтобы сериализовать несколько компонентов внутри себя, если не на самом деле легче отслеживать как программиста.

5) следующий логический шаг вниз по этому пути - удалить системы и поместить этот код непосредственно в объект, где он имеет все данные, необходимые для работы. Мы все можем видеть, что это означает =)

ответил Patrick Hughes 6 J000000Saturday13 2013, 23:25:36
3

Сохранение как сущности вместе не так важно, как вы могли бы подумать, поэтому трудно думать о действительной причине, отличной от «потому что это единица». Но поскольку вы действительно делаете это для согласованности кеша, а не с логической связностью, это может иметь смысл.

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

Чтобы продолжить объединение объектов для логической согласованности, единственной причиной, по которой я мог бы объединить сущности, является идентификация в моих миссиях. Мне нужно знать, если вы только что создали тип сущности A или тип B, и я обойду это ... вы уже догадались: добавление нового компонента, который идентифицирует сборку, которая объединяет эту сущность. Даже тогда я не собираю все компоненты вместе для большой задачи, мне просто нужно знать, что это такое. Поэтому я не думаю, что эта часть ужасно полезна.

ответил John McDonald 6 J000000Saturday13 2013, 23:04:10

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132