Suslik
> нет у систем состояния
догма. Systems = системЫ. Log- система, и конечно имеет состояние. система без состояния - это чистая функция(синус). Тебе нравятся такие системы? Ни вопрос, юзай.
Но вообще я и не хочу пускаться в словоблудие про системы. Systems.Get<Log>() - вполне удобный код доступа к любому сквозному функционалу. И это в стиле DI, облегчает TDD.. и придумано ни мной. А вот в чем состоит ECS твой - ты и сам не понимашь.
/A\
> от создателей танков (тут есть про сингл компоненты)
> https://habr.com/ru/company/wargaming/blog/245321/
да, здесь конкретики побольше. Мне это напоминает Dd in memory + опять все тот же DI. Никакой отмены ООП, актор-based логики нет. Бд в памяти позволяет делать какие то запросы, примерно также как в бизнес приложениях ORM ни отменяет специфические запросы на сервер с SQL.
Почему это выдается за новый убер подход - хрен знает.
идея с архетипами мне понравилась, узнал для себя много нового, читая про существующие оптимизированные ECS фреймворки. прочитал много идей по поводу того, как эффективно реализуется сам ECS: быстрый поиск компонентов, группировка по архетипам итп. это очень правильные идеи, я считаю, и мне кажется, переход игровой логики на продвинутые реализациям ECS можно сравнить к переходу рендера на фреймграф. как во фреймграфе, так и в ECS, мы сообщаем программе больше данных об общем устройстве нашей системы (множества игровых объектов или структуре всего кадра) и далее, обладая полной информацией, можем реализовать более сильные алгоритмы, которые оптимизируют работу с ними.
например, фреймграф может определить, сколько именно рендертаргетов нужно на весь кадр и реюзать их при необходимости. а правильно реализованная ECS может автоматически параллелить системы по характеру их доступа к компонентам.
однако, у меня остаётся не меньше вопросов, как правильно решать тривиальные проблемы, используя чистый ECS подход. то есть легко сказать : "системы не содержат данных, компоненты не содержат логики", но гораздо сложнее понять, как именно это предполагается делать в некоторых конкретных случаях.
/A\
> 1. Я думаю правильная ECS архитектура выглядит так:
>
> Компоненты это просто база данных (айди меша, материала и тд)
> системы создают свою, оптимизированную репрезентацию. Нужна иерархия - собирают
> граф зависимостей, нужно кэшировать пайплайны для вулкана - цепляют компонент с
> пайплайном.
например, это прямым образом расходится с основополагающим принципом, что в системах не содержится данных. однако, я тоже не представляю, как именно это должно работать, например, с физическим движком, который, являясь системой, просто by design ещё как содержит в себе оптимизированные для себя данные. вообще любая 3rd party система вроде рендерера всегда будет содержать какакие-то данные, я не видел stateless renderer'ов или stateless физических движков.
с другой стороны, в близзардовском докладе они говорили, что если какая-то система хочет построить своё представление объектов, то правильнее вынести это представление в отдельные компоненты и с большой вероятностью это представление может понадобиться ещё каким-то системам. однако, это никак не отвечает на вопрос, как хранить представления, которые не являются локальными для энтитей — например, общее AABB tree всей сцены, всякие графы порталов между комнатами и прочие нелокальные сущности, не относящиеся к отдельным энтитям. тот же фреймграф не получится хранить в компонентах энтитей, хотя он и рендерит эти самые энтити.
например, прикольно, что во flecs системы вообще — это функции, принимают на вход-выход указанный набор компонент. это, я считаю, классная идея, потому что by design эти функции не могут содержать в себе данных и поэтому сам движок ecs может производить над ними очень алгоритмически сильные оптимизации вроде автоматического распараллеливания или даже векторизации:
struct Position { float x; float y; }; struct Speed { float value; }; int main(int argc, char *argv[]) { flecs::world world( argc, argv); flecs::component<Position>( world, "Position"); flecs::component<Speed>( world, "Speed"); flecs::system<Position, Speed>( world) .each( []( flecs::entity e, Position& p, Speed& s) { p.x += s.value * e.delta_time( ); p.y += s.value * e.delta_time( ); }); flecs::entity( world, "MyEntity") .set<Position>( {0, 0}) .set<Speed>( {1}); while ( world.progress( )) { } }
Suslik
> должно работать, например, с физическим движком, который, являясь системой,
> просто by design ещё как содержит в себе оптимизированные для себя данные.
В твоем первом видео же ответ. Физдвижок - не система, а компонент.
jaguard
> В твоем первом видео же ответ. Физдвижок - не система, а компонент.
да, но меня смущает, что так больше никто кроме них не считает, лол. также не забывай, что компоненты не должны содержать логики. а 3rd party физдвижок ещё как содержит кучу логики. у них у самих ссылка на физдвижок хранится в их PhysicsSystem, что, вообще говоря, не совсем чисто.
Suslik
> прикольно, что во flecs системы вообще — это функции, принимают на вход-выход указанный набор компонент
У меня вообще нет "систем", просто функция или лямбда, в которой указаны требуемые компоненты и сингл-компоненты.
Системы как классы у меня хранят айди запросов для быстрого поиска по архетипам.
/A\
> У меня вообще нет "систем", просто функция или лямбда, в которой указаны
> требуемые компоненты и сингл-компоненты.
> Системы как классы у меня хранят айди запросов для быстрого поиска по
> архетипам.
покажи пример кода, который делает то же самое, что код выше, с использованием твоего синтаксиса?
PS вообще здорово, что я тему создал. иначе б я навелосипедил, как я себе это сам представлял, когда можно гораздо лучше.
Suslik
до оптимизаций запросов было так
Registry reg; for (int i = 0; i < 100; ++i) { reg.CreateEntity<Position, Speed>( ); } reg.AssignSingleComponent<TimeDelta>( ) = 1.0 / 60.0; reg.Enque( [] ( ArrayView< Tuple< /*count*/size_t, ReadAccess<Speed>, WriteAccess<Position>>> chunks, Tuple<TimeDelta> single) { for ( auto& chunk : chunks) { auto speed = chunk.Get<Speed>( ); auto position = chunk.Get<Position>( ); for ( size_t i = 0, count = chunk.Get<0>( ); i < count; ++i) { position[i] += speed[i] * single.Get<TimeDelta>( ); } } });
После оптимизации все эти ReadAccess и WriteAccess ушли в закэшированные запросы.
Исходники моей ECS тут
Кстати:
flecs::entity e ... e.delta_time()
для деятелей вроде меня, кто не является экспертом по ECS, но хочет узнать больше, очень крутой источник соображений по дизайну вот тут: https://github.com/SanderMertens/flecs/blob/master/Manual.md#design-goals там рассказывается, что именно делать не рекомендуется и почему. если задуматься, то это очевидно, но я лично не задумывался.
/A\
> это же не ECS !
как я понимаю, это связано с тем, что они в дизайн закладывают разную частоту обновления для разных энтитей, поэтому таймстеп может быть свой для каждой энтити, определяется самой ecs и является частью её дизайна. но я тоже покорчился, когда это читал.
/A\
> до оптимизаций запросов было так
интересно. у тебя, в отличие от них, поддерживается ReadAccess/WriteAccess, что, опять же, лично для меня явялется отсылкой к фреймграфам. ещё у тебя поддерживаются single component'ы.
однако, мне у них больше нравится, как осуществляется доступ к чанкам через массивы, а не через итераторы:
flecs::system<Position, Velocity>(world) .action( []( const flecs::rows& rows, flecs::column<Position> p, flecs::column<Velocity> v) { for ( auto row : rows) { p[row].x += v[row].x; p[row].y += v[row].y; std::cout << "Moved " << rows.entity( row).name( ) << " to {" << p[row].x << ", " << p[row].y << "}" << std::endl; } });
Suslik
> однако, мне у них больше нравится, как осуществляется доступ к чанкам через массивы, а не через итераторы:
у меня тоже через индексы, в функцию передается массив чанков (с разными археипами), а потом по каждому чанку пробегаешь как по массиву.
Suslik
> PS вообще здорово, что я тему создал. иначе б я навелосипедил, как я себе это
> сам представлял, когда можно гораздо лучше.
Один мой друг целый год изучал ECS и писал свою оптимизированную версию и то недописал.
Но мне было у кого спрашивать, когда я все это изучал.
Написать ECS в лучших традициях это еще не так сложно, а вот потом вылезут архитектурные проблемы, типа того же "куда засунуть дебаг рендер". Но почему-то мало кто делится своим опытом.
/A\
> у меня тоже через индексы, в функцию передается массив чанков (с разными
> археипами), а потом по каждому чанку пробегаешь как по массиву.
а, точно, я осознал. у flecs разница в том, что они вызывают один раз лямбду на чанк, а ты сам итерируешься по чанкам. разница, например, в том, что ты обязан сам где-то выделять память под массив чанков для каждого итерирования, а они могут не выделять и строить их на ходу.
ещё мне у тебя кажется странным тут:
> for (size_t i = 0, count = chunk.Get<0>(); i < count; ++i)
почему ты сделал первым элементом тупла количество, вместо того, чтобы сделать какой-нибудь метод вроде .GetSize()? что если я забуду вот тут:
> reg.Enque([] (ArrayView< Tuple< /*count*/size_t, ReadAccess<Speed>, WriteAccess<Position>>> chunks, Tuple<TimeDelta> single)
перечислить size_t?
ещё мне кажется, что методы вроде AssignSingleComponent<TimeDelta>() можно было бы избежать и вместо этого сделать так:
reg.GetSingletonEntity().SetComponent<TimeDelta>( ...);
> Один мой друг целый год изучал ECS и писал свою оптимизированную версию и то недописал.
я склоняюсь к мысли не изобретать велосипед в этом, потому что мне более-менее нравятся сразу несколько готовых реализаций. поэтому я присматриваюсь к тому, чтобы взять одну из них.
> Написать ECS в лучших традициях это еще не так сложно, а вот потом вылезут архитектурные проблемы, типа того же "куда засунуть дебаг рендер". Но почему-то мало кто делится своим опытом.
я всё ещё читаю все ссылки, которые ты привёл выше. если ещё что-то вспомнишь, пость. пока что очень полезно и я много нового узнал.
читаю документацию к EnTT: https://skypjack.github.io/entt/md_docs_md_entity.html
Many of the tools described below give the possibility to get this information and have been designed around this need.
вообще я заметил, что бывают области, в которых вообще нет решения, которое меня бы просто, блин, устраивало. например, я не нашёл нормального (удобного, компактного) загрузчика 3д моделей. или, например, я не нашёл нормального(в моём понимании) загрузчика текстур. однако, душа радуется, когда ищешь библиотеку, и находишь сразу несколько нормальных, и сидишь, выбираешь, какая из них нравится больше, хотя в общем-то, нравятся все.
ещё оттуда же прочитал, что энтити — это интовый id + версия. версия, карл, это же гениально! то есть у них id — это просто индекс в свой внутренний пул. но если энтити удалить и пересоздать, то этот индекс будет переиспользован и я последние несколько лет ломал голову, как нормально написать функцию вроде IsAlive(id) для подобных систем с пулом, построенном на массиве. это же гениально, можно просто использовать версию объекта с этим индексом и проверить, совпадает ли версия ныне живущего энтити, соответствующий этому id, с тем, который запросили. аа, как я сам до этого не догадался?!