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

8.0 KiB
Raw Blame History

Version License Discord QQ

Auto Injections for DragonECS

Readme Languages:

Русский

English(WIP)

The extension is designed to reduce the amount of code by simplifying dependency injection by doing injections automatically.

Warning

The project is a work in progress, API may change.

While the English version of the README is incomplete, you can view the Russian version.

Оглавление


Installation

Versioning semantics - Open

Environment

Requirements:

  • Dependency: DragonECS
  • Minimum version of C# 7.3;

Optional:

  • Game engines with C#: Unity, Godot, MonoGame, etc.

Tested with:

  • Unity: Minimum version 2021.2.0.

Unity Installation

  • Unity Package

The package supports installation as a Unity package by adding the Git URL in the PackageManager:

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

Or add the package entry to Packages/manifest.json:

"com.dcfa_pixels.dragonecs-auto_injections": "https://github.com/DCFApixels/DragonECS-AutoInjections.git",
  • Source Code

The package source code can also be copied directly into the project.


Integration

Add the AutoInject() call to the pipeline Builder. Example:

_pipeline = EcsPipeline.New()
    .Inject(world)
    .Inject(_timeService)
    .Add(new TestSystem())
    .Add(new VelocitySystem())
    .Add(new ViewSystem())
    .AutoInject() // Done — automatic injections enabled
    .BuildAndInit();

Important

Ensure AutoInject() is called during initialization; otherwise nothing will work.

Dependency Injection

The [DI] attribute replaces the IEcsInject<T> interface. Fields marked with this attribute automatically receive dependencies injected into the pipeline. Example

[DI] EcsDefaultWorld _world;

Injection can also be done via a property or method:

EcsDefaultWorld _world;

// A set accessor is required.
[DI] EcsDefaultWorld World { set => _world = value; } 

// Methods must have exactly one argument.
[DI] void InjectWorld(EcsDefaultWorld world) => _world = world;

Aggressive injection (without the [DI] attribute) is enabled by calling .AutoInject(true).


Auto Builder for aspects

AutoInjections also simplifies building aspects. The following attributes are available:

Attributes for initializing pool fields:

  • [Inc] - caches the pool and adds the component type to the include constraint of the aspect (equivalent to Inc<T>());
  • [Exc] - caches the pool and adds the component type to the exclude constraint (equivalent to Exc<T>());
  • [Opt] - only caches the pool (equivalent to Opt<T>);

Attribute for combining aspects:

  • [Combine(order)] - caches the aspect and merges constraints from aspects (equivalent to Combine<TOtherAspect>(int)); order sets combine order (default 0);

Additional attributes for specifying aspect constraints. They can be applied to the aspect itself or any field inside:

  • [IncImplicit(type)] - adds Type from the constructor to the include constraint (equivalent to Inc<T>());
  • [ExcImplicit(type)] - adds Type from the constructor to the exclude constraint (equivalent to Exc<T>());

To initialize an aspect, it is not necessary to inherit from EcsAspect. Example:

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

Auto Runners

To obtain runners without adding them manually, use [BindWithRunner(type)] and GetRunnerAuto<T>().

[BindWithRunner(typeof(DoSomethingProcessRunner))]
interface IDoSomethingProcess : IEcsProcess
{
    void Do();
}
// Runner implementation. See built-in processes for example 
sealed class DoSomethingProcessRunner : EcsRunner<IDoSomethingProcess>, IDoSomethingProcess
{
    public void Do() 
    {
        foreach (var item in Process) item.Do();
    }
}

//...
// If the runner wasn't added to the pipeline, GetRunnerAuto will automatically add an instance of DoSomethingProcessRunner.
_pipeline.GetRunnerAuto<IDoSomethingProcess>().Do();

Code Example

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;
        }
    }
}
Same code but without 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.Inc<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;
        }
    }
}

Non-null injections

To ensure a field marked with [DI] is initialized even if injection does not occur, pass a fallback type to the attribute constructor. In the example below the field Foo will receive the injected Foo instance or an instance of FooDummy : Foo if injection was not performed.

The provided type must have a parameterless constructor and be either the same type as the field or derived from it.

The extension will also report if any [DI]-marked fields remain uninitialized after the pre-injection phase.


License

The MIT License: Open