Search Unity

How to inject dependencies into system?

Discussion in 'Entity Component System' started by quabug, Jul 17, 2018.

  1. quabug

    quabug

    Joined:
    Jul 18, 2015
    Posts:
    66
    I just trying to rewrite my code based on Entitas to Unity ECS.
    The problem is it seems like systems were created internally inside ECS framework, not manually by user, which make it hard to inject dependency by hand or by framework like Zenject.
    I have noticed there's an example which setup things by a static method https://github.com/Unity-Technologi.../Pure/Assets/GameCode/EnemySpawnSystem.cs#L20
    Is it the way Unity ECS recommend to do to inject?

    Here is a system wrote with Entitas which inject timeService and config by Zenject.
    Is it possible to do the same thing in Unity ECS?

    Code (CSharp):
    1.    public class MoveSystem : IExecuteSystem // ComponentSystem
    2.     {
    3.         private readonly IConfig _config;
    4.         private readonly ITimeService _timeService;
    5.         private readonly IGroup<CoreEntity> _group;
    6.  
    7.         public MoveSystem(CoreContext context, ITimeService timeService, IConfig config)
    8.         {
    9.             _timeService = timeService;
    10.             _config = config;
    11.             _group = context.GetGroup(CoreMatcher
    12.                 .AllOf(CoreMatcher.Velocity, CoreMatcher.Position)
    13.             );
    14.         }
    15.  
    16.         void IExecuteSystem.Execute()
    17.         {
    18.             var entities = _group.GetEntities();
    19.             foreach (var entity in entities)
    20.             {
    21.                 var offset = entity.velocity.Value * _timeService.DeltaTime;
    22.                 if (math.lengthSquared(offset) > _config.SqrtMinMoveDistance)
    23.                 {
    24.                     var position = entity.position.Value + offset;
    25.                     entity.ReplacePosition(position);
    26.                 }
    27.             }
    28.         }
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    You can just inject another system with [Inject]

    [Unity.Entities.Inject] private TimeSystem _timeSystem;


    Outside of that you can create systems manually with

    World.Active.CreateManager<T>(params object[] constructorArgumnents)


    Or if you want to do the construction yourself or use some kind of third party injection (eg. zenject) I wrote an extension that let's you add managers that you've created yourself

    https://forum.unity.com/threads/request-for-world-addmanager.539271/#post-3558224

    This being how I handle it with Zenject.

    Make sure you add
    [DisableAutoCreation]
    to your systems when creating them yourself.
     
  3. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    Also don't forget update PlayerLoop if you create system manually.
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    Oh yeah that's important. I mentioned that in my other post but forgot to mention it here.

     ScriptBehaviourUpdateOrder.UpdatePlayerLoop(World);
     
  5. quabug

    quabug

    Joined:
    Jul 18, 2015
    Posts:
    66
    tertle, thank you for your help, and your awesome code inspire me to this solution.
    Code (CSharp):
    1.  
    2.     public class SystemFactory
    3.     {
    4.         private readonly DiContainer _container;
    5.         private readonly World _world;
    6.      
    7.         public SystemFactory(DiContainer container, World world)
    8.         {
    9.             _container = container;
    10.             _world = world;
    11.         }
    12.  
    13.         public void Create<T>() where T : ScriptBehaviourManager
    14.         {
    15.             var typeInfo = TypeAnalyzer.GetInfo<T>();
    16.             var paramValues = new object[typeInfo.ConstructorInjectables.Count()];
    17.             var concreteType = typeof(T);
    18.             var index = 0;
    19.             foreach (var injectInfo in typeInfo.ConstructorInjectables)
    20.             {
    21.                 object value;
    22.                 var args = new InjectArgs {
    23.                     ExtraArgs = InjectUtil.CreateArgList(new object[0]),
    24.                     Context = new InjectContext(_container, concreteType, null),
    25.                 };
    26.  
    27.                 if (!InjectUtil.PopValueWithType(
    28.                     args.ExtraArgs, injectInfo.MemberType, out value))
    29.                 {
    30.                     using (var context = injectInfo.SpawnInjectContext(
    31.                         _container, args.Context, null, args.ConcreteIdentifier))
    32.                     {
    33.                         value = _container.Resolve(context);
    34.                     }
    35.                 }
    36.                 paramValues[index] = value;
    37.                 index++;
    38.             }
    39.             _world.CreateManager<T>(paramValues);
    40.         }
    41.     }
    42.  
    Code (CSharp):
    1.  
    2. Container.BindInstance(new World("Core"));
    3. Container.BindIFactory<World, SystemFactory>().To<SystemFactory>();
    4.  
    Code (CSharp):
    1.  
    2.     sealed class GameMain
    3.     {
    4.         public GameMain(World world, IFactory<World, SystemFactory> factory)
    5.         {
    6.             var systemFactory = factory.Create(world);
    7.             systemFactory.Create<SomeSystem>();
    8.         }
    9.  
     
    bobbaluba and Fido789 like this.
  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    This is probably a nicer and safer way of doing it than what I proposed. I might look at doing something similar.