using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using NotNull = JetBrains.Annotations.NotNullAttribute;
namespace Unity.Services.Core.Internal
{
///
/// Utility to initialize all Unity services from a single endpoint.
///
class UnityServicesInternal : IUnityServices
{
internal const string InitSuccessEventInvocationError = "Exception in services initialization success event handler: ";
internal const string InitFailureEventInvocationError = "Exception in services initialization failure event handler: ";
public event Action Initialized;
public event Action InitializeFailed;
public ServicesInitializationState State { get; private set; }
public InitializationOptions Options
{
get => Registry.Options;
internal set => Registry.Options = value;
}
internal bool CanInitialize;
TaskCompletionSource m_Initialization;
[NotNull]
internal CoreRegistry Registry { get; }
[NotNull]
internal CoreMetrics Metrics { get; }
[NotNull]
internal CoreDiagnostics Diagnostics { get; }
public UnityServicesInternal(
[NotNull] CoreRegistry registry,
[NotNull] CoreMetrics coreMetrics,
[NotNull] CoreDiagnostics coreDiagnostics)
{
Registry = registry;
Metrics = coreMetrics;
Diagnostics = coreDiagnostics;
}
///
/// Single entry point to initialize all used services.
///
///
/// The options to customize services initialization.
///
///
/// Return a handle to the initialization operation.
///
public async Task InitializeAsync(InitializationOptions options)
{
try
{
if (options is null)
{
options = new InitializationOptions();
}
if (!HasRequestedInitialization()
|| HasInitializationFailed())
{
Registry.Options = options;
m_Initialization = new TaskCompletionSource();
}
if (!CanInitialize
|| State != ServicesInitializationState.Uninitialized)
{
await m_Initialization.Task;
}
else
{
await InitializeServicesAsync();
}
bool HasInitializationFailed()
{
return m_Initialization.Task.IsCompleted
&& m_Initialization.Task.Status != TaskStatus.RanToCompletion;
}
TriggerInitializeSuccess();
}
catch (Exception e)
{
TriggerInitializeFailed(e);
throw;
}
}
public string GetIdentifier()
{
return Registry.InstanceId;
}
void TriggerInitializeSuccess()
{
try
{
Initialized?.Invoke();
}
catch (Exception e)
{
CoreLogger.LogError($"{InitSuccessEventInvocationError} {e}");
}
}
void TriggerInitializeFailed(Exception initException)
{
try
{
InitializeFailed?.Invoke(initException);
}
catch (Exception e)
{
CoreLogger.LogError($"{InitFailureEventInvocationError} {e}");
}
}
public T GetService()
{
return Registry.GetService();
}
bool HasRequestedInitialization()
{
return !(m_Initialization is null);
}
async Task InitializeServicesAsync()
{
State = ServicesInitializationState.Initializing;
var initStopwatch = new Stopwatch();
initStopwatch.Start();
var dependencyTree = Registry.PackageRegistry.Tree;
if (dependencyTree is null)
{
var reason = new NullReferenceException("Services require a valid dependency tree to be initialized.");
FailServicesInitialization(reason);
throw reason;
}
var sortedPackageTypeHashes = new List(dependencyTree.PackageTypeHashToInstance.Count);
try
{
SortPackages();
await InitializePackagesAsync();
}
catch (Exception reason)
{
FailServicesInitialization(reason);
throw;
}
SucceedServicesInitialization();
void SortPackages()
{
var sorter = new DependencyTreeInitializeOrderSorter(dependencyTree, sortedPackageTypeHashes);
sorter.SortRegisteredPackagesIntoTarget();
}
async Task InitializePackagesAsync()
{
var initializer = new CoreRegistryInitializer(Registry, sortedPackageTypeHashes);
await initializer.InitializeRegistryAsync();
}
void FailServicesInitialization(Exception reason)
{
State = ServicesInitializationState.Uninitialized;
initStopwatch.Stop();
m_Initialization.TrySetException(reason);
}
void SucceedServicesInitialization()
{
State = ServicesInitializationState.Initialized;
Registry.LockComponentRegistration();
initStopwatch.Stop();
m_Initialization.TrySetResult(null);
}
}
internal void SendInitializationMetrics(List packageInitInfos)
{
foreach (var initInfo in packageInitInfos)
{
Metrics.SendInitTimeMetricForPackage(initInfo.PackageType, initInfo.InitializationTimeInSeconds);
}
}
internal void EnableInitialization()
{
CanInitialize = true;
}
internal async Task EnableInitializationAsync()
{
CanInitialize = true;
CorePackageRegistry.Instance.Lock();
if (!HasRequestedInitialization())
return;
await InitializeServicesAsync();
}
}
}