Вопрос, насколько отличается -O2 и -O3? По моему опыту например SIMD clang/gcc поагрессивней в SIMD оптимизациях при -O3, но реально я не никогда особо не замерял, так как для критической части кода SIMDы писал вручную.
я тоже не замерял разницу O2 и O3, указал O2 потому что на MSVC нет О3, а хотелось что-то универсальное написать. Но по ощущениям - О3 очень агрессивен, и намного охотнее векторизует циклы. Вообще основной посыл был в том что - профилируйте на билдах с оптимизиациями. Я не раз встречался на работе с тем, что ко мне приходили и говорили - лагает! Я расчехляю профилировщик, собираю билд, и не лагает, вот совсем. А оказывалось, что человек замерял на дебаг билде :) Я сам будучи джуном как-то долго и упорно оптимизровал работу с std::vector под дебагом, на дебаге стало работать действительно быстрей, правда на релизе замедлилось :)
Категорически не согласен, причем вы сами приводите пример #10 когда это не так -- как-будто хочется уточнения. Ну и касательно примера 10 тоже хочется отметить, что это должно сворачиваться в SIMD.
Ценное уточнение. Я, наверное, не до конца раскрыл мысль, но моя идея в том что нужно писать минимально необходимое количество кода. То есть весь сахар, функции, абстракции и тп не бесплатные, и в hot path могут стрельнуть. Я вообще подумывал: "а не написать ли ecs на С"... :)
Ну и отдельно хотелось бы отметить что у вас в кучу смешаны оптимизации разных типов, хотел бы от вас комментарий по опыту. Лично я при оптимизации стараюсь придерживаться приоритета кэш промахи -> ветвление -> арифметика, сам не до конца уверен что это универсальный подход. А какого подхода придерживаетесь вы?
Согласен с вашим подходом. Какой смысл оптимизировать итерацию по std::map если проблема в первую очередь в физике, а не в коде. Сначала нужно правильно данные подготовить, потом минимизировать накладные расходы на их обработку. В целом, если данные в памяти лежат эффективно, то это уже успех. Причем это касается как проектирования новой системы так и оптимизации уже готовых. На работе не часто приходят задачи - сделай чтобы работало так быстро насколько это возможно, и как правило низковисящие фрукты это - неправильный контейнер, неправильный доступ, лишние аллокации. А это всё как раз кэш мисы. И когда этот вопрос решаешь - то качество уже приемлемо для продакшена.
Про деление на константу - да, компилятор заменяет на умножение + сдвиг. Добавлю в статью, спасибо!
Согласен, если try/catch далеко от hot path - влияние минимально. Моя рекомендация больше про "не оборачивать каждый вызов в цикле", а не "никогда не использовать исключения". Для I/O и редких ошибок - исключения ок В hot-path, как вы верно подметили - как повезет
Вы правы, switch-case тоже имеет indirect jump и misprediction. Но разница в том, что код case'ов находится рядом (один I-cache), а виртуальные функции разбросаны по памяти. Плюс при switch компилятор видит весь код и может оптимизировать, а при virtual - нет.
Но в целом согласен - если альтернатива виртуалке это switch на 20 типов, разница будет небольшой :)
call(A*, int volatile&):
mov rax, qword ptr [rdi]
mov rax, qword ptr [rax + 16]
jmp rax ; всё еще indirect call
call(C*, int volatile&):
mov eax, dword ptr [rsi] ; сработал inline, больше нет call
lea eax, [rax + 4*rax]
lea eax, [rax + 2*rax]
mov dword ptr [rsi], eax
ret
во вторых, indirect jmp + два mov это не бесплатно само по себе, это дополнительные инструкции в hot path
в третьих, indirect call усложняет branch prediction процесса, misprediction это откат конвейера - 15-20 циклов на ровном месте, это дороже инструкций vtable. В hot path это может быть критично, но это дешево
https://quick-bench.com/q/wZIoe19-C6zRnts3Js5OufEmI4U - тут видно что при 4 типах из-за как раз missprediction время выполнения увеличивается в 4 раз хотя на 1 типе разницы почти, inline ≈ direct call вероятно из-за погрешностей бенчмарка, из-за того что в inline почти не происходит работы он должен был быть быстрее direct call раза в 4, но у меня в бенчах получалось либо 0, либо примерно direct call, может у вас получится написать бенчмарк который корректно замерит :)
А они в этом кейсе меня и уделывают к сожалению. Я рад что вы подсветили мне этот кейс, пока что выглядит как трейд оф архитектуры, но мне эти бенчмарки мозолят глаза и хочется как-то ускориться
Да, обычный вызов (direct call) CPU предсказывает гораздо лучше.
Причина не в кэше памяти, а в том, что у direct call цель перехода известна заранее - адрес зашит прямо в инструкции. CPU может заранее начать подтягивать инструкции и почти не ошибается.
В случае virtual вызова цель становится известна только после чтения vptr и vtable, и сам переход - это indirect call. Такие переходы CPU предсказывает хуже, особенно если реальные типы объектов часто меняются.
Если vtable и код функции лежат в L1-кэше это ускоряет доступ но не отменяет indirect, CPU узнаёт адрес слишком поздно, и при ошибке предсказания приходится сбрасывать конвейер.
Привет, я немного раскрыл этот вопрос в своей следующей статье. В случае итерации по [B|F|M] при существовании секторов [A | B | C], [D | F | G] и отдельного [ M ] - отбор будет через sparse, но без лишних прыжков по памяти, за О(1) мы получаем сразу указатель на компонент, это быстро. Но сама итерация по таким наборам будет не максимально быстрой, как минимум потому что кэш будет забит лишними данными.
Это не оптимальный кейс.
Необходимость паковать компоненты рядом - редкая, потому что профит от этого может быть не везде, а иногда вред перекроет профит. Но плюс в том - что эта настройка в ваших руках :)
Архетипные ECS (flecs, Unity DOTS) тоже требуют "знать" структуру сущностей - просто это скрыто за рантайм-миграциями. В DOTS ты всё равно явно создаёшь архетипы через EntityManager.CreateArchetype() для производительности.
В ecss это explicit, а не implicit - честнее и предсказуемее. Дефолт: один компонент на сектор. Группировка - опциональная оптимизация для hot path, а не требование.
По поводу итерации, да, это worst case. Но: eсли [B|F] частый запрос - просто не группируйте их, бенчмарк iter_sparse_multi тестирует именно это - ecss там 0.5x от entt, не катастрофа, учитывая что в других кейсах ecss быстрее. Особенно создание и удаление новых компонентов - https://wagnerks.github.io/ecss_benchmarks/
Архетипы платят эту цену каждый раз при добавлении/удалении компонента. В реальной игре это сотни операций: подобрал предмет, вошёл в триггер, получил бафф и тд и тп
Разница в том что эту структуру нужно заранее создать, назвать ее AandB, хранить как один компонент, и при итерации в системах брать AandB, а потом вытаскивать явно A или/и B. И у вас не может отсутствовать A или B, они полюбому есть.
У меня сache locality без жёсткой связи: компоненты хранятся рядом в секторе [A | B | C], что даёт те же преимущества кеша, что и ручная структура, но без потери гибкости ECS (можно запрашивать только A, можно добавлять/удалять компоненты независимо).
Тоесть это вопрос удобства и гибкости, я автоматизировал создание таких структур, и позволяю работать с вложенными в нее компонентами как с обычным.
flecs - архетипы, и с точки зрения простой итерации это лучший кейс, из-за этого она быстрее. Можно ли приблизиться к ней без архетипов и мержа данных - я буду пробовать :)
а ecs оперируют компонентами которые уже содержат несколько элементарных типов. И прям по честному - ECS почти(возможно кто-то это сделал, но это ужас с точки зрения интерфейса) никогда не SoA, нужно разворачивать каким-то образом структуры компонентов на составляющие, укладывать их в памяти, а потом в системах еще и правильно эти данные использовать чтобы выжать максимум из этого. В мою ecs можно положить компонент posX, posY, posZ и это будет почти настойщий SoA, но нужно делать это явно
В итоге SoA в контексте ECS это упрощение, где за единицу информации берется компонент, это дает некоторый бонус с точки зрения кэша и скорости итерации, но не все бенефиты SoA.
Отвечая на вопрос - чем это сильно лучше чем храните в векторе - тем что менеджер создания и распределения компонентов, а так же правильной итерации по несколькик компонентам одновременно. Но можно просто сделать N векторов и через i обращаться к каждому в цикле. В предыдущей статье я как раз описывал что-то подобное как самый просто пример ECS. А мой подход лучше тем - что я могу класть физически близко компоненты которые используются часто, и повышать дружелюбность кэша там где это выгодно.
Агрегатный компонент = архетип. Так делает часть ECS, и это рабочий вариант.
Но минусы такие:
количество архетипов растёт взрывным образом,
при каждом изменении набора компонентов нужно мигрировать сущность между архетипами,
миграции - это копирование данных,
сложность увеличивается пропорционально числу комбинаций.
То есть агрегатный компонент - это жёстко связанный тип. Sector в ECSS решает ту же задачу, но на уровне памяти, а не типов.
по бенчмарку - честно говоря пока не особо, есть вероятность что я неправильно замерил скорость для flecs, планирую поковыряться в этом, потому что если flecs может быстрее, то я тоже так хочу :) Или flecs использует архетипы и у него меньше накладных расходов на итерацию
В моей ECS линейность относится только к размещению компонентов в памяти - чтобы итерация была максимально быстрой и кэш-френдли. Это не заменяет и не отменяет spatial-структур вроде BVH.
Системы, которым нужно искать отдельные объекты используют отдельные структуры данных - например, BVH, Octree, Quadtree, uniform grid и т. д. Они работают поверх ECS, а не внутри неё. У меня в движке, например, есть отдельная octree система в которой лежат entityid.
ECS хранит данные максимально плотно, а алгоритмы типа BVH используют эти данные.
Линейность по entityId никак не конфликтует с использованием деревьев для игрового мира. Она лишь ускоряет итерацию в системах, которым нужно пройти по компонентам в памяти.
Привет! Да, одного прохода действительно хватает. Алгоритм работает в O(N): я просто сканирую линейный упорядоченный массив секторов и “уплотняю” живые данные влево, пропуская мёртвые.
Так как структура уже отсортирована и не требует дополнительных поисков, дефрагментация - это один линейный проход без каких-либо вложенных операций, поэтому скорость не проседает.
Привет! Компиляция занимает секунды на моей машине. Но чем больше будет компонентов тем дольше будет компиляция, спасибо темплейтам и тому как они быстро и классно собираются(нет) :harold: https://github.com/wagnerks/ecss/actions/runs/18601409608/job/53040504015 если интересно: тут можно посмотреть сколько занимает build на гитхабовский серверах, в среднем в районе 30 секунд. Я потратил какое-то время на оптимизацию времени сборки, основной оверхед темплейты, и наверняка еще есть куда оптимизировать.
По поводу серализации - я думал над ней, но пока не дошли руки. Пришивать ее "жоско" к моей ECS не хочется. Вероятно сделаю отдельным модулем которому можно будет скормить реестр, и это будет выглядеть примерно так:
Serializer s;
Registry r;
auto obj = s.serialize(r);
Мне просто не хочется смешивать ECS с чем-то ещё, особенно с вещами, которые можно реализовать поверх неё.
Например, сигналы: они есть во многих фреймворках, но их спокойно можно реализовать поверх ECS, не внутри неё - и такая реализация даже будет быстрее, чем generic-вариант, который создаёт оверхед даже если вы им не пользуетесь. А городить зоопарк выключателей мне тоже не хочется - я и так настрадался с опциональным включением thread safety :)
Про сравнения с классическими подходами: имеете ввиду c обычным вектором структур? Я делал такие сравнения пока оптимизировал, запишу себе добавить этот бенчмарк, у меня уже небольшой список вырисовывается :)
Надеюсь смогу к следующей статье (примерно через неделю), где как раз буду рассказывать про итерацию.
И за p.s. спасибо! поправил. И за спасибо тоже спасибо))
Я бы не брался говорить что есть прям правильный и неправильный путь
Есть оптимальные и нет.
Главное, на мой взгляд, в ecs- это организовать быструю итерацию по компонентам и быстрый доступ к данным.
Система это просто функция которая обновляет данные в компонентах. Сущность - просто хэндл, вокруг которого компоненты объединены.
Дальше начинаются варианты реализации хранения: массивы, архетипы, соа/аос, свои контейнеры и т.п. А то как организованы системы у каждого может быть своё виденье в зависимости от решаемых задач.
Можно почитать документацию по entt или flecs, например, что бы получить какое то представление. У моей ecss тоже есть документация, ее можно найти в гитхабе. Это даст представление.
Абсолютно верное уточнение что map это не array, сразу после блока с кодом я об этом пишу :)
Этот пример был призван проиллюстрировать реальный способ поиска компонента по его id, но естественно это не оптимальный подход.
Это cpp подобный псевдокод, он не должен собираться, он лишь показывает концепцию.
В случае с ecs нам часто нужно несколько компонентов сразу в одной системе, системой может являться обычная функция, для наглядности псевдокод обновляет данные через системы передавая их в них напрямую.
В следующий раз добавлю пометку "псевдокод", спасибо за фидбек!
Привет, спасибо!
я тоже не замерял разницу O2 и O3, указал O2 потому что на MSVC нет О3, а хотелось что-то универсальное написать. Но по ощущениям - О3 очень агрессивен, и намного охотнее векторизует циклы.
Вообще основной посыл был в том что - профилируйте на билдах с оптимизиациями.
Я не раз встречался на работе с тем, что ко мне приходили и говорили - лагает!
Я расчехляю профилировщик, собираю билд, и не лагает, вот совсем. А оказывалось, что человек замерял на дебаг билде :)
Я сам будучи джуном как-то долго и упорно оптимизровал работу с std::vector под дебагом, на дебаге стало работать действительно быстрей, правда на релизе замедлилось :)
Ценное уточнение. Я, наверное, не до конца раскрыл мысль, но моя идея в том что нужно писать минимально необходимое количество кода. То есть весь сахар, функции, абстракции и тп не бесплатные, и в hot path могут стрельнуть.
Я вообще подумывал: "а не написать ли ecs на С"... :)
Согласен с вашим подходом.
Какой смысл оптимизировать итерацию по std::map если проблема в первую очередь в физике, а не в коде.
Сначала нужно правильно данные подготовить, потом минимизировать накладные расходы на их обработку.
В целом, если данные в памяти лежат эффективно, то это уже успех.
Причем это касается как проектирования новой системы так и оптимизации уже готовых.
На работе не часто приходят задачи - сделай чтобы работало так быстро насколько это возможно, и как правило низковисящие фрукты это - неправильный контейнер, неправильный доступ, лишние аллокации. А это всё как раз кэш мисы. И когда этот вопрос решаешь - то качество уже приемлемо для продакшена.
Про деление на константу - да, компилятор заменяет на умножение + сдвиг. Добавлю в статью, спасибо!
Согласен, если try/catch далеко от hot path - влияние минимально. Моя рекомендация больше про "не оборачивать каждый вызов в цикле", а не "никогда не использовать исключения".
Для I/O и редких ошибок - исключения ок
В hot-path, как вы верно подметили - как повезет
Вы правы, switch-case тоже имеет indirect jump и misprediction.
Но разница в том, что код case'ов находится рядом (один I-cache), а виртуальные функции разбросаны по памяти. Плюс при switch компилятор видит весь код и может оптимизировать, а при virtual - нет.
Но в целом согласен - если альтернатива виртуалке это switch на 20 типов, разница будет небольшой :)
во первых, virtual не даст заинлайнить код, и оверхед обычного вызова, про который вы пишете, будет в дополненение к оверхеду виртуализации.
https://godbolt.org/z/4vrx1aTrz - тут я убрал noinline и это хорошо видно в ассемблере
во вторых, indirect jmp + два mov это не бесплатно само по себе, это дополнительные инструкции в hot path
в третьих, indirect call усложняет branch prediction процесса, misprediction это откат конвейера - 15-20 циклов на ровном месте, это дороже инструкций vtable. В hot path это может быть критично, но это дешево
https://quick-bench.com/q/wZIoe19-C6zRnts3Js5OufEmI4U - тут видно что при 4 типах из-за как раз missprediction время выполнения увеличивается в 4 раз
хотя на 1 типе разницы почти, inline ≈ direct call вероятно из-за погрешностей бенчмарка, из-за того что в inline почти не происходит работы он должен был быть быстрее direct call раза в 4, но у меня в бенчах получалось либо 0, либо примерно direct call, может у вас получится написать бенчмарк который корректно замерит :)
А они в этом кейсе меня и уделывают к сожалению. Я рад что вы подсветили мне этот кейс, пока что выглядит как трейд оф архитектуры, но мне эти бенчмарки мозолят глаза и хочется как-то ускориться
Привет!
Да, обычный вызов (direct call) CPU предсказывает гораздо лучше.
Причина не в кэше памяти, а в том, что у direct call цель перехода известна заранее - адрес зашит прямо в инструкции. CPU может заранее начать подтягивать инструкции и почти не ошибается.
В случае virtual вызова цель становится известна только после чтения vptr и vtable, и сам переход - это indirect call. Такие переходы CPU предсказывает хуже, особенно если реальные типы объектов часто меняются.
Если vtable и код функции лежат в L1-кэше это ускоряет доступ но не отменяет indirect, CPU узнаёт адрес слишком поздно, и при ошибке предсказания приходится сбрасывать конвейер.
https://godbolt.org/z/ca31T8cf6 - я сделал минимальный пример на котором видно как работает виртуализация
Справедливости ради, если компилятор может девиртуализировать вызов, он превращается в обычный direct call и весь этот overhead исчезает
ответил не туда :)
Привет, я немного раскрыл этот вопрос в своей следующей статье.
В случае итерации по [B|F|M] при существовании секторов [A | B | C], [D | F | G] и отдельного [ M ] - отбор будет через sparse, но без лишних прыжков по памяти, за О(1) мы получаем сразу указатель на компонент, это быстро.
Но сама итерация по таким наборам будет не максимально быстрой, как минимум потому что кэш будет забит лишними данными.
Это не оптимальный кейс.
Необходимость паковать компоненты рядом - редкая, потому что профит от этого может быть не везде, а иногда вред перекроет профит. Но плюс в том - что эта настройка в ваших руках :)
По поводу "ада менеджмента":
Архетипные ECS (flecs, Unity DOTS) тоже требуют "знать" структуру сущностей - просто это скрыто за рантайм-миграциями. В DOTS ты всё равно явно создаёшь архетипы через EntityManager.CreateArchetype() для производительности.
В ecss это explicit, а не implicit - честнее и предсказуемее. Дефолт: один компонент на сектор. Группировка - опциональная оптимизация для hot path, а не требование.
По поводу итерации, да, это worst case. Но: eсли [B|F] частый запрос - просто не группируйте их, бенчмарк iter_sparse_multi тестирует именно это - ecss там 0.5x от entt, не катастрофа, учитывая что в других кейсах ecss быстрее. Особенно создание и удаление новых компонентов - https://wagnerks.github.io/ecss_benchmarks/
Архетипы платят эту цену каждый раз при добавлении/удалении компонента. В реальной игре это сотни операций: подобрал предмет, вошёл в триггер, получил бафф и тд и тп
Разница в том что эту структуру нужно заранее создать, назвать ее AandB, хранить как один компонент, и при итерации в системах брать AandB, а потом вытаскивать явно A или/и B.
И у вас не может отсутствовать A или B, они полюбому есть.
У меня сache locality без жёсткой связи: компоненты хранятся рядом в секторе [A | B | C], что даёт те же преимущества кеша, что и ручная структура, но без потери гибкости ECS (можно запрашивать только A, можно добавлять/удалять компоненты независимо).
Тоесть это вопрос удобства и гибкости, я автоматизировал создание таких структур, и позволяю работать с вложенными в нее компонентами как с обычным.
привет
https://wagnerks.github.io/ecss_benchmarks/
добавил sparse iteration в бенчмарки, можете глянуть :)
https://wagnerks.github.io/ecss_benchmarks/ - я добавил в бенчи обычный вектор для сравнения, можете посмотреть
flecs - архетипы, и с точки зрения простой итерации это лучший кейс, из-за этого она быстрее. Можно ли приблизиться к ней без архетипов и мержа данных - я буду пробовать :)
Привет!
Технически SoA это следующая конструкция:
а ecs оперируют компонентами которые уже содержат несколько элементарных типов.
И прям по честному - ECS почти(возможно кто-то это сделал, но это ужас с точки зрения интерфейса) никогда не SoA, нужно разворачивать каким-то образом структуры компонентов на составляющие, укладывать их в памяти, а потом в системах еще и правильно эти данные использовать чтобы выжать максимум из этого.
В мою ecs можно положить компонент posX, posY, posZ и это будет почти настойщий SoA, но нужно делать это явно
В итоге SoA в контексте ECS это упрощение, где за единицу информации берется компонент, это дает некоторый бонус с точки зрения кэша и скорости итерации, но не все бенефиты SoA.
Отвечая на вопрос - чем это сильно лучше чем храните в векторе - тем что менеджер создания и распределения компонентов, а так же правильной итерации по несколькик компонентам одновременно.
Но можно просто сделать N векторов и через i обращаться к каждому в цикле. В предыдущей статье я как раз описывал что-то подобное как самый просто пример ECS.
А мой подход лучше тем - что я могу класть физически близко компоненты которые используются часто, и повышать дружелюбность кэша там где это выгодно.
Агрегатный компонент = архетип. Так делает часть ECS, и это рабочий вариант.
Но минусы такие:
количество архетипов растёт взрывным образом,
при каждом изменении набора компонентов нужно мигрировать сущность между архетипами,
миграции - это копирование данных,
сложность увеличивается пропорционально числу комбинаций.
То есть агрегатный компонент - это жёстко связанный тип. Sector в ECSS решает ту же задачу, но на уровне памяти, а не типов.
по бенчмарку - честно говоря пока не особо, есть вероятность что я неправильно замерил скорость для flecs, планирую поковыряться в этом, потому что если flecs может быстрее, то я тоже так хочу :) Или flecs использует архетипы и у него меньше накладных расходов на итерацию
Привет!
В моей ECS линейность относится только к размещению компонентов в памяти - чтобы итерация была максимально быстрой и кэш-френдли. Это не заменяет и не отменяет spatial-структур вроде BVH.
Системы, которым нужно искать отдельные объекты используют отдельные структуры данных - например, BVH, Octree, Quadtree, uniform grid и т. д. Они работают поверх ECS, а не внутри неё. У меня в движке, например, есть отдельная octree система в которой лежат entityid.
ECS хранит данные максимально плотно, а алгоритмы типа BVH используют эти данные.
Линейность по entityId никак не конфликтует с использованием деревьев для игрового мира. Она лишь ускоряет итерацию в системах, которым нужно пройти по компонентам в памяти.
Привет!
Да, одного прохода действительно хватает. Алгоритм работает в O(N): я просто сканирую линейный упорядоченный массив секторов и “уплотняю” живые данные влево, пропуская мёртвые.
Так как структура уже отсортирована и не требует дополнительных поисков, дефрагментация - это один линейный проход без каких-либо вложенных операций, поэтому скорость не проседает.
Привет!
Компиляция занимает секунды на моей машине.
Но чем больше будет компонентов тем дольше будет компиляция, спасибо темплейтам и тому как они быстро и классно собираются(нет) :harold:
https://github.com/wagnerks/ecss/actions/runs/18601409608/job/53040504015
если интересно: тут можно посмотреть сколько занимает build на гитхабовский серверах, в среднем в районе 30 секунд. Я потратил какое-то время на оптимизацию времени сборки, основной оверхед темплейты, и наверняка еще есть куда оптимизировать.
По поводу серализации - я думал над ней, но пока не дошли руки. Пришивать ее "жоско" к моей ECS не хочется. Вероятно сделаю отдельным модулем которому можно будет скормить реестр, и это будет выглядеть примерно так:
Мне просто не хочется смешивать ECS с чем-то ещё, особенно с вещами, которые можно реализовать поверх неё.
Например, сигналы: они есть во многих фреймворках, но их спокойно можно реализовать поверх ECS, не внутри неё - и такая реализация даже будет быстрее, чем generic-вариант, который создаёт оверхед даже если вы им не пользуетесь. А городить зоопарк выключателей мне тоже не хочется - я и так настрадался с опциональным включением thread safety :)
Про сравнения с классическими подходами: имеете ввиду c обычным вектором структур? Я делал такие сравнения пока оптимизировал, запишу себе добавить этот бенчмарк, у меня уже небольшой список вырисовывается :)
Надеюсь смогу к следующей статье (примерно через неделю), где как раз буду рассказывать про итерацию.
И за p.s. спасибо! поправил.
И за спасибо тоже спасибо))
Я бы не брался говорить что есть прям правильный и неправильный путь
Есть оптимальные и нет.
Главное, на мой взгляд, в ecs- это организовать быструю итерацию по компонентам и быстрый доступ к данным.
Система это просто функция которая обновляет данные в компонентах. Сущность - просто хэндл, вокруг которого компоненты объединены.
Дальше начинаются варианты реализации хранения: массивы, архетипы, соа/аос, свои контейнеры и т.п. А то как организованы системы у каждого может быть своё виденье в зависимости от решаемых задач.
Можно почитать документацию по entt или flecs, например, что бы получить какое то представление. У моей ecss тоже есть документация, ее можно найти в гитхабе. Это даст представление.
Привет!
Абсолютно верное уточнение что map это не array, сразу после блока с кодом я об этом пишу :)
Этот пример был призван проиллюстрировать реальный способ поиска компонента по его id, но естественно это не оптимальный подход.
Это cpp подобный псевдокод, он не должен собираться, он лишь показывает концепцию.
В случае с ecs нам часто нужно несколько компонентов сразу в одной системе, системой может являться обычная функция, для наглядности псевдокод обновляет данные через системы передавая их в них напрямую.
В следующий раз добавлю пометку "псевдокод", спасибо за фидбек!
Привет! Добавлю такой кейс и поисследую возможные оптимизации
у меня итерация идёт по первому компоненту из списка в for, и в таком кейсе будет 5000 итераций
Второй компонент может быть nullptr, это можно проверить простым ифом