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