Відповідь завжди полягає у використанні масиву або std :: vector. Такі типи, як пов'язаний список або std :: map, зазвичай абсолютно жахливі в іграх, і це, безумовно, включає такі випадки, як колекції ігрових об'єктів.
Ви повинні зберігати самі об'єкти (а не покажчики на них) у масиві / векторі.
Ви хочете суцільну пам'ять. Ти дуже хочеш цього. Ітерація над будь-якими даними в безперервній пам'яті накладає багато помилок кеша в цілому і знімає можливість компілятора і процесора робити ефективне попереднє завантаження кешу. Це одне може вбити продуктивність.
Ви також хочете уникати розподілу пам’яті та розсилок. Вони дуже повільні, навіть із швидким розподільником пам'яті. Я бачив, як ігри отримують 10-кратну частоту кадрів у секунду, просто видаляючи кілька сотень розподілу пам'яті кожного кадру. Здається, це не так вже й погано, але може бути.
Нарешті, більшість структур даних, які вам цікаві для управління ігровими об'єктами, можуть бути набагато ефективніше реалізовані в масиві чи векторі, ніж вони можуть мати дерево або список.
Наприклад, для видалення ігрових об'єктів можна використовувати swap-and-pop. Легко реалізується з чимось на зразок:
std::swap(objects[index], objects.back());
objects.pop_back();
Ви також можете просто позначити об'єкти як видалені та поставити їх індекс у вільний список наступного разу, коли вам потрібно створити новий об’єкт, але робити заміну і попп краще. Це дозволяє робити простий цикл для всіх живих об'єктів, не розгалужуючи окрім самого циклу. Для інтеграції фізики кульок тощо, це може бути значним підвищенням продуктивності.
Що ще важливіше, ви можете знайти об'єкти з простою парою пошуку таблиць із стабільної унікальної, використовуючи структуру карти слотів.
Ваші ігрові об’єкти мають індекс у своєму головному масиві. Їх можна дуже ефективно шукати лише за допомогою цього індексу (набагато швидше, ніж карта чи навіть хеш-таблиця). Однак індекс не є стабільним через своп і поп при видаленні об’єктів.
Карта слотів вимагає двох шарів непрямості, але обидва - це прості пошукові масиви з постійними індексами. Вони швидкі . Дійсно швидко.
Основна ідея полягає у тому, що у вас є три масиви: ваш основний список об’єктів, ваш список непрямості та безкоштовний список для переліку непрямості. Ваш основний список об'єктів містить ваші фактичні об’єкти, де кожен об’єкт знає свій унікальний ідентифікатор. Унікальний ідентифікатор складається з індексу та тегу версії. Список непрямості - це просто масив індексів до основного списку об'єктів. Вільний список - це стек індексів до непрямого списку.
Коли ви створюєте об’єкт у головному списку, ви знаходите невикористаний запис у списку непрямості (використовуючи безкоштовний список). Запис у непрямому списку вказує на невикористаний запис у головному списку. Ви ініціалізуєте ваш об'єкт у цьому місці та встановлюєте його унікальний ідентифікатор до індексу обраної вами списку непрямості та наявного тегу версії в головному елементі списку плюс один.
Коли ви знищуєте об'єкт, ви робите своп-поп як звичайний, але також збільшуєте номер версії. Потім ви також додаєте до вільного списку індекс непрямого переліку (частина унікального ідентифікатора об’єкта). Під час переміщення об'єкта як частини підкачки, ви також оновите його запис у списку непрямості на його нове місце.
Приклад псевдокоду:
Object:
int index
int version
other data
SlotMap:
Object objects[]
int slots[]
int freelist[]
int count
Get(id):
index = indirection[id.index]
if objects[index].version = id.version:
return &objects[index]
else:
return null
CreateObject():
index = freelist.pop()
objects[count].index = id
objects[count].version += 1
indirection[index] = count
Object* object = &objects[count].object
object.initialize()
count += 1
return object
Remove(id):
index = indirection[id.index]
if objects[index].version = id.version:
objects[index].version += 1
objects[count - 1].version += 1
swap(objects[index].data, objects[count - 1].data)
Шар непрямості дозволяє мати стабільний ідентифікатор (індекс у шар непрямості, куди записи не переміщуються) для ресурсу, який може переміщатися під час ущільнення (основний список об'єктів).
Тег версії дозволяє зберігати ідентифікатор до об'єкта, який може бути видалений. Наприклад, у вас є ідентифікатор (10,1). Об'єкт з індексом 10 видаляється (скажімо, ваша куля потрапляє в об’єкт і знищується). Об'єкт у цьому місці пам'яті в головному списку об'єктів потім має свій номер версії, надісланий (10,2). Якщо ви спробуєте переглянути (10,1) ще раз із застарілого ідентифікатора, пошук повертає цей об’єкт через індекс 10, але може побачити, що номер версії змінився, тому ідентифікатор більше не дійсний.
Це абсолютна найшвидша структура даних, яку ви можете мати зі стабільним ідентифікатором, який дозволяє переміщувати об’єкти в пам'яті, що важливо для локальності даних та узгодженості кешу. Це швидше, ніж будь-яка реалізація хеш-таблиці; хеш-таблиці як мінімум потрібно обчислити хеш (більше інструкцій, ніж пошук таблиці), а потім повинен слідувати хеш-ланцюжку (або пов'язаний список у жахливому випадку std :: unordered_map, або список з відкритою адресою в будь-яка нерозумна реалізація хеш-таблиці), а потім має зробити порівняння значень для кожного ключа (не дорожче, але можливо менш дороге, ніж перевірка тегів версії). Дуже хороша хеш-таблиця (не та, яка застосовується в будь-якій реалізації STL, оскільки STL призначає хеш-таблицю, яка оптимізується для різних випадків використання, ніж ви граєте для списку ігрових об'єктів), може економити на одному непрямому,
Ви можете вдосконалити базовий алгоритм. Використання, наприклад, std :: deque для основного списку об'єктів; ще один додатковий шар непрямості, але дозволяє об’єкти вставляти у повний список без недійсних тимчасових покажчиків, які ви придбали зі слотмапи.
Ви також можете уникнути зберігання індексу всередині об'єкта, оскільки індекс можна обчислити з адреси пам'яті об'єкта (це - об'єкти), а ще краще це потрібно лише при видаленні об'єкта, і в цьому випадку ви вже маєте ідентифікатор об'єкта (і, отже, індекс) як параметр.
Вибачення за списання; Я не відчуваю, що це найясніший опис. Пізно і важко пояснити, не витрачаючи більше часу, ніж у мене на зразки коду.