Дві основні переваги, які я постійно чую з похвалою щодо сутнісних систем, - це 1) легка побудова нових типів сутностей через те, що не потрібно заплутуватися зі складною ієрархією успадкування, і 2) ефективність кешу.
Зауважте, що (1) є перевагою дизайну на основі компонентів , а не лише ES / ECS. Ви можете використовувати компоненти багатьма способами, які не мають частини "системи", і вони працюють чудово (і в багатьох архівах як Інді, так і AAA використовуються такі архітектури).
Стандартна об'єктна модель Unity (з використанням GameObject
та MonoBehaviour
об'єктами) не є ECS, а є компонентною конструкцією. Більш нова функція Unity ECS - це, звичайно, фактична ECS.
Системи повинні мати можливість працювати з більш ніж одним компонентом, тобто і система візуалізації, і фізика повинні мати доступ до компонента перетворення.
Деякі ECS сортують контейнери для компонентів за ідентифікаційним кодом Entity, що означає, що відповідні компоненти в кожній групі будуть в одному порядку.
Це означає, що якщо ви лінійно повторюєте графічний компонент, ви також лінійно повторюєте відповідні компоненти перетворення. Можливо, ви пропускаєте деякі з перетворень (оскільки у вас можуть бути обсяги фізичних тригерів, які ви не рендеруєте чи подібні), але оскільки ви завжди проскакуєте вперед у пам'яті (і, як правило, не на величезних відстанях), ви все одно рухаєтесь мати підвищення ефективності.
Це аналогічно тому, як у вас є рекомендований підхід для HPC «Структура масивів» (SOA). Процесор і кеш можуть працювати з декількома лінійними масивами майже так само, як і з одним лінійним масивом, і набагато краще, ніж вони можуть мати доступ до випадкової пам'яті.
Ще одна стратегія, що використовується в деяких впровадженнях ECS - включаючи Unity ECS - полягає у розподілі компонентів на основі архетипу відповідної сутності. Тобто, все Сутності з точно набором компонентів ( PhysicsBody
, Transform
) будуть виділені окремо від осіб з різними компонентами (наприклад PhysicsBody
, Transform
, і Renderable
).
Системи в таких проектах працюють, спочатку знаходячи всі архетипи, які відповідають їхнім вимогам (які мають необхідний набір компонентів), ітерацію цього списку архетипів та ітерацію компонентів, що зберігаються в кожному відповідному архетипі. Це дозволяє отримати повністю лінійний та справжній доступ до компонентів O (1) в межах архетипу і дозволяє системам знаходити сумісні об'єкти з дуже низькими накладними витратами (шляхом пошуку невеликого списку архетипів, а не пошуку потенційно сотень тисяч сутностей).
Ви можете мати вказівники для зберігання компонентів на інші компоненти або покажчики на об'єкти, які зберігають вказівники на компоненти.
Компоненти, що посилаються на інші компоненти того ж об'єкта, нічого не потрібно зберігати. Для посилання на компоненти на інші об'єкти просто збережіть ідентифікатор особи.
Якщо компоненту дозволено існувати більше одного разу для однієї сутності, і вам потрібно посилатися на певний екземпляр, зберігайте ідентифікатор іншої сутності та індекс компонента для цієї сутності. Багато реалізацій ECS не допускають цього випадку, однак саме тому, що робить ці операції менш ефективними.
Ви можете переконатися, що кожен компонентний масив 'n' великий, де 'n' - кількість об'єктів, що живуть у системі
Використовуйте ручки (наприклад, індекси + маркери генерації), а не покажчики, і тоді ви можете змінювати розмір масивів, не боячись порушити посилання на об'єкт.
Ви також можете скористатися підходом "нарізаний масив" (масив масивів), подібним до багатьох поширених std::deque
реалізацій (хоча без жалюгідного невеликого розміру згаданих реалізацій), якщо ви хочете дозволити покажчики з якихось причин або якщо ви виміряли проблеми з змінити розмір продуктивності масиву.
По-друге, це все припускаючи, що сутності обробляються лінійно в списку кожен кадр / галочку, але насправді це не часто так
Це залежить від сутності. Так, для багатьох випадків використання це неправда. Дійсно, саме тому я так сильно наголошую на різниці між компонентним дизайном (хорошим) та сутнісною системою (конкретна форма КБР).
Деякі ваші компоненти, безумовно, будуть легко обробляти лінійно. Навіть у звичайно «важких для дерева» випадках використання ми, безумовно, спостерігали підвищення продуктивності від використання щільно упакованих масивів (переважно у випадках, в яких N максимум декілька сотень, як агенти AI у типовій грі).
Деякі розробники також встановили, що переваги продуктивності використання лінійно-розподілених структур даних, орієнтованих на дані, переважають за перевагою продуктивності використання "розумніших" деревних структур. Все, звичайно, залежить від гри та конкретних випадків використання.
Скажімо, ви використовуєте рендерінг сектору / порталу або октат для відключення оклюзії. Можливо, ви зможете постійно зберігати об'єкти в секторі / вузлі, але ви будете стрибати, подобається вам це чи ні.
Ви здивуєтеся, наскільки масив все ще допомагає. Ви стрибаєте в набагато меншій області пам’яті, ніж «куди завгодно», і навіть при всіх стрибках ви все ще набагато частіше опинитесь у чомусь у кеші. З деревом певного розміру чи меншим, ви навіть зможете заздалегідь встановити всю річ у кеш і ніколи не матимете пропуску кешу на цьому дереві.
Є також дерева структури, які побудовані для проживання в щільно укупованих масивах. Наприклад, у своєму октрисі ви можете використовувати структуру, що нагадує купу (батьки перед дітьми, брати і сестри поруч один з одним) і гарантувати, що навіть коли ви "вибиваєте" дерево, ви завжди ітератуєте вперед у масиві, що допомагає процесор оптимізує доступ до пам'яті / пошук кешу.
Що важливо зробити. Процесор x86 - це складний звір. Процесор ефективно працює з оптимізатором мікрокоду на вашому машинному коді, розбиваючи його на менший мікрокод і впорядковуючи інструкції, прогнозуючи схеми доступу до пам’яті і т.д. як працюють процесор чи кеш-пам'ять.
Тоді у вас є інші системи, які можуть віддати перевагу об'єктам, що зберігаються в іншому порядку.
Ви можете зберігати їх кілька разів. Після того, як ви знімете масиви до мінімальних деталей, ви можете виявити, що ви фактично економите пам’ять (оскільки ви видалили свої 64-бітні покажчики і можете використовувати менші індекси) при такому підході.
Ви можете переплутати ваш масив об'єктів замість того, щоб зберігати окремі масиви, але ви все одно витрачаєте пам’ять
Це протилежно до хорошого використання кешу. Якщо все, що вас цікавить, - це перетворення та графічні дані, навіщо змушувати машину витрачати час на те, щоб перетягувати всі ті інші дані для фізики та AI, а також введення та налагодження тощо?
Це звичайно на користь системи ECS проти монолітних ігрових об'єктів (хоча це не реально застосовується при порівнянні з іншими архітектурами, що базуються на компонентах).
Для чого це варто, більшість "виробничих" ECS-реалізацій, про які я знаю, використовують переплетене сховище. Популярний підхід, який я згадував раніше (наприклад, використовується в Unity ECS), дуже чітко побудований для використання переплетеного сховища для компонентів, пов'язаних з архетипом.
AI є безглуздим, якщо він не може вплинути на стан перетворення або анімації, що використовується для надання суб'єкта.
Тільки тому, що AI не може ефективно отримати доступ до даних перетворення лінійно, не означає, що жодна інша система не може ефективно використовувати цю оптимізацію макета даних. Ви можете використовувати запакований масив для трансформації даних, не зупиняючи ігрових логічних систем від того, щоб робити те, що спеціальні логічні ігри ігрових систем зазвичай роблять.
Ви також забули кеш-код . Коли ви використовуєте системний підхід ECS (на відміну від більш наївної архітектури компонентів), ви гарантуєте, що ви використовуєте той самий маленький цикл коду і не переходите назад і назад через віртуальні таблиці функцій до асортименту випадкових Update
функцій, розповсюджених по всьому ваш бінарний. Отже, у випадку AI, ви дійсно хочете зберегти всі ваші різні компоненти AI (адже, безумовно, у вас є більше одного, щоб ви могли складати поведінку!) В окремих відрах і обробляти кожен список окремо, щоб отримати найкраще використання кеш-коду.
Із затримкою черги подій (де система генерує список подій, але не відправляє їх, поки система не закінчить обробку всіх об'єктів), ви можете переконатися, що кеш коду добре використовується, зберігаючи події.
Використовуючи підхід, коли кожна система знає, які черги подій читати за кадр, ви навіть можете зробити події читання швидкими. Або швидше, ніж без, принаймні.
Пам'ятайте, продуктивність не є абсолютною. Вам не потрібно усунути кожен останній пропущений кеш, щоб почати бачити продуктивність хорошого дизайну, орієнтованого на дані.
Наразі триває активне дослідження щодо того, щоб багато ігрових систем краще працювали з архітектурою ECS та моделями дизайну, орієнтованого на дані. Подібно до деяких дивовижних речей, які ми спостерігали за допомогою SIMD в останні роки (наприклад, JSON-аналізатори), ми спостерігаємо все більше і більше речей, зроблених з архітектурою ECS, яка не здається інтуїтивно зрозумілою для класичних ігрових архітектур, але пропонує ряд переваги (швидкість, багатопотоковість, тестуваність тощо).
Або, можливо, є гібридний підхід, який використовують усі, але ніхто не говорить про це
Про це я виступав раніше, особливо для людей, які скептично ставляться до архітектури ECS: використовуйте хороші підходи, орієнтовані на дані, до компонентів, де продуктивність є критичною. Використовуйте більш просту архітектуру, де простота покращує час розробки. Не взувайте будь-який компонент у чітке завищене визначення компонентності, як пропонує ECS. Розробіть архітектуру компонентів таким чином, щоб ви могли легко використовувати підходи, схожі на ECS, де вони мають сенс і використовувати простішу структуру компонентів, де підхід, подібний ECS, не має сенсу (або має менший сенс, ніж структура дерева тощо) .
Я особисто відносно недавно перейшов на справжню силу ECS. Хоча для мене вирішальним фактором було щось, що рідко згадується про ECS: це робить тести для написання ігрових систем та логіки майже тривіальними порівняно з жорстко поєднаними логічними компонентами, що базуються на компонентах, з якими я працював у минулому. Оскільки архітектури ECS застосовують всю логіку в Системах, які просто споживають Компоненти та виробляють оновлення компонентів, створити "макетний" набір компонентів для перевірки поведінки системи досить просто; оскільки більшість логічних ігор повинні жити виключно в Системах, це фактично означає, що тестування всіх ваших систем забезпечить досить високе покриття коду вашої логіки гри. Системи можуть використовувати макетні залежності (наприклад, інтерфейси GPU) для тестів із значно меншою складністю або впливом на продуктивність, ніж ви '
Як осторонь, ви можете відзначити, що багато людей говорять про ECS, не розуміючи, що це таке. Я бачу класичний Unity, який називають ECS з пригнічувальною частотою, ілюструючи, що занадто багато розробників ігор порівнюють "ECS" з "Components" і майже повністю ігнорують частину "Entity System". Ви бачите, що в Інтернеті багато кохання накопичується, коли велика частина людей дійсно просто виступає за компонентний дизайн, а не власне ECS. На даний момент сперечатися це майже безглуздо; Система ECS була зіпсована з її початкового значення в загальний термін, і ви можете також прийняти, що "ECS" не означає те саме, що "ECS, орієнтоване на дані". : /