sschmid / Entitas

Entitas is a super fast Entity Component System (ECS) Framework specifically made for C# and Unity
MIT License
7.17k stars 1.11k forks source link

Service dependencies in systems #689

Closed YimingIsCOLD closed 6 years ago

YimingIsCOLD commented 6 years ago

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

roygear commented 6 years ago

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

FNGgames commented 6 years ago

I know which one I'd recommend ;-)

ghost commented 6 years ago
  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.
kdrzymala commented 6 years ago

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.

ghost commented 6 years ago

@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?

kdrzymala commented 6 years ago

@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)

ghost commented 6 years ago

@kdrzymala Pretty cool! Thanks for the links.

YimingIsCOLD commented 6 years ago

@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?

kdrzymala commented 6 years ago

@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.

ghost commented 6 years ago

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

YimingIsCOLD commented 6 years ago

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