Comments 7
Было бы интересно посмотреть бенчмарки "плохого случая" - когда есть скажем 10к энтитей, у 5000 есть компонент А, у 5000 есть компонент B, но только у 100 есть и компонент и А и В. Насколько быстрой в этом случае будет итерация по сущностям содержащим и А и В. entt в этом случае предлагает связанные группы, другие неархетипные ecs на этом случае ломаются или вводят фильтры содержащие список сущностей.
Привет! Добавлю такой кейс и поисследую возможные оптимизации
у меня итерация идёт по первому компоненту из списка в for, и в таком кейсе будет 5000 итераций
Второй компонент может быть nullptr, это можно проверить простым ифом
у меня итерация идёт по первому компоненту из списка в for, и в таком кейсе будет 5000 итераций
Стандартная оптимизация - итерироваться по тому из компонентов, которых меньше. Тогда если в мире будет 5000 компонентов А и 200 компонентов B, то придется делать всего 200 итераций.
Но для "плохого случая" она не поможет, да.
std::map<Transform> t;
главное чтобы вы понимали, что в таком виде оно не очень ecs. std::map представляет собой бинарное дерево, то есть ноды дерева расположены где-то в случайном месте памяти. ECS же старается сделать так чтобы одинаковые компоненты лежали как можно ближе друг дргу для cache locality. В классическом SoA оно выглядело бы как-то так
Transform t[];
PhysicsComp p[];
IsRenderable r[];
Mesh m[];То есть некоторые линейные массивы, последовательная обработка которых отлично предсказывалась процессорным branch predictor. В случае map такого не будет происходить, т.к. обход бинарного дерева не будет проходить по последовательной памяти, а по веткам дерева. std::unordered_map или std::flat_map могут исправить эту ситуацию.
Ну и отдельно стоит заметить, что любой std::*map обычно принимает два шаблонных параметра - тип ключа и тип собственно элемента, так что ваш пример не соберётся.
Ну и обновление в ECS обычно происходит на уровне систем, а не на уровне энтитей.
for (auto [_,tran]: t) tran.tick(); // update transform system
for (auto [_,phys]: p) phys.tick(); // update physics system
/*the rest of systems*/Привет!
Абсолютно верное уточнение что map это не array, сразу после блока с кодом я об этом пишу :)
Этот пример был призван проиллюстрировать реальный способ поиска компонента по его id, но естественно это не оптимальный подход.
Это cpp подобный псевдокод, он не должен собираться, он лишь показывает концепцию.
В случае с ecs нам часто нужно несколько компонентов сразу в одной системе, системой может являться обычная функция, для наглядности псевдокод обновляет данные через системы передавая их в них напрямую.
В следующий раз добавлю пометку "псевдокод", спасибо за фидбек!
А что есть почитать по теме, как делать правильно?
Я бы не брался говорить что есть прям правильный и неправильный путь
Есть оптимальные и нет.
Главное, на мой взгляд, в ecs- это организовать быструю итерацию по компонентам и быстрый доступ к данным.
Система это просто функция которая обновляет данные в компонентах. Сущность - просто хэндл, вокруг которого компоненты объединены.
Дальше начинаются варианты реализации хранения: массивы, архетипы, соа/аос, свои контейнеры и т.п. А то как организованы системы у каждого может быть своё виденье в зависимости от решаемых задач.
Можно почитать документацию по entt или flecs, например, что бы получить какое то представление. У моей ecss тоже есть документация, ее можно найти в гитхабе. Это даст представление.
Гибкая ECS с кастомными layout-профилями: как я строил ECSS внутри своего игрового движка