Pull to refresh

Comments 30

Если пишите про компонентную систему, то надо тогда и про систему зависимостей пост в догонку написать.
Что есть «система зависимостей»? Фильтрация сущностей с определенными компонентами, включая составные?
Точнее, система внедрения зависимостей. Да вообще всё, что с ними связано. Это очень важная система, про которую стоит написать. Например, https://habr.com/post/245589 или как совместить эту систему с ECS. И какие есть нюансы, какие решения. В общем, тема для статьи, однозначно.
Это зависит от реализации ECS, применительно к той, которой пользуется автор — все делается автоматически через разметку атрибутами без использования конструкторов:
sealed class TweenPosition {
    public Transform Target;
    public Vector3 From;
    public Vector3 To;
    public float Time = 0f;
    public float Factor = 0f;
}
sealed class TweenPositionSystem : IEcsRunSystem {
    [EcsWorld]
    GameWorld _world;

    [EcsFilterInclude (typeof (TweenPosition))]
    EcsFilter _positionFilter;

    void IEcsRunSystem.Run () {
        for (var i = 0; i < _positionFilter.EntitiesCount; i++) {
            var entity = _positionFilter.Entities[i];
            var tween = _world.GetComponent<TweenPosition> (entity);
            tween.Factor += Time.deltaTime / tween.Time;
            tween.Target.localPosition = Vector3.Lerp (tween.From, tween.To, tween.Factor);
            if (tween.Factor >= 1f) {
                tween.Target.localPosition = tween.To;
                _world.RemoveComponent<TweenPosition> (entity);
            }
        }
    }
}

"_world" — это инстанс-контейнер для всех сущностей, так же предоставляющий апи по управлению ими.
"_positionFilter" — сюда будут автоматически набиваться сущности, на которых гарантированно есть компонент «TweenPosition».
Регистрация систем выглядит примерно так:
var world = GameWorld.Instance;
_systems = new EcsSystems (world)
#if DEV_ENV
    .Add (new Client.Systems.DebugHelpers.FpsCounterSystem ())
#endif
    .Add (new UserSettingsSystem ())
    .Add (new UserProgressSystem ())
    .Add (new InitLevelSystem ())
    .Add (new InitEnvironmentSystem ())
    .Add (new InitStaticsSystem ())
    .Add (new InitDynamicsSystem ())
    .Add (new InitSwitchesSystem ())
    .Add (new InitPlatformsSystem ())
    .Add (new FinishLevelSystem ())
    .Add (new GameUiBoxesSystem ())
    .Add (new UserDesktopInputSystem ())
    .Add (new UserMobileInputSystem ())
    .Add (new FastTurnSystem ())
    .Add (new TrapDetectSystem ())
    .Add (new TweenPositionSystem ())
// ...
    .Add (new InitGameUiSystem ());
_systems.Initialize ();

Обновление вызывается примерно так:
void Update () {
    _systems.Run ();
}

Это все есть в README у каждой реализации ECS и на статью это не тянет.
Да и этот пост на статью не тянет, но его зачем-то написали. Тем более, эта тема интересна с точки зрения теории, а не кода.
Да и комментарий не стоило писать про конкретные особенности DI в реализации, если интересовала только теория.
Неужели вы думаете, что какую-либо реализацию нельзя развить в теорию? Это распространённое заблуждение, что, ограничивая задачу с одной стороны, мы ограничиваем её в целом.
Я имел в виду особенности совместимости ECS и DI в данном случае.
Нельзя, потому что это особенность реализации. Где-то DI делается исключительно через конструктор + все остальные поля запрашиваются руками через апи, что дает много бойлерплейта (Entitas), где-то делается автоматически через reflection (штатная ECS). Никакой разницы нет, функции выполняются идентичные, поэтому не совсем понимаю, почему эта тема показалась настолько важной.
Гибкость и масштабируемость (добавление новых, удаление старых систем и компонентов)

А потом вдруг оказывается, что никакой гибкости нету, и системы, например DI, намертво зашиты в ECS. Какая-то нестыковочка получается.
Так гибкость дается в управлении состояниями компонентов, а не в инициализации, которая является особенностью реализации. Если так рассуждать, то можно докопаться и до того, что юнити нас ограничивает в любом случае необходимостью использования MonoBehaviour-ов, что вообще невозможно использовать — негибко и зашито намертво.
А разве автор не также рассуждает? И приходит к выводу, что встроенная ECS действительно плохая, поэтому и рассказывает о новой «правильной» ECS, которая позволяет сделать всё то, для чего она предназначена.
Советую перечитать статью, автор об этом не говорит, говорят об этом сами юнитехи, когда говорят, что нельзя использовать их ECS в роли основного архитектурного фреймворка для всего приложения. Еще есть проблемы с референсными типами (решения нет): forum.unity.com/threads/alternative-to-using-string.523240
Но в этом и заключается основное различие между компонентной системой Unity и ECS — логика в ECS обязательно должна быть отделена от данных. Это позволяет очень гибко менять логику (даже удалять / добавлять её), не ломая данные

Но разве это не говорит о том, что можно просто заменить DIS, и ничего другое не пострадает? И речь тут идёт не об инициализации самой системы, а её функционировании — внедрении зависимостей по ходу работы редактора и самой игры.

Т.е. я могу заменить поиск объектов на кэш объектов, и всё будет прекрасно работать дальше. Иными словами, я могу выбрать любую (или же нет?) DIS (какую?) и подключить её обособленно (или с бубном?). Про это и следовало бы написать. Всё-таки, это и наиболее проблемная и часто используемая грань разработки.
Кеш тут не сильно поможет — его нужно будет валидировать руками на каждое изменение в компонентах, а «выборки» могут быть составными (например, 4-5 компонентов по AND + 2 компонента по NOT), над такими кешами придется хорошо подумать. Собственно, всю эту ручную работу и делает любой ECS фреймворк. Как именно — особенности реализации.
А если я захочу использовать свою DIS, то мне придётся отказаться от ECS? Что же это за ECS такая?
Никто не мешает использовать свое решение для инжекта своих данных в системы. Данные, связанные с ECS, инжектятся самой ECS.
Какие данные? Скорость бега игрока? Кастомные компоненты? Цвет полосы загрузки? Как отличить эти данные, от используемых в самой игре? Как тогда называется модель организации всех этих данных, логики и системы, разрабатываемых лично моими руками? ECSOS? А зачем тогда нужна ECS, если она не работает с моими данными? Вы меня запутали(
Начнем с того, что зачем своя система инжекта, если есть штатная? Все, что связано с юнити-компонентами — никак не взаимодействует с ECS, связи нужно делать руками и кидать в ECS-компоненты, это можно делать любыми средствами.
Действительно, стоит перечитать статью — я так не говорил, вы немного переврали.
Это статья про ECS в Unity, и можно вполне говорить про неё в отрыве от DI, потому что как уже написали — это особенности реализации.
Но почему тогда вы пишете, что ECS — это модель сосуществования контейнеров, данных и систем, а теперь утверждаете, что системы — это, оказывается, уже неотделимая часть ECS, которая контролируется исключительно той или иной ECS, которую нельзя разделить, заменить или как-то редактировать?
Так мы говорим про разные системы видимо
Все эти данные, сгруппированные в контейнеры, обрабатываются логикой, существующей исключительно в виде “Систем” — “чистых” классов с определенными методами для выполнения.

В ECS, «системы» — это внутренняя логика приложения, разбитая на модули, каждая система выполняет определенную задачу. Смотрите пример.

А вы говорите про DI, которая внешняя, и может быть разная в каждой ECS.
Entitas есть и бесплатный, но рефакторинг без рослина, превращается в попаболь.
В статье написано про кодогенератор, который бесплатный есть только как часть старой версии Entitas.
Кодогенератор есть и в платной и в платной версии, другое дело что в платной он на roslyn и работает даже если проект не компилируется
Может я плохо понял идею нового подхода, но что мне мешало раньше (в принципе я так и делал) отделять тот же input от основной логики (реализация которых чаще всего была не в MonoBehaviour классах) моего приложения. Из прочитанной статьи я не уловил профита новой системы.
Основная идея в том, что логику можно удалять / добавлять путем редактирования списка активных систем в ECS — при этом ничего остального менять не потребуется. В юнити этого можно достичь только через менеджеры, основанные на MonoBehaviour-ах, но это будет все-равно медленнее чем чистые классы. Вторая особенность — это быстрая выборка сущностей-GameObject-ов с определенными компонентами, по сути аналог FindObjectsOfType(), но позволяет работать с комплексными типами, например:
[EcsFilterInclude (typeof (IsBoxAnimationStarted))]
[EcsFilterExclude (typeof (TweenPosition))]
EcsFilter _tweenedBoxes;

Звучит примерно как «хочу получить все сущности, на которых висит компонент IsBoxAnimationStarted и отсутствует компонент TweenPosition». Это делается без потери производительности — по сути все совместимые фильтры обновляются только в момент добавления / удаления компонентов по событию, а не каждый фрейм. Сильно напоминает выборки из реляционной базы данных.
Ни в статье, ни в комментариях, нигде никто не упомянул, зачем ECS вообще нужна — для распараллеливания логики по ядрам. Это всё, ради чего оно и задумывалось, вместе с Job System. В ECS ваши системы работают независимо друг от друга, а значит могут быть спокойны распараллелены, а так же обрабатывать сущности (Entities) пачками, тоже разделяя пачки по ядрам.
Это ни коим образом не задача ECS, а просто побочный эффект и часть реализации в штатном решении Unity ECS, что ограничивает применение обработки в Job-ах (запрет на референсные типы — как часть этой проблемы).
Нужно смотреть на время синхронизации потоков. По моим тестам количество сущностей должно быть не менее 1к на каждый поток с обработкой сложнее простого перебора в цикле — тогда имеет смысл параллелить, иначе все сожрет синхронизация и получится даже медленнее: leopotam.com/16
Спасибо за статью) Есть пара вопросов.
1) Существуют ли спидтесты — сравнения производительности Unity ECS (с включенным оптимизатором) и описанных в статье альтернативных систем?
2) Как вообще уживается ECS и data-oriented design? В Unity ECS, насколько я понимаю, все ограничения связаны как раз с решением объединить эти два подхода (и внедрением своего компилятора, оптимизирующего все и вся)
1. Спидтестов к штатной ECS нет, потому что оно еще не в релизе + придется ограничивать себя MarshalByValue типами внутри компонентов — получается синтетика вместо реальных тестов. Нельзя использовать ссылки на инстансы классов, нельзя использовать строки и т.п — все то, что в любом случае необходимо использовать для использования ECS в качестве основного архитектурного паттерна для приложения.
2. DOD — это не про расположение данных, а про сам механизм обработки: «одна команда (система) — много данных (компонентов)», или SIMD. Т.е по сути ECS можно называть паттерном, построенным по принципам DOD. То, что «используем только MarshalByValue для плотного расположения в памяти и надежде на использование кеша» — это особенности реализации, когда меняем постоянное копирование больших блоков данных внутри «движка» на скорость обработки этих данных пользовательским кодом.
Sign up to leave a comment.

Articles