499 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
		
		
			
		
	
	
			499 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
|  | // Disable unreachable code warning caused by DEBUG | |||
|  | #pragma warning disable 0162 | |||
|  | 
 | |||
|  | namespace SRF.Service | |||
|  | { | |||
|  |     using System; | |||
|  |     using System.Collections.Generic; | |||
|  |     using System.Linq; | |||
|  |     using System.Reflection; | |||
|  |     using Components; | |||
|  |     using Helpers; | |||
|  |     using Internal; | |||
|  |     using UnityEngine; | |||
|  |     using Object = UnityEngine.Object; | |||
|  | 
 | |||
|  |     [AddComponentMenu(ComponentMenuPaths.SRServiceManager)] | |||
|  |     public class SRServiceManager : SRAutoSingleton<SRServiceManager> | |||
|  |     { | |||
|  | #if SRDEBUG | |||
|  | 		public const bool EnableLogging = true; | |||
|  | #else | |||
|  |         public const bool EnableLogging = false; | |||
|  | #endif | |||
|  | 
 | |||
|  | #if (!UNITY_2017 && !UNITY_2018 && !UNITY_2019) || UNITY_2019_3_OR_NEWER | |||
|  |         [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] | |||
|  |         public static void RuntimeInitialize() | |||
|  |         { | |||
|  |             // To handle entering play mode without a domain reload, need to reset the state of the service manager. | |||
|  |             _hasQuit = false; | |||
|  |         } | |||
|  | #endif | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Register the assembly that contains type <typeparamref name="TType"/> with the service manager. | |||
|  |         /// </summary> | |||
|  |         /// <typeparam name="TType"></typeparam> | |||
|  |         public static void RegisterAssembly<TType>() | |||
|  |         { | |||
|  | #if NETFX_CORE | |||
|  |             var assembly = typeof(TType).GetTypeInfo().Assembly; | |||
|  | #else | |||
|  |             var assembly = typeof(TType).Assembly; | |||
|  | #endif | |||
|  | 
 | |||
|  |             if (_assemblies.Contains(assembly)) | |||
|  |             { | |||
|  |                 return; | |||
|  |             } | |||
|  | 
 | |||
|  |             _assemblies.Add(assembly); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Is there a service loading? | |||
|  |         /// </summary> | |||
|  |         public static bool IsLoading | |||
|  |         { | |||
|  |             get { return LoadingCount > 0; } | |||
|  |         } | |||
|  | 
 | |||
|  |         public static int LoadingCount = 0; | |||
|  | 
 | |||
|  |         public static T GetService<T>() where T : class | |||
|  |         { | |||
|  |             var s = GetServiceInternal(typeof(T)) as T; | |||
|  | 
 | |||
|  |             if (s == null && (!_hasQuit || EnableLogging)) | |||
|  |             { | |||
|  |                 Debug.LogWarning("Service {0} not found. (HasQuit: {1})".Fmt(typeof(T).Name, _hasQuit)); | |||
|  |             } | |||
|  | 
 | |||
|  |             return s; | |||
|  |         } | |||
|  | 
 | |||
|  |         public static object GetService(Type t) | |||
|  |         { | |||
|  |             var s = GetServiceInternal(t); | |||
|  | 
 | |||
|  |             if (s == null && (!_hasQuit || EnableLogging)) | |||
|  |             { | |||
|  |                 Debug.LogWarning("Service {0} not found. (HasQuit: {1})".Fmt(t.Name, _hasQuit)); | |||
|  |             } | |||
|  | 
 | |||
|  |             return s; | |||
|  |         } | |||
|  | 
 | |||
|  |         private static object GetServiceInternal(Type t) | |||
|  |         { | |||
|  |             if (_hasQuit || !Application.isPlaying) | |||
|  |             { | |||
|  |                 return null; | |||
|  |             } | |||
|  | 
 | |||
|  |             var services = Instance._services; | |||
|  | 
 | |||
|  |             for (var i = 0; i < services.Count; i++) | |||
|  |             { | |||
|  |                 var s = services[i]; | |||
|  | 
 | |||
|  |                 if (t.IsAssignableFrom(s.Type)) | |||
|  |                 { | |||
|  |                     if (s.Object == null) | |||
|  |                     { | |||
|  |                         UnRegisterService(t); | |||
|  |                         break; | |||
|  |                     } | |||
|  | 
 | |||
|  |                     return s.Object; | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             return Instance.AutoCreateService(t); | |||
|  |         } | |||
|  | 
 | |||
|  |         public static bool HasService<T>() where T : class | |||
|  |         { | |||
|  |             return HasService(typeof(T)); | |||
|  |         } | |||
|  | 
 | |||
|  |         public static bool HasService(Type t) | |||
|  |         { | |||
|  |             if (_hasQuit || !Application.isPlaying) | |||
|  |             { | |||
|  |                 return false; | |||
|  |             } | |||
|  | 
 | |||
|  |             var services = Instance._services; | |||
|  | 
 | |||
|  |             for (var i = 0; i < services.Count; i++) | |||
|  |             { | |||
|  |                 var s = services[i]; | |||
|  | 
 | |||
|  |                 if (t.IsAssignableFrom(s.Type)) | |||
|  |                 { | |||
|  |                     return s.Object != null; | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             return false; | |||
|  |         } | |||
|  | 
 | |||
|  |         public static void RegisterService<T>(object service) where T : class | |||
|  |         { | |||
|  |             RegisterService(typeof(T), service); | |||
|  |         } | |||
|  | 
 | |||
|  |         private static void RegisterService(Type t, object service) | |||
|  |         { | |||
|  |             if (_hasQuit) | |||
|  |             { | |||
|  |                 return; | |||
|  |             } | |||
|  | 
 | |||
|  |             if (HasService(t)) | |||
|  |             { | |||
|  |                 if (GetServiceInternal(t) == service) | |||
|  |                 { | |||
|  |                     return; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 throw new Exception("Service already registered for type " + t.Name); | |||
|  |             } | |||
|  | 
 | |||
|  |             UnRegisterService(t); | |||
|  | 
 | |||
|  |             if (!t.IsInstanceOfType(service)) | |||
|  |             { | |||
|  |                 throw new ArgumentException("service {0} must be assignable from type {1}".Fmt(service.GetType(), t)); | |||
|  |             } | |||
|  | 
 | |||
|  |             Instance._services.Add(new Service { | |||
|  |                 Object = service, | |||
|  |                 Type = t | |||
|  |             }); | |||
|  |         } | |||
|  | 
 | |||
|  |         public static void UnRegisterService<T>() where T : class | |||
|  |         { | |||
|  |             UnRegisterService(typeof(T)); | |||
|  |         } | |||
|  | 
 | |||
|  |         private static void UnRegisterService(Type t) | |||
|  |         { | |||
|  |             if (_hasQuit || !HasInstance) | |||
|  |             { | |||
|  |                 return; | |||
|  |             } | |||
|  | 
 | |||
|  |             if (!HasService(t)) | |||
|  |             { | |||
|  |                 return; | |||
|  |             } | |||
|  | 
 | |||
|  |             var services = Instance._services; | |||
|  | 
 | |||
|  |             for (var i = services.Count - 1; i >= 0; i--) | |||
|  |             { | |||
|  |                 var s = services[i]; | |||
|  | 
 | |||
|  |                 if (s.Type == t) | |||
|  |                 { | |||
|  |                     services.RemoveAt(i); | |||
|  |                 } | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         [Serializable] | |||
|  |         private class Service | |||
|  |         { | |||
|  |             public object Object; | |||
|  |             public Type Type; | |||
|  |         } | |||
|  | 
 | |||
|  |         [Serializable] | |||
|  |         private class ServiceStub | |||
|  |         { | |||
|  |             public Func<object> Constructor; | |||
|  |             public Type InterfaceType; | |||
|  |             public Func<Type> Selector; | |||
|  |             public Type Type; | |||
|  | 
 | |||
|  |             public override string ToString() | |||
|  |             { | |||
|  |                 var s = InterfaceType.Name + " ("; | |||
|  | 
 | |||
|  |                 if (Type != null) | |||
|  |                 { | |||
|  |                     s += "Type: " + Type; | |||
|  |                 } | |||
|  |                 else if (Selector != null) | |||
|  |                 { | |||
|  |                     s += "Selector: " + Selector; | |||
|  |                 } | |||
|  |                 else if (Constructor != null) | |||
|  |                 { | |||
|  |                     s += "Constructor: " + Constructor; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 s += ")"; | |||
|  | 
 | |||
|  |                 return s; | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         private static readonly List<Assembly> _assemblies = new List<Assembly>(2); | |||
|  | 
 | |||
|  |         private readonly SRList<Service> _services = new SRList<Service>(); | |||
|  | 
 | |||
|  |         private List<ServiceStub> _serviceStubs; | |||
|  | 
 | |||
|  |         private static bool _hasQuit; | |||
|  | 
 | |||
|  |         protected override void Awake() | |||
|  |         { | |||
|  |             _hasQuit = false; | |||
|  |             base.Awake(); | |||
|  |             DontDestroyOnLoad(CachedGameObject); | |||
|  | 
 | |||
|  |             CachedGameObject.hideFlags = HideFlags.NotEditable; | |||
|  |         } | |||
|  | 
 | |||
|  |         protected void UpdateStubs() | |||
|  |         { | |||
|  |             if (_serviceStubs != null) | |||
|  |             { | |||
|  |                 return; | |||
|  |             } | |||
|  | 
 | |||
|  |             RegisterAssembly<SRServiceManager>(); | |||
|  |            | |||
|  |             _serviceStubs = new List<ServiceStub>(); | |||
|  | 
 | |||
|  |             var types = new List<Type>(); | |||
|  | 
 | |||
|  |             foreach (var assembly in _assemblies) | |||
|  |             { | |||
|  |                 try | |||
|  |                 { | |||
|  | #if NETFX_CORE | |||
|  |                     types.AddRange(assembly.ExportedTypes); | |||
|  | #else | |||
|  |                     types.AddRange(assembly.GetExportedTypes()); | |||
|  | #endif | |||
|  |                 } | |||
|  |                 catch (Exception e) | |||
|  |                 { | |||
|  |                     Debug.LogError("[SRServiceManager] Error loading assembly {0}".Fmt(assembly.FullName), this); | |||
|  |                     Debug.LogException(e); | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             foreach (var type in types) | |||
|  |             { | |||
|  |                 ScanType(type); | |||
|  |             } | |||
|  | 
 | |||
|  |             if (EnableLogging) | |||
|  |             { | |||
|  |                 var serviceStrings = | |||
|  |                     _serviceStubs.Select(p => "	{0}".Fmt(p)).ToArray(); | |||
|  | 
 | |||
|  |                 Debug.Log("[SRServiceManager] Services Discovered: {0} \n  {1}".Fmt(serviceStrings.Length, | |||
|  |                     string.Join("\n  ", serviceStrings))); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         protected object AutoCreateService(Type t) | |||
|  |         { | |||
|  |             UpdateStubs(); | |||
|  | 
 | |||
|  |             foreach (var stub in _serviceStubs) | |||
|  |             { | |||
|  |                 if (stub.InterfaceType != t) | |||
|  |                 { | |||
|  |                     continue; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 object service = null; | |||
|  | 
 | |||
|  |                 if (stub.Constructor != null) | |||
|  |                 { | |||
|  |                     service = stub.Constructor(); | |||
|  |                 } | |||
|  |                 else | |||
|  |                 { | |||
|  |                     var serviceType = stub.Type; | |||
|  | 
 | |||
|  |                     if (serviceType == null) | |||
|  |                     { | |||
|  |                         serviceType = stub.Selector(); | |||
|  |                     } | |||
|  | 
 | |||
|  |                     service = DefaultServiceConstructor(t, serviceType); | |||
|  |                 } | |||
|  | 
 | |||
|  |                 if (!HasService(t)) | |||
|  |                 { | |||
|  |                     RegisterService(t, service); | |||
|  |                 } | |||
|  | 
 | |||
|  |                 if (EnableLogging) | |||
|  |                 { | |||
|  |                     Debug.Log("[SRServiceManager] Auto-created service: {0} ({1})".Fmt(stub.Type, stub.InterfaceType), | |||
|  |                         service as Object); | |||
|  |                 } | |||
|  | 
 | |||
|  |                 return service; | |||
|  |             } | |||
|  | 
 | |||
|  |             return null; | |||
|  |         } | |||
|  | 
 | |||
|  |         protected void OnApplicationQuit() | |||
|  |         { | |||
|  |             _hasQuit = true; | |||
|  |         } | |||
|  | 
 | |||
|  |         private static object DefaultServiceConstructor(Type serviceIntType, Type implType) | |||
|  |         { | |||
|  |             // If mono-behaviour based, create a gameobject for this service | |||
|  |             if (typeof(MonoBehaviour).IsAssignableFrom(implType)) | |||
|  |             { | |||
|  |                 var go = new GameObject("_S_" + serviceIntType.Name); | |||
|  |                 return go.AddComponent(implType); | |||
|  |             } | |||
|  | 
 | |||
|  |             // If ScriptableObject based, create an instance | |||
|  |             if (typeof(ScriptableObject).IsAssignableFrom(implType)) | |||
|  |             { | |||
|  |                 var obj = ScriptableObject.CreateInstance(implType); | |||
|  |                 return obj; | |||
|  |             } | |||
|  | 
 | |||
|  |             // If just a standard C# object, just create an instance | |||
|  |             return Activator.CreateInstance(implType); | |||
|  |         } | |||
|  | 
 | |||
|  | #region Type Scanning | |||
|  | 
 | |||
|  |         private void ScanType(Type type) | |||
|  |         { | |||
|  |             var attribute = SRReflection.GetAttribute<ServiceAttribute>(type); | |||
|  | 
 | |||
|  |             if (attribute != null) | |||
|  |             { | |||
|  |                 _serviceStubs.Add(new ServiceStub { | |||
|  |                     Type = type, | |||
|  |                     InterfaceType = attribute.ServiceType | |||
|  |                 }); | |||
|  |             } | |||
|  | 
 | |||
|  |             ScanTypeForConstructors(type, _serviceStubs); | |||
|  |             ScanTypeForSelectors(type, _serviceStubs); | |||
|  |         } | |||
|  | 
 | |||
|  |         private static void ScanTypeForSelectors(Type t, List<ServiceStub> stubs) | |||
|  |         { | |||
|  |             var methods = GetStaticMethods(t); | |||
|  | 
 | |||
|  |             foreach (var method in methods) | |||
|  |             { | |||
|  |                 var attrib = SRReflection.GetAttribute<ServiceSelectorAttribute>(method); | |||
|  | 
 | |||
|  |                 if (attrib == null) | |||
|  |                 { | |||
|  |                     continue; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 if (method.ReturnType != typeof(Type)) | |||
|  |                 { | |||
|  |                     Debug.LogError("ServiceSelector must have return type of Type ({0}.{1}())".Fmt(t.Name, method.Name)); | |||
|  |                     continue; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 if (method.GetParameters().Length > 0) | |||
|  |                 { | |||
|  |                     Debug.LogError("ServiceSelector must have no parameters ({0}.{1}())".Fmt(t.Name, method.Name)); | |||
|  |                     continue; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 var stub = stubs.FirstOrDefault(p => p.InterfaceType == attrib.ServiceType); | |||
|  | 
 | |||
|  |                 if (stub == null) | |||
|  |                 { | |||
|  |                     stub = new ServiceStub { | |||
|  |                         InterfaceType = attrib.ServiceType | |||
|  |                     }; | |||
|  | 
 | |||
|  |                     stubs.Add(stub); | |||
|  |                 } | |||
|  | 
 | |||
|  | #if NETFX_CORE | |||
|  |                 stub.Selector = (Func<Type>)method.CreateDelegate(typeof(Func<Type>)); | |||
|  | #else | |||
|  |                 stub.Selector = (Func<Type>)Delegate.CreateDelegate(typeof(Func<Type>), method); | |||
|  | #endif | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         private static void ScanTypeForConstructors(Type t, List<ServiceStub> stubs) | |||
|  |         { | |||
|  |             var methods = GetStaticMethods(t); | |||
|  | 
 | |||
|  |             foreach (var method in methods) | |||
|  |             { | |||
|  |                 var attrib = SRReflection.GetAttribute<ServiceConstructorAttribute>(method); | |||
|  | 
 | |||
|  |                 if (attrib == null) | |||
|  |                 { | |||
|  |                     continue; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 if (method.ReturnType != attrib.ServiceType) | |||
|  |                 { | |||
|  |                     Debug.LogError("ServiceConstructor must have return type of {2} ({0}.{1}())".Fmt(t.Name, method.Name, | |||
|  |                         attrib.ServiceType)); | |||
|  |                     continue; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 if (method.GetParameters().Length > 0) | |||
|  |                 { | |||
|  |                     Debug.LogError("ServiceConstructor must have no parameters ({0}.{1}())".Fmt(t.Name, method.Name)); | |||
|  |                     continue; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 var stub = stubs.FirstOrDefault(p => p.InterfaceType == attrib.ServiceType); | |||
|  | 
 | |||
|  |                 if (stub == null) | |||
|  |                 { | |||
|  |                     stub = new ServiceStub { | |||
|  |                         InterfaceType = attrib.ServiceType | |||
|  |                     }; | |||
|  | 
 | |||
|  |                     stubs.Add(stub); | |||
|  |                 } | |||
|  | 
 | |||
|  |                 var m = method; | |||
|  |                 stub.Constructor = () => m.Invoke(null, null); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  | #endregion | |||
|  | 
 | |||
|  | #region Reflection | |||
|  | 
 | |||
|  |         private static MethodInfo[] GetStaticMethods(Type t) | |||
|  |         { | |||
|  | #if !NETFX_CORE | |||
|  |             return t.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); | |||
|  | #else | |||
|  |             return t.GetTypeInfo().DeclaredMethods.Where(p => p.IsStatic).ToArray(); | |||
|  | #endif | |||
|  |         } | |||
|  | 
 | |||
|  | #endregion | |||
|  |     } | |||
|  | } |