//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. } } }