新增 FlowScope Git 包雏形
This commit is contained in:
301
Packages/com.flowscope.gamecore/Runtime/Container/Container.cs
Normal file
301
Packages/com.flowscope.gamecore/Runtime/Container/Container.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FlowScope.Container
|
||||
{
|
||||
public sealed class Container : IDisposable
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static Stack<Type> _resolutionStack;
|
||||
|
||||
private readonly Container _parent;
|
||||
private readonly Dictionary<Type, ContainerRegistration> _registrations = new Dictionary<Type, ContainerRegistration>();
|
||||
private readonly List<Container> _children = new List<Container>();
|
||||
private readonly List<IDisposable> _ownedDisposables = new List<IDisposable>();
|
||||
private bool _disposed;
|
||||
|
||||
public Container()
|
||||
{
|
||||
}
|
||||
|
||||
private Container(Container parent)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public void RegisterInstance<T>(T instance)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
RegisterLocal(typeof(T), ContainerRegistration.ForInstance(instance));
|
||||
}
|
||||
|
||||
public void RegisterFactory<T>(Func<Container, T> factory)
|
||||
{
|
||||
RegisterSingletonFactory(factory);
|
||||
}
|
||||
|
||||
public void RegisterSingletonFactory<T>(Func<Container, T> factory)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
RegisterLocal(typeof(T), ContainerRegistration.ForSingletonFactory(this, c => factory(c)));
|
||||
}
|
||||
|
||||
public void RegisterScoped<T>(Func<Container, T> factory)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
RegisterLocal(typeof(T), ContainerRegistration.ForScopedFactory(this, c => factory(c)));
|
||||
}
|
||||
|
||||
public void RegisterTransient<T>(Func<Container, T> factory)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
RegisterLocal(typeof(T), ContainerRegistration.ForTransientFactory(this, c => factory(c)));
|
||||
}
|
||||
|
||||
public void RegisterType<TInterface, TImplementation>() where TImplementation : TInterface
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
RegisterType(typeof(TInterface), typeof(TImplementation));
|
||||
}
|
||||
|
||||
public void RegisterType<TImplementation>()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
RegisterType(typeof(TImplementation), typeof(TImplementation));
|
||||
}
|
||||
|
||||
public void RegisterAssembly()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
GeneratedFactories.RegisterAssembly(this);
|
||||
}
|
||||
|
||||
public T Resolve<T>()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return (T)Resolve(typeof(T));
|
||||
}
|
||||
|
||||
public bool TryResolve<T>(out T value)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (!TryFindRegistration(typeof(T), out var registration))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = (T)ResolveRegistration(typeof(T), registration);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Container CreateScope()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var child = new Container(this);
|
||||
_children.Add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
var exceptions = new List<Exception>();
|
||||
|
||||
for (var i = _children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
TryDispose(_children[i], exceptions);
|
||||
}
|
||||
|
||||
for (var i = _ownedDisposables.Count - 1; i >= 0; i--)
|
||||
{
|
||||
TryDispose(_ownedDisposables[i], exceptions);
|
||||
}
|
||||
|
||||
_children.Clear();
|
||||
_ownedDisposables.Clear();
|
||||
ForgetScopedInstancesFor(this);
|
||||
_registrations.Clear();
|
||||
|
||||
if (_parent != null)
|
||||
{
|
||||
_parent._children.Remove(this);
|
||||
}
|
||||
|
||||
if (exceptions.Count > 0)
|
||||
{
|
||||
throw new AggregateException("One or more container-owned instances failed to dispose.", exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
internal object Resolve(Type serviceType)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (!TryFindRegistration(serviceType, out var registration))
|
||||
{
|
||||
throw new InvalidOperationException($"No registration found for {FormatType(serviceType)}.");
|
||||
}
|
||||
|
||||
return ResolveRegistration(serviceType, registration);
|
||||
}
|
||||
|
||||
internal void RegisterFactory(Type serviceType, Func<Container, object> factory)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (serviceType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serviceType));
|
||||
}
|
||||
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
RegisterLocal(serviceType, ContainerRegistration.ForTransientFactory(this, factory));
|
||||
}
|
||||
|
||||
internal void TrackOwnedDisposable(IDisposable disposable)
|
||||
{
|
||||
_ownedDisposables.Add(disposable);
|
||||
}
|
||||
|
||||
private void RegisterType(Type serviceType, Type implementationType)
|
||||
{
|
||||
if (!serviceType.IsAssignableFrom(implementationType))
|
||||
{
|
||||
throw new InvalidOperationException($"{FormatType(implementationType)} cannot be assigned to {FormatType(serviceType)}.");
|
||||
}
|
||||
|
||||
if (!GeneratedFactories.TryGetFactory(implementationType, out var factory))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"No generated factory registered for {FormatType(implementationType)}. Use GeneratedFactories.Register, ReflectionFactoryBuilder.RegisterType, or RegisterFactory before resolving this type.");
|
||||
}
|
||||
|
||||
RegisterFactory(serviceType, factory);
|
||||
}
|
||||
|
||||
private void RegisterLocal(Type serviceType, ContainerRegistration registration)
|
||||
{
|
||||
if (_registrations.ContainsKey(serviceType))
|
||||
{
|
||||
throw new InvalidOperationException($"{FormatType(serviceType)} is already registered in this container scope.");
|
||||
}
|
||||
|
||||
_registrations.Add(serviceType, registration);
|
||||
}
|
||||
|
||||
private object ResolveRegistration(Type serviceType, ContainerRegistration registration)
|
||||
{
|
||||
_resolutionStack = _resolutionStack ?? new Stack<Type>();
|
||||
|
||||
if (_resolutionStack.Contains(serviceType))
|
||||
{
|
||||
var chain = _resolutionStack.Reverse()
|
||||
.Concat(new[] { serviceType })
|
||||
.Select(t => t.Name);
|
||||
throw new InvalidOperationException($"Circular dependency detected: {string.Join(" -> ", chain)}");
|
||||
}
|
||||
|
||||
_resolutionStack.Push(serviceType);
|
||||
try
|
||||
{
|
||||
return registration.Resolve(this);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_resolutionStack.Pop();
|
||||
if (_resolutionStack.Count == 0)
|
||||
{
|
||||
_resolutionStack = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryFindRegistration(Type serviceType, out ContainerRegistration registration)
|
||||
{
|
||||
if (_registrations.TryGetValue(serviceType, out registration))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_parent != null)
|
||||
{
|
||||
return _parent.TryFindRegistration(serviceType, out registration);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ForgetScopedInstancesFor(Container scope)
|
||||
{
|
||||
foreach (var registration in _registrations.Values)
|
||||
{
|
||||
registration.ForgetScope(scope);
|
||||
}
|
||||
|
||||
_parent?.ForgetScopedInstancesFor(scope);
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Container));
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryDispose(IDisposable disposable, List<Exception> exceptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatType(Type type) => type.FullName ?? type.Name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 554409c4a7e84909a72a89d17aeb2b0b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FlowScope.Container
|
||||
{
|
||||
internal sealed class ContainerRegistration
|
||||
{
|
||||
private enum Lifetime
|
||||
{
|
||||
Instance,
|
||||
Singleton,
|
||||
Scoped,
|
||||
Transient
|
||||
}
|
||||
|
||||
private readonly Container _owner;
|
||||
private readonly Func<Container, object> _factory;
|
||||
private readonly Lifetime _lifetime;
|
||||
private readonly Dictionary<Container, object> _scopedInstances;
|
||||
private object _instance;
|
||||
private bool _created;
|
||||
|
||||
private ContainerRegistration(
|
||||
Container owner,
|
||||
Func<Container, object> factory,
|
||||
object instance,
|
||||
bool created,
|
||||
Lifetime lifetime)
|
||||
{
|
||||
_owner = owner;
|
||||
_factory = factory;
|
||||
_instance = instance;
|
||||
_created = created;
|
||||
_lifetime = lifetime;
|
||||
_scopedInstances = lifetime == Lifetime.Scoped
|
||||
? new Dictionary<Container, object>()
|
||||
: null;
|
||||
}
|
||||
|
||||
public static ContainerRegistration ForInstance(object instance)
|
||||
{
|
||||
return new ContainerRegistration(null, null, instance, true, Lifetime.Instance);
|
||||
}
|
||||
|
||||
public static ContainerRegistration ForSingletonFactory(Container owner, Func<Container, object> factory)
|
||||
{
|
||||
return new ContainerRegistration(owner, factory, null, false, Lifetime.Singleton);
|
||||
}
|
||||
|
||||
public static ContainerRegistration ForScopedFactory(Container owner, Func<Container, object> factory)
|
||||
{
|
||||
return new ContainerRegistration(owner, factory, null, false, Lifetime.Scoped);
|
||||
}
|
||||
|
||||
public static ContainerRegistration ForTransientFactory(Container owner, Func<Container, object> factory)
|
||||
{
|
||||
return new ContainerRegistration(owner, factory, null, false, Lifetime.Transient);
|
||||
}
|
||||
|
||||
public object Resolve(Container requestScope)
|
||||
{
|
||||
switch (_lifetime)
|
||||
{
|
||||
case Lifetime.Instance:
|
||||
return _instance;
|
||||
case Lifetime.Singleton:
|
||||
return ResolveSingleton();
|
||||
case Lifetime.Scoped:
|
||||
return ResolveScoped(requestScope);
|
||||
case Lifetime.Transient:
|
||||
return CreateOwnedInstance(requestScope);
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported container lifetime {_lifetime}.");
|
||||
}
|
||||
}
|
||||
|
||||
public void ForgetScope(Container scope)
|
||||
{
|
||||
_scopedInstances?.Remove(scope);
|
||||
}
|
||||
|
||||
private object ResolveSingleton()
|
||||
{
|
||||
if (!_created)
|
||||
{
|
||||
_instance = CreateOwnedInstance(_owner);
|
||||
_created = true;
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
|
||||
private object ResolveScoped(Container requestScope)
|
||||
{
|
||||
if (_scopedInstances.TryGetValue(requestScope, out var instance))
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
|
||||
instance = CreateOwnedInstance(requestScope);
|
||||
_scopedInstances.Add(requestScope, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private object CreateOwnedInstance(Container owner)
|
||||
{
|
||||
var instance = _factory(owner);
|
||||
|
||||
if (instance is IDisposable disposable)
|
||||
{
|
||||
owner.TrackOwnedDisposable(disposable);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d024d7ed8fd642b6948a216b3a676e22
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FlowScope.Container
|
||||
{
|
||||
public static class GeneratedFactories
|
||||
{
|
||||
private static readonly Dictionary<Type, Func<Container, object>> Factories = new Dictionary<Type, Func<Container, object>>();
|
||||
|
||||
public static void Register<TImplementation>(Func<Container, TImplementation> factory)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
Factories[typeof(TImplementation)] = c => factory(c);
|
||||
}
|
||||
|
||||
public static bool TryGetFactory(Type implementationType, out Func<Container, object> factory)
|
||||
{
|
||||
if (implementationType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(implementationType));
|
||||
}
|
||||
|
||||
return Factories.TryGetValue(implementationType, out factory);
|
||||
}
|
||||
|
||||
public static void RegisterAssembly(Container container)
|
||||
{
|
||||
if (container == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(container));
|
||||
}
|
||||
|
||||
foreach (var pair in Factories)
|
||||
{
|
||||
container.RegisterFactory(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc50e3c76ee147b59d2663e872ced1a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace FlowScope.Container
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class InjectableAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 039257eaaf6c4ac198a3504795b52df0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FlowScope.Container
|
||||
{
|
||||
public static class ReflectionFactoryBuilder
|
||||
{
|
||||
public static void RegisterType<TImplementation>(Container container)
|
||||
{
|
||||
if (container == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(container));
|
||||
}
|
||||
|
||||
container.RegisterFactory(typeof(TImplementation), BuildFactory(typeof(TImplementation)));
|
||||
}
|
||||
|
||||
public static void RegisterType<TInterface, TImplementation>(Container container)
|
||||
where TImplementation : TInterface
|
||||
{
|
||||
if (container == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(container));
|
||||
}
|
||||
|
||||
container.RegisterFactory(typeof(TInterface), BuildFactory(typeof(TImplementation)));
|
||||
}
|
||||
|
||||
public static Func<Container, object> BuildFactory(Type implementationType)
|
||||
{
|
||||
if (implementationType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(implementationType));
|
||||
}
|
||||
|
||||
if (!Attribute.IsDefined(implementationType, typeof(InjectableAttribute)))
|
||||
{
|
||||
throw new InvalidOperationException($"{FormatType(implementationType)} must be marked with InjectableAttribute for reflection registration.");
|
||||
}
|
||||
|
||||
var constructors = implementationType.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
|
||||
if (constructors.Length != 1)
|
||||
{
|
||||
throw new InvalidOperationException($"{FormatType(implementationType)} must declare exactly one public constructor.");
|
||||
}
|
||||
|
||||
var constructor = constructors[0];
|
||||
var parameters = constructor.GetParameters();
|
||||
|
||||
return container =>
|
||||
{
|
||||
var arguments = parameters
|
||||
.Select(parameter => container.Resolve(parameter.ParameterType))
|
||||
.ToArray();
|
||||
return constructor.Invoke(arguments);
|
||||
};
|
||||
}
|
||||
|
||||
private static string FormatType(Type type) => type.FullName ?? type.Name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d2827e4c16e4b4ea60a84f709d54665
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user