DragonECS-AutoInjections/README-RU.md
2026-03-30 10:13:09 +08:00

10 KiB
Raw Permalink Blame History

Version License Discord QQ

Auto Injections for DragonECS

Readme Languages:

Русский

English(WIP)

Расширение автоматизирует внедрение зависимостей, что позволяет сократить объем кода и упростить разработку.

Warning

Проект в стадии разработки. API может меняться.

Оглавление


Установка

Семантика версионирования - Открыть

Окружение

Обязательные требования:

  • Зависимость: DragonECS
  • Минимальная версия C# 7.3;

Поддерживает:

  • Игровые движки с C#: Unity, Godot, MonoGame и т.д.

Протестировано на:

  • Unity: Минимальная версия 2021.2.0.

Установка для Unity

  • Unity-модуль

Поддерживается установка в виде Unity-модуля при помощи добавления git-URL в PackageManager:

https://github.com/DCFApixels/DragonECS-AutoInjections.git

Или ручного добавления этой строчки в Packages/manifest.json:

"com.dcfa_pixels.dragonecs-auto_injections": "https://github.com/DCFApixels/DragonECS-AutoInjections.git",
  • В виде исходников

Можно так же напрямую скопировать в проект исходники фреймворка.


Интеграция

Добавьте вызов метода AutoInject() для Builder-а пайплайна. Пример:

_pipeline = EcsPipeline.New()
    .Inject(world)
    .Inject(_timeService)
    .Add(new TestSystem())
    .Add(new VelocitySystem())
    .Add(new ViewSystem())
    .AutoInject() // Готово, автоматические внедрения активированы
    .BuildAndInit();

Important

Проверьте что в инициализации добавлен вызов AutoInject(), иначе ничего не будет работать.


Инъекция зависимостей

Атрибут [DI] заменяет интерфейс IEcsInject<T>, Поля, отмеченные этим атрибутом, автоматически получают зависимости, внедрённые в Pipeline. Пример:

[DI] EcsDefaultWorld _world;

Так же можно делать внедрение через свойство или метод:

EcsDefaultWorld _world;

//Обязательно наличие set блока.  
[DI] EcsDefaultWorld World { set => _world = value; } 

//Количество аргументов должно быть равно 1.
[DI] void InjectWorld(EcsDefaultWorld world) => _world = world;

Агрессивная инъекция (без атрибута [DI]) включается вызовом .AutoInject(true).


Auto Builder аспектов

Так же AutoInjections упрощает построение аспектов. Теперь аспекту Для этого есть следующие атрибуты:

Атрибуты для инициализации полей с пулами:

  • [Inc] - кеширует пул и добавит тип компонента в включающее ограничение аспекта, аналог метода Inc<T>();
  • [Exc] - кеширует пул и добавит тип компонента в исключающее ограничение аспекта, аналог метода Exc<T>();
  • [Opt] - только кеширует пул, аналог метода Opt<T>();

Атрибут для комбинирования аспектов:

  • [Combine(order)] - кеширует аспект и скомбинирует ограничения аспектов, аналог метода Combine<TOtherAspect>(int), аргумент order задает порядок комбинирования, по умлочанию order = 0;

Дополнительные атрибуты только для задания ограничений аспекта. Их можно применить к самому аспекту, либо к любому полю внутри. Используйте атрибуты:

  • [IncImplicit(type)] - добавит в включающее ограничение указанный в конструкторе тип type, аналог метода Inc<T>();
  • [ExcImplicit(type)] - добавит в исключающее ограничение указанный в конструкторе тип type, аналог метода Exc<T>();

Для инициализации аспекта не обязательно наследоваться от EcsAspect, Пример:

class Aspect
{
    [ExcImplicit(typeof(FreezedTag))]
    [Inc] public EcsPool<Pose> poses;
    [Inc] public EcsPool<Velocity> velocities;
}

Auto Runner-ы

Для получения раннеров без добавления, есть атрибут [BindWithRunner(type)] и метод GetRunnerAuto<T>().

[BindWithRunner(typeof(DoSomethingProcessRunner))]
interface IDoSomethingProcess : IEcsProcess
{
    void Do();
}
//Реализация раннера. Пример реализации можно так же посмотреть в встроенных процессах 
sealed class DoSomethingProcessRunner : EcsRunner<IDoSomethingProcess>, IDoSomethingProcess
{
    public void Do() 
    {
        foreach (var item in Process) item.Do();
    }
}

//...
// Если в пайплайн не был добавлен раннер, то GetRunnerAuto автоматически добавит экземпляр DoSomethingProcessRunner.
_pipeline.GetRunnerAuto<IDoSomethingProcess>().Do();

Пример кода

class VelocitySystemDI : IEcsRun
{
    class Aspect
    {
        [ExcImplicit(typeof(FreezedTag))]
        [Inc] public EcsPool<Pose> poses;
        [Inc] public EcsPool<Velocity> velocities;
    }

    [DI] EcsDefaultWorld _world;
    [DI] TimeService _time;

    public void Run()
    {
        foreach (var e in _world.Where(out Aspect a))
        {
            a.poses.Get(e).position += a.velocities.Read(e).value * _time.DeltaTime;
        }
    }
}
Тот же код но без AutoInjections
class VelocitySystem : IEcsRun, IEcsInject<EcsDefaultWorld>, IEcsInject<TimeService>
{
    class Aspect : EcsAspect
    {
        public EcsPool<Pose> poses;
        public EcsPool<Velocity> velocities;
        public Aspect(Builder b)
        {
            b.Exc<FreezedTag>();
            poses = b.Ince<Pose>();
            velocities = b.Inc<Velocity>();
        }
    }

    EcsDefaultWorld _world;
    TimeService _time;

    public void Inject(EcsDefaultWorld obj) => _world = obj;
    public void Inject(TimeService obj) => _time = obj;

    public void Run()
    {
        foreach (var e in _world.Where(out Aspect a))
        {
            a.poses.Get(e).position += a.velocities.Read(e).value * _time.DeltaTime;
        }
    }
}

Не null инъекции

Чтобы поле, отмеченное атрибутом [DI], было проинициализировано даже в случае отсутствия инъекции, в конструктор атрибута можно передать тип-заглушку. В примере ниже поле foo получит экземпляр Foo из инъекции или экземпляр FooDummy : Foo, если инъекция не была выполнена.

[DI(typeof(FooDummy))] Foo foo;

Переданный тип должен иметь конструктор без параметров и быть либо того же типа, что и поле, либо производным от него.

Расширение также сообщит, если после завершения предварительной инъекции остались непроинициализированные поля с атрибутом [DI].


Лицензия

MIT Лицензия: Открыть