1 module jaster.ioc.depinject;
2 
3 /// Describes the lifetime of a service.
4 enum ServiceLifetime
5 {
6     /++
7      + The service is constructed every single time it is requested.
8      +
9      + These services are the most expensive to use, as they need to be constructed *every single time* they're requested, which also
10      + puts strain on the GC.
11      + ++/
12     Transient,
13 
14     /++
15      + The service is only constructed a single time for every `ServiceProvider`, regardless of which scope is used to access it.
16      +
17      + These services are the least expensive to use, as they are only constructed a single time per `ServiceProvider`.
18      + ++/
19     Singleton,
20 
21     /++
22      + The service is constructed once per scope.
23      +
24      + These services are between `Transient` and `Singleton` in terms of performance. It mostly depends on how often scopes are created/destroyed
25      + in your program.
26      +
27      + See_Also:
28      +  `ServiceProvider.createScope`
29      + ++/
30     Scoped
31 }
32 
33 // Mixin for things like asSingletonRuntime, or asTransient. aka: boilerplate
34 private mixin template ServiceLifetimeFunctions(ServiceLifetime Lifetime)
35 {
36     import std.conv : to;
37     static immutable Suffix       = Lifetime.to!string;
38     static immutable FullLifetime = "ServiceLifetime."~Suffix;
39 
40     @safe nothrow pure
41     public static
42     {
43         ///
44         mixin("alias as"~Suffix~"Runtime = asRuntime!("~FullLifetime~");");
45 
46         ///
47         mixin("alias as"~Suffix~"(alias BaseType, alias ImplType) = asTemplated!("~FullLifetime~", BaseType, ImplType);");
48 
49         ///
50         mixin("alias as"~Suffix~"(alias ImplType) = asTemplated!("~FullLifetime~", ImplType, ImplType);");
51     }
52 }
53 
54 /++
55  + Describes a service.
56  +
57  + This struct shouldn't be created directly, you must use one of the static construction functions.
58  +
59  + For example, if you wanted the service to be a singleton, you could do `asSingleton!(IBaseType, ImplementationType)`.
60  + ++/
61 struct ServiceInfo
62 {
63     alias FactoryFunc               = Object delegate(ref ServiceScope);
64     alias FactoryFuncFor(T)         = T delegate(ref ServiceScope);
65     enum  isValidBaseType(T)        = (is(T == class) || is(T == interface));
66     enum  isValidImplType(BaseT, T) = (is(T == class) && (is(T : BaseT) || is(T == BaseT)));
67     enum  isValidImplType(T)        = isValidImplType!(T, T);
68 
69     private
70     {
71         TypeInfo        _baseType;
72         TypeInfo        _implType;
73         FactoryFunc     _factory;
74         ServiceLifetime _lifetime;
75         TypeInfo[]      _dependencies;
76 
77         @safe @nogc
78         this(TypeInfo baseType, TypeInfo implType, FactoryFunc func, ServiceLifetime lifetime, TypeInfo[] dependencies) nothrow pure
79         {
80             this._baseType      = baseType;
81             this._implType      = implType;
82             this._factory       = func;
83             this._lifetime      = lifetime;
84             this._dependencies  = dependencies;
85 
86             assert(func !is null, "The factory function is null. The `asXXXRuntime` functions can't auto-generate one sadly, so provide your own.");
87         }
88     }
89 
90     /// This is mostly for unit tests.
91     @safe
92     bool opEquals(const ServiceInfo rhs) const pure nothrow
93     {
94         return 
95         (
96             this._baseType is rhs._baseType
97          && this._implType is rhs._implType
98          && this._lifetime == rhs._lifetime
99         );
100     }
101 
102     /// So we can use this struct as an AA key more easily
103     @trusted // @trusted since we're only converting a pointer to a number, without doing anything else to it. 
104     size_t toHash() const pure nothrow
105     {
106         const baseTypePointer  = cast(size_t)(cast(void*)this._baseType);
107         const implTypePointer  = cast(size_t)(cast(void*)this._implType);
108         const lifetimeAsNumber = cast(size_t)this._lifetime;
109 
110         // NOTE: This is just something completely random I made up. I'll research into a proper technique eventually, this just has to exist *in some form* for now.
111         return (baseTypePointer ^ implTypePointer) * lifetimeAsNumber;
112     }
113 
114     @safe nothrow pure
115     public static
116     {
117         /++
118          + An internal function, public due to necessity, however will be used to explain the `asXXXRuntime` functions.
119          +
120          + e.g. `asSingletonRuntime`, `asTransientRuntime`, and `asScopedRuntime`.
121          +
122          + Notes:
123          +  Unlike the `asTemplated` constructor (and things like `asSingleton`, `asScoped`, etc.), this function isn't able to produce
124          +  a list of dependency types, so therefore will need to be provided by you, the user, if you're hoping to make use of `ServiceProvider`'s
125          +  dependency loop guard.
126          +
127          +  You only need to provide a list of types that the `factory` function will try to directly retrieve from a `ServiceScope`, not the *entire* dependency chain.
128          + ++/
129         ServiceInfo asRuntime(ServiceLifetime Lifetime)(TypeInfo baseType, TypeInfo implType, FactoryFunc factory, TypeInfo[] dependencies = null)
130         {
131             return ServiceInfo(baseType, implType, factory, Lifetime, dependencies);
132         }
133 
134         /++
135          + An internal function, public due to necessity, however will be used to explain the `asXXX` functions.
136          +
137          + e.g. `asSingleton`, `asTransient`, and `asScoped`.
138          +
139          + Notes:
140          +  This constructor is able to automatically generate the list of dependencies, which will allow `ServiceProvider` to check for
141          +  dependency loops.
142          +
143          +  If `factory` is `null`, then the factory becomes a call to `Injector.construct!ImplType`, which should be fine for most cases.
144          + ++/
145         ServiceInfo asTemplated(ServiceLifetime Lifetime, alias BaseType, alias ImplType)(FactoryFuncFor!ImplType factory = null)
146         if(isValidBaseType!BaseType && isValidImplType!(BaseType, ImplType))
147         {
148             import std.meta : Filter;
149             import std.traits : Parameters;
150 
151             enum isClassOrInterface(T)  = is(T == class) || is(T == interface);
152             alias ImplTypeCtor          = Injector.FindCtor!ImplType;
153             alias CtorParams            = Parameters!ImplTypeCtor;
154             alias CtorParamsFiltered    = Filter!(isClassOrInterface, CtorParams);
155 
156             TypeInfo[] deps;
157             deps.length = CtorParamsFiltered.length;
158 
159             static foreach(i, dep; CtorParamsFiltered)
160                 deps[i] = typeid(dep);
161 
162             if(factory is null)
163                 factory = (ref services) => Injector.construct!ImplType(services);
164 
165             return ServiceInfo(typeid(BaseType), typeid(ImplType), factory, Lifetime, deps);
166         }
167     }
168 
169     mixin ServiceLifetimeFunctions!(ServiceLifetime.Singleton);
170     mixin ServiceLifetimeFunctions!(ServiceLifetime.Transient);
171     mixin ServiceLifetimeFunctions!(ServiceLifetime.Scoped);
172 }
173 ///
174 //@safe nothrow pure
175 unittest
176 {
177     static interface I {}
178     static class C : I {}
179 
180     Object dummyFactory(ref ServiceScope){ return null; }
181 
182     // Testing: All 3 aliases can be found, 1 alias per lifetime, which also tests that all lifetimes are handled properly.
183     assert(
184         ServiceInfo.asSingletonRuntime(typeid(I), typeid(C), &dummyFactory)
185         ==
186         ServiceInfo(typeid(I), typeid(C), &dummyFactory, ServiceLifetime.Singleton, null)
187     );
188 
189     assert(
190         ServiceInfo.asTransient!(I, C)((ref provider) => new C())
191         ==
192         ServiceInfo(typeid(I), typeid(C), &dummyFactory, ServiceLifetime.Transient, null) // NOTE: Factory func is ignored in opEquals, so `dummyFactory` here is fine.
193     );
194 
195     assert(
196         ServiceInfo.asScoped!C()
197         ==
198         ServiceInfo(typeid(C), typeid(C), &dummyFactory, ServiceLifetime.Scoped, null)
199     );
200 
201     // opEquals and opHash don't care about dependencies (technically I think they should, but meh), so we have to test directly.
202     static class WithDeps
203     {
204         this(C c, I i, int a){}
205     }
206     
207     auto deps = ServiceInfo.asScoped!WithDeps()._dependencies;
208     assert(deps.length == 2);
209     assert(deps[0] is typeid(C));
210     assert(deps[1] is typeid(I));
211 }
212 
213 /++
214  + Provides access to a service scope.
215  +
216  + Description:
217  +  The idea of having 'scopes' for services comes from the fact that this library was inspired by ASP Core's Dependency Injection,
218  +  which provides similar functionality.
219  +
220  +  In ASP Core there is a need for each HTTP request to have its own 'ecosystem' for services (its own 'scope').
221  +
222  +  For example, you don't want your database context to be shared between different requests at the same time, as each request needs
223  +  to make/discard their own chnages to the database. Having a seperate database context between each request (scope) allows this to be
224  +  achieved easily.
225  +
226  +  For a lot of programs, you probably won't need to use scopes at all, and can simply use `ServiceProvider.defaultScope` for all of your
227  +  needs. Use what's best for your case.
228  +
229  +  See https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection as the documentation there is mostly appropriate for this library as well.
230  +
231  + Master & Slave `ServiceScope`:
232  +  There are two variants of `ServiceScope` that can be accessed - master scopes, and slave scopes.
233  +
234  +  The principal is pretty simple: Master scopes are the 'true' accessor to the scope so therefore contain the ability to also destroy the scope.
235  +
236  +  Slave scopes, however, can be seen more as a 'reference' accessor to the scope, meaning that they cannot destroy the underlying scope in anyway.
237  +
238  + Destroying a scope:
239  +  There are two ways for a service scope to be destroyed.
240  +
241  +  1. The master `ServiceScope` object has its dtor run. Because the `ServiceScope` is the master accessor, then the scope's lifetime is directly tied to the `ServiceScope`'s lifetime.
242  +
243  +  2. A call to `ServiceProvider.destroyScope` is made, where a master `ServiceScope` is passed to it.
244  +
245  +  The `ServiceScope` object is non-copyable, so can only be moved via functions like `std.algorithm.mutation.move`
246  +
247  +  Currently when a scope it destroyed nothing really happens except that the `ServiceProvider` clears its cache of service instances for that specific scope, and allows
248  +  another scope to be created in its place.
249  +
250  +  In the future I will be adding more functionality onto when a scope is destroyed, as the current behaviour is a bit undesirable for multiple reasons (e.g.
251  +  if you destroy a scope, any uses of a `ServiceScopeAccessor` for the destroyed scope can trigger a bug-check assert).
252  +
253  + See_Also:
254  +  `ServiceProvider.createScope`, `ServiceProvider.destroyScope`
255  + ++/
256 struct ServiceScope
257 {
258     private size_t          _index;
259     private ServiceProvider _provider;
260     private bool            _isMasterReference; // True = Scope is destroyed via this object. False = Scope can't be destroyed via this object.
261 
262     @disable
263     this(this);
264 
265     ~this()
266     {
267         if(this._provider !is null && this._isMasterReference)
268             this._provider.destroyScope(this);
269     }
270 
271     /++
272      + Attempts to retrieve a service of the given `baseType`, otherwise returns `null`.
273      + ++/
274     Object getServiceOrNull(TypeInfo baseType)
275     {
276         return this._provider.getServiceOrNull(baseType, this);
277     }
278 
279     /++
280      + Attempts to retrieve a service of the given base type `T`, otherwise returns `null`.
281      + ++/
282     T getServiceOrNull(alias T)()
283     if(ServiceInfo.isValidBaseType!T)
284     {
285         auto service = this.getServiceOrNull(typeid(T));
286         if(service is null)
287             return null;
288 
289         auto casted = cast(T)service;
290         assert(casted !is null, "Invalid cast.");
291 
292         return casted;
293     }
294     ///
295     unittest
296     {
297         static interface IPrime
298         {
299             int getPrime();
300         }
301 
302         static class PrimeThree : IPrime
303         {
304             int getPrime()
305             {
306                 return 3;
307             }
308         }
309 
310         auto services = new ServiceProvider([ServiceInfo.asTransient!(IPrime, PrimeThree)]);
311         auto service = services.defaultScope.getServiceOrNull!IPrime();
312         assert(service !is null);
313         assert(service.getPrime() == 3);
314     }
315 }
316 
317 // Not an interface since a testing-specific implementation has no worth atm, and it'd mean making `serviceScope` virtual.
318 /++
319  + A built-in service that allows access to a slave `ServiceScope` for whichever scope this service
320  + was constructed for.
321  +
322  + Description:
323  +  Because `ServiceScope` cannot be copied, it can be a bit annoying for certain services to gain access to it should they need
324  +  manual access of fetching scoped services.
325  +
326  +  As an alternative, services can be injected with this helper service which allows the creation of slave `ServiceScope`s.
327  +
328  + See_Also:
329  +  The documentation for `ServiceScope`.
330  + ++/
331 final class ServiceScopeAccessor
332 {
333     // Since ServiceScope can't be copied, and we shouldn't exactly move it from its default location, we need to re-store
334     // some of its info for later.
335     private ServiceProvider _provider; 
336     private size_t          _index;
337 
338     private this(ref ServiceScope serviceScope)
339     {
340         this._provider = serviceScope._provider;
341         this._index    = serviceScope._index;
342     }
343 
344     /++
345      + Returns: A slave `ServiceScope`.
346      + ++/
347     @property @safe @nogc
348     ServiceScope serviceScope() nothrow pure
349     {
350         return ServiceScope(this._index, this._provider, false); // false = Not master scope object.
351     }
352 }
353 
354 // Not an interface since a testing-specific implementation has little worth atm, and it'd mean making functions virtual.
355 /++
356  + Provides most of the functionality for managing and using services.
357  +
358  + Dependency_Checking:
359  +  During construction, the `ServiceProvider` will perform a check to ensure that none of its registered services contain a 
360  +  dependency loop, i.e. making sure that no service directly/indirectly depends on itself.
361  +
362  +  If you're creating your `ServiceInfo` via the `ServiceInfo.asSingleton`, `ServiceInfo.asTransient`, or `ServiceInfo.asScoped` constructors,
363  +  then this functionality is entirely automatic.
364  +
365  +  If however you're using the `asXXXRuntime` constructor varient, then it is down to the user to provide an array of `TypeInfo` to that constructor,
366  +  representing the service's dependencies. Failing to provide the correct data will cause this guard check to not detect the loop, and later down the line
367  +  this will cause an infinite loop leading into a crash.
368  +
369  +  In the future, I may add a `version()` block that will make `ServiceProvider.getServiceOrNull` also perform dependency loop checks. The reason this is not
370  +  performed by default is due to performance concerns (especially once your services start to grow in number).
371  +
372  + Lifetime_Checking:
373  +  During construction, the `ServiceProvider` will perform a check to ensure that none of its registered services contains a dependency on
374  +  another service with an incompatible lifetime.
375  +
376  +  Likewise with depenency checking (see section above), you will need to ensure that you provide the correct data when using the `asXXXRuntime` constructors
377  +  for `ServiceInfo`.
378  +
379  +  The following is a table of valid lifetime pairings:
380  +
381  +      * Transient - [Transient, Singleton]
382  +
383  +      * Scoped - [Transient, Scoped, Singleton]
384  +
385  +      * Singleton - [Transient, Singleton]
386  + ++/
387 final class ServiceProvider
388 {
389     import std.typecons : Nullable, nullable;
390 
391     alias ServiceInstanceDictionary = Object[ServiceInfo];
392     enum BITS_PER_MASK = long.sizeof * 8;
393 
394     private
395     {
396         struct ScopeInfo
397         {
398             ServiceInstanceDictionary instances;
399             ServiceScopeAccessor      accessor; // Written in a way that they only have to be constructed once, so GC isn't as mad.
400         }
401 
402         ServiceScope                _defaultScope;
403         ServiceInfo[]               _allServices;
404         ScopeInfo[]                 _scopes;
405         ServiceInstanceDictionary   _singletons;
406         long[]                      _scopeInUseMasks;
407 
408         Object getServiceOrNull(TypeInfo baseType, ref scope ServiceScope serviceScope)
409         {
410             assert(serviceScope._provider is this, "Attempting to use service scope who does not belong to this `ServiceProvider`.");
411 
412             auto infoNullable = this.getServiceInfoForBaseType(baseType);
413             if(infoNullable.isNull)
414                 return null;
415 
416             auto info = infoNullable.get();
417             final switch(info._lifetime) with(ServiceLifetime)
418             {
419                 case Transient:
420                     return info._factory(serviceScope);
421 
422                 case Scoped:
423                     auto ptr = (cast()info in this._scopes[serviceScope._index].instances); // 'cus apparently const is too painful for it to handle.
424                     if(ptr !is null)
425                         return *ptr;
426 
427                     auto instance = info._factory(serviceScope);
428                     this._scopes[serviceScope._index].instances[cast()info] = instance;
429                     return instance;
430 
431                 case Singleton:
432                     // TODO: Functionise this
433                     auto ptr = (cast()info in this._singletons); // 'cus apparently const is too painful for it to handle.
434                     if(ptr !is null)
435                         return *ptr;
436 
437                     auto instance = info._factory(serviceScope);
438                     this._singletons[cast()info] = instance;
439                     return instance;
440             }
441         }
442 
443         @safe
444         ref long getScopeMaskByScopeIndex(size_t index) nothrow
445         {
446             const indexIntoArray = (index / BITS_PER_MASK);
447 
448             if(indexIntoArray >= this._scopeInUseMasks.length)
449                 this._scopeInUseMasks.length = indexIntoArray + 1;
450 
451             return this._scopeInUseMasks[indexIntoArray];
452         }
453 
454         @safe
455         bool isScopeInUse(size_t index) nothrow
456         {
457             const bitInMask = (index % BITS_PER_MASK);
458             const mask      = this.getScopeMaskByScopeIndex(index);
459 
460             return (mask & (1 << bitInMask)) > 0;
461         }
462 
463         @safe
464         void setScopeInUse(size_t index, bool isInUse) nothrow
465         {
466             const bitInMask = (index % BITS_PER_MASK);
467             const bitToUse  = (1 << bitInMask);
468 
469             if(isInUse)
470                 this.getScopeMaskByScopeIndex(index) |= bitToUse;
471             else
472                 this.getScopeMaskByScopeIndex(index) &= ~bitToUse;
473         }
474         ///
475         unittest
476         {
477             auto services = new ServiceProvider(null);
478             assert(!services.isScopeInUse(65));
479 
480             services.setScopeInUse(65, true);
481             assert(services.isScopeInUse(65));
482             assert(services._scopeInUseMasks.length == 2);
483             assert(services._scopeInUseMasks[1] == 0b10); // 65 % 64 = 1. 1 << 1 = 0b10
484             
485             services.setScopeInUse(65, false);
486             assert(!services.isScopeInUse(65));
487         }
488 
489         @safe
490         void assertLifetimesAreCompatible()
491         {
492             @safe
493             bool areCompatible(const ServiceLifetime consumer, const ServiceLifetime dependency)
494             {
495                 final switch(consumer) with(ServiceLifetime)
496                 {
497                     case Transient:
498                     case Singleton:
499                         return dependency != Scoped;
500 
501                     case Scoped:
502                         return true;
503                 }
504             }
505 
506             foreach(service; this._allServices)
507             {
508                 foreach(dependency; service._dependencies)
509                 {
510                     auto dependencyInfo = this.getServiceInfoForBaseType(dependency);
511                     if(dependencyInfo.isNull)
512                         continue;
513 
514                     if(!areCompatible(service._lifetime, dependencyInfo.get._lifetime))
515                     {
516                         import std.format : format;
517                         assert(
518                             false,
519                             "%s service %s cannot depend on %s service %s as their lifetimes are incompatible".format(
520                                 service._lifetime,
521                                 service._baseType,
522                                 dependencyInfo.get._lifetime,
523                                 dependency
524                             )
525                         );
526                     }
527                 }
528             }
529         }
530         //
531         unittest
532         {
533             import std.exception : assertThrown, assertNotThrown;
534 
535             static class Scoped
536             {
537             }
538 
539             static class Transient
540             {
541                 this(Scoped){}
542             }
543 
544             static class GoodTransient
545             {
546             }
547 
548             static class GoodScoped
549             {
550                 this(GoodTransient){}
551             }
552 
553             assertThrown!Throwable(new ServiceProvider([
554                 ServiceInfo.asScoped!Scoped,
555                 ServiceInfo.asTransient!Transient
556             ]));
557 
558             assertNotThrown!Throwable(new ServiceProvider([
559                 ServiceInfo.asScoped!GoodScoped,
560                 ServiceInfo.asTransient!GoodTransient
561             ]));
562 
563             // Uncomment when tweaking the error message.
564             // new ServiceProvider([
565             //     ServiceInfo.asScoped!Scoped,
566             //     ServiceInfo.asTransient!Transient
567             // ]);
568         }
569 
570         @safe
571         void assertNoDependencyLoops()
572         {
573             TypeInfo[] typeToTestStack;
574             TypeInfo[] typeToTestStackWhenLoopIsFound; // Keep a copy of the stack once a loop is found, so we can print out extra info.
575             
576             @trusted
577             bool dependsOn(TypeInfo typeToTest, TypeInfo dependencyType)
578             {
579                 import std.algorithm : canFind;
580 
581                 if(typeToTestStack.canFind!"a is b"(typeToTest))
582                     return false; // Since we would've returned true otherwise, which would end the recursion.
583 
584                 typeToTestStack ~= typeToTest;
585                 scope(exit) typeToTestStack.length -= 1;
586 
587                 auto serviceInfoNullable = this.getServiceInfoForBaseType(typeToTest);
588                 if(serviceInfoNullable.isNull)
589                     return false; // Since the service doesn't exist anyway.
590 
591                 auto serviceInfo = serviceInfoNullable.get();
592                 foreach(dependency; serviceInfo._dependencies)
593                 {
594                     if(dependency is dependencyType || dependsOn(cast()dependency, dependencyType))
595                     {
596                         if(typeToTestStackWhenLoopIsFound.length == 0)
597                             typeToTestStackWhenLoopIsFound = typeToTestStack.dup;
598 
599                         return true;
600                     }
601                 }
602 
603                 return false;
604             }
605 
606             foreach(service; this._allServices)
607             {
608                 if(dependsOn(service._baseType, service._baseType))
609                 {
610                     import std.algorithm : map, joiner;
611                     import std.format : format;
612 
613                     assert(
614                         false,
615                         "Circular dependency detected, %s depends on itself:\n%s -> %s".format(
616                             service._baseType,
617                             typeToTestStackWhenLoopIsFound.map!(t => t.toString())
618                                                           .joiner(" -> "),
619                             service._baseType
620                         )
621                     );
622                 }
623             }
624         }
625         ///
626         unittest
627         {
628             import std.exception : assertThrown;
629 
630             // Technically shouldn't catch asserts, but this is just for testing.
631             assertThrown!Throwable(new ServiceProvider([
632                 ServiceInfo.asSingleton!CA,
633                 ServiceInfo.asSingleton!CB
634             ]));
635 
636             // Uncomment when tweaking with the error message.
637             // new ServiceProvider([
638             //     ServiceInfo.asSingleton!CA,
639             //     ServiceInfo.asSingleton!CB
640             // ]);
641         }
642         version(unittest) // Because of forward referencing being required.
643         {
644             static class CA
645             {
646                 this(CB){}
647             }
648             static class CB
649             {
650                 this(CA) {}
651             }
652         }
653     }
654 
655     /++
656      + Constructs a new `ServiceProvider` that makes use of the given `services`.
657      +
658      + Builtin_Services:
659      +  * [Singleton] `ServiceProvider` - `this`
660      +  
661      +  * [Scoped] `ServiceScopeAccessor` - A service for easily accessing slave `ServiceScope`s.
662      +
663      + Assertions:
664      +  No service inside of `services` is allowed to directly or indirectly depend on itself.
665      +  e.g. A depends on B which depends on A. This is not allowed.
666      +
667      + Params:
668      +  services = Information about all of the services that can be provided.
669      + ++/
670     this(ServiceInfo[] services)
671     {
672         this._defaultScope = this.createScope();
673 
674         // I'm doing it this weird way to try and make the GC less angry.
675         const EXTRA_SERVICES = 2;
676         this._allServices.length                = services.length + EXTRA_SERVICES;
677         this._allServices[0..services.length]   = services[0..$];
678         this._allServices[services.length]      = ServiceInfo.asSingleton!ServiceProvider((ref _) => this); // Allow ServiceProvider to be injected.
679         this._allServices[services.length + 1]  = ServiceInfo.asScoped!ServiceScopeAccessor((ref serviceScope) // Add service to allowed services to access their scope's... scope.
680         {
681             auto instance = this._scopes[serviceScope._index].accessor;
682             if(instance !is null)
683                 return instance;
684 
685             instance = new ServiceScopeAccessor(serviceScope);
686             this._scopes[serviceScope._index].accessor = instance;
687 
688             return instance;            
689         });
690 
691         this.assertNoDependencyLoops();
692         this.assertLifetimesAreCompatible();
693     }
694 
695     public final
696     {
697         /++
698          + Creates a new scope.
699          +
700          + Performance:
701          +  For creating a scope, most of the performance cost is only made during the very first creation of a scope of a specific
702          +  index. e.g. If you create scope[index=1], destroy it, then make another scope[index=1], the second scope should be made faster.
703          +
704          +  The speed difference is likely negligable either way though.
705          +
706          +  Most of the performance costs of making a scope will come from the creation of scoped services for each new scope, but those
707          +  are only performed lazily anyway.
708          +
709          + Returns:
710          +  A master `ServiceScope`.
711          + ++/
712         @safe
713         ServiceScope createScope()
714         {
715             size_t index = 0;
716             foreach(i; 0..ulong.sizeof * 8)
717             {
718                 if(!this.isScopeInUse(i))
719                 {
720                     index = i;
721                     this.setScopeInUse(i, true);
722                     break;
723                 }
724             }
725 
726             if(this._scopes.length <= index)
727                 this._scopes.length = (index + 1);
728 
729             return ServiceScope(index, this, true);
730         }
731         ///
732         unittest
733         {
734             import std.format : format;
735 
736             // Basic test to make sure the "in use" mask works properly.
737             auto provider = new ServiceProvider(null);
738 
739             ServiceScope[3] scopes;
740             foreach(i; 0..scopes.length)
741             {
742                 scopes[i] = provider.createScope();
743                 assert(scopes[i]._index == i + 1, format("%s", scopes[i]._index));
744             }
745 
746             provider.destroyScope(scopes[1]); // Index 2
747             assert(scopes[1]._index == 0);
748             assert(scopes[1]._provider is null);
749             assert(provider._scopeInUseMasks[0] == 0b1011);
750 
751             scopes[1] = provider.createScope();
752             assert(scopes[1]._index == 2);
753             assert(provider._scopeInUseMasks[0] == 0b1111);
754         }
755 
756         /++
757          + Destroys a scope.
758          +
759          + Behaviour:
760          +  Currently, destroying a scope simply means that the `ServiceProvider` can reuse a small amount of memory
761          +  whenever a new scope is created.
762          +
763          +  In the future I'd like to add more functionality, such as detecting scoped services that implement specific
764          +  interfaces for things such as `IDisposableService`, `IReusableService`, etc.
765          +
766          + Params:
767          +  serviceScope = The master `ServiceScope` representing the scope to destroy.
768          + ++/
769         void destroyScope(ref scope ServiceScope serviceScope)
770         {
771             assert(serviceScope._provider is this, "Attempting to destroy service scope who does not belong to this `ServiceProvider`.");
772             assert(this.isScopeInUse(serviceScope._index), "Bug?");
773             assert(serviceScope._isMasterReference, "Attempting to destroy service scope who is not the master reference for the scope. (Did this ServiceScope come from ServiceScopeAccessor?)");
774 
775             // For now, just clear the AA. Later on I'll want to add more behaviour though.
776             this.setScopeInUse(serviceScope._index, false);
777             this._scopes[serviceScope._index].instances.clear();
778 
779             serviceScope._index = 0;
780             serviceScope._provider = null;
781         }
782 
783         /++
784          + Returns:
785          +  The `ServiceInfo` for the given `baseType`, or `null` if the `baseType` is not known by this `ServiceProvider`.
786          + ++/
787         @safe
788         Nullable!(const(ServiceInfo)) getServiceInfoForBaseType(TypeInfo baseType) const nothrow pure
789         {
790             foreach(service; this._allServices)
791             {
792                 if(service._baseType is baseType)
793                     return nullable(service);
794             }
795 
796             return typeof(return).init;
797         }
798 
799         /// ditto
800         Nullable!(const(ServiceInfo)) getServiceInfoForBaseType(alias BaseType)() const
801         if(ServiceInfo.isValidBaseType!BaseType)
802         {
803             return this.getServiceInfoForBaseType(typeid(BaseType));
804         }
805         ///
806         unittest
807         {
808             static class C {}
809 
810             auto info = ServiceInfo.asScoped!C();
811             const provider = new ServiceProvider([info]);
812 
813             assert(provider.getServiceInfoForBaseType!C() == info);
814         }
815 
816         @property @safe @nogc
817         ref ServiceScope defaultScope() nothrow pure
818         {
819             return this._defaultScope;
820         }
821     }
822 }
823 
824 /++
825  + A static class containing functions to easily construct objects with Dependency Injection, or
826  + execute functions where their parameters are injected via Dependency Injection.
827  + ++/
828 static final class Injector
829 {
830     import std.traits : ReturnType, isSomeFunction;
831 
832     public static final
833     {
834         /++
835          + Executes a function where all of its parameters are retrieved from the given `ServiceScope`.
836          +
837          + Limitations:
838          +  Currently you cannot provide your own values for parameters that shouldn't be injected.
839          +
840          + Behaviour:
841          +  For parameters that are a `class` or `interface`, an attempt to retrieve them as a service from
842          +  `services` is made. These parameters will be `null` if no service for them was found.
843          +
844          +  For parameters of other types, they are left as their `.init` value.
845          +
846          + Params:
847          +  services = The `ServiceScope` allowing access to any services to be injected into the function call.
848          +  func     = The function to execute.
849          +
850          + Returns:
851          +  Whatever `func` returns.
852          + ++/
853         ReturnType!F execute(F)(ref ServiceScope services, F func)
854         if(isSomeFunction!F)
855         {
856             import std.traits : Parameters;
857 
858             alias FuncParams = Parameters!F;
859             FuncParams params;
860 
861             static foreach(i, ParamT; FuncParams)
862             {{
863                 static if(is(ParamT == class) || is(ParamT == interface))
864                     params[i] = services.getServiceOrNull!ParamT;
865             }}
866 
867             static if(is(ReturnType!F == void))
868                 func(params);
869             else
870                 return func(params);
871         }
872         ///
873         unittest
874         {
875             static class AandB
876             {
877                 int a = 1;
878                 int b = 3;
879             }
880 
881             static int addAandB(AandB ab)
882             {
883                 return ab.a + ab.b;
884             }
885 
886             auto services = new ServiceProvider([ServiceInfo.asSingleton!AandB]);
887             
888             assert(Injector.execute(services.defaultScope, &addAandB) == 4);
889         }
890 
891         /++
892          + Constructs a `class` or `struct` via Dependency Injection.
893          +
894          + Limitations:
895          +  See `Injector.execute`.
896          +
897          +  There are no guard checks implemented to ensure services with incompatible lifetimes aren't being used together. However, `ServiceProvider` does contain
898          +  a check for this, please refer to its documentation.
899          +
900          +  There are no guard checks implemented to block circular references between services. However, `ServiceProvider` does contain
901          +  a check for this, please refer to its documentation.
902          +
903          + Behaviour:
904          +  See `Injector.execute` for what values are injected into the ctor's parameters.
905          +
906          +  If the type has a normal ctor, then the result of `__traits(getMember, T, "__ctor")` is used as the constructor.
907          +  Types with multiple ctors are undefined behaviour.
908          +
909          +  If the type contains a static function called `injectionCtor`, then that function takes priority over any normal ctor
910          +  and will be used to construct the object. Types with multiple `injectionCtor`s are undefined behaviour.
911          +
912          +  If the type does not contain any of the above ctor functions then:
913          +   
914          +      * If the type is a class, `new T()` is used (if possible, otherwise compiler error).
915          +
916          +      * If the type is a `struct`, `T.init` is used.
917          +
918          + Params:
919          +  services = The `ServiceScope` allowing access to any services to be injected into the newly constructed object.
920          +
921          + Returns:
922          +  The newly constructed object.
923          + ++/
924         T construct(alias T)(ref ServiceScope services)
925         if(is(T == class) || is(T == struct))
926         {
927             import std.traits : Parameters;
928 
929             alias Ctor = Injector.FindCtor!T;
930 
931             static if(Injector.isStaticFuncCtor!Ctor) // Special ctors like `injectionCtor`
932             {
933                 alias CtorParams = Parameters!Ctor;
934                 return Injector.execute(services, (CtorParams params) => T.injectionCtor(params));
935             }
936             else static if(Injector.isBuiltinCtor!Ctor) // Normal ctor
937             {
938                 alias CtorParams = Parameters!Ctor;
939 
940                 static if(is(T == class))
941                     return Injector.execute(services, (CtorParams params) => new T(params));
942                 else
943                     return Injector.execute(services, (CtorParams params) => T(params));
944             }
945             else // NoValidCtor
946             {
947                 static if(is(T == class))
948                     return new T();
949                 else
950                     return T.init;
951             }
952         }
953 
954         /// `FindCtor` will evaluate to this no-op function if it can't find a proper ctor.
955         static void NoValidCtor(){}
956 
957         /++
958          + Finds the most appropriate ctor for use with injection.
959          +
960          + Notes:
961          +  Types that have multiple overloads of a ctor produce undefined behaviour.
962          +
963          +  You can use `Injector.isBuiltinCtor` and `Injector.isStaticFuncCtor` to determine what type of Ctor was chosen.
964          +
965          + Returns:
966          +  Either the type's `__ctor`, the type's `injectionCtor`, or `NoValidCtor` if no appropriate ctor was found.
967          + ++/
968         template FindCtor(T)
969         {
970             static if(__traits(hasMember, T, "injectionCtor"))
971                 alias FindCtor = __traits(getMember, T, "injectionCtor");
972             else static if(__traits(hasMember, T, "__ctor"))
973                 alias FindCtor = __traits(getMember, T, "__ctor");
974             else
975                 alias FindCtor = NoValidCtor;
976         }
977 
978         enum isBuiltinCtor(alias F)     = __traits(identifier, F) == "__ctor";
979         enum isStaticFuncCtor(alias F)  = !isBuiltinCtor!F && __traits(identifier, F) != "NoValidCtor";
980     }
981 }
982 
983 // Testing transient services
984 unittest
985 {
986     static class Transient
987     {
988         int increment = 0;
989 
990         int getI()
991         {
992             return this.increment++;
993         }
994     }
995 
996     auto services = new ServiceProvider([ServiceInfo.asTransient!Transient]);
997     
998     auto serviceA = services.defaultScope.getServiceOrNull!Transient();
999     auto serviceB = services.defaultScope.getServiceOrNull!Transient();
1000 
1001     assert(serviceA.getI() == 0);
1002     assert(serviceA.getI() == 1);
1003     assert(serviceB.getI() == 0);
1004 }
1005 
1006 // Testing scoped services
1007 unittest
1008 {
1009     static class Scoped
1010     {
1011         int increment = 0;
1012 
1013         int getI()
1014         {
1015             return this.increment++;
1016         }
1017     }
1018 
1019     auto services = new ServiceProvider([ServiceInfo.asScoped!Scoped]);
1020     auto scopeB   = services.createScope();
1021 
1022     auto serviceA1 = services.defaultScope.getServiceOrNull!Scoped();
1023     auto serviceA2 = services.defaultScope.getServiceOrNull!Scoped(); // So I can test that it's using the same one.
1024     auto serviceB  = scopeB.getServiceOrNull!Scoped();
1025     
1026     assert(serviceA1 is serviceA2, "Scoped didn't work ;(");
1027     assert(serviceA1.getI() == 0);
1028     assert(serviceA2.getI() == 1);
1029     assert(serviceB.getI() == 0);
1030 
1031     services.destroyScope(scopeB);
1032 
1033     scopeB   = services.createScope();
1034     serviceB = scopeB.getServiceOrNull!Scoped();
1035     assert(serviceB.getI() == 0);
1036 }
1037 
1038 // Testing singleton services
1039 unittest
1040 {
1041     static class Singleton
1042     {
1043         int increment = 0;
1044 
1045         int getI()
1046         {
1047             return this.increment++;
1048         }
1049     }
1050 
1051     auto services = new ServiceProvider([ServiceInfo.asSingleton!Singleton]);
1052     auto scopeB   = services.createScope();
1053 
1054     auto serviceA = services.defaultScope.getServiceOrNull!Singleton;
1055     auto serviceB = scopeB.getServiceOrNull!Singleton;
1056 
1057     assert(serviceA !is null && serviceA is serviceB);
1058     assert(serviceA.getI() == 0);
1059     assert(serviceB.getI() == 1);
1060 }
1061 
1062 // Testing scope dtor behaviour
1063 unittest
1064 {
1065     auto services = new ServiceProvider(null);
1066     auto scopeB = services.createScope();
1067     assert(scopeB._index == 1);
1068 
1069     // Test #1, seeing if setting a new value for a scope variable performs the dtor properly.
1070     {
1071         scopeB = services.createScope();
1072         assert(scopeB._index == 2);
1073         assert(services._scopeInUseMasks[0] == 0b101);
1074     }
1075 
1076     services.destroyScope(scopeB);
1077 
1078     // Test #2, seeing if going out of scope uses the dtor properly (it should if the first case works, but may as well add a test anyway :P)
1079     {
1080         auto scopedScope = services.createScope();
1081         assert(scopedScope._index == 1);
1082 
1083         scopeB = services.createScope();
1084         assert(scopeB._index == 2);
1085     }
1086 
1087     assert(services._scopeInUseMasks[0] == 0b101);
1088     scopeB = services.createScope();
1089     assert(scopeB._index == 1);
1090     assert(services._scopeInUseMasks[0] == 0b11);
1091 }
1092 
1093 // Test ServiceProvider injection
1094 unittest
1095 {
1096     auto services = new ServiceProvider(null);
1097 
1098     assert(services.defaultScope.getServiceOrNull!ServiceProvider() is services);
1099 }
1100 
1101 // Test ServiceScopeAccessor
1102 unittest
1103 {
1104     auto services = new ServiceProvider(null);
1105     
1106     // Also test to make sure the slaveScope doesn't destroy the scope outright.
1107     {
1108         const slaveScope = services.defaultScope.getServiceOrNull!ServiceScopeAccessor().serviceScope;
1109         assert(slaveScope._provider is services);
1110         assert(slaveScope._index == 0);
1111         assert(!slaveScope._isMasterReference);
1112     }
1113     assert(services._scopeInUseMasks[0] == 0b1);
1114 
1115     // Test to make sure we only construct the accessor a single time per scope index.
1116     auto masterScope = services.createScope();
1117     assert(services._scopes[1].accessor is null);
1118     assert(masterScope.getServiceOrNull!ServiceScopeAccessor() !is null);
1119     assert(services._scopes[1].accessor !is null);
1120 
1121     auto accessor = masterScope.getServiceOrNull!ServiceScopeAccessor();
1122     services.destroyScope(masterScope);
1123     masterScope = services.createScope();
1124     assert(services._scopes[1].accessor is accessor);
1125 }