using System;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Jovian.Utilities {
///
/// Editor use-only.
/// Allows easy access between different instances. Supports 1 instance per type.
/// Add instances and retrieve elsewhere.
/// Read more: https://en.wikipedia.org/wiki/Service_locator_pattern
///
public static class EditorServiceLocator {
private sealed class ServiceNotFoundException : Exception {
public ServiceNotFoundException(Type type) : base($"ServiceNotFoundException. Type={type}") { }
}
private static Dictionary serviceContainer = new();
public static bool IsDirty { get; private set; }
#if UNITY_EDITOR
public static Dictionary ServiceContainer => serviceContainer;
#endif
[RuntimeInitializeOnLoadMethod]
public static void Init() {
serviceContainer = new();
}
#if UNITY_EDITOR
[InitializeOnEnterPlayMode]
private static void Reset(EnterPlayModeOptions options) {
serviceContainer?.Clear();
IsDirty = true;
}
#endif
///
/// Add an instance to the locator. Will throw an exception if a type already exists in the locator.
///
///
[Conditional("UNITY_EDITOR")]
public static void Add(T service) {
#if UNITY_EDITOR
if (service == null) {
throw new NullReferenceException($"Service is null. Expected instance of type '{typeof(T)}'");
}
serviceContainer.Add(typeof(T), service);
IsDirty = true;
#else
UnityEngine.Debug.LogWarning($"EditorServiceLocator should not be used outside of the Editor");
#endif
}
///
/// Add an instance to the locator. Will replace any existing instance without an exception.
/// An alias for
///
[Conditional("UNITY_EDITOR")]
public static void AddOrReplace(T service) {
Set(service);
}
///
/// Add an instance to the locator. Will replace any existing instance without an exception.
/// An alias for
///
[Conditional("UNITY_EDITOR")]
public static void Set(T service) {
#if UNITY_EDITOR
if (service == null) {
throw new NullReferenceException($"Service is null. Expected instance of type '{typeof(T)}'");
}
serviceContainer[typeof(T)] = service;
IsDirty = true;
#else
UnityEngine.Debug.LogWarning($"EditorServiceLocator should not be used outside of the Editor");
#endif
}
///
/// Removes any type matching the instance passed in from the locator. This is good practice.
///
///
///
///
[Conditional("UNITY_EDITOR")]
public static void Remove(T service) {
#if UNITY_EDITOR
if (service == null) {
throw new NullReferenceException($"Service is null. Expected instance of type '{typeof(T)}'");
}
serviceContainer.Remove(typeof(T));
IsDirty = true;
#else
UnityEngine.Debug.LogWarning($"EditorServiceLocator should not be used outside of the Editor");
#endif
}
///
/// Retrieves an instance from the locator matching the type. Will throw an exception if nothing is found.
///
///
public static T Get() {
#if UNITY_EDITOR
if (serviceContainer.TryGetValue(typeof(T), out object service)) {
return (T)service;
}
#endif
throw new ServiceNotFoundException(typeof(T));
}
[Obsolete("Use Get or TryGet(out T) since they follow the C# conventions for TryGet")]
public static T TryGet() {
#if UNITY_EDITOR
if (serviceContainer.TryGetValue(typeof(T), out object service)) {
return (T)service;
}
#endif
return default;
}
///
/// Retrieves an instance from the locator matching the type. Returns true/false based on success.
///
public static bool TryGet(out T instance) {
#if UNITY_EDITOR
if (serviceContainer.TryGetValue(typeof(T), out object service)) {
instance = (T)service;
return instance != null;
}
#endif
instance = default;
return false;
}
[Conditional("UNITY_EDITOR")]
public static void Clear() {
serviceContainer.Clear();
IsDirty = true;
}
[Conditional("UNITY_EDITOR")]
public static void Clean() {
IsDirty = false;
}
}
}