Skip to content

Commit a786d39

Browse files
committed
Add type policy and candidate provider interfaces
Introduces IIntrinsicTypePolicy, ITypeCandiateProvider, and ITypeCompatibilityPolicy interfaces with default implementations for type filtering and compatibility checks. Adds Unity 2023.2+ specific implementations to support generic variance in type compatibility and candidate discovery. These abstractions improve extensibility and support for advanced type scenarios in SerializeReferenceExtensions.
1 parent 4cb8292 commit a786d39

10 files changed

Lines changed: 334 additions & 0 deletions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
3+
namespace MackySoft.SerializeReferenceExtensions.Editor
4+
{
5+
public interface IIntrinsicTypePolicy
6+
{
7+
bool IsAllowed (Type candiateType);
8+
}
9+
10+
public sealed class DefaultIntrinsicTypePolicy : IIntrinsicTypePolicy
11+
{
12+
13+
public static readonly DefaultIntrinsicTypePolicy Instance = new DefaultIntrinsicTypePolicy();
14+
15+
public bool IsAllowed (Type candiateType)
16+
{
17+
return
18+
(candiateType.IsPublic || candiateType.IsNestedPublic || candiateType.IsNestedPrivate) &&
19+
!candiateType.IsAbstract &&
20+
!candiateType.IsGenericType &&
21+
!typeof(UnityEngine.Object).IsAssignableFrom(candiateType) &&
22+
Attribute.IsDefined(candiateType, typeof(SerializableAttribute)) &&
23+
!Attribute.IsDefined(candiateType, typeof(HideInTypeMenuAttribute));
24+
}
25+
}
26+
}

Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/IIntrinsicTypePolicy.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using UnityEditor;
5+
6+
namespace MackySoft.SerializeReferenceExtensions.Editor
7+
{
8+
public interface ITypeCandiateProvider
9+
{
10+
IEnumerable<Type> GetTypeCandidates (Type baseType);
11+
}
12+
13+
public sealed class DefaultTypeCandiateProvider : ITypeCandiateProvider
14+
{
15+
16+
public static readonly DefaultTypeCandiateProvider Instance = new DefaultTypeCandiateProvider(DefaultIntrinsicTypePolicy.Instance);
17+
18+
private readonly IIntrinsicTypePolicy intrinsicTypePolicy;
19+
20+
public DefaultTypeCandiateProvider (IIntrinsicTypePolicy intrinsicTypePolicy)
21+
{
22+
this.intrinsicTypePolicy = intrinsicTypePolicy ?? throw new ArgumentNullException(nameof(intrinsicTypePolicy));
23+
}
24+
25+
public IEnumerable<Type> GetTypeCandidates (Type baseType)
26+
{
27+
return TypeCache.GetTypesDerivedFrom(baseType)
28+
.Append(baseType)
29+
.Where(intrinsicTypePolicy.IsAllowed);
30+
}
31+
}
32+
}

Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ITypeCandiateProvider.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
3+
namespace MackySoft.SerializeReferenceExtensions.Editor
4+
{
5+
public interface ITypeCompatibilityPolicy
6+
{
7+
bool IsCompatible (Type baseType, Type candiateType);
8+
}
9+
10+
public sealed class DefaultTypeCompatibilityPolicy : ITypeCompatibilityPolicy
11+
{
12+
13+
public static readonly DefaultTypeCompatibilityPolicy Instance = new DefaultTypeCompatibilityPolicy();
14+
15+
public bool IsCompatible (Type baseType, Type candiateType)
16+
{
17+
if (baseType == null)
18+
{
19+
throw new ArgumentNullException(nameof(baseType));
20+
}
21+
if (candiateType == null)
22+
{
23+
throw new ArgumentNullException(nameof(candiateType));
24+
}
25+
26+
if (baseType.IsGenericTypeDefinition || baseType.ContainsGenericParameters)
27+
{
28+
return false;
29+
}
30+
31+
return baseType.IsAssignableFrom(candiateType);
32+
}
33+
}
34+
}

Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ITypeCompatibilityPolicy.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace MackySoft.SerializeReferenceExtensions.Editor
5+
{
6+
#if UNITY_2023_2_OR_NEWER
7+
public sealed class Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy : ITypeCompatibilityPolicy
8+
{
9+
10+
public static readonly Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy Instance = new Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy();
11+
12+
public bool IsCompatible (Type baseType, Type candiateType)
13+
{
14+
if (baseType == null)
15+
{
16+
throw new ArgumentNullException(nameof(baseType));
17+
}
18+
if (candiateType == null)
19+
{
20+
throw new ArgumentNullException(nameof(candiateType));
21+
}
22+
23+
// If not generic, fall back to standard assignability check
24+
if (!baseType.IsGenericType)
25+
{
26+
return baseType.IsAssignableFrom(candiateType);
27+
}
28+
29+
if (baseType.IsGenericTypeDefinition || baseType.ContainsGenericParameters)
30+
{
31+
return false;
32+
}
33+
34+
// NOTE: baseType is constructed generic type
35+
Type genericTypeDefinition = baseType.GetGenericTypeDefinition();
36+
Type[] targetTypeArguments = baseType.GetGenericArguments();
37+
Type[] genericTypeParameters = genericTypeDefinition.GetGenericArguments();
38+
39+
// Check interfaces
40+
foreach (Type interfaceType in candiateType.GetInterfaces())
41+
{
42+
if (!interfaceType.IsGenericType)
43+
{
44+
continue;
45+
}
46+
if (interfaceType.GetGenericTypeDefinition() != genericTypeDefinition)
47+
{
48+
continue;
49+
}
50+
51+
Type[] sourceTypeArguments = interfaceType.GetGenericArguments();
52+
if (AreAllGenericArgumentsCompatible(genericTypeParameters, targetTypeArguments, sourceTypeArguments))
53+
{
54+
return true;
55+
}
56+
}
57+
58+
// Check base types
59+
for (Type t = candiateType; t != null && t != typeof(object); t = t.BaseType)
60+
{
61+
if (!t.IsGenericType)
62+
{
63+
continue;
64+
}
65+
if (t.GetGenericTypeDefinition() != genericTypeDefinition)
66+
{
67+
continue;
68+
}
69+
70+
Type[] sourceTypeArguments = t.GetGenericArguments();
71+
if (AreAllGenericArgumentsCompatible(genericTypeParameters, targetTypeArguments, sourceTypeArguments))
72+
{
73+
return true;
74+
}
75+
}
76+
77+
return false;
78+
}
79+
80+
private static bool AreAllGenericArgumentsCompatible (Type[] genericTypeParameters, Type[] targetTypeArguments, Type[] sourceTypeArguments)
81+
{
82+
if (genericTypeParameters.Length != targetTypeArguments.Length)
83+
{
84+
return false;
85+
}
86+
if (sourceTypeArguments.Length != targetTypeArguments.Length)
87+
{
88+
return false;
89+
}
90+
91+
for (int i = 0; i < genericTypeParameters.Length; i++)
92+
{
93+
var variance = genericTypeParameters[i].GenericParameterAttributes & GenericParameterAttributes.VarianceMask;
94+
95+
Type sourceTypeArgument = sourceTypeArguments[i];
96+
Type targetTypeArgument = targetTypeArguments[i];
97+
98+
if (variance == GenericParameterAttributes.Contravariant)
99+
{
100+
// NOTE: in T = source must be able to accept the target.
101+
if (!sourceTypeArgument.IsAssignableFrom(targetTypeArgument))
102+
{
103+
return false;
104+
}
105+
}
106+
else if (variance == GenericParameterAttributes.Covariant)
107+
{
108+
// NOTE: out T = target must be able to accept the source.
109+
if (!targetTypeArgument.IsAssignableFrom(sourceTypeArgument))
110+
{
111+
return false;
112+
}
113+
}
114+
else
115+
{
116+
// invariant
117+
if (sourceTypeArgument != targetTypeArgument)
118+
{
119+
return false;
120+
}
121+
}
122+
}
123+
124+
return true;
125+
}
126+
}
127+
#endif
128+
}

Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#if UNITY_2023_2_OR_NEWER
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Reflection;
5+
6+
namespace MackySoft.SerializeReferenceExtensions.Editor
7+
{
8+
public sealed class Unity_2023_2_OrNewer_TypeCandiateProvider : ITypeCandiateProvider
9+
{
10+
11+
public static readonly Unity_2023_2_OrNewer_TypeCandiateProvider Instance = new Unity_2023_2_OrNewer_TypeCandiateProvider(
12+
DefaultIntrinsicTypePolicy.Instance,
13+
Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy.Instance
14+
);
15+
16+
private Dictionary<Type, List<Type>> m_TypeCache = new Dictionary<Type, List<Type>>();
17+
18+
private readonly IIntrinsicTypePolicy m_IntrinsicTypePolicy;
19+
private readonly ITypeCompatibilityPolicy m_TypeCompatibilityPolicy;
20+
21+
private Unity_2023_2_OrNewer_TypeCandiateProvider (
22+
IIntrinsicTypePolicy intrinsicTypePolicy,
23+
ITypeCompatibilityPolicy typeCompatibilityPolicy
24+
)
25+
{
26+
m_IntrinsicTypePolicy = intrinsicTypePolicy ?? throw new ArgumentNullException(nameof(intrinsicTypePolicy));
27+
m_TypeCompatibilityPolicy = typeCompatibilityPolicy ?? throw new ArgumentNullException(nameof(typeCompatibilityPolicy));
28+
}
29+
30+
public IEnumerable<Type> GetTypeCandidates (Type baseType)
31+
{
32+
if (!baseType.IsGenericType)
33+
{
34+
return DefaultTypeCandiateProvider.Instance.GetTypeCandidates(baseType);
35+
}
36+
37+
return GetTypesWithGeneric(baseType);
38+
}
39+
40+
private IEnumerable<Type> GetTypesWithGeneric (Type baseType)
41+
{
42+
if (m_TypeCache.TryGetValue(baseType, out List<Type> result))
43+
{
44+
return result;
45+
}
46+
47+
result = new List<Type>();
48+
49+
IEnumerable<Type> types = EnumerateAllTypesSafely();
50+
foreach (Type type in types)
51+
{
52+
if (!m_IntrinsicTypePolicy.IsAllowed(type))
53+
{
54+
continue;
55+
}
56+
if (!m_TypeCompatibilityPolicy.IsCompatible(baseType, type))
57+
{
58+
continue;
59+
}
60+
61+
result.Add(type);
62+
}
63+
64+
// Include the base type itself if allowed
65+
if (m_IntrinsicTypePolicy.IsAllowed(baseType) && m_TypeCompatibilityPolicy.IsCompatible(baseType, baseType))
66+
{
67+
result.Add(baseType);
68+
}
69+
70+
m_TypeCache.Add(baseType, result);
71+
return result;
72+
}
73+
74+
private static IEnumerable<Type> EnumerateAllTypesSafely ()
75+
{
76+
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
77+
{
78+
Type[] types;
79+
try
80+
{
81+
types = asm.GetTypes();
82+
}
83+
catch (ReflectionTypeLoadException ex)
84+
{
85+
types = ex.Types;
86+
}
87+
88+
if (types == null)
89+
{
90+
continue;
91+
}
92+
93+
foreach (var t in types)
94+
{
95+
if (t != null)
96+
{
97+
yield return t;
98+
}
99+
}
100+
}
101+
}
102+
}
103+
}
104+
#endif

Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/Unity_2023_2_OrNewer_TypeCandiateProvider.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)