Entitas-csharp: Service dependencies in systems

Created on 25 Apr 2018  路  11Comments  路  Source: sschmid/Entitas-CSharp

Hi,

What is the best way to pass services into an Entitas systems? There is a discussion in #609 with some pros and cons that @sschmid have point out but still could not decide on what ways to do it.

  1. Singleton, like match-one example

  2. Using a meta context to pass registered service to system. Just like the Game Architecture With Entitas in the wiki tutorial made by @FNGGames

  3. Use dependencies injection technique like Zenject.

Cheers

discussion question

Most helpful comment

Hey, here's my 2c.

  1. Singleton. Yep, it's singleton, we all know this one all too well :) Not testable, poor maintainability and modularity. In particular: it's not easy do IMyService and then swap implementations with singletons, which can be really useful.

  2. Kind of reminds me of the service locator antipattern. Much better than singleton, but still has issues. What if service A depends on B and B depends on C? You have to manually construct the objects in the correct order. Small change ( B no longer depends on C but on D) can result in need to rewrite stuff. Rewriting stuff -> potentially introducing bugs.

  3. This is what we do and IMO Entitas works sweet with Zenject. You don't need to worry on the correct order of initializing things - Zenject keeps track of the dependency graph and constructs everything in the correct order.

This is how we glue Entitas and Zenject together. You need an InjectableFeature:

https://gist.github.com/kdrzymala/23d37c745377957cf9e63aeb55ceb6b5

Then in your GameController you do:

[Inject]
DiContainer _container;
[Inject]
Contexts _contexts;
void Start()
{
  _systems = new InjectableFeature( "Root system" );
  CreateSystems( _contexts ); // all your _systems.Add(new ASystem); goes here
  _systems.IncjectSelfAndChildren( _container );
  _systems.Initialize();
}

And that's it. Magic can now happen :) You can use [Inject] in your systems now. We abstract quite a few things that way: saving (we have a couple strategies that can be swapped), logging, analytcs (mutliple providers), ads (same as analytics), object pooling, etc.

Also - you no longer need to use Contexts.sharedinstance (which is a singleton) - you can inject Contexts into your MonoBehaviours (this is where you'd normally use Contexts.sharedinstance).

Zenject and Entitas are the top 2 awesome things happened to me in the past 2-3 years :) Together they help to create loosely coupled, testable and maintainable code.

All 11 comments

I think there is no BEST way in this case. Only preferable way you like.

I know which one _I'd_ recommend ;-)

  1. As parameter of the system ctor
    ^ this is simple and we are using it. Advantage is you see the dependency of the system and in unit testing you just need to substitute the interface. Disadvantage is, if you are not bundling the dependencies in some container, you are passing a whole bunch of references through the features to your systems.

Hey, here's my 2c.

  1. Singleton. Yep, it's singleton, we all know this one all too well :) Not testable, poor maintainability and modularity. In particular: it's not easy do IMyService and then swap implementations with singletons, which can be really useful.

  2. Kind of reminds me of the service locator antipattern. Much better than singleton, but still has issues. What if service A depends on B and B depends on C? You have to manually construct the objects in the correct order. Small change ( B no longer depends on C but on D) can result in need to rewrite stuff. Rewriting stuff -> potentially introducing bugs.

  3. This is what we do and IMO Entitas works sweet with Zenject. You don't need to worry on the correct order of initializing things - Zenject keeps track of the dependency graph and constructs everything in the correct order.

This is how we glue Entitas and Zenject together. You need an InjectableFeature:

https://gist.github.com/kdrzymala/23d37c745377957cf9e63aeb55ceb6b5

Then in your GameController you do:

[Inject]
DiContainer _container;
[Inject]
Contexts _contexts;
void Start()
{
  _systems = new InjectableFeature( "Root system" );
  CreateSystems( _contexts ); // all your _systems.Add(new ASystem); goes here
  _systems.IncjectSelfAndChildren( _container );
  _systems.Initialize();
}

And that's it. Magic can now happen :) You can use [Inject] in your systems now. We abstract quite a few things that way: saving (we have a couple strategies that can be swapped), logging, analytcs (mutliple providers), ads (same as analytics), object pooling, etc.

Also - you no longer need to use Contexts.sharedinstance (which is a singleton) - you can inject Contexts into your MonoBehaviours (this is where you'd normally use Contexts.sharedinstance).

Zenject and Entitas are the top 2 awesome things happened to me in the past 2-3 years :) Together they help to create loosely coupled, testable and maintainable code.

@kdrzymala
I like your solution very much. Just one thing that I don't understand fully: how would a unit test for a reactive system look a like with zenject where I need to substitute a service?

@StormRene This where DI frameworks shine ;-) TL;DR; you just do different bindings for tests.

Zenject docs have a bit more to say about it. I'll just guide you there, as there's no point of repeating this stuff:
https://github.com/modesttree/Zenject/blob/master/Documentation/WritingAutomatedTests.md
https://github.com/modesttree/Zenject/blob/master/Documentation/AutoMocking.md

(seriously, Zenject has awesome documentation)

@kdrzymala
Pretty cool! Thanks for the links.

@kdrzymala
Zenject is really interesting. I shall give it a try. Thanks for sharing.

Should we have a small dependencies injection framework built into Entitas?

@YimingIsCOLD IMO Zenject works great and is opensource, so I'd say that there's no point in re-inwenting the wheel.
Only downside is (if it matters to you) that it has dependencies to UnityEngine. If that's an issue you could try Ninject - which is quite similar.

@kdrzymala There is also a version that works unity-independent on the release page _Zenject-NonUnity.v5.5.1.zip_ .

@kdrzymala Okay. Zenject documentation is really awesome and with the code snippet you shared, I had already setup and started using Zenject.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Stals picture Stals  路  4Comments

jakovd picture jakovd  路  3Comments

FiveP picture FiveP  路  3Comments

vaudevillian picture vaudevillian  路  3Comments

angelotadres picture angelotadres  路  5Comments