Skip to content

Instantly share code, notes, and snippets.

@Simie
Last active August 29, 2015 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Simie/297fa9d0940ef54f52be to your computer and use it in GitHub Desktop.
Save Simie/297fa9d0940ef54f52be to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using SRF.Components;
using SRF.Helpers;
using SRF.Internal;
using UnityEngine;
using Object = UnityEngine.Object;
// Disable unreachable code warning caused by DEBUG
#pragma warning disable 0162
namespace SRF.Service
{
[AddComponentMenu(ComponentMenuPaths.SRServiceManager)]
public class SRServiceManager : SRAutoSingleton<SRServiceManager>
{
#if DEBUG
public const bool EnableLogging = true;
#else
public const bool EnableLogging = false;
#endif
/// <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 (int 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 (int 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 Type Type;
public object Object;
}
[Serializable]
private class ServiceStub
{
public Type InterfaceType;
public Type Type;
public Func<Type> Selector;
public Func<object> Constructor;
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 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;
_serviceStubs = new List<ServiceStub>();
var types = new List<Type>();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) {
var n = assembly.GetName().Name;
// Filter down to user assemblies only
if(n == "mscorlib" ||
n == "System" ||
n == "UnityEngine" ||
n.StartsWith("System.") ||
n.StartsWith("UnityEngine.") ||
n.StartsWith("Mono.") ||
n.StartsWith("Boo.") ||
n.StartsWith("UnityEditor") ||
n.StartsWith("Unity.") ||
n.StartsWith("UnityScript") ||
n.StartsWith("nunit.") ||
n.StartsWith("I18N") ||
n.StartsWith("ICSharpCode"))
continue;
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);
}
var serviceStrings =
_serviceStubs.Select(p => " {0}".Fmt(p)).ToArray();
if (EnableLogging) {
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 {
Type 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);
}
#if NETFX_CORE
stub.Constructor = (Func<object>)method.CreateDelegate(typeof(Func<object>));
#else
stub.Constructor = (Func<object>)Delegate.CreateDelegate(t, method);
#endif
}
}
#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
}
#if NETFX_CORE
private sealed class AppDomain
{
public static AppDomain CurrentDomain { get; private set; }
static AppDomain()
{
CurrentDomain = new AppDomain();
}
public Assembly[] GetAssemblies()
{
return GetAssemblyListAsync().Result.ToArray();
}
private async System.Threading.Tasks.Task<IEnumerable<Assembly>> GetAssemblyListAsync()
{
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var assemblies = new List<Assembly>();
foreach (var file in await folder.GetFilesAsync())
{
if (file.FileType == ".dll" || file.FileType == ".exe")
{
System.Diagnostics.Debug.WriteLine("Found: " + file.Path);
try {
var name = new AssemblyName() {Name = file.DisplayName};
var asm = Assembly.Load(name);
assemblies.Add(asm);
} catch {
System.Diagnostics.Debug.WriteLine("Error loading " + file.Name);
}
}
}
return assemblies;
}
}
#endif
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment