Некоторые программисты помнят, когда впервые появилось ООП. Насколько я знаю, все началось с C++ (по крайней мере, это самый известный). «Инновационный» способ просмотра данных. Это было первое, что направляло нас, давало нам направление в том, как структурировать наше программное обеспечение, или, по крайней мере, должно было.

Объектно-ориентированное программирование

Давайте будем честными, все ООП — это просто тяжелый и медленный кусок мусора (я говорю вам о Java), от которого почему-то трудно избавиться. Мы живем в мире, полном объектов и ассоциаций, так что я могу понять, почему мы выбрали этот путь, хотя я считаю очень глупым привносить наши реальные правила в архитектуру программного обеспечения. Не поймите меня неправильно, даже я однажды был поражен «изящностью» ООП (это было примерно через два месяца после того, как я начал учиться программировать). Но если вы на самом деле попытаетесь построить какую-то иерархию объектов, большую и сложную, вы столкнетесь с проблемой, а именно с тем, как на самом деле структурировать этот беспорядок. Случаи создания набора гигантских объектов с сотнями свойств для учета каждого случая и использования не являются чем-то необычным. Иногда вы в конечном итоге наследуете от объекта, но не используете всю его функциональность или, что еще хуже, копируете и вставляете некоторую логику. Когда дело доходит до этого, я всегда следую правилу Newer Repeat Your Self.

Другой вид

Итак, что нам нужно сделать, чтобы преодолеть OOF? На самом деле ничего нового, просто простые союзы/компоненты/структуры, другое мышление и немного сахара метапрограммирования. В то время как ООП решает проблемы во время выполнения, мы лучше подготовимся заранее. Наследование будет заменено композицией, массив объектов объектами массивов, указатели с идентификаторами. Большинство и для большинства отношения будут определяться поведением, а не необходимым расположением данных. Да, это ECS, и мы будем злоупотреблять им в полной мере.

Система компонентов сущности

Давайте сначала сосредоточимся на концепции, а затем перейдем к реализации в следующей части. Итак, что именно делает вашу архитектуру ESC? Честно говоря, единственная полезная вещь, о которой говорит его название, это то, что вы должны использовать компоненты. Что еще сложнее, так это то, как вы их используете. Мы могли бы построить объект из компонентов, верно? Встроить их в более крупную структуру и… НЕТ. Сущность не является объектом, хотя этот термин настолько общий, что может означать что угодно. На нашем диалекте ECS это просто указатель на компоненты или точнее ID. Беззнакового целого достаточно, чтобы создать сущность, а компоненты хранятся в собственном хранилище. Это хранилище должно иметь возможность принимать идентификатор и давать нам указатель на компонент. Мы не сохраняем указатель на потом, а используем его сразу и удаляем как можно скорее, потому что хранилище может перераспределиться и измениться адрес, мы сохраняем только идентификатор. То, как мы храним компоненты, крайне важно для того, насколько хорошо работает наше приложение. Подумайте немного, что может быть лучшим контейнером для нашей цели? Может быть… HashMap? Звучит неплохо, вы можете использовать уникальные идентификаторы для доступа к определенному набору компонентов с оптимальным временем. Ну, это некоторые из задач, которые нам нужно выполнить, но не все.

Проблемы

Нам нужно перебрать компоненты. Мы также хотим выполнять итерацию одновременно в нескольких потоках. К некоторым компонентам будет много доступа. Что, если мы хотим отобразить какой-то компонент, если порядок компонентов меняется каждый раз, когда перераспределяется хэш-карта, поскольку нам приходится все перефразировать, это может привести к уродливым визуальным ошибкам. Другая проблема заключается в том, что карта почти полностью заполнена, поэтому итерация может натолкнуться на пустые места и должна их пропустить. Самая большая проблема заключается в том, что наш O (1) не гарантируется, иногда нам может очень не повезти, и сложность будет намного выше. Хотя есть ли что-то лучше?

Объединение и бинарный поиск

Почему в заголовке два варианта? Ну, потому что это зависит от того, насколько много компонентов, когда решаете, какой из них использовать. Как бинарный поиск может помочь нам в любом случае? Давайте сначала объясним это.

BS

Двоичный поиск — это алгоритм, который может найти элемент в коллекции со сложностью O(log2 n), предполагая, что коллекция отсортирована. Самым большим преимуществом является то, что это гарантированно максимальная сложность. Одна вещь, которую вы можете не понять на первый взгляд, заключается в том, что BS также хорош для сортировки. Мы можем выяснить, куда вставить, чтобы сохранить отсортированную коллекцию, с той же сложностью. Итак, мы получили отсортированный массив, в котором порядок компонентов предсказуем. Нам не нужно перефразировать каждый раз, когда мы увеличиваем размер контейнера. 100% эффективность использования памяти и быстрая итерация. Единственным компромиссом является сложность, и даже это зависит.

Объединение

С другой стороны, у нас есть пул. Если у каждой сущности есть компонент, мы можем просто использовать индексы в качестве идентификаторов и отслеживать свободные идентификаторы. Бесплатные идентификаторы могут храниться в массиве и при необходимости извлекаться двоичным поиском. Там будут пустые места, но только минимальное количество, о котором нам на самом деле не нужно заботиться, поскольку мы можем просто использовать BS вместо этого, если оно не подходит.

Все вместе

Теперь, когда мы разобрались с нашим хранилищем, мы можем набросать нашу структуру ESC.

В начале мы объявляем имя нашей структуры ECS и тип идентификатора. Затем у нас есть два раздела для компонентов, хранящихся в poolStorage, и раздел для компонентов, хранящихся в BS. Далее идет раздел под названием сущности, который содержит что-то вроде определения объекта (но это не так). Похоже на какой-то странный воображаемый язык, созданный для построения ECS, не так ли? Ну, я сразу испорчу. Мы создадим этот специфичный для предметной области синтаксис с помощью метапрограммирования.

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

Конец

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