// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using Unity.Profiling;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit
{
///
/// Static class that represents the Mixed Reality Toolkit service registry.
///
///
/// The service registry is used to enable discovery of and access to active Mixed Reality Toolkit services at
/// runtime without requiring direct code reference to a singleton style component.
///
public static class MixedRealityServiceRegistry
{
///
/// The service registry store where the key is the Type of the service interface and the value is
/// a pair in which they key is the service instance and the value is the registrar instance.
///
private static Dictionary>> registry =
new Dictionary>>();
///
/// A cache used to power
///
///
/// Lists are sorted in ascending priority order (i.e. services with a smaller priority
/// value are first in the list).
///
private static Dictionary> allServicesByRegistrar =
new Dictionary>();
///
/// A cache used to power
///
///
/// The list is sorted in ascending priority order (i.e. services with a smaller priority
/// value are first in the list).
///
private static List allServices = new List();
///
/// A comparer used to sort the allServices and allServiceByRegistrar lists in-place.
///
private static readonly Comparer ascendingOrderComparer =
Comparer.Create((i1, i2) => i1.Priority.CompareTo(i2.Priority));
///
/// Static constructor.
///
static MixedRealityServiceRegistry()
{ }
///
/// Adds an instance to the registry.
///
/// The interface type of the service being added.
/// Instance of the service to add.
/// Instance of the registrar manages the service.
///
/// True if the service was successfully added, false otherwise.
///
public static bool AddService(T serviceInstance, IMixedRealityServiceRegistrar registrar) where T : IMixedRealityService
{
if (serviceInstance == null)
{
// Adding a null service instance is not supported.
return false;
}
if (serviceInstance is IMixedRealityDataProvider)
{
// Data providers are generally not used by application code. Services that intend for clients to
// directly communicate with their data providers will expose a GetDataProvider or similarly named
// method.
return false;
}
if (TryGetService(out _, serviceInstance.Name))
{
return false;
}
Type interfaceType = typeof(T);
// Ensure we have a place to put our newly registered service.
if (!registry.ContainsKey(interfaceType))
{
registry.Add(interfaceType, new List>());
}
List> services = registry[interfaceType];
services.Add(new KeyValuePair(serviceInstance, registrar));
AddServiceToCache(serviceInstance, registrar);
return true;
}
///
/// Removes an instance from the registry.
///
/// The interface type of the service being removed.
/// Instance of the service to remove.
/// Instance of the registrar manages the service.
///
/// True if the service was successfully removed, false otherwise.
///
public static bool RemoveService(T serviceInstance, IMixedRealityServiceRegistrar registrar) where T : IMixedRealityService
{
return RemoveServiceInternal(typeof(T), serviceInstance, registrar);
}
///
/// Removes an instance from the registry.
///
/// The interface type of the service being removed.
/// Instance of the service to remove.
///
/// True if the service was successfully removed, false otherwise.
///
public static bool RemoveService(T serviceInstance) where T : IMixedRealityService
{
T tempService;
IMixedRealityServiceRegistrar registrar;
if (!TryGetService(out tempService, out registrar))
{
return false;
}
if (!object.ReferenceEquals(serviceInstance, tempService))
{
return false;
}
return RemoveServiceInternal(typeof(T), serviceInstance, registrar);
}
///
/// Removes an instance from the registry.
///
/// The interface type of the service being removed.
/// The friendly name of the service to remove.
///
/// True if the service was successfully removed, false otherwise.
///
public static bool RemoveService(string name) where T : IMixedRealityService
{
T tempService;
IMixedRealityServiceRegistrar registrar;
if (!TryGetService(out tempService, out registrar, name))
{
return false;
}
return RemoveServiceInternal(typeof(T), tempService, registrar);
}
///
/// Removes an instance from the registry.
///
/// The interface type of the service being removed.
/// Instance of the service to remove.
/// Instance of the registrar manages the service.
///
/// True if the service was successfully removed, false otherwise.
///
private static bool RemoveServiceInternal(
Type interfaceType,
IMixedRealityService serviceInstance,
IMixedRealityServiceRegistrar registrar)
{
if (!registry.ContainsKey(interfaceType)) { return false; }
List> services = registry[interfaceType];
bool removed = services.Remove(new KeyValuePair(serviceInstance, registrar));
if (services.Count == 0)
{
// If the last service was removed, the key can be removed.
registry.Remove(interfaceType);
}
RemoveServiceFromCache(serviceInstance, registrar);
return removed;
}
///
/// Adds the given service/registrar combination to the GetAllServices cache
///
private static void AddServiceToCache(
IMixedRealityService service,
IMixedRealityServiceRegistrar registrar)
{
// Services are stored in ascending priority order - adding them to the
// list requires that we re-enforce that order. This must happen
// in both the allServices and allServicesByRegistrar data structures.
allServices.Add(service);
allServices.Sort(ascendingOrderComparer);
if (!allServicesByRegistrar.ContainsKey(registrar))
{
allServicesByRegistrar.Add(registrar, new List());
}
allServicesByRegistrar[registrar].Add(service);
allServicesByRegistrar[registrar].Sort(ascendingOrderComparer);
}
///
/// Removes the given service/registrar combination from the GetAllServices cache
///
private static void RemoveServiceFromCache(
IMixedRealityService service,
IMixedRealityServiceRegistrar registrar)
{
// Removing from the sorted list keeps sort order, so re-sorting isn't necessary
allServices.Remove(service);
if (allServicesByRegistrar.ContainsKey(registrar))
{
allServicesByRegistrar[registrar].Remove(service);
if (allServicesByRegistrar[registrar].Count == 0)
{
allServicesByRegistrar.Remove(registrar);
}
}
}
///
/// Gets the first instance of the requested service from the registry that matches the given query.
///
/// The interface type of the service being requested.
/// Output parameter to receive the requested service instance.
/// Optional name of the service.
///
/// True if the requested service is being returned, false otherwise.
///
public static bool TryGetService(
out T serviceInstance,
string name = null) where T : IMixedRealityService
{
return TryGetService(
out serviceInstance,
out _, // The registrar out param is not used, it can be discarded.
name);
}
///
/// Gets the first instance of the requested service from the registry that matches the given query.
///
/// The interface type of the service being requested.
/// Output parameter to receive the requested service instance.
/// Output parameter to receive the registrar that loaded the service instance.
/// Optional name of the service.
///
/// True if the requested service is being returned, false otherwise.
///
public static bool TryGetService(
out T serviceInstance,
out IMixedRealityServiceRegistrar registrar,
string name = null) where T : IMixedRealityService
{
Type interfaceType = typeof(T);
if (TryGetServiceInternal(interfaceType, out IMixedRealityService tempService, out registrar, name))
{
Debug.Assert(tempService is T, "The service in the registry does not match the expected type.");
serviceInstance = (T)tempService;
return true;
}
serviceInstance = default(T);
registrar = null;
return false;
}
///
/// Gets the first instance of the requested service from the registry that matches the given query.
///
/// The interface type of the service being requested.
/// Output parameter to receive the requested service instance.
/// Output parameter to receive the registrar that loaded the service instance.
/// Optional name of the service.
///
/// True if the requested service is being returned, false otherwise.
///
public static bool TryGetService(Type interfaceType,
out IMixedRealityService serviceInstance,
out IMixedRealityServiceRegistrar registrar,
string name = null)
{
if (!typeof(IMixedRealityService).IsAssignableFrom(interfaceType))
{
Debug.LogWarning($"Cannot find type {interfaceType.Name} since it does not extend IMixedRealityService");
serviceInstance = null;
registrar = null;
return false;
}
return TryGetServiceInternal(interfaceType, out serviceInstance, out registrar, name);
}
private static readonly ProfilerMarker TryGetServiceInternalPerfMarker = new ProfilerMarker("[MRTK] MixedRealityServiceRegistry.TryGetServiceInternal");
private static bool TryGetServiceInternal(Type interfaceType,
out IMixedRealityService serviceInstance,
out IMixedRealityServiceRegistrar registrar,
string name = null)
{
using (TryGetServiceInternalPerfMarker.Auto())
{
// Assume failed and return null unless proven otherwise
serviceInstance = null;
registrar = null;
// If there is an entry for the interface key provided, search that small list first
if (registry.ContainsKey(interfaceType))
{
if (FindEntry(registry[interfaceType], interfaceType, name, out serviceInstance, out registrar))
{
return true;
}
}
// Either there is no entry for the interface type, or it was not placed in that list.
// Services can have multiple supported interfaces thus they may match the requested query but be placed in a different registry bin
// Thus, search all bins until a match is found
foreach (var list in registry.Values)
{
if (FindEntry(list, interfaceType, name, out serviceInstance, out registrar))
{
return true;
}
}
return false;
}
}
private static readonly ProfilerMarker FindEntryPerfMarker = new ProfilerMarker("[MRTK] MixedRealityServiceRegistry.FindEntry");
///
/// Helper method to search list of IMixedRealityService/IMixedRealityServiceRegistrar pairs to find first service that matches name and interface type query
///
/// list of IMixedRealityService/IMixedRealityServiceRegistrar pairs to search
/// type of interface to check
/// name of service to check. Wildcard if null or empty
/// reference to IMixedRealityService matching query, null otherwise
/// reference to IMixedRealityServiceRegistrar matching query, null otherwise
/// true if found first entry to match query, false otherwise
private static bool FindEntry(List> serviceList,
Type interfaceType,
string name,
out IMixedRealityService serviceInstance,
out IMixedRealityServiceRegistrar registrar)
{
using (FindEntryPerfMarker.Auto())
{
// Assume failed and return null unless proven otherwise
serviceInstance = null;
registrar = null;
for (int i = 0; i < serviceList.Count; ++i)
{
var svc = serviceList[i].Key;
if ((string.IsNullOrEmpty(name) || svc.Name == name) && interfaceType.IsAssignableFrom(svc.GetType()))
{
serviceInstance = svc;
registrar = serviceList[i].Value;
return true;
}
}
return false;
}
}
///
/// Clears the registry cache of all services
///
public static void ClearAllServices()
{
if (registry != null)
{
registry.Clear();
allServices.Clear();
allServicesByRegistrar.Clear();
}
}
///
/// Returns readonly list of all services registered
///
///
/// The list is sorted in ascending priority order.
///
public static IReadOnlyList GetAllServices()
{
return allServices;
}
///
/// Returns readonly list of all services registered for given registrar
///
/// Registrar object to filter services by
///
/// The list is sorted in ascending priority order.
///
/// Readonly list of all services registered for given registrar, all services if parameter null.
/// If given a registrar that the registry is not aware of, returns null.
///
public static IReadOnlyCollection GetAllServices(IMixedRealityServiceRegistrar registrar)
{
if (registrar == null)
{
return GetAllServices();
}
if (allServicesByRegistrar.TryGetValue(registrar, out List services))
{
return services;
}
return null;
}
}
}