Однако разумно ли также создавать приложения, использующие архитектуру Component-Entity-System, общую для игровых движков?
Для меня абсолютно. Я работаю в Visual FX и изучал широкий спектр систем в этой области, их архитектуры (включая CAD / CAM), жаждал SDK и любых работ, которые позволили бы мне понять плюсы и минусы кажущихся бесконечными архитектурных решений, которые могут быть сделаны, даже самые тонкие из них не всегда оказывают тонкое воздействие.
VFX очень похож на игры в том, что есть одна центральная концепция «сцены» с окнами просмотра, которые отображают визуализированные результаты. Также обычно происходит много цикличной обработки, постоянно вращающейся вокруг этой сцены в анимационных контекстах, где может происходить физика, порождающие частицы частицы, порождающие анимацию и рендеринг сетки, анимации движения и т. Д., И, в конечном счете, для их рендеринга. все пользователю в конце.
Другой концепцией, подобной, по крайней мере, очень сложным игровым движкам, была необходимость в аспекте «дизайнера», в котором дизайнеры могли бы гибко проектировать сцены, в том числе возможность самостоятельно создавать легковесные программы (скрипты и узлы).
За эти годы я обнаружил, что ECS идеально подходит. Конечно, это никогда не будет полностью отделено от субъективности, но я бы сказал, что это, по-видимому, создает меньше проблем. Это решило намного больше серьезных проблем, с которыми мы всегда боролись, и в то же время дало нам лишь несколько новых мелких проблем.
Традиционный ООП
Более традиционные ООП-подходы могут быть действительно эффективными, когда у вас есть твердое понимание требований к проектированию, но не требований к реализации. Будь то с помощью более плоского многоинтерфейсного подхода или более вложенного иерархического подхода ABC, он имеет тенденцию цементировать проект и усложнять его изменение, делая реализацию проще и безопаснее для изменения. Всегда существует потребность в нестабильности в любом продукте, который выходит за рамки одной версии, поэтому подходы ООП, как правило, отклоняют стабильность (трудность изменения и отсутствие причин для изменения) к уровню разработки и нестабильность (простота изменения и причины изменения) до уровня реализации.
Однако, несмотря на изменяющиеся требования конечных пользователей, как дизайн, так и реализация могут нуждаться в частых изменениях. Вы можете найти что-то странное, например, сильную потребность конечного пользователя в аналогичном существе, которое должно быть одновременно и растительным, и животным, полностью аннулируя всю концептуальную модель, которую вы создали. Обычные объектно-ориентированные подходы не защищают вас здесь, и иногда могут сделать такие неожиданные, принципиальные изменения еще сложнее. Когда речь идет об областях, очень критичных к производительности, причины изменений в проекте еще больше умножаются.
Объединение нескольких гранулярных интерфейсов для формирования соответствующего интерфейса объекта может очень помочь в стабилизации клиентского кода, но это не помогает в стабилизации подтипов, которые иногда могут привести к уменьшению количества клиентских зависимостей. Например, у вас может быть один интерфейс, который используется только частью вашей системы, но с тысячами различных подтипов, реализующих этот интерфейс. В этом случае поддержание сложных подтипов (сложных, потому что у них так много разнородных интерфейсных обязанностей), может стать кошмаром, а не кодом, использующим их через интерфейс. ООП, как правило, переносит сложность на уровень объекта, в то время как ECS передает ее на уровень клиента («системы»), и это может быть идеальным, когда существует очень мало систем, но целая куча соответствующих «объектов» («сущностей»).
Класс также владеет своими данными в частном порядке и, таким образом, может поддерживать инварианты самостоятельно. Тем не менее, существуют «грубые» инварианты, которые на самом деле все еще трудно поддерживать, когда объекты взаимодействуют друг с другом. Чтобы сложная система в целом находилась в допустимом состоянии, часто необходимо учитывать сложный граф объектов, даже если их индивидуальные инварианты должным образом поддерживаются. Традиционные подходы в стиле ООП могут помочь в поддержании гранулярных инвариантов, но на самом деле могут затруднить поддержание широких, грубых инвариантов, если объекты сосредоточены на крошечных гранях системы.
Вот где такие подходы или варианты ECS для построения лего-блоков могут быть очень полезны. Также, когда системы имеют более грубый дизайн, чем обычный объект, становится проще поддерживать такие грубые инварианты с высоты птичьего полета системы. Множество маленьких объектных взаимодействий превращаются в одну большую систему, фокусирующуюся на одной широкой задаче, а не на маленьких маленьких предметах, фокусирующихся на маленьких маленьких задачах с графом зависимостей, который будет покрывать километр бумаги.
И все же мне пришлось смотреть за пределы своей области, игровой индустрии, чтобы узнать об ECS, хотя я всегда был ориентирован на данные. Кроме того, как ни странно, я почти самостоятельно пробился к ECS, просто перебирая и пытаясь придумать лучший дизайн. Хотя я не прошел весь путь и упустил очень важную деталь, которая заключается в формализации «системной» части и сжатии компонентов вплоть до необработанных данных.
Я попытаюсь объяснить, как я остановился на ECS и как он решил все проблемы с предыдущими итерациями проекта. Я думаю, что это поможет подчеркнуть, почему ответом может быть очень сильное «да», что ECS потенциально применим далеко за пределами игровой индустрии.
Архитектура грубой силы 1980-х годов
Первая архитектура, над которой я работал в индустрии VFX, имела давнее наследие, которое прошло уже более десяти лет с тех пор, как я присоединился к компании. Это была грубая грубая C-кодировка (не на C, как я люблю C, но способ ее использования здесь был действительно грубым). Миниатюрный и слишком упрощенный фрагмент напоминал такие зависимости:
И это чрезвычайно упрощенная схема одного крошечного кусочка системы. Каждый из этих клиентов на диаграмме («Рендеринг», «Физика», «Движение») получит некоторый «универсальный» объект, через который они будут проверять поле типа, например:
void transform(struct Object* obj, const float mat[16])
{
switch (obj->type)
{
case camera:
// cast to camera and do something with camera fields
break;
case light:
// cast to light and do something with light fields
break;
...
}
}
Конечно, со значительно более уродливым и сложным кодом, чем этот. Часто из этих случаев переключения будут вызываться дополнительные функции, которые будут рекурсивно выполнять переключение снова и снова и снова. Эта схема и код может почти выглядеть ECS-лайт, но не было никакого сильного сущность-компонент различия ( « является этот объект камера?», А не „не этот объект обеспечит движение?“), И никакой формализации „системы“ ( просто куча вложенных функций, идущих повсюду и смешивающих обязанности). В этом случае, почти все было сложно, любая функция была потенциальной возможностью для катастрофы, ожидающей, чтобы случиться.
Наша процедура тестирования здесь часто должна была проверять такие вещи, как сетки, отдельно от других типов элементов, даже если идентичные вещи происходили с обоими, так как грубая сила характера кодирования здесь (часто сопровождаемого большим количеством копирования и вставки) часто выполнялась Весьма вероятно, что в противном случае та же самая логика может потерпеть неудачу от одного типа элемента к другому. Попытка расширить систему для обработки новых типов элементов была довольно безнадежной, даже несмотря на то, что существовала явно выраженная потребность конечного пользователя, поскольку это было слишком сложно, когда мы изо всех сил пытались справиться с существующими типами элементов.
Некоторые плюсы:
- Э-э-э ... не требует никакого инженерного опыта, я полагаю? Эта система не требует каких-либо знаний даже о базовых понятиях, таких как полиморфизм, это абсолютно грубая сила, поэтому я предполагаю, что даже новичок сможет понять часть кода, даже если профессионал при отладке едва его поддерживает.
Некоторые минусы:
- Технический кошмар. Наша маркетинговая команда действительно почувствовала необходимость хвастаться тем, что мы исправили более 2000 уникальных ошибок за один трехлетний цикл. Для меня это то, что должно быть смущено тем, что у нас было так много ошибок, и этот процесс, вероятно, все еще исправлял только около 10% от общего количества ошибок, число которых постоянно росло.
- О самом жестком из возможных решений.
Архитектура СОМ 1990-х годов
Большая часть индустрии VFX использует этот стиль архитектуры из того, что я собрал, читая документы об их дизайнерских решениях и просматривая их комплекты для разработки программного обеспечения.
Это может быть не совсем COM на уровне ABI (некоторые из этих архитектур могут иметь плагины, написанные только с использованием одного и того же компилятора), но имеют много сходных характеристик с интерфейсными запросами к объектам, чтобы увидеть, какие интерфейсы поддерживают их компоненты.
При таком подходе аналогичная transform
функция, описанная выше, стала напоминать эту форму:
void transform(Object obj, const Matrix& mat)
{
// Wrapper that performs an interface query to see if the
// object implements the IMotion interface.
MotionRef motion(obj);
// If the object supported the IMotion interface:
if (motion.valid())
{
// Transform the item through the IMotion interface.
motion->transform(mat);
...
}
}
Это подход, на котором остановилась новая команда этой старой кодовой базы, чтобы в конечном итоге провести рефакторинг. И это было значительное улучшение по сравнению с оригиналом с точки зрения гибкости и удобства обслуживания, но были некоторые проблемы, о которых я расскажу в следующем разделе.
Некоторые плюсы:
- Значительно более гибкий / расширяемый / обслуживаемый, чем предыдущее решение для грубой силы.
- Обеспечивает строгое соответствие многим принципам SOLID, делая каждый интерфейс полностью абстрактным (без сохранения состояния, без реализации, только чистые интерфейсы).
Некоторые минусы:
- Много шаблонов. Наши компоненты должны были быть опубликованы через реестр для создания экземпляров объектов, поддерживаемые ими интерфейсы требовали как наследования («реализации» в Java) интерфейса, так и предоставления некоторого кода, чтобы указать, какие интерфейсы были доступны в запросе.
- Повсюду продвигается дублированная логика в результате чистых интерфейсов. Например, все реализованные компоненты
IMotion
всегда будут иметь одинаковое состояние и одинаковую реализацию для всех функций. Чтобы смягчить это, мы начали бы централизовать базовые классы и вспомогательные функции по всей системе для вещей, которые, как правило, избыточно реализуются одинаковым образом для одного и того же интерфейса, и, возможно, с множественным наследованием, происходящим за укрытием, но это было довольно грязный под капотом, хотя в коде клиента все было просто.
- Неэффективность: сеансы vtune часто демонстрировали основную
QueryInterface
функцию, почти всегда проявляющуюся как точка доступа от середины до верхней точки, а иногда даже точка доступа № 1. Чтобы смягчить это, мы бы сделали такие вещи, как рендеринг частей кеша кодовой базы списка объектов, о которых уже известно, что они поддерживаютIRenderable
, но это значительно увеличило сложность и затраты на обслуживание. Аналогично, это было сложнее измерить, но мы заметили некоторые определенные замедления по сравнению с кодированием в стиле C, которое мы делали раньше, когда каждый отдельный интерфейс требовал динамической диспетчеризации. Такие вещи, как неправильные прогнозы ветвей и барьеры оптимизации, трудно измерить за пределами небольшого аспекта кода, но пользователи просто замечали, что отзывчивость пользовательского интерфейса и подобные вещи ухудшаются, сравнивая предыдущие и более новые версии программного обеспечения рядом друг с другом. сторона для областей, где алгоритмическая сложность не изменилась, только константы.
- Было все еще трудно рассуждать о правильности на более широком системном уровне. Несмотря на то, что это было значительно проще, чем в предыдущем подходе, было все еще трудно понять сложные взаимодействия между объектами в этой системе, особенно с некоторыми из оптимизаций, которые начали становиться необходимыми для нее.
- У нас были проблемы с настройкой наших интерфейсов. Несмотря на то, что в системе, использующей интерфейс, может быть только одно широкое место, требования конечных пользователей будут меняться в зависимости от версий, и нам придется в конечном итоге вносить каскадные изменения во все классы, которые реализуют интерфейс, чтобы приспособить новую функцию, добавленную к интерфейс, например, если не было какого-то абстрактного базового класса, который уже централизовал логику под капотом (некоторые из них проявились бы в середине этих каскадных изменений в надежде не повторять это снова и снова).
Прагматичный ответ: композиция
Одна из вещей, которую мы замечали раньше (или, по крайней мере, я так делал), вызывала проблемы, заключалась в том, что она IMotion
могла быть реализована 100 различными классами, но с точно такой же реализацией и связанным состоянием. Кроме того, он будет использоваться только несколькими системами, такими как рендеринг, движение по ключевым кадрам и физика.
Таким образом, в таком случае мы могли бы иметь отношение 3 к 1 между системами, использующими интерфейс к интерфейсу, и отношение 100 к 1 между подтипами, реализующими интерфейс к интерфейсу.
В этом случае сложность и обслуживание будут резко искажены для внедрения и обслуживания 100 подтипов вместо 3 клиентских систем, от которых зависит IMotion
. Это переместило все наши трудности с обслуживанием на обслуживание этих 100 подтипов, а не на 3 места, использующих интерфейс. Обновление 3 мест в коде с небольшим количеством или без «косвенных эфферентных связей» (как в зависимости от него, но косвенно через интерфейс, а не в виде прямой зависимости), нет ничего сложного: обновление 100 мест подтипа с загрузкой «косвенных эфферентных связей» , довольно большое дело *.
* Я понимаю, что странно и неправильно ввергать определение «эфферентных связей» в этом смысле с точки зрения реализации, я просто не нашел лучшего способа описать сложность обслуживания, связанную, когда и интерфейс, и соответствующие реализации сотен подтипов должен измениться.
Поэтому мне пришлось приложить немало усилий, но я предложил, чтобы мы постарались стать немного более прагматичными и ослабить идею «чистого интерфейса». Для меня не имело смысла делать что-то наподобие IMotion
абстрактного и не имеющего состояния, если мы не увидели преимущества для него, имеющего богатое разнообразие реализаций. В нашем случае, IMotion
если иметь богатое разнообразие реализаций, это на самом деле превратилось бы в настоящий кошмар обслуживания, поскольку мы не хотели разнообразия. Вместо этого мы пытались создать единую реализацию движения, которая действительно хороша против изменения требований клиента, и часто работали над идеей чистого интерфейса, пытаясь заставить каждого разработчика IMotion
использовать одну и ту же реализацию и связанное состояние, чтобы мы не Т дублирующие цели.
Таким образом, интерфейсы стали больше похожи на широкие, Behaviors
связанные с сущностью. IMotion
просто стал бы Motion
«компонентом» (я изменил способ, которым мы определили «компонент», с COM на тот, где ближе к обычному определению части, составляющей «завершенную» сущность).
Вместо этого:
class IMotion
{
public:
virtual ~IMotion() {}
virtual void transform(const Matrix& mat) = 0;
...
};
Мы развили это до чего-то большего:
class Motion
{
public:
void transform(const Matrix& mat)
{
...
}
...
private:
Matrix transformation;
...
};
Это является вопиющим нарушением принципа инверсии зависимости, чтобы начать переход от абстрактного к конкретному, но для меня такой уровень абстракции полезен, только если мы можем предвидеть подлинную потребность в каком-то будущем, вне разумных сомнений и не использование смешных сценариев «что если», полностью оторванных от пользовательского опыта (что, вероятно, в любом случае потребовало бы изменения дизайна), для такой гибкости.
Таким образом, мы начали развиваться до этого дизайна. QueryInterface
стало больше похоже QueryBehavior
. Кроме того, стало казаться бессмысленным использовать наследование здесь. Вместо этого мы использовали композицию. Объекты превратились в набор компонентов, доступность которых можно запрашивать и внедрять во время выполнения.
Некоторые плюсы:
- В нашем случае было намного проще поддерживать, чем в предыдущей, чисто интерфейсной системе в стиле COM. Непредвиденные сюрпризы, такие как изменение требований или жалобы на рабочие процессы, могут быть легче учтены с помощью одной очень центральной и очевидной
Motion
реализации, например, и не распределены по сотне подтипов.
- Дали совершенно новый уровень гибкости, который нам действительно нужен. В нашей предыдущей системе, поскольку наследование моделирует статические отношения, мы могли эффективно определять новые сущности только во время компиляции в C ++. Мы не могли сделать это на языке сценариев, например, используя композиционный подход, мы могли бы связывать воедино новые сущности во время выполнения, просто прикрепляя к ним компоненты и добавляя их в список. «Сущность» превратилась в чистый холст, на котором мы могли бы просто собрать на лету коллаж из всего, что нам нужно, и соответствующие системы автоматически распознают и обрабатывают эти сущности.
Некоторые минусы:
- Мы все еще испытывали трудности в отделе эффективности и работоспособности в критически важных областях. Каждая система все равно в конечном итоге захочет кэшировать компоненты сущностей, которые обеспечивают такое поведение, чтобы избежать повторного прохождения через них всех и проверки того, что было доступно. Каждая система, требующая производительности, делала это немного по-разному и была склонна к различному набору ошибок при невозможности обновления этого кэшированного списка и, возможно, структуры данных (если была задействована какая-либо форма поиска, такая как выборка из усеченного контура или трассировка лучей) в некоторых неясное событие смены сцены, например
- Было все еще что-то неуклюжее и сложное, что я не мог понять, что связано со всеми этими мелкими, поведенческими, простыми объектами. Мы по-прежнему порождали множество событий, связанных с взаимодействиями между этими «поведенческими» объектами, которые иногда были необходимы, и в результате получился очень децентрализованный код. Каждый маленький предмет было легко проверить на правильность и, взятый индивидуально, часто был совершенно правильным. И все же казалось, что мы пытались поддерживать огромную экосистему, состоящую из маленьких деревень, и пытались рассуждать о том, что они все делают по отдельности, и складывают все вместе. Кодовая база 80-х в стиле C чувствовалась как один эпический, перенаселенный мегаполис, который определенно был кошмаром обслуживания,
- Потеря гибкости из-за отсутствия абстракции, но в области, где мы никогда не сталкивались с реальной потребностью в ней, поэтому вряд ли это практический довод (хотя определенно, по крайней мере, теоретический).
- Сохранять совместимость с ABI всегда было трудно, и это усложняло необходимость требовать стабильных данных, а не только стабильного интерфейса, связанного с «поведением». Тем не менее, мы могли бы легко добавить новые поведения и просто отказаться от существующих, если необходимо изменение состояния, и это, возможно, было бы проще, чем делать обратные сальдо под интерфейсами на уровне подтипов для обработки проблем управления версиями.
Одним из явлений, которое произошло, было то, что, поскольку мы потеряли абстракцию этих поведенческих компонентов, у нас их стало больше. Например, вместо абстрактного IRenderable
компонента мы бы прикрепили объект к конкретному Mesh
или PointSprites
компоненту. Система рендеринга будет знать, как визуализировать Mesh
и PointSprites
компоненты, и будет находить объекты, которые предоставляют такие компоненты, и рисовать их. В других случаях у нас были разные визуализируемые объекты, подобные тому, SceneLabel
что мы обнаружили в ретроспективе, поэтому SceneLabel
в этих случаях мы добавляли a к соответствующим объектам (возможно, в дополнение к a Mesh
). Реализация системы рендеринга будет затем обновлена, чтобы знать, как рендерить сущности, которые ее предоставили, и это было довольно легко изменить.
В этом случае объект, состоящий из компонентов, также может затем использоваться в качестве компонента для другого объекта. Мы построили бы все таким образом, подключив блоки lego.
ECS: системы и компоненты необработанных данных
Эта последняя система была настолько, насколько я сделал это самостоятельно, и мы все еще убивали ее с помощью COM. Мне казалось, что он хочет стать системой сущностей-компонентов, но я не был знаком с ней в то время. Я смотрел на примеры в стиле COM, которые насыщали мое поле, когда я должен был искать игровые движки ААА для архитектурного вдохновения. Я наконец начал делать это.
Чего мне не хватало, так это нескольких ключевых идей:
- Формализация «систем» для обработки «компонентов».
- «Компоненты» - это необработанные данные, а не объекты поведения, объединенные в более крупный объект.
- Сущности как не более чем строгий идентификатор, связанный с набором компонентов.
Я, наконец, покинул эту компанию и начал работать над ECS как инди (все еще работаю над этим, истощая мои сбережения), и это была самая простая система для управления на сегодняшний день.
Что я заметил в подходе ECS, так это в том, что он решил проблемы, с которыми я до сих пор боролся. Для меня самое главное, я чувствовал, что мы управляли «городами» здорового размера, а не маленькими деревушками со сложными взаимодействиями. Его было не так сложно поддерживать, как монолитный «мегаполис», слишком большой по численности населения, чтобы эффективно управлять им, но он не был таким хаотичным, как мир, заполненный крошечными деревушками, взаимодействующими друг с другом, где просто думать о торговых путях в между ними образовался кошмарный граф. ECS перевела всю сложность на громоздкие «системы», такие как система рендеринга, «город» здорового размера, а не «перенаселенный мегаполис».
Компоненты, превращающиеся в необработанные данные, сначала показались мне странными , поскольку они нарушают даже базовый принцип сокрытия информации ООП. Это было своего рода вызов одной из самых больших ценностей ООП, которой я дорожил, а именно ее способности поддерживать инварианты, которые требовали инкапсуляции и сокрытия информации. Но это стало не беспокоить, поскольку быстро стало очевидно, что происходит с дюжиной или около того широких систем, преобразующих эти данные, вместо того, чтобы такая логика была распределена по сотням и тысячам подтипов, реализующих комбинацию интерфейсов. Я склонен думать об этом как в стиле ООП, за исключением случаев, когда системы предоставляют функциональность и реализацию для доступа к данным, компоненты предоставляют данные, а объекты предоставляют компоненты.
Стало еще проще , нелогично, рассуждать о побочных эффектах, вызванных системой, когда было всего несколько громоздких систем, преобразующих данные в широкие проходы. Система стала намного более «плоской», мои стеки вызовов стали меньше, чем когда-либо прежде, для каждого потока. Я мог думать о системе на этом уровне наблюдателя и не наталкиваться на странные сюрпризы.
Точно так же он упростил даже критичные для производительности области в отношении устранения этих запросов. Поскольку идея «Системы» стала очень формализованной, система могла подписаться на интересующие ее компоненты и просто получить кэшированный список объектов, которые удовлетворяют этим критериям. Каждому человеку не нужно было управлять этой оптимизацией кэширования, она стала централизованной в одном месте.
Некоторые плюсы:
- Кажется, просто решить почти все основные архитектурные проблемы, с которыми я столкнулся в своей карьере, не чувствуя себя застрявшим в дизайнерском уголке, когда сталкиваюсь с неожиданными потребностями.
Некоторые минусы:
- Мне до сих пор бывает трудно обдумать это, и это не самая зрелая или устоявшаяся парадигма даже в игровой индустрии, где люди спорят о том, что именно это означает и как что-то делать. Это определенно не то, что я мог бы сделать с прежней командой, с которой я работал, которая состояла из членов, глубоко привязанных к образу мышления в стиле COM или образу мышления в стиле C 1980-х годов исходной кодовой базы. Иногда я путаюсь с тем, как моделировать отношения в стиле графа между компонентами, но я всегда находил решение, которое не оказалось ужасным позже, когда я могу просто сделать компонент зависимым от другого («это движение компонент зависит от этого другого как родительский, и система будет использовать запоминание, чтобы избежать повторного выполнения одних и тех же вычислений рекурсивного движения ", например)
- ABI все еще сложен, но пока я даже рискну сказать, что это проще, чем чисто интерфейсный подход. Это изменение в мышлении: стабильность данных становится единственной целью для ABI, а не стабильности интерфейса, и в некоторых отношениях легче достичь стабильности данных, чем стабильности интерфейса (например: нет соблазнов изменить функцию только потому, что ей нужен новый параметр. Подобные вещи происходят внутри грубых системных реализаций, которые не нарушают ABI).
Однако разумно ли также создавать приложения, использующие архитектуру Component-Entity-System, общую для игровых движков?
Так или иначе, я бы сказал абсолютно «да», с моим личным примером VFX, являющимся сильным кандидатом. Но это все еще довольно похоже на потребности игр.
Я не практиковал это в более отдаленных районах, полностью оторванных от проблем игровых движков (VFX очень похож), но мне кажется, что гораздо больше областей являются хорошими кандидатами для подхода ECS. Может быть, даже система с графическим интерфейсом подойдет для одного, но я все еще использую более ООП подход (но без глубокого наследования в отличие от Qt, например).
Это широко неисследованная территория, но мне кажется подходящим всякий раз, когда ваши сущности могут состоять из богатой комбинации «черт» (и точно, какую комбинацию черт они предоставляют, когда-либо подверженных изменениям), и где у вас есть несколько обобщенных системы, которые обрабатывают объекты, которые имеют необходимые черты.
В таких случаях это становится очень практичной альтернативой любому сценарию, где у вас может возникнуть соблазн использовать что-то вроде множественного наследования или эмуляции концепции (например, миксины) только для создания сотен или более комбинаций в иерархии глубокого наследования или сотен комбинаций. классов в плоской иерархии, реализующих определенную комбинацию интерфейсов, но там, где число ваших систем немного (десятки, например).
В этих случаях сложность кодовой базы начинает ощущаться более пропорциональной количеству систем, а не количеству комбинаций типов, поскольку каждый тип теперь является просто сущностью, составляющей компоненты, которые являются не более чем необработанными данными. Системы с графическим интерфейсом естественным образом соответствуют этим видам спецификаций, где они могут иметь сотни возможных типов виджетов, комбинированных с другими базовыми типами или интерфейсами, но только несколько систем для их обработки (система макетов, система рендеринга и т. Д.). Если бы система с графическим интерфейсом использовала ECS, вероятно, было бы намного проще рассуждать о правильности системы, когда все функции обеспечивались горсткой этих систем вместо сотен различных типов объектов с унаследованными интерфейсами или базовыми классами. Если бы система с графическим интерфейсом использовала ECS, виджеты не имели бы функциональности, только данные. Только несколько систем, которые обрабатывают объекты виджетов, будут иметь функциональность. То, как будут обрабатываться переопределенные события для виджета, мне неизвестно, но, основываясь на моем ограниченном опыте, я не нашел случая, когда этот тип логики не мог бы передаваться централизованно в данную систему таким образом, чтобы в Оглядываясь назад, мы получили гораздо более элегантное решение, которое я когда-либо ожидал.
Я хотел бы видеть это занятым в большем количестве областей, поскольку это было спасателем в моей. Конечно, он не подходит, если ваш дизайн не ломается таким образом, от сущностей, объединяющих компоненты, до грубых систем, которые обрабатывают эти компоненты, но если они естественным образом соответствуют такой модели, это самая замечательная вещь, с которой я когда-либо сталкивался ,