Групування об'єктів одного компонента встановлюється в лінійну пам'ять


12

Ми починаємо з базового підходу системи-компоненти-сутності .

Створимо збірки (термін, похідний від цієї статті) лише з інформації про типи компонентів . Це робиться динамічно під час виконання, так само, як ми додавали б / видаляли компоненти до об'єкта по черзі, але давайте назвемо його точніше, оскільки мова йде лише про інформацію про тип.

Тоді ми будуємо сутності, задаючи збірку для кожного з них. Після того, як ми створюємо сутність, її збірка стає непорушною, це означає, що ми не можемо безпосередньо змінити її на місці, але все-таки ми можемо отримати підпис існуючої сутності до локальної копії (разом із вмістом), внести належні зміни до неї та створити нову сутність з нього.

Тепер для ключового поняття: кожного разу, коли створена сутність, вона присвоюється об'єкту, який називається актором збірки , що означає, що всі об'єкти одного підпису будуть знаходитися в одному контейнері (наприклад, в 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;

    //...
};

Отже, колись з якоїсь причини в нашій логіці гри ми хочемо відслідковувати новостворену сутність, всередині відра ми реєструємо entitywatcher , і як тільки сутність повинна бути 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, який визначає, коли перейти до наступного відра. Стрибки будуть чисто прозорими для логіки.


Я також читав статтю Ренді Галлія про зберігання всіх компонентів у векторах і дозволяю їх системам просто їх обробляти. Я бачу там дві великі проблеми: що робити, якщо я хочу оновити лише підмножину сутностей (подумайте, наприклад, про вилучення). Через це компоненти будуть знову поєднані з сутностями. Для кожного етапу ітерації компонентів я повинен перевірити, чи вибрано для оновлення сутність, якій вона належить. Інша проблема полягає в тому, що деяким системам потрібно знову обробляти кілька різних типів компонентів, забираючи знову перевагу когерентності кешу. Будь-які ідеї, як вирішити ці питання?
tiguchi

Відповіді:


7

Ви по суті створили статичну об'єктну систему з розподільником пулу та з динамічними класами.

Я ще в шкільні часи написав об'єктну систему, яка майже однаково працює з вашою системою "збірок", хоча я завжди схильний називати "збірки" або "кресленнями", або "архетипами" у власних проектах. Архітектура більше боліла в стику, ніж наївні об'єктні системи, і не мала вимірних переваг у порівнянні з деякими більш гнучкими конструкціями, з якими я порівнював її. Можливість динамічно змінювати об’єкт, не потребуючи переймовування або перерозподілу, надзвичайно важлива під час роботи над редактором ігор. Дизайнери захочуть перетягнути n-drop компоненти на ваші визначення об'єкта. У коді виконання може виникнути необхідність ефективно змінювати компоненти в деяких проектах, хоча мені це особисто не подобається. Залежно від того, як ви зв’язуєте посилання на об'єкти у своєму редакторі,

У більшості нетривіальних випадків ви отримаєте гіршу когерентність кешу, ніж ви думаєте. Наприклад, ваша система AI не піклується про Renderкомпоненти, але в кінцевому підсумку ітерація над ними як частина кожної сутності. Об'єкти, які повторюються, є більшими, а запити кешліна в кінцевому підсумку витягують непотрібні дані, і з кожним запитом повертається менше цілих об'єктів). Це все ще буде краще, ніж наївний метод, а об’єктивний склад об'єкта наївного методу використовується навіть у великих двигунах AAA, тому вам, мабуть, не потрібно , але, принаймні, не варто думати, що ви не можете його вдосконалити далі.

Ваш підхід робить найбільш підходить для деякихкомпоненти, але не всі. Мені дуже не подобається ECS, оскільки він виступає за те, щоб завжди розміщувати кожен компонент в окремому контейнері, що має сенс для фізики чи графіки чи чогось іншого, але зовсім не має сенсу, якщо ви дозволяєте використовувати декілька компонентів скрипту чи AI, що можна використовувати. Якщо ви дозволите використовувати систему компонентів для більше, ніж просто вбудованих об'єктів, але також як спосіб дизайнерів та програмістів ігрових процесів складати поведінку об'єкта, то це може мати сенс згрупувати всі компоненти AI (які часто взаємодіють) або весь сценарій компоненти (оскільки ви хочете оновити їх усі в одній партії). Якщо вам потрібна найефективніша система, то вам знадобиться комбінація схем розподілу та зберігання компонентів і вчасно вкажіть, що найкраще підходить для кожного конкретного типу компонента.


Я сказав: ми не можемо змінити підпис суб’єкта господарювання, і я мав на увазі, що ми не можемо безпосередньо змінити його на місці, але все-таки ми можемо просто отримати наявну збірку до локальної копії, внести зміни до неї та знову завантажити як нову сутність - і ці операції досить дешеві, як я показав у запитанні. Ще раз - є лише ОДНІЙ "відро" класу. "Збори" / "Підписи" / "Давайте назвемо, як би ми хотіли" можна створювати динамічно під час виконання, як у стандартному підході, я навіть підходив би до того, щоб думати про організацію як про "підпис".
Патрик Чачурський

І я сказав, що ви не обов'язково хочете мати справу з реіфікацією. "Створення нової сутності" потенційно може означати перелом усіх існуючих ручок на об'єкт, залежно від того, як працює ваша система обробки. Ваш дзвінок, якщо вони досить дешеві чи ні. Я виявив, що це просто біль у попці, з якою доводиться стикатися.
Шон Міддлічч

Гаразд, зараз я зрозумів ваше питання. У всякому разі, я думаю, що навіть якщо додавання / видалення було трохи дорожчим, це відбувається настільки випадково, що все-таки варто значно спростити процес доступу до компонентів, що відбувається в режимі реального часу. Отже, накладні витрати на "зміну" є незначним. Що стосується вашого прикладу AI, чи не варто все-таки цим декільком системам, яким так чи інакше потрібні дані з декількох компонентів?
Патрик Чачурський

Моя думка полягала в тому, що AI - це місце, де ваш підхід кращий, але для інших компонентів це не обов'язково.
Шон Міддлітч

4

Що ви зробили, це реінжиніринг об'єктів C ++. Причина, чому це здається очевидним, полягає в тому, що якщо ви заміните слово "сутність" на "клас", а "компонент" на "член", це стандартна конструкція OOP з використанням mixins.

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

2) узгодженість пам'яті є найбільш важливою для типу даних, а не в об'єкті, що об'єднує кілька типів даних в одному місці. Це одна з причин створення компонентних систем для відходу від фрагментації пам'яті класу + об'єктів.

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

4) компонент так само простий, щоб серіалізувати себе, ніж складний об'єкт, щоб серіалізувати кілька компонентів всередині себе, якщо насправді не простіше відстежувати, як програміста.

5) наступним логічним кроком цього шляху є видалення Системи та введення цього коду безпосередньо в об'єкт, де він має всі дані, необхідні для роботи. Всі ми можемо бачити, що це означає =)


2) можливо я не розумію кешування повністю, але скажімо, існує система, яка працює з 10 компонентами. У стандартному підході обробка кожної сутності означає доступ до оперативної пам’яті 10 разів, оскільки компоненти розкидані у випадкових місцях пам’яті, навіть якщо пули використовуються - адже різні компоненти належать до різних пулів. Чи не було б "важливим" кешувати цілу сутність одразу та обробляти всі компоненти без жодного пропуску кешу, навіть не роблячи пошуку словників? Також я вніс редагування, щоб висвітлити 1) пункт
Патрик Чачурський

@Sean Middleditch у своїй відповіді добре описує цю пошкодження кешування.
Патрік Х'юз

3) Вони жодним чином не є когерентними об'єктами. Щодо компонента A, який знаходиться поруч із компонентом B в пам'яті, це просто "когерентність пам'яті", а не "логічна когерентність", як зазначив Джон. Коли вони створюють, навіть можуть змішувати компоненти підпису в будь-якому бажаному порядку та принципах. 4) це може бути так само просто "відслідковувати", якщо у нас достатньо абстрагування - про що ми говоримо - це лише схема зберігання, яка надається за допомогою ітераторів, і, можливо, карта зміщення байтів може зробити обробку такою ж простою, як і при стандартному підході.
Патрик Чачурський

5) І я не думаю, що щось в цій ідеї вказує на цей напрямок. Справа не в тому, що я не хочу погоджуватися з вами, мені просто цікаво, куди ця дискусія може призвести, хоча все-таки це, мабуть, призведе до свого роду «виміряти її» або до відомої «передчасної оптимізації». :)
Патрик Чачурський

@PatrykCzachurski, але ваші системи не працюють з 10 компонентами.
користувач253751

3

Збереження подібних сутностей не так важливо, як ви могли б подумати, ось чому важко придумати вагому причину, відмінну від "тому що це одиниця". Але оскільки ви дійсно робите це для когерентності кешу на відміну від логічної когерентності, це може мати сенс.

Однією з труднощів у вас може бути взаємодія між компонентами в різних відрах. Це не страшно прямо вперед, щоб, наприклад, знайти те, що може здійснити AI, вам потрібно буде пройти всі відра, які можуть містити дійсну ціль. Без зовнішньої структури даних виявлення зіткнень може бути однаково важким.

Щоб продовжувати об’єднання організацій для логічної узгодженості, єдина причина, що мені могло б утримати об'єднання разом, - це для ідентифікації в моїх місіях. Мені потрібно знати, чи ви тільки що створили сутність типу A або тип B, і я це обходжу по ... ви здогадалися: додавши новий компонент, який ідентифікує збірку, що поєднує цю сутність. Вже тоді я не збираю всі компоненти разом для грандіозного завдання, мені просто потрібно знати, що це таке. Тож я не думаю, що ця частина страшенно корисна.


Я мушу визнати, що я не зовсім розумію вашу відповідь. Що ви маєте на увазі під "логічною когерентністю"? Про труднощі у взаємодії я зробив правку.
Патрик Чачурський

"Логічна узгодженість", як у: Має "логічний сенс" тримати всі компоненти, що складають сутність Дерева, близько.
Джон Макдональд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.