10 KiB
Auto Injections for DragonECS
| Readme Languages: | ||
|
Русский |
English(WIP) |
|
Расширение автоматизирует внедрение зависимостей, что позволяет сократить объем кода и упростить разработку.
Warning
Проект в стадии разработки. API может меняться.
Оглавление
- Установка
- Интеграция
- Инъекция зависимостей
- Auto Builder аспектов
- Auto Runner-ы
- Пример кода
- Не null инъекции
Установка
Семантика версионирования - Открыть
Окружение
Обязательные требования:
- Зависимость: 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>();
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 : EcsAspectAuto
{
[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 Лицензия: Открыть