private ActinInstantiator getOrCreateExternalInstantiator(Type type) { if (!this.Running) { throw new AccessViolationException("The director has not yet started running."); } var success = false; ActinInstantiator inst; lock (lockInstantiators) { this.instantiators.TryGetValue(type, out inst); } if (!success) { inst = new ActinInstantiator(type); lock (lockInstantiators) { //It's better to lock for the whole creation process, as this ensures all internal instantiator //state is also locked. This only happens when a new type is discovered, so it shouldn't //cause too much delay in exchange for that safety. var skippedBecauseConcreteLineageRequired = !inst.Build(t => { if (!this.instantiators.TryGetValue(t, out var dependencyInstantiator)) { dependencyInstantiator = new ActinInstantiator(t); this.instantiators[t] = dependencyInstantiator; } return(dependencyInstantiator); }); if (skippedBecauseConcreteLineageRequired) { throw new ApplicationException($"{type.Name} must remove the [Parent] or [Sibling] attribute. Top level external objects may not have Parents or Siblings injected."); } instantiators[type] = inst; } } return(inst); }
//Init ======================= Init: private async Task <ConfigureUtil> init(Action <ConfigureUtil> configure) { this.runtimeLog = new LogDispatcher(this.Clock); lock (lockRunning) { if (__running__) { return(null); } __running__ = true; } this.AddSingletonDependency(this, typeof(ICreateInstanceActorForScene)); //Get configuration from the user: var config = new ConfigureUtil(); configure(config); config.Sanitize(); //Add this director to the static list of running directors: lockDirectors.EnterWriteLock(); try { if (directors.ContainsKey(config.DirectorName)) { throw new ApplicationException($"There is already a running director with the name '{config.DirectorName}'"); } directors[config.DirectorName] = this; } finally { lockDirectors.ExitWriteLock(); } //Get an instance of the start up log, or throw an exception: var log = new LogDispatcherForActor(new LogDispatcher(this.Clock), "Before Start"); Actor startUpLogAsActor; { var startUpLogInstantiator = new ActinInstantiator(config.StartUpLogType); if (!startUpLogInstantiator.Build((t) => { throw new ApplicationException($"{config.StartUpLogType.Name} is being used as the 'StartUp' Log, and must not have any dependencies."); })) { throw new ApplicationException($"{config.StartUpLogType.Name} is being used as the 'StartUp' Log, and must not have any dependencies."); } lock (lockInstantiators) { instantiators[config.StartUpLogType] = startUpLogInstantiator; } var startUpLog = startUpLogInstantiator.GetSingletonInstance(this) as IActinLogger; if (startUpLog == null) { throw new ApplicationException("The 'StartUp' Log must implement IActinLogger."); } startUpLogAsActor = startUpLog as Actor; log.AddDestination(startUpLog); if (startUpLog is ActinStandardLogger && !string.IsNullOrWhiteSpace(config.StandardLogOutputFolder)) { var standardLogger = startUpLog as ActinStandardLogger; standardLogger.SetClock(this.Clock); standardLogger.SetLogFolderPath(config.StandardLogOutputFolder); } } try { //Do manual user start up: log.Info("Director Initializing"); await config.RunBeforeStart(new ActorUtil(null, this.Clock) { Log = log, }); //Do automated DI startup: foreach (var a in config.AssembliesToCheckForDI) { try { foreach (var t in a.GetTypes()) { if (t.HasAttribute <SingletonAttribute>() || t.HasAttribute <InstanceAttribute>()) { lock (lockInstantiators) { if (!instantiators.ContainsKey(t)) { //If it's already contained, then it was manually added as a Singleton dependency. //We can't add it again, as when manually added, a singleton instance was provided. instantiators[t] = new ActinInstantiator(t); } } } } } catch (Exception ex) { var msg = $"Actin Failed in assembly {a.FullName}. Inner Exception: {ex.Message}"; log.Error(msg, ex); await runStartUpLog(); throw new Exception(msg, ex); } } lock (lockInstantiators) { //At this point, we should only have manually added singletons, and attribute marked Singleton or Instance classes. var rootableInstantiators = instantiators.Values.ToList(); rootableInstantiators = rootableInstantiators.Where(config.RootActorFilter).ToList(); var skipped = new List <ActinInstantiator>(); foreach (var instantiator in rootableInstantiators) { try { var skippedBecauseConcreteLineageRequired = !instantiator.Build(t => { if (!this.instantiators.TryGetValue(t, out var dependencyInstantiator)) { dependencyInstantiator = new ActinInstantiator(t); this.instantiators[t] = dependencyInstantiator; } return(dependencyInstantiator); }); if (skippedBecauseConcreteLineageRequired) { skipped.Add(instantiator); } } catch (Exception ex) { throw new ApplicationException($"Failed to build rootable type {instantiator.Type.Name}: {ex.Message}", ex); } } var skippedAndNeverBuilt = skipped.Where(x => !x.WasBuilt).ToList(); if (skippedAndNeverBuilt.Any()) { throw new AggregateException(skippedAndNeverBuilt.Select( x => new ApplicationException($"{x.Type.Name} has a concrete [Parent] or [Sibling], but its parent was not found in the dependency chain." + "Most likely you forgot to mark the parent class with a [Singleton] or [Instance] attribute." + "If the Parent is a Scene, or not always available, then you must instead use [FlexibleParent] or [FlexibleSibling]." + "Note that the flexible attributes do not do type checking on start-up."))); } foreach (var singletonInstantiator in rootableInstantiators.Where(x => x.IsRootSingleton)) { var singleton = singletonInstantiator.GetSingletonInstance(this); } } if (!TryGetSingleton(config.RuntimeLogType, out var rtLog)) { throw new ApplicationException($"Actin failed to get a singleton instance of the 'Runtime' log. Ensure you've marked {config.RuntimeLogType.Name} with the Singleton attribute, or manually added it to the Director as a singleton."); } var rtLogAsIActinLogger = rtLog as IActinLogger; if (rtLogAsIActinLogger == null) { throw new ApplicationException($"{config.RuntimeLogType} must implement IActinLogger, as it is being used as the 'Runtime' Log."); } runtimeLog.AddDestination(rtLogAsIActinLogger); await config.RunAfterStart(new ActorUtil(null, this.Clock) { Log = new LogDispatcherForActor(runtimeLog, "After Start"), }); return(config); } catch (Exception ex) when(logFailedStartup(ex)) { //Exception is always unhandled, this is a nicer way to ensure logging before the exception propagates. return(null); } bool logFailedStartup(Exception ex) { log.Log(new ActinLog { Time = Clock.Now, Location = "StartUp", UserMessage = "Actin failed to start.", Details = ex?.ToString(), Type = LogType.Error, }); runStartUpLog().Wait(); return(false); } async Task runStartUpLog() { try { if (startUpLogAsActor != null) { var disposeHandle = await startUpLogAsActor.Init(() => new DispatchData { MainLog = new ConsoleLogger(), }); if (disposeHandle != null) { lock (lockDisposeHandles) { disposeHandles.Add(disposeHandle); } } await startUpLogAsActor.Run(() => new DispatchData { MainLog = new ConsoleLogger(), }); } } catch { //Nowhere to put this if the log is failing. } } }
internal void ResolveDependencies(object instance, DependencyType dependencyType, object parent, ActinInstantiator parentInstantiator, Director director) { if (director is null) { throw new NullReferenceException("Director may not be null."); } var asActor = instance as Actor_SansType; if (asActor != null) { asActor.Instantiator = this; //Used for automatically disposing child dependencies. asActor.Util = new ActorUtil(asActor, director.Clock); } Func <AccessorInstantiatorPair, bool> notSet = (x) => x.Accessor.GetVal(instance) == null; //Set and Resolve Singletons: foreach (var dep in SingletonDependencies.Where(notSet)) { dep.Accessor.SetVal(instance, dep.Instantiator.GetSingletonInstance(director)); } //Set Child Instances: var unresolvedInstanceDependencies = new List <AccessorInstantiatorPairWithInstance>(); foreach (var dep in InstanceDependencies.Where(notSet)) { var childInstance = dep.Instantiator.CreateNew(); unresolvedInstanceDependencies.Add(new AccessorInstantiatorPairWithInstance { Pair = dep, Instance = childInstance, }); dep.Accessor.SetVal(instance, childInstance); } if (instance != null) //It should never be null, but it doesn't hurt to be safe. //Note that it's important that we called this before we resolved child dependencies. //The director assumes that the order it receives dependencies in is the order in //which they should be initialized. { director.RegisterInjectedDependency(instance); } //Resolve Child Instances: foreach (var dep in unresolvedInstanceDependencies) { dep.Pair.Instantiator.ResolveDependencies(dep.Instance, DependencyType.Instance, instance, this, director); } if (dependencyType == DependencyType.Instance && parent != null) { //Means we can have parents and siblings: foreach (var dep in ParentDependencies.Where(notSet)) { try { dep.Accessor.SetVal(instance, parent); } catch when(dep.Accessor.Markers.Contains(nameof(FlexibleParentAttribute))) { //Swallow the exception, as FlexibleParents may be different types. }