AyCode.Core/AyCode.Models.Server/DynamicMethods/AcDynamicMethodRegistry.cs

116 lines
3.9 KiB
C#

using System.Collections.Concurrent;
using System.Reflection;
using AyCode.Services.SignalRs;
namespace AyCode.Models.Server.DynamicMethods;
/// <summary>
/// Registry for dynamic method lookups with lazy initialization.
/// Caches method metadata statically (by messageTag), resolves instances per-request.
/// </summary>
/// <typeparam name="TAttribute">The attribute type used to mark methods (e.g., SignalRAttribute)</typeparam>
public class AcDynamicMethodRegistry<TAttribute> where TAttribute : TagAttribute
{
/// <summary>
/// Statikus cache: messageTag → (DeclaringType, MethodInfo)
/// A reflection eredménye, nem változik runtime-ban.
/// null érték = már kerestük, de nem találtuk.
/// </summary>
private static readonly ConcurrentDictionary<int, (Type DeclaringType, AcMethodInfoModel<TAttribute> Method)?> _methodLookupCache = new();
/// <summary>
/// Instance array - NEM statikus, request/Hub-specifikus.
/// Array is faster than List for small fixed-size collections (2-5 elements).
/// </summary>
private object[] _instances = [];
private int _count;
/// <summary>
/// Gets or sets the capacity of the instance array.
/// Set this before calling Register() to avoid allocations.
/// </summary>
public int CahcheSizeCapacity
{
get => _instances.Length;
set => _instances = new object[value];
}
/// <summary>
/// Registers an instance for method lookup.
/// No reflection happens here - just stores the instance reference.
/// </summary>
public void Register(object instance)
{
if (_count >= _instances.Length)
{
// CahcheSizeCapacity not set or exceeded - resize
var newSize = _instances.Length == 0 ? 4 : _instances.Length * 2;
Array.Resize(ref _instances, newSize);
}
_instances[_count++] = instance;
}
/// <summary>
/// Finds the method and instance for a given messageTag.
/// Uses cached lookup when possible, falls back to lazy search.
/// </summary>
public (object Instance, AcMethodInfoModel<TAttribute> Method)? GetMethodByMessageTag(int messageTag)
{
// 1. Check cache first
if (_methodLookupCache.TryGetValue(messageTag, out var cached))
{
if (cached == null)
return null; // Already searched, not found
// Find the instance of the cached type
var instance = FindInstanceByType(cached.Value.DeclaringType);
if (instance != null)
return (instance, cached.Value.Method);
}
// 2. Lazy search through registered instances
for (var i = 0; i < _count; i++)
{
var instance = _instances[i];
var type = instance.GetType();
// Search methods with TAttribute
foreach (var methodInfo in type.GetMethods())
{
if (methodInfo.GetCustomAttribute(typeof(TAttribute)) is not TAttribute attribute)
continue;
if (attribute.MessageTag == messageTag)
{
var method = new AcMethodInfoModel<TAttribute>(attribute, methodInfo);
_methodLookupCache[messageTag] = (type, method);
return (instance, method);
}
}
}
// 3. Not found - cache this result too
_methodLookupCache[messageTag] = null;
return null;
}
/// <summary>
/// Finds an instance by its type from the registered instances.
/// </summary>
private object? FindInstanceByType(Type type)
{
for (var i = 0; i < _count; i++)
{
var instance = _instances[i];
if (instance.GetType() == type)
return instance;
}
return null;
}
/// <summary>
/// Gets the number of registered instances.
/// </summary>
public int Count => _count;
}