Files
trail-into-darkness/Packages/com.jovian.utilities/Runtime/EditorRuntimeAccessible/EditorServiceLocator.cs
2026-04-02 07:22:33 +02:00

154 lines
5.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Jovian.Utilities {
/// <summary>
/// <b>Editor use-only.</b><br/>
/// Allows easy access between different instances. Supports 1 instance per type. <br/>
/// Add instances and retrieve elsewhere.<br/><br/>
/// Read more: https://en.wikipedia.org/wiki/Service_locator_pattern
/// </summary>
public static class EditorServiceLocator {
private sealed class ServiceNotFoundException : Exception {
public ServiceNotFoundException(Type type) : base($"ServiceNotFoundException. Type={type}") { }
}
private static Dictionary<Type, object> serviceContainer = new();
public static bool IsDirty { get; private set; }
#if UNITY_EDITOR
public static Dictionary<Type, object> 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
/// <summary>
/// Add an instance to the locator. Will throw an exception if a type already exists in the locator.
/// <seealso cref="AddOrReplace{T}"/>
/// </summary>
[Conditional("UNITY_EDITOR")]
public static void Add<T>(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
}
/// <summary>
/// Add an instance to the locator. Will replace any existing instance without an exception.
/// An alias for <see cref="Set{T}"/>
/// </summary>
[Conditional("UNITY_EDITOR")]
public static void AddOrReplace<T>(T service) {
Set(service);
}
/// <summary>
/// Add an instance to the locator. Will replace any existing instance without an exception.
/// An alias for <see cref="AddOrReplace{T}"/>
/// </summary>
[Conditional("UNITY_EDITOR")]
public static void Set<T>(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
}
/// <summary>
/// Removes any type matching the instance passed in from the locator. This is good practice.
/// </summary>
/// <param name="service"></param>
/// <typeparam name="T"></typeparam>
/// <exception cref="NullReferenceException"></exception>
[Conditional("UNITY_EDITOR")]
public static void Remove<T>(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
}
/// <summary>
/// Retrieves an instance from the locator matching the type. Will throw an exception if nothing is found.
/// <seealso cref="TryGet{T}(out T)"/>
/// </summary>
public static T Get<T>() {
#if UNITY_EDITOR
if (serviceContainer.TryGetValue(typeof(T), out object service)) {
return (T)service;
}
#endif
throw new ServiceNotFoundException(typeof(T));
}
[Obsolete("Use Get<T> or TryGet<T>(out T) since they follow the C# conventions for TryGet")]
public static T TryGet<T>() {
#if UNITY_EDITOR
if (serviceContainer.TryGetValue(typeof(T), out object service)) {
return (T)service;
}
#endif
return default;
}
/// <summary>
/// Retrieves an instance from the locator matching the type. Returns true/false based on success.
/// </summary>
public static bool TryGet<T>(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;
}
}
}