// MIT License - Copyright (c) 2025 wallstop
// Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE
namespace WallstopStudios.UnityHelpers.Tags
{
using System;
using System.Collections.Generic;
using Core.Attributes;
using Core.Helper;
using UnityEngine;
using Utils;
///
/// Serialized cache of attribute metadata to avoid runtime reflection.
/// This asset is automatically generated in the Editor.
/// When the prewarm toggle is enabled, a runtime hook pre-initializes relational component
/// reflection helpers before the first scene loads to avoid first-use stalls.
///
[ScriptableSingletonPath("Wallstop Studios/Unity Helpers")]
[AllowDuplicateCleanup]
[AutoLoadSingleton(RuntimeInitializeLoadType.BeforeSceneLoad)]
public sealed class AttributeMetadataCache : ScriptableObjectSingleton
{
[Header("Initialization")]
[Tooltip(
"If enabled, pre-warms RelationalComponent reflection caches at runtime before the first scene loads. Useful to avoid first-use stalls on IL2CPP or slow devices."
)]
[SerializeField]
private bool _prewarmRelationalOnLoad = false;
///
/// Categorizes a relational attribute reference discovered on an .
///
///
///
/// Relational attributes allow a component to expose references to related components so modifications can propagate
/// (e.g., parent/child links for hierarchical buffs). These values are serialized into the cache to avoid runtime reflection.
///
///
/// Typical usage happens via auto-generated metadata; you generally do not set this manually.
///
///
public enum RelationalAttributeKind : byte
{
[Obsolete("Default uninitialized value - should never be used")]
Unknown = 0,
///
/// The relational field points to a parent component.
///
Parent = 1,
///
/// The relational field points to a child component.
///
Child = 2,
///
/// The relational field points to a sibling component.
///
Sibling = 3,
}
///
/// Describes the collection shape of a relational field captured in metadata.
///
public enum FieldKind : byte
{
[Obsolete("Default uninitialized value - should never be used")]
None = 0,
///
/// A single reference value.
///
Single = 1,
///
/// An array of values.
///
Array = 2,
///
/// A of values.
///
List = 3,
///
/// A of values.
///
HashSet = 4,
}
///
/// Serializable entry describing attribute field names for a single component type.
///
[Serializable]
public sealed class TypeFieldMetadata
{
///
/// Assembly-qualified component type name.
///
public string typeName;
///
/// Attribute field names discovered on the component.
///
public string[] fieldNames;
///
/// Creates a new metadata entry for a component type.
///
/// Assembly-qualified name of the component type.
/// Attribute field names found on that type.
public TypeFieldMetadata(string typeName, string[] fieldNames)
{
this.typeName = typeName;
this.fieldNames = fieldNames;
}
}
///
/// Serializable entry describing a relational attribute field on a component.
///
[Serializable]
public sealed class RelationalFieldMetadata
{
///
/// The name of the relational field on the component.
///
public string fieldName;
///
/// The relationship classification (parent/child/sibling).
///
public RelationalAttributeKind attributeKind;
///
/// The collection shape of the field (single, array, list, hashset).
///
public FieldKind fieldKind;
///
/// The assembly-qualified element type name for the field (for collections) or the field type (for singles).
///
public string elementTypeName;
///
/// Indicates whether the element type is an interface (affects resolution and validation).
///
public bool isInterface;
///
/// Creates a relational metadata entry for a component field.
///
/// The field name on the component.
/// How the field relates to other components.
/// Collection shape of the field.
/// Assembly-qualified element or field type.
/// Whether the element type is an interface.
public RelationalFieldMetadata(
string fieldName,
RelationalAttributeKind attributeKind,
FieldKind fieldKind,
string elementTypeName,
bool isInterface
)
{
this.fieldName = fieldName;
this.attributeKind = attributeKind;
this.fieldKind = fieldKind;
this.elementTypeName = elementTypeName;
this.isInterface = isInterface;
}
}
///
/// Runtime-resolved relational field metadata with references resolved.
///
public readonly struct ResolvedRelationalFieldMetadata
{
///
/// Creates a resolved relational metadata entry.
///
/// The relational field name on the component.
/// Relationship classification.
/// Collection shape of the field.
/// Resolved element type (or field type for singles).
/// Whether the element type is an interface.
public ResolvedRelationalFieldMetadata(
string fieldName,
RelationalAttributeKind attributeKind,
FieldKind fieldKind,
Type elementType,
bool isInterface
)
{
FieldName = fieldName;
AttributeKind = attributeKind;
FieldKind = fieldKind;
ElementType = elementType;
IsInterface = isInterface;
}
///
/// The name of the relational field.
///
public string FieldName { get; }
///
/// Relationship classification for the field.
///
public RelationalAttributeKind AttributeKind { get; }
///
/// Collection shape of the field.
///
public FieldKind FieldKind { get; }
///
/// Resolved CLR type for the element/field.
///
public Type ElementType { get; }
///
/// Indicates if the element type is an interface.
///
public bool IsInterface { get; }
}
///
/// Serializable entry describing all relational fields for a component type.
///
[Serializable]
public sealed class RelationalTypeMetadata
{
///
/// Assembly-qualified component type name.
///
public string typeName;
///
/// Relational attribute fields discovered on the component.
///
public RelationalFieldMetadata[] fields;
///
/// Creates relational metadata for a component type.
///
/// Assembly-qualified name of the component type.
/// Relational fields discovered on that type.
public RelationalTypeMetadata(string typeName, RelationalFieldMetadata[] fields)
{
this.typeName = typeName;
this.fields = fields;
}
}
[SerializeField]
private string[] _allAttributeNames = Array.Empty();
[NonSerialized]
private string[] _computedAllAttributeNames;
[NonSerialized]
private bool _computedAllAttributeNamesIncludesTests;
[SerializeField]
private TypeFieldMetadata[] _typeMetadata = Array.Empty();
[SerializeField]
internal RelationalTypeMetadata[] _relationalTypeMetadata =
Array.Empty();
///
/// Serialized entry describing an auto-loaded singleton dependency.
///
[Serializable]
public sealed class AutoLoadSingletonEntry
{
///
/// Assembly-qualified type name of the singleton to load.
///
public string typeName;
///
/// Whether the singleton should be created, fetched, or ignored.
///
public SingletonAutoLoadKind kind;
///
/// Unity load phase used to initialize the singleton.
///
public RuntimeInitializeLoadType loadType;
///
/// Default constructor for serialization.
///
public AutoLoadSingletonEntry() { }
///
/// Creates a new singleton auto-load entry.
///
/// Assembly-qualified type name to load.
/// How the singleton should be handled.
/// Unity load phase for initialization.
public AutoLoadSingletonEntry(
string typeName,
SingletonAutoLoadKind kind,
RuntimeInitializeLoadType loadType
)
{
this.typeName = typeName;
this.kind = kind;
this.loadType = loadType;
}
}
[SerializeField]
private AutoLoadSingletonEntry[] _autoLoadSingletons =
Array.Empty();
internal string[] SerializedAttributeNames => _allAttributeNames ?? Array.Empty();
internal TypeFieldMetadata[] SerializedTypeMetadata =>
_typeMetadata ?? Array.Empty();
internal RelationalTypeMetadata[] SerializedRelationalTypeMetadata =>
_relationalTypeMetadata ?? Array.Empty();
internal AutoLoadSingletonEntry[] SerializedAutoLoadSingletons =>
_autoLoadSingletons ?? Array.Empty();
// Compound key for element type lookup
private readonly struct ElementTypeKey : IEquatable
{
private readonly Type _componentType;
private readonly string _fieldName;
public ElementTypeKey(Type componentType, string fieldName)
{
_componentType = componentType;
_fieldName = fieldName;
}
public bool Equals(ElementTypeKey other)
{
return _componentType == other._componentType
&& string.Equals(_fieldName, other._fieldName, StringComparison.Ordinal);
}
public override bool Equals(object obj)
{
return obj is ElementTypeKey other && Equals(other);
}
public override int GetHashCode()
{
return Objects.HashCode(_componentType, _fieldName);
}
}
private readonly object _lookupLock = new();
// Runtime lookups using Type as key (much faster than string comparison)
private Dictionary _typeFieldsLookup;
private Dictionary _relationalFieldsLookup;
private Dictionary _resolvedRelationalFieldsLookup;
private Dictionary _elementTypeLookup; // Cache resolved element types
private static readonly object _typeResolutionLock = new();
private static readonly Dictionary _resolvedTypeCache = new(
StringComparer.Ordinal
);
private static readonly object _missingTypeLock = new();
private static readonly HashSet _loggedMissingTypeKeys = new(
StringComparer.Ordinal
);
///
/// Alphabetical list of all attribute field names discovered across registered types.
/// Includes test-only attributes when test assemblies are loaded.
///
///
/// Callers typically use this to drive tooling (e.g., dropdowns). The list is cached and refreshed automatically when test assemblies are present.
///
///
///
/// AttributeMetadataCache cache = AttributeMetadataCache.Instance;
/// foreach (string attributeName in cache.AllAttributeNames)
/// {
/// Debug.Log($"Attribute: {attributeName}");
/// }
///
///
public string[] AllAttributeNames
{
get
{
bool hasTestAssemblies = AttributeMetadataFilters.HasTestAssembliesLoaded();
if (
_computedAllAttributeNames == null
|| (hasTestAssemblies && !_computedAllAttributeNamesIncludesTests)
|| (!hasTestAssemblies && _computedAllAttributeNamesIncludesTests)
)
{
string[] baseNames = _allAttributeNames ?? Array.Empty();
_computedAllAttributeNames = hasTestAssemblies
? AttributeMetadataFilters.MergeWithExcludedAttributeNames(baseNames)
: baseNames;
_computedAllAttributeNamesIncludesTests = hasTestAssemblies;
}
return _computedAllAttributeNames;
}
}
///
/// Auto-load singleton entries configured for this cache.
///
/// Entries are sorted for determinism and safe to iterate without additional allocation.
public AutoLoadSingletonEntry[] AutoLoadSingletons => SerializedAutoLoadSingletons;
private void OnEnable()
{
_computedAllAttributeNames = null;
_computedAllAttributeNamesIncludesTests = false;
BuildLookup();
}
#if UNITY_2019_1_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void RuntimePrewarmRelationalIfEnabled()
{
try
{
AttributeMetadataCache inst = Instance;
if (inst != null && inst._prewarmRelationalOnLoad)
{
RelationalComponentInitializer.Report report =
RelationalComponentInitializer.Initialize(types: null, logSummary: false);
Debug.Log(
$"AttributeMetadataCache: Relational prewarm enabled. TypesWarmed={report.TypesWarmed}, FieldsWarmed={report.FieldsWarmed}, Warnings={report.Warnings}, Errors={report.Errors}."
);
}
}
catch (Exception e)
{
Debug.LogError(
$"AttributeMetadataCache: Exception during relational prewarm on load: {e.Message}\n{e}"
);
}
}
#endif
private void BuildLookup()
{
lock (_lookupLock)
{
if (
_typeFieldsLookup != null
&& _relationalFieldsLookup != null
&& _resolvedRelationalFieldsLookup != null
&& _elementTypeLookup != null
)
{
return;
}
Dictionary typeFieldsLookup = new(_typeMetadata?.Length ?? 0);
Dictionary relationalFieldsLookup = new(
_relationalTypeMetadata?.Length ?? 0
);
Dictionary resolvedRelationalFieldsLookup =
new(_relationalTypeMetadata?.Length ?? 0);
Dictionary elementTypeLookup = new(
_relationalTypeMetadata?.Length ?? 0
);
if (_typeMetadata != null)
{
foreach (TypeFieldMetadata metadata in _typeMetadata)
{
if (
metadata == null
|| !TryResolveType(metadata.typeName, out Type componentType)
)
{
LogMissingType(metadata?.typeName, "attribute component");
continue;
}
string[] fieldNames = metadata.fieldNames ?? Array.Empty();
typeFieldsLookup[componentType] = fieldNames;
}
}
if (_relationalTypeMetadata != null)
{
foreach (RelationalTypeMetadata metadata in _relationalTypeMetadata)
{
if (
metadata == null
|| !TryResolveType(metadata.typeName, out Type relationalType)
)
{
LogMissingType(metadata?.typeName, "relational component");
continue;
}
RelationalFieldMetadata[] fields =
metadata.fields ?? Array.Empty();
relationalFieldsLookup[relationalType] = fields;
ResolvedRelationalFieldMetadata[] resolvedFields =
new ResolvedRelationalFieldMetadata[fields.Length];
for (int i = 0; i < fields.Length; i++)
{
RelationalFieldMetadata field = fields[i];
Type elementType = null;
if (!string.IsNullOrWhiteSpace(field.elementTypeName))
{
if (TryResolveType(field.elementTypeName, out elementType))
{
elementTypeLookup[
new ElementTypeKey(relationalType, field.fieldName)
] = elementType;
}
else
{
LogMissingType(field.elementTypeName, "relational element");
}
}
resolvedFields[i] = new ResolvedRelationalFieldMetadata(
field.fieldName,
field.attributeKind,
field.fieldKind,
elementType,
field.isInterface
);
}
resolvedRelationalFieldsLookup[relationalType] = resolvedFields;
}
}
_typeFieldsLookup = typeFieldsLookup;
_relationalFieldsLookup = relationalFieldsLookup;
_resolvedRelationalFieldsLookup = resolvedRelationalFieldsLookup;
_elementTypeLookup = elementTypeLookup;
}
}
#if UNITY_INCLUDE_TESTS
///
/// Rebuilds all cached lookup tables. Intended for editor and test usage.
///
///
/// This clears internal dictionaries and forces a full rebuild, ensuring test isolation.
///
public void ForceRebuildForTests()
{
lock (_lookupLock)
{
_typeFieldsLookup = null;
_relationalFieldsLookup = null;
_resolvedRelationalFieldsLookup = null;
_elementTypeLookup = null;
}
BuildLookup();
}
#endif
///
/// Attempts to retrieve attribute field names for a given component type.
///
/// Component type that owns attribute fields.
/// Output array of field names if present.
/// true when metadata exists for the type; otherwise, false.
///
///
/// if (AttributeMetadataCache.Instance.TryGetFieldNames(typeof(AttributesComponent), out var names))
/// {
/// Debug.Log($"Found {names.Length} attribute fields.");
/// }
///
///
public bool TryGetFieldNames(Type type, out string[] fieldNames)
{
if (_typeFieldsLookup == null)
{
BuildLookup();
}
// ReSharper disable once PossibleNullReferenceException
return _typeFieldsLookup.TryGetValue(type, out fieldNames);
}
///
/// Attempts to retrieve relational field metadata for a given component type.
///
/// Component type declaring relational attributes.
/// Output array of serialized relational metadata.
/// true when relational metadata exists; otherwise, false.
public bool TryGetRelationalFields(
Type type,
out RelationalFieldMetadata[] relationalFields
)
{
if (_relationalFieldsLookup == null)
{
BuildLookup();
}
// ReSharper disable once PossibleNullReferenceException
return _relationalFieldsLookup.TryGetValue(type, out relationalFields);
}
///
/// Populates with the set of component types that declare
/// relational component fields.
///
/// List that receives relational component types.
/// Thrown when is null.
public void CollectRelationalComponentTypes(List destination)
{
if (destination == null)
{
throw new ArgumentNullException(nameof(destination));
}
if (_relationalFieldsLookup == null)
{
BuildLookup();
}
// ReSharper disable once PossibleNullReferenceException
foreach (KeyValuePair pair in _relationalFieldsLookup)
{
Type componentType = pair.Key;
if (componentType == null)
{
continue;
}
if (pair.Value is { Length: > 0 })
{
destination.Add(componentType);
}
}
}
///
/// Attempts to retrieve relational metadata with resolved runtime references.
///
/// Component type declaring relational attributes.
/// Output array of resolved relational metadata.
/// true when resolved metadata exists; otherwise, false.
public bool TryGetResolvedRelationalFields(
Type type,
out ResolvedRelationalFieldMetadata[] relationalFields
)
{
if (_resolvedRelationalFieldsLookup == null)
{
BuildLookup();
}
// ReSharper disable once PossibleNullReferenceException
return _resolvedRelationalFieldsLookup.TryGetValue(type, out relationalFields);
}
///
/// Attempts to resolve the element type for a relational field on a component.
///
/// Component type declaring the field.
/// Name of the relational field.
/// Resolved element type when available.
/// true when a matching element type exists; otherwise, false.
public bool TryGetElementType(Type componentType, string fieldName, out Type elementType)
{
if (_elementTypeLookup == null)
{
BuildLookup();
}
// ReSharper disable once PossibleNullReferenceException
return _elementTypeLookup.TryGetValue(
new ElementTypeKey(componentType, fieldName),
out elementType
);
}
private static bool TryResolveType(string typeName, out Type type)
{
if (string.IsNullOrWhiteSpace(typeName))
{
type = null;
return false;
}
lock (_typeResolutionLock)
{
if (_resolvedTypeCache.TryGetValue(typeName, out type))
{
return type != null;
}
type = ReflectionHelpers.TryResolveType(typeName);
_resolvedTypeCache[typeName] = type;
return type != null;
}
}
private static void LogMissingType(string typeName, string context)
{
if (string.IsNullOrWhiteSpace(typeName) || string.IsNullOrWhiteSpace(context))
{
return;
}
string key = string.Concat(context, ":", typeName);
lock (_missingTypeLock)
{
if (!_loggedMissingTypeKeys.Add(key))
{
return;
}
}
Debug.LogWarning(
string.Format(
"AttributeMetadataCache: Unable to resolve {0} type '{1}'. The cached entry will be ignored. Regenerate the cache to refresh the metadata.",
context,
typeName
)
);
}
#if UNITY_EDITOR
///
/// Sets all serialized metadata fields and rebuilds internal lookups when the normalized data changes.
///
/// All attribute names discovered.
/// Attribute field metadata per component type.
/// Relational field metadata per component type.
/// Auto-load singleton entries.
/// true when the serialized cache changed; otherwise, false.
public bool SetMetadata(
string[] allAttributeNames,
TypeFieldMetadata[] typeMetadata,
RelationalTypeMetadata[] relationalTypeMetadata,
AutoLoadSingletonEntry[] autoLoadSingletons
)
{
string[] normalizedAttributeNames = SortAttributeNames(allAttributeNames);
TypeFieldMetadata[] normalizedTypeMetadata = SortTypeMetadata(typeMetadata);
RelationalTypeMetadata[] normalizedRelationalMetadata = SortRelationalTypeMetadata(
relationalTypeMetadata
);
AutoLoadSingletonEntry[] normalizedAutoLoad = SortAutoLoadSingletonEntries(
autoLoadSingletons
);
if (
StringArraysEqual(_allAttributeNames, normalizedAttributeNames)
&& TypeMetadataArraysEqual(_typeMetadata, normalizedTypeMetadata)
&& RelationalMetadataArraysEqual(
_relationalTypeMetadata,
normalizedRelationalMetadata
)
&& AutoLoadSingletonEntriesEqual(_autoLoadSingletons, normalizedAutoLoad)
)
{
return false;
}
_allAttributeNames = normalizedAttributeNames;
_typeMetadata = normalizedTypeMetadata;
_relationalTypeMetadata = normalizedRelationalMetadata;
_autoLoadSingletons = normalizedAutoLoad;
_computedAllAttributeNames = null;
_computedAllAttributeNamesIncludesTests = false;
_typeFieldsLookup = null;
_relationalFieldsLookup = null;
_resolvedRelationalFieldsLookup = null;
_elementTypeLookup = null;
UnityEditor.EditorUtility.SetDirty(this);
return true;
}
private static string[] SortAttributeNames(string[] attributeNames)
{
if (attributeNames == null || attributeNames.Length == 0)
{
return Array.Empty();
}
string[] result = new string[attributeNames.Length];
Array.Copy(attributeNames, result, attributeNames.Length);
Array.Sort(result, StringComparer.Ordinal);
return result;
}
private static TypeFieldMetadata[] SortTypeMetadata(TypeFieldMetadata[] typeMetadata)
{
if (typeMetadata == null || typeMetadata.Length == 0)
{
return Array.Empty();
}
int nonNullCount = 0;
foreach (TypeFieldMetadata typeFieldMetadata in typeMetadata)
{
if (typeFieldMetadata != null)
{
nonNullCount++;
}
}
if (nonNullCount == 0)
{
return Array.Empty();
}
TypeFieldMetadata[] result = new TypeFieldMetadata[nonNullCount];
int resultIndex = 0;
foreach (TypeFieldMetadata metadata in typeMetadata)
{
if (metadata == null)
{
continue;
}
string typeName = metadata.typeName ?? string.Empty;
string[] fieldNames = metadata.fieldNames ?? Array.Empty();
string[] sortedFieldNames =
fieldNames.Length == 0 ? Array.Empty() : CopyAndSort(fieldNames);
result[resultIndex] = new TypeFieldMetadata(typeName, sortedFieldNames);
resultIndex++;
}
Array.Sort(result, CompareTypeFieldMetadata);
return result;
}
private static RelationalTypeMetadata[] SortRelationalTypeMetadata(
RelationalTypeMetadata[] relationalTypeMetadata
)
{
if (relationalTypeMetadata == null || relationalTypeMetadata.Length == 0)
{
return Array.Empty();
}
int nonNullCount = 0;
foreach (RelationalTypeMetadata typeMetadata in relationalTypeMetadata)
{
if (typeMetadata != null)
{
nonNullCount++;
}
}
if (nonNullCount == 0)
{
return Array.Empty();
}
RelationalTypeMetadata[] result = new RelationalTypeMetadata[nonNullCount];
int resultIndex = 0;
foreach (RelationalTypeMetadata metadata in relationalTypeMetadata)
{
if (metadata == null)
{
continue;
}
string typeName = metadata.typeName ?? string.Empty;
RelationalFieldMetadata[] sortedFields = SortRelationalFields(metadata.fields);
result[resultIndex] = new RelationalTypeMetadata(typeName, sortedFields);
resultIndex++;
}
Array.Sort(result, CompareRelationalTypeMetadata);
return result;
}
private static RelationalFieldMetadata[] SortRelationalFields(
RelationalFieldMetadata[] relationalFields
)
{
if (relationalFields == null || relationalFields.Length == 0)
{
return Array.Empty();
}
int nonNullCount = 0;
foreach (RelationalFieldMetadata relationalField in relationalFields)
{
if (relationalField != null)
{
nonNullCount++;
}
}
if (nonNullCount == 0)
{
return Array.Empty();
}
RelationalFieldMetadata[] result = new RelationalFieldMetadata[nonNullCount];
int resultIndex = 0;
foreach (RelationalFieldMetadata field in relationalFields)
{
if (field == null)
{
continue;
}
result[resultIndex] = new RelationalFieldMetadata(
field.fieldName,
field.attributeKind,
field.fieldKind,
field.elementTypeName,
field.isInterface
);
resultIndex++;
}
Array.Sort(result, CompareRelationalFieldMetadata);
return result;
}
private static string[] CopyAndSort(string[] values)
{
string[] result = new string[values.Length];
Array.Copy(values, result, values.Length);
Array.Sort(result, StringComparer.Ordinal);
return result;
}
private static int CompareTypeFieldMetadata(TypeFieldMetadata left, TypeFieldMetadata right)
{
if (ReferenceEquals(left, right))
{
return 0;
}
if (left == null)
{
return -1;
}
if (right == null)
{
return 1;
}
int typeNameComparison = string.CompareOrdinal(left.typeName, right.typeName);
if (typeNameComparison != 0)
{
return typeNameComparison;
}
string[] leftFields = left.fieldNames ?? Array.Empty();
string[] rightFields = right.fieldNames ?? Array.Empty();
int lengthComparison = leftFields.Length.CompareTo(rightFields.Length);
if (lengthComparison != 0)
{
return lengthComparison;
}
for (int i = 0; i < leftFields.Length; i++)
{
int fieldComparison = string.CompareOrdinal(leftFields[i], rightFields[i]);
if (fieldComparison != 0)
{
return fieldComparison;
}
}
return 0;
}
private static bool StringArraysEqual(string[] left, string[] right)
{
left ??= Array.Empty();
right ??= Array.Empty();
if (left.Length != right.Length)
{
return false;
}
for (int i = 0; i < left.Length; i++)
{
if (!string.Equals(left[i], right[i], StringComparison.Ordinal))
{
return false;
}
}
return true;
}
private static bool TypeMetadataArraysEqual(
TypeFieldMetadata[] left,
TypeFieldMetadata[] right
)
{
left ??= Array.Empty();
right ??= Array.Empty();
if (left.Length != right.Length)
{
return false;
}
for (int i = 0; i < left.Length; i++)
{
if (CompareTypeFieldMetadata(left[i], right[i]) != 0)
{
return false;
}
}
return true;
}
private static bool RelationalMetadataArraysEqual(
RelationalTypeMetadata[] left,
RelationalTypeMetadata[] right
)
{
left ??= Array.Empty();
right ??= Array.Empty();
if (left.Length != right.Length)
{
return false;
}
for (int i = 0; i < left.Length; i++)
{
if (CompareRelationalTypeMetadata(left[i], right[i]) != 0)
{
return false;
}
}
return true;
}
private static bool AutoLoadSingletonEntriesEqual(
AutoLoadSingletonEntry[] left,
AutoLoadSingletonEntry[] right
)
{
left ??= Array.Empty();
right ??= Array.Empty();
if (left.Length != right.Length)
{
return false;
}
for (int i = 0; i < left.Length; i++)
{
AutoLoadSingletonEntry leftEntry = left[i];
AutoLoadSingletonEntry rightEntry = right[i];
if (ReferenceEquals(leftEntry, rightEntry))
{
continue;
}
if (leftEntry == null || rightEntry == null)
{
return false;
}
if (
!string.Equals(
leftEntry.typeName,
rightEntry.typeName,
StringComparison.Ordinal
)
|| leftEntry.kind != rightEntry.kind
|| leftEntry.loadType != rightEntry.loadType
)
{
return false;
}
}
return true;
}
private static int CompareRelationalTypeMetadata(
RelationalTypeMetadata left,
RelationalTypeMetadata right
)
{
if (ReferenceEquals(left, right))
{
return 0;
}
if (left == null)
{
return -1;
}
if (right == null)
{
return 1;
}
int typeNameComparison = string.CompareOrdinal(left.typeName, right.typeName);
if (typeNameComparison != 0)
{
return typeNameComparison;
}
RelationalFieldMetadata[] leftFields =
left.fields ?? Array.Empty();
RelationalFieldMetadata[] rightFields =
right.fields ?? Array.Empty();
int lengthComparison = leftFields.Length.CompareTo(rightFields.Length);
if (lengthComparison != 0)
{
return lengthComparison;
}
for (int i = 0; i < leftFields.Length; i++)
{
int fieldComparison = CompareRelationalFieldMetadata(leftFields[i], rightFields[i]);
if (fieldComparison != 0)
{
return fieldComparison;
}
}
return 0;
}
private static int CompareRelationalFieldMetadata(
RelationalFieldMetadata left,
RelationalFieldMetadata right
)
{
if (ReferenceEquals(left, right))
{
return 0;
}
if (left == null)
{
return -1;
}
if (right == null)
{
return 1;
}
int fieldNameComparison = string.CompareOrdinal(left.fieldName, right.fieldName);
if (fieldNameComparison != 0)
{
return fieldNameComparison;
}
int attributeComparison = left.attributeKind.CompareTo(right.attributeKind);
if (attributeComparison != 0)
{
return attributeComparison;
}
int fieldKindComparison = left.fieldKind.CompareTo(right.fieldKind);
if (fieldKindComparison != 0)
{
return fieldKindComparison;
}
int elementTypeComparison = string.CompareOrdinal(
left.elementTypeName,
right.elementTypeName
);
if (elementTypeComparison != 0)
{
return elementTypeComparison;
}
if (left.isInterface == right.isInterface)
{
return 0;
}
return left.isInterface ? -1 : 1;
}
private static AutoLoadSingletonEntry[] SortAutoLoadSingletonEntries(
AutoLoadSingletonEntry[] entries
)
{
if (entries == null || entries.Length == 0)
{
return Array.Empty();
}
List result = new(entries.Length);
foreach (AutoLoadSingletonEntry entry in entries)
{
if (entry == null || string.IsNullOrWhiteSpace(entry.typeName))
{
continue;
}
result.Add(new AutoLoadSingletonEntry(entry.typeName, entry.kind, entry.loadType));
}
if (result.Count == 0)
{
return Array.Empty();
}
result.Sort((left, right) => string.CompareOrdinal(left.typeName, right.typeName));
return result.ToArray();
}
#endif
}
}