DI
This commit is contained in:
117
Runtime/Engine/Code/Foundation/DIContext.cs
Normal file
117
Runtime/Engine/Code/Foundation/DIContext.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace RebootKit.Engine.Foundation {
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method)]
|
||||
public class InjectAttribute : Attribute {
|
||||
}
|
||||
|
||||
public class DIContext {
|
||||
private const BindingFlags k_fieldsBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
private const BindingFlags k_methodsBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
public interface IFieldInjector {
|
||||
bool Inject(FieldInfo field, object target, DIContext context);
|
||||
}
|
||||
|
||||
private static readonly Logger Logger = new(nameof(DIContext));
|
||||
|
||||
private readonly Dictionary<Type, object> _bindingsMaps = new();
|
||||
private readonly List<IFieldInjector> _fieldInjectors = new();
|
||||
|
||||
public DIContext() {
|
||||
Bind(this);
|
||||
|
||||
AddInjector(new InjectAttributeFieldInjector());
|
||||
}
|
||||
|
||||
public void AddInjector(IFieldInjector injector) {
|
||||
_fieldInjectors.Add(injector);
|
||||
}
|
||||
|
||||
public void Bind(Type type, object obj) {
|
||||
if (!_bindingsMaps.TryAdd(type, obj)) {
|
||||
Logger.Error($"Cannot bind to '{type}', slot is already occupied");
|
||||
}
|
||||
}
|
||||
|
||||
public void Bind<TBind>(TBind obj) {
|
||||
Bind(typeof(TBind), obj);
|
||||
}
|
||||
|
||||
public object Resolve(Type type) {
|
||||
if (_bindingsMaps.TryGetValue(type, out object obj)) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
Logger.Error($"Couldn't resolve `{type}`");
|
||||
return null;
|
||||
}
|
||||
|
||||
public T Resolve<T>() {
|
||||
return (T) Resolve(typeof(T));
|
||||
}
|
||||
|
||||
public void Inject<T>(T target) {
|
||||
Type type = typeof(T);
|
||||
|
||||
foreach (FieldInfo field in type.GetFields(k_fieldsBindingFlags)) {
|
||||
if (!InjectField(field, target)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (MethodInfo method in type.GetMethods(k_methodsBindingFlags)) {
|
||||
if (!Attribute.IsDefined(method, typeof(InjectAttribute))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Type[] paramsTypes = method.GetParameters()
|
||||
.Select(t => t.ParameterType)
|
||||
.ToArray();
|
||||
|
||||
object[] instances = new object[paramsTypes.Length];
|
||||
|
||||
for (int i = 0; i < paramsTypes.Length; ++i) {
|
||||
instances[i] = Resolve(paramsTypes[i]);
|
||||
|
||||
if (instances[i] == null) {
|
||||
Logger.Error($"Failed to resolve method parameter of type `{paramsTypes[i]}`");
|
||||
}
|
||||
}
|
||||
|
||||
method.Invoke(target, instances);
|
||||
}
|
||||
}
|
||||
|
||||
private bool InjectField(FieldInfo field, object target) {
|
||||
for (int i = _fieldInjectors.Count - 1; i >= 0; i--) {
|
||||
if (_fieldInjectors[i].Inject(field, target, this)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private class InjectAttributeFieldInjector : IFieldInjector {
|
||||
public bool Inject(FieldInfo field, object target, DIContext context) {
|
||||
if (!Attribute.IsDefined(field, typeof(InjectAttribute))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
object instance = context.Resolve(field.FieldType);
|
||||
|
||||
if (instance == null) {
|
||||
Logger.Error($"Cannot resolve `{field.FieldType}`");
|
||||
return false;
|
||||
}
|
||||
|
||||
field.SetValue(target, instance);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Engine/Code/Foundation/DIContext.cs.meta
Normal file
3
Runtime/Engine/Code/Foundation/DIContext.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cee4133e4a594126868703cc035663cd
|
||||
timeCreated: 1742001112
|
||||
@@ -16,7 +16,6 @@ namespace RebootKit.Engine.Foundation {
|
||||
}
|
||||
|
||||
RR.Shared = null;
|
||||
ServicesLocator.Shared.UnRegisterAll();
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
SceneManager.LoadScene(0, LoadSceneMode.Single);
|
||||
|
||||
22
Runtime/Engine/Code/Foundation/IService.cs
Normal file
22
Runtime/Engine/Code/Foundation/IService.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RebootKit.Engine.Foundation {
|
||||
public interface IService : IDisposable {
|
||||
UniTask OnInit(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public abstract class ServiceMonoBehaviour : MonoBehaviour, IService {
|
||||
public abstract UniTask OnInit(CancellationToken cancellationToken);
|
||||
public abstract void Dispose();
|
||||
}
|
||||
|
||||
public abstract class ServiceAsset<T> : ScriptableObject where T : IService {
|
||||
public abstract T Create();
|
||||
}
|
||||
|
||||
public abstract class ServiceAsset : ServiceAsset<IService> {
|
||||
}
|
||||
}
|
||||
22
Runtime/Engine/Code/Foundation/SceneDI.cs
Normal file
22
Runtime/Engine/Code/Foundation/SceneDI.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RebootKit.Engine.Foundation {
|
||||
public interface IDependencyInstaller {
|
||||
void Install(DIContext context);
|
||||
}
|
||||
|
||||
public abstract class SceneDependencyInstaller : MonoBehaviour, IDependencyInstaller {
|
||||
public abstract void Install(DIContext context);
|
||||
}
|
||||
|
||||
public class SceneDI : MonoBehaviour, IDependencyInstaller {
|
||||
[SerializeField]
|
||||
private SceneDependencyInstaller[] _sceneInstallers;
|
||||
|
||||
public void Install(DIContext context) {
|
||||
foreach (SceneDependencyInstaller installer in _sceneInstallers) {
|
||||
installer.Install(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Engine/Code/Foundation/SceneDI.cs.meta
Normal file
3
Runtime/Engine/Code/Foundation/SceneDI.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8dd28652b58c4d689ab3f2f9354d7589
|
||||
timeCreated: 1742006992
|
||||
@@ -1,94 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RebootKit.Engine.Foundation {
|
||||
public interface IService : IDisposable {
|
||||
UniTask OnInit(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public abstract class ServiceMonoBehaviour : MonoBehaviour, IService {
|
||||
public abstract UniTask OnInit(CancellationToken cancellationToken);
|
||||
public abstract void Dispose();
|
||||
}
|
||||
|
||||
public abstract class ServiceAsset<T> : ScriptableObject where T : IService {
|
||||
public abstract T Create();
|
||||
}
|
||||
|
||||
public abstract class ServiceAsset : ServiceAsset<IService> {
|
||||
}
|
||||
|
||||
public class ServicesLocator {
|
||||
private static readonly Logger _logger = new(nameof(ServicesLocator));
|
||||
|
||||
private static ServicesLocator _shared;
|
||||
|
||||
public static ServicesLocator Shared {
|
||||
get {
|
||||
if (_shared == null) {
|
||||
_shared = new ServicesLocator();
|
||||
}
|
||||
|
||||
return _shared;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<Type, IService> _servicesMap = new();
|
||||
|
||||
public void Register(IService service, Type type) {
|
||||
if (!_servicesMap.TryAdd(type, service)) {
|
||||
_logger.Error($"There is already game service of type `{type}`");
|
||||
return;
|
||||
}
|
||||
|
||||
service.OnInit(default);
|
||||
|
||||
_logger.Info($"Registered service of type {type.Name}");
|
||||
}
|
||||
|
||||
public void Register<T>(T service) where T : IService {
|
||||
Register(service, service.GetType());
|
||||
}
|
||||
|
||||
public async UniTask RegisterAsync<T>(T service, CancellationToken cancellationToken = default) where T : IService {
|
||||
if (!_servicesMap.TryAdd(typeof(T), service)) {
|
||||
_logger.Error($"There is already game service of type `{typeof(T).Name}`");
|
||||
return;
|
||||
}
|
||||
|
||||
await service.OnInit(cancellationToken);
|
||||
_logger.Info($"Registered service of type {typeof(T).Name}");
|
||||
}
|
||||
|
||||
public void UnRegister(Type type) {
|
||||
if (_servicesMap.TryGetValue(type, out IService service)) {
|
||||
service.Dispose();
|
||||
_servicesMap.Remove(type);
|
||||
_logger.Info($"Unregistered service of type {type.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
public void UnRegisterAll() {
|
||||
foreach (IService service in _servicesMap.Values) {
|
||||
service.Dispose();
|
||||
}
|
||||
_servicesMap.Clear();
|
||||
}
|
||||
|
||||
public IService Get(Type type) {
|
||||
if (_servicesMap.TryGetValue(type, out IService service)) {
|
||||
return service;
|
||||
}
|
||||
|
||||
Debug.LogAssertionFormat($"Couldn't find service of type `{type}`");
|
||||
return null;
|
||||
}
|
||||
|
||||
public T Get<T>() where T : IService {
|
||||
return (T) Get(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user