diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2614402..3f31c6f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -41,6 +41,9 @@ jobs: - name: Build uses: game-ci/unity-builder@v4 env: - UNITY_LICENSE: ${{ secrets.UNITY_LICENSE_2020 }} + UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} + UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} + UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} with: targetPlatform: ${{ matrix.targetPlatform }} + unityVersion: 2021.3.45f2 diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index b22b69c..f83c6c3 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -5,7 +5,7 @@ on: push: { branches: [main] } env: - UNITY_LICENSE: ${{ secrets.UNITY_LICENSE_2020 }} + UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} jobs: build: @@ -34,7 +34,7 @@ jobs: UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} with: - unityVersion: 2021.3.29f1 + unityVersion: 2021.3.45f2 buildMethod: MackySoft.PackageTools.Editor.UnityPackageExporter.Export # Upload diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4537728..57149e8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -65,9 +65,9 @@ jobs: needs: [update-packagejson] strategy: matrix: - unity: ["2021.3.29f1"] + unity: ["2021.3.45f2"] include: - - unityVersion: 2021.3.29f1 + - unityVersion: 2021.3.45f2 license: UNITY_LICENSE runs-on: ubuntu-latest timeout-minutes: 15 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..1a4225f --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,55 @@ +name: Tests + +on: + push: + branches-ignore: + - documentation + - gh-pages + pull_request: {} + +jobs: + test: + name: ${{ matrix.testMode }} on Unity ${{ matrix.unityVersion }} + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + projectPath: + - . + unityVersion: + - 2021.3.45f2 + - 2023.2.22f1 + testMode: + - editmode + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + lfs: true + + - name: Cache + uses: actions/cache@v4 + with: + path: ${{ matrix.projectPath }}/Library + key: Library-${{ runner.os }}-${{ matrix.projectPath }}-${{ matrix.unityVersion }}-${{ matrix.testMode }}-${{ hashFiles('**/Packages/manifest.json', '**/Packages/packages-lock.json', '**/ProjectSettings/ProjectVersion.txt') }} + restore-keys: | + Library-${{ runner.os }}-${{ matrix.projectPath }}-${{ matrix.unityVersion }}-${{ matrix.testMode }}- + Library-${{ runner.os }}-${{ matrix.projectPath }}-${{ matrix.unityVersion }}- + Library-${{ runner.os }}-${{ matrix.projectPath }}- + + - name: Tests + uses: game-ci/unity-test-runner@v4 + id: tests + env: + UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} + UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} + UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} + with: + projectPath: ${{ matrix.projectPath }} + unityVersion: ${{ matrix.unityVersion }} + testMode: ${{ matrix.testMode }} + artifactsPath: test-results + artifactsName: TestResults-${{ matrix.unityVersion }}-${{ matrix.testMode }} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8a4dc9d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,51 @@ +# Unity-SerializeReferenceExtensions — AGENTS.md + +## Project overview +This repository provides editor tooling for Unity's [SerializeReference], including `SubclassSelector` +to pick concrete implementations of interfaces / abstract types in the Inspector. + +Primary risk area: type discovery & filtering (what types appear in the selector). + +## Repository layout +- Repository URL: `https://github.com/mackysoft/Unity-SerializeReferenceExtensions` +- Package source: `Assets/MackySoft/MackySoft.SerializeReferenceExtensions` +- Editor code: `.../Editor/**` +- Tests: `.../Tests/**` + +## Unity compatibility (critical) +- Minimum supported Unity: 2021.3 (baseline for development/testing). +- Unity 2023.2+ has enhanced generic type support (variance, etc.). Changes must not break 2021.3 behavior and guarded by `UNITY_2023_2_OR_NEWER`. + +## CI (GitHub Actions) +I use GameCI `unity-test-runner`. +- Always run EditMode tests. +- Run a Unity matrix that includes: + - 2021.3.x (minimum baseline) + - 2023.2+ (generic/variance feature gate) + +## How to run tests locally +### EditMode +Run Unity in batchmode: +``` +PROJECT_ROOT="$(pwd)" +RESULT_XML="$PROJECT_ROOT/TestResults/editmode.xml" + +mkdir -p "$(dirname "$RESULT_XML")" + +"" -batchmode -nographics -quit \ + -projectPath "$PROJECT_ROOT" \ + -runTests -testPlatform editmode \ + -testResults "$RESULT_XML" +``` + +## Architecture guardrails +- Runtime surface area should remain minimal (mainly attributes / data structures). +- Editor implementation (PropertyDrawer/UI/type search) must stay under `Editor/`. +- Avoid introducing UnityEditor references into Runtime assemblies. + +## Coding conventions +- Prefer `UnityEditor.TypeCache` for type discovery. Avoid full AppDomain scans unless necessary. +- Keep allocations low in IMGUI paths (e.g., `OnGUI`). +- Keep public API stable; if changing type filtering behavior, add/adjust tests. +- As a general rule, you should follow the restrictions on SerializeReference in Unity's official documentation. + - https://docs.unity3d.com/ScriptReference/SerializeReference.html \ No newline at end of file diff --git a/Assets/Example/Example_Generics.cs b/Assets/Example/Example_Generics.cs index bc4f604..f94145e 100644 --- a/Assets/Example/Example_Generics.cs +++ b/Assets/Example/Example_Generics.cs @@ -85,6 +85,12 @@ public class Example_Generics : MonoBehaviour { [SerializeReference, SubclassSelector] + public IContravarianceAction contravarianceAction = null; + + [SerializeReference, SubclassSelector] + public BaseAction baseAction = null; + + [SerializeReference, SubclassSelector] public List> contravarianceActions = new List>(); [SerializeReference, SubclassSelector] diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorDrawer.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorDrawer.cs index f87f59c..157725c 100644 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorDrawer.cs +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorDrawer.cs @@ -150,8 +150,9 @@ TypePopupCache GetTypePopup (SerializedProperty property) { var state = new AdvancedDropdownState(); Type baseType = ManagedReferenceUtility.GetType(managedReferenceFieldTypename); - var popup = new AdvancedTypePopup( - TypeSearch.GetTypes(baseType), + var types = TypeSearchService.TypeCandiateService.GetDisplayableTypes(baseType); + var popup = new AdvancedTypePopup( + types, k_MaxTypePopupLineCount, state ); diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch.cs deleted file mode 100644 index 7b6bd97..0000000 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using UnityEditor; -using System.Reflection; - -namespace MackySoft.SerializeReferenceExtensions.Editor -{ - public static class TypeSearch - { - -#if UNITY_2023_2_OR_NEWER - static readonly Dictionary> m_TypeCache = new Dictionary>(); -#endif - - public static IEnumerable GetTypes (Type baseType) - { -#if UNITY_2023_2_OR_NEWER - // NOTE: This is a generics solution for Unity 2023.2 and later. - // 2023.2 because SerializeReference supports generic type instances and because the behaviour is stable. - if (baseType.IsGenericType) - { - return GetTypesWithGeneric(baseType); - } - else - { - return GetTypesUsingTypeCache(baseType); - } -#else - return GetTypesUsingTypeCache(baseType); -#endif - } - - static IEnumerable GetTypesUsingTypeCache (Type baseType) - { - return TypeCache.GetTypesDerivedFrom(baseType) - .Append(baseType) - .Where(IsValidType); - } - -#if UNITY_2023_2_OR_NEWER - static IEnumerable GetTypesWithGeneric (Type baseType) - { - if (m_TypeCache.TryGetValue(baseType, out List result)) - { - return result; - } - - result = new List(); - Type genericTypeDefinition = null; - Type[] targetTypeArguments = null; - Type[] genericTypeParameters = null; - - if (baseType.IsGenericType) - { - genericTypeDefinition = baseType.GetGenericTypeDefinition(); - targetTypeArguments = baseType.GetGenericArguments(); - genericTypeParameters = genericTypeDefinition.GetGenericArguments(); - } - else - { - genericTypeDefinition = baseType; - targetTypeArguments = Type.EmptyTypes; - genericTypeParameters = Type.EmptyTypes; - } - - IEnumerable types = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(x => x.GetTypes()) - .Where(IsValidType); - - foreach (Type type in types) - { - Type[] interfaceTypes = type.GetInterfaces(); - foreach (Type interfaceType in interfaceTypes) - { - if (!interfaceType.IsGenericType || interfaceType.GetGenericTypeDefinition() != genericTypeDefinition) - { - continue; - } - - Type[] sourceTypeArguments = interfaceType.GetGenericArguments(); - - bool allParametersMatch = true; - - for (int i = 0; i < genericTypeParameters.Length; i++) - { - var variance = genericTypeParameters[i].GenericParameterAttributes & GenericParameterAttributes.VarianceMask; - - Type sourceTypeArgument = sourceTypeArguments[i]; - Type targetTypeArgument = targetTypeArguments[i]; - - if (variance == GenericParameterAttributes.Contravariant) - { - if (!sourceTypeArgument.IsAssignableFrom(targetTypeArgument)) - { - allParametersMatch = false; - break; - } - } - else if (variance == GenericParameterAttributes.Covariant) - { - if (!targetTypeArgument.IsAssignableFrom(sourceTypeArgument)) - { - allParametersMatch = false; - break; - } - } - else - { - if (sourceTypeArgument != targetTypeArgument) - { - allParametersMatch = false; - break; - } - } - } - - if (allParametersMatch) - { - result.Add(type); - break; - } - } - } - - m_TypeCache.Add(baseType, result); - return result; - } -#endif - - static bool IsValidType (Type type) - { - return - (type.IsPublic || type.IsNestedPublic || type.IsNestedPrivate) && - !type.IsAbstract && - !type.IsGenericType && - !typeof(UnityEngine.Object).IsAssignableFrom(type) && - Attribute.IsDefined(type, typeof(SerializableAttribute)) && - !Attribute.IsDefined(type, typeof(HideInTypeMenuAttribute)); - } - } -} \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch.cs.meta deleted file mode 100644 index a9af592..0000000 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5ea24172520aa2646954c1d246e7ba5d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch.meta new file mode 100644 index 0000000..648076c --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 565d90a06c6ff6d45881a68742e61f5b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IIntrinsicTypePolicy.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IIntrinsicTypePolicy.cs new file mode 100644 index 0000000..b707ef4 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IIntrinsicTypePolicy.cs @@ -0,0 +1,9 @@ +using System; + +namespace MackySoft.SerializeReferenceExtensions.Editor +{ + public interface IIntrinsicTypePolicy + { + bool IsAllowed (Type candiateType); + } +} \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IIntrinsicTypePolicy.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IIntrinsicTypePolicy.cs.meta new file mode 100644 index 0000000..2841a2e --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IIntrinsicTypePolicy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 67c3f8c80bdf1514184495b1ff985d7c \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/ITypeCandiateProvider.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/ITypeCandiateProvider.cs new file mode 100644 index 0000000..ea65b44 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/ITypeCandiateProvider.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace MackySoft.SerializeReferenceExtensions.Editor +{ + public interface ITypeCandiateProvider + { + IEnumerable GetTypeCandidates (Type baseType); + } +} \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/ITypeCandiateProvider.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/ITypeCandiateProvider.cs.meta new file mode 100644 index 0000000..4ec4253 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/ITypeCandiateProvider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0f3d3f0c0347423459e5e3ef96cf0026 \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/ITypeCompatibilityPolicy.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/ITypeCompatibilityPolicy.cs new file mode 100644 index 0000000..612d689 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/ITypeCompatibilityPolicy.cs @@ -0,0 +1,9 @@ +using System; + +namespace MackySoft.SerializeReferenceExtensions.Editor +{ + public interface ITypeCompatibilityPolicy + { + bool IsCompatible (Type baseType, Type candiateType); + } +} \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/ITypeCompatibilityPolicy.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/ITypeCompatibilityPolicy.cs.meta new file mode 100644 index 0000000..685ac90 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/ITypeCompatibilityPolicy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 657baec9653b0914398f2075153d3275 \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IntrinsicTypePolicy.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IntrinsicTypePolicy.meta new file mode 100644 index 0000000..d1d6e71 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IntrinsicTypePolicy.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 954f01eacf065bb409380a285e789133 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IntrinsicTypePolicy/DefaultIntrinsicTypePolicy.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IntrinsicTypePolicy/DefaultIntrinsicTypePolicy.cs new file mode 100644 index 0000000..d7158ae --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IntrinsicTypePolicy/DefaultIntrinsicTypePolicy.cs @@ -0,0 +1,23 @@ +using System; + +namespace MackySoft.SerializeReferenceExtensions.Editor +{ + public sealed class DefaultIntrinsicTypePolicy : IIntrinsicTypePolicy + { + + public static readonly DefaultIntrinsicTypePolicy Instance = new DefaultIntrinsicTypePolicy(); + + public bool IsAllowed (Type candiateType) + { + return + (candiateType.IsPublic || candiateType.IsNestedPublic || candiateType.IsNestedPrivate) && + !candiateType.IsAbstract && + !candiateType.IsGenericType && + !candiateType.IsPrimitive && + !candiateType.IsEnum && + !typeof(UnityEngine.Object).IsAssignableFrom(candiateType) && + Attribute.IsDefined(candiateType, typeof(SerializableAttribute)) && + !Attribute.IsDefined(candiateType, typeof(HideInTypeMenuAttribute)); + } + } +} \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IntrinsicTypePolicy/DefaultIntrinsicTypePolicy.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IntrinsicTypePolicy/DefaultIntrinsicTypePolicy.cs.meta new file mode 100644 index 0000000..b83bcbd --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/IntrinsicTypePolicy/DefaultIntrinsicTypePolicy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2e5f93f8c91d2cf4188bb5a20ba23767 \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider.meta new file mode 100644 index 0000000..8304904 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 89e60a25798499c40a631afebaa392e2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider/DefaultTypeCandiateProvider.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider/DefaultTypeCandiateProvider.cs new file mode 100644 index 0000000..49b18bd --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider/DefaultTypeCandiateProvider.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; + +namespace MackySoft.SerializeReferenceExtensions.Editor +{ + public sealed class DefaultTypeCandiateProvider : ITypeCandiateProvider + { + + public static readonly DefaultTypeCandiateProvider Instance = new DefaultTypeCandiateProvider(DefaultIntrinsicTypePolicy.Instance); + + private readonly IIntrinsicTypePolicy intrinsicTypePolicy; + + public DefaultTypeCandiateProvider (IIntrinsicTypePolicy intrinsicTypePolicy) + { + this.intrinsicTypePolicy = intrinsicTypePolicy ?? throw new ArgumentNullException(nameof(intrinsicTypePolicy)); + } + + public IEnumerable GetTypeCandidates (Type baseType) + { + return TypeCache.GetTypesDerivedFrom(baseType) + .Append(baseType) + .Where(intrinsicTypePolicy.IsAllowed); + } + } +} \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider/DefaultTypeCandiateProvider.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider/DefaultTypeCandiateProvider.cs.meta new file mode 100644 index 0000000..2a11b33 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider/DefaultTypeCandiateProvider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 26d67765ac30b4040b240089ae466c64 \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider/Unity_2023_2_OrNewer_TypeCandiateProvider.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider/Unity_2023_2_OrNewer_TypeCandiateProvider.cs new file mode 100644 index 0000000..07831e6 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider/Unity_2023_2_OrNewer_TypeCandiateProvider.cs @@ -0,0 +1,104 @@ +#if UNITY_2023_2_OR_NEWER +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace MackySoft.SerializeReferenceExtensions.Editor +{ + public sealed class Unity_2023_2_OrNewer_TypeCandiateProvider : ITypeCandiateProvider + { + + public static readonly Unity_2023_2_OrNewer_TypeCandiateProvider Instance = new Unity_2023_2_OrNewer_TypeCandiateProvider( + DefaultIntrinsicTypePolicy.Instance, + Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy.Instance + ); + + private Dictionary> m_TypeCache = new Dictionary>(); + + private readonly IIntrinsicTypePolicy m_IntrinsicTypePolicy; + private readonly ITypeCompatibilityPolicy m_TypeCompatibilityPolicy; + + private Unity_2023_2_OrNewer_TypeCandiateProvider ( + IIntrinsicTypePolicy intrinsicTypePolicy, + ITypeCompatibilityPolicy typeCompatibilityPolicy + ) + { + m_IntrinsicTypePolicy = intrinsicTypePolicy ?? throw new ArgumentNullException(nameof(intrinsicTypePolicy)); + m_TypeCompatibilityPolicy = typeCompatibilityPolicy ?? throw new ArgumentNullException(nameof(typeCompatibilityPolicy)); + } + + public IEnumerable GetTypeCandidates (Type baseType) + { + if (!baseType.IsGenericType) + { + return DefaultTypeCandiateProvider.Instance.GetTypeCandidates(baseType); + } + + return GetTypesWithGeneric(baseType); + } + + private IEnumerable GetTypesWithGeneric (Type baseType) + { + if (m_TypeCache.TryGetValue(baseType, out List result)) + { + return result; + } + + result = new List(); + + IEnumerable types = EnumerateAllTypesSafely(); + foreach (Type type in types) + { + if (!m_IntrinsicTypePolicy.IsAllowed(type)) + { + continue; + } + if (!m_TypeCompatibilityPolicy.IsCompatible(baseType, type)) + { + continue; + } + + result.Add(type); + } + + // Include the base type itself if allowed + if (m_IntrinsicTypePolicy.IsAllowed(baseType) && m_TypeCompatibilityPolicy.IsCompatible(baseType, baseType)) + { + result.Add(baseType); + } + + m_TypeCache.Add(baseType, result); + return result; + } + + private static IEnumerable EnumerateAllTypesSafely () + { + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + Type[] types; + try + { + types = asm.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + types = ex.Types; + } + + if (types == null) + { + continue; + } + + foreach (var t in types) + { + if (t != null) + { + yield return t; + } + } + } + } + } +} +#endif diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider/Unity_2023_2_OrNewer_TypeCandiateProvider.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider/Unity_2023_2_OrNewer_TypeCandiateProvider.cs.meta new file mode 100644 index 0000000..64a280c --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateProvider/Unity_2023_2_OrNewer_TypeCandiateProvider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9553ecdd6faccf14db431d2eca328aa9 \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateService.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateService.cs new file mode 100644 index 0000000..31c63d6 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateService.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MackySoft.SerializeReferenceExtensions.Editor +{ + public sealed class TypeCandiateService + { + + private readonly ITypeCandiateProvider typeCandiateProvider; + private readonly IIntrinsicTypePolicy intrinsicTypePolicy; + private readonly ITypeCompatibilityPolicy typeCompatibilityPolicy; + + private readonly Dictionary typeCache = new Dictionary(); + + public TypeCandiateService (ITypeCandiateProvider typeCandiateProvider, IIntrinsicTypePolicy intrinsicTypePolicy, ITypeCompatibilityPolicy typeCompatibilityPolicy) + { + this.typeCandiateProvider = typeCandiateProvider ?? throw new ArgumentNullException(nameof(typeCandiateProvider)); + this.intrinsicTypePolicy = intrinsicTypePolicy ?? throw new ArgumentNullException(nameof(intrinsicTypePolicy)); + this.typeCompatibilityPolicy = typeCompatibilityPolicy ?? throw new ArgumentNullException(nameof(typeCompatibilityPolicy)); + } + + public IReadOnlyList GetDisplayableTypes (Type baseType) + { + if (baseType == null) + { + throw new ArgumentNullException(nameof(baseType)); + } + if (typeCache.TryGetValue(baseType, out Type[] cachedTypes)) + { + return cachedTypes; + } + + var candiateTypes = typeCandiateProvider.GetTypeCandidates(baseType); + var result = candiateTypes + .Where(intrinsicTypePolicy.IsAllowed) + .Where(t => typeCompatibilityPolicy.IsCompatible(baseType, t)) + .Distinct() + .ToArray(); + + typeCache.Add(baseType, result); + return result; + } + } +} \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateService.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateService.cs.meta new file mode 100644 index 0000000..e0aca9d --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateService.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ac8a4cf19e3d741459fb7ce66abe1be4 \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy.meta new file mode 100644 index 0000000..c594fc7 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 93cc4fbdac6a5054580dbf1f197092f6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy/DefaultTypeCompatibilityPolicy.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy/DefaultTypeCompatibilityPolicy.cs new file mode 100644 index 0000000..8b80dac --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy/DefaultTypeCompatibilityPolicy.cs @@ -0,0 +1,29 @@ +using System; + +namespace MackySoft.SerializeReferenceExtensions.Editor +{ + public sealed class DefaultTypeCompatibilityPolicy : ITypeCompatibilityPolicy + { + + public static readonly DefaultTypeCompatibilityPolicy Instance = new DefaultTypeCompatibilityPolicy(); + + public bool IsCompatible (Type baseType, Type candiateType) + { + if (baseType == null) + { + throw new ArgumentNullException(nameof(baseType)); + } + if (candiateType == null) + { + throw new ArgumentNullException(nameof(candiateType)); + } + + if (baseType.IsGenericTypeDefinition || baseType.ContainsGenericParameters) + { + return false; + } + + return baseType.IsAssignableFrom(candiateType); + } + } +} \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy/DefaultTypeCompatibilityPolicy.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy/DefaultTypeCompatibilityPolicy.cs.meta new file mode 100644 index 0000000..b33436b --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy/DefaultTypeCompatibilityPolicy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e2a0be0521ea0864ebad1ee2ba4af27f \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy/Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy/Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy.cs new file mode 100644 index 0000000..11df80c --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy/Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy.cs @@ -0,0 +1,128 @@ +#if UNITY_2023_2_OR_NEWER +using System; +using System.Reflection; + +namespace MackySoft.SerializeReferenceExtensions.Editor +{ + public sealed class Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy : ITypeCompatibilityPolicy + { + + public static readonly Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy Instance = new Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy(); + + public bool IsCompatible (Type baseType, Type candiateType) + { + if (baseType == null) + { + throw new ArgumentNullException(nameof(baseType)); + } + if (candiateType == null) + { + throw new ArgumentNullException(nameof(candiateType)); + } + + // If not generic, fall back to standard assignability check + if (!baseType.IsGenericType) + { + return baseType.IsAssignableFrom(candiateType); + } + + if (baseType.IsGenericTypeDefinition || baseType.ContainsGenericParameters) + { + return false; + } + + // NOTE: baseType is constructed generic type + Type genericTypeDefinition = baseType.GetGenericTypeDefinition(); + Type[] targetTypeArguments = baseType.GetGenericArguments(); + Type[] genericTypeParameters = genericTypeDefinition.GetGenericArguments(); + + // Check interfaces + foreach (Type interfaceType in candiateType.GetInterfaces()) + { + if (!interfaceType.IsGenericType) + { + continue; + } + if (interfaceType.GetGenericTypeDefinition() != genericTypeDefinition) + { + continue; + } + + Type[] sourceTypeArguments = interfaceType.GetGenericArguments(); + if (AreAllGenericArgumentsCompatible(genericTypeParameters, targetTypeArguments, sourceTypeArguments)) + { + return true; + } + } + + // Check base types + for (Type t = candiateType; t != null && t != typeof(object); t = t.BaseType) + { + if (!t.IsGenericType) + { + continue; + } + if (t.GetGenericTypeDefinition() != genericTypeDefinition) + { + continue; + } + + Type[] sourceTypeArguments = t.GetGenericArguments(); + if (AreAllGenericArgumentsCompatible(genericTypeParameters, targetTypeArguments, sourceTypeArguments)) + { + return true; + } + } + + return false; + } + + private static bool AreAllGenericArgumentsCompatible (Type[] genericTypeParameters, Type[] targetTypeArguments, Type[] sourceTypeArguments) + { + if (genericTypeParameters.Length != targetTypeArguments.Length) + { + return false; + } + if (sourceTypeArguments.Length != targetTypeArguments.Length) + { + return false; + } + + for (int i = 0; i < genericTypeParameters.Length; i++) + { + var variance = genericTypeParameters[i].GenericParameterAttributes & GenericParameterAttributes.VarianceMask; + + Type sourceTypeArgument = sourceTypeArguments[i]; + Type targetTypeArgument = targetTypeArguments[i]; + + if (variance == GenericParameterAttributes.Contravariant) + { + // NOTE: in T = source must be able to accept the target. + if (!sourceTypeArgument.IsAssignableFrom(targetTypeArgument)) + { + return false; + } + } + else if (variance == GenericParameterAttributes.Covariant) + { + // NOTE: out T = target must be able to accept the source. + if (!targetTypeArgument.IsAssignableFrom(sourceTypeArgument)) + { + return false; + } + } + else + { + // invariant + if (sourceTypeArgument != targetTypeArgument) + { + return false; + } + } + } + + return true; + } + } +} +#endif \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy/Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy/Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy.cs.meta new file mode 100644 index 0000000..919a676 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCompatibilityPolicy/Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 342868b30f7a5b442bd48e0ba125f8dc \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeSearchService.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeSearchService.cs new file mode 100644 index 0000000..2e85c40 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeSearchService.cs @@ -0,0 +1,26 @@ +namespace MackySoft.SerializeReferenceExtensions.Editor +{ + public static class TypeSearchService + { + + public static readonly IIntrinsicTypePolicy IntrinsicTypePolicy; + public static readonly ITypeCompatibilityPolicy TypeCompatibilityPolicy; + public static readonly ITypeCandiateProvider TypeCandiateProvider; + public static readonly TypeCandiateService TypeCandiateService; + + static TypeSearchService () + { + IntrinsicTypePolicy = DefaultIntrinsicTypePolicy.Instance; + +#if UNITY_2023_2_OR_NEWER + TypeCompatibilityPolicy = Unity_2023_2_OrNewer_GenericVarianceTypeCompatibilityPolicy.Instance; + TypeCandiateProvider = Unity_2023_2_OrNewer_TypeCandiateProvider.Instance; +#else + TypeCompatibilityPolicy = DefaultTypeCompatibilityPolicy.Instance; + TypeCandiateProvider = DefaultTypeCandiateProvider.Instance; +#endif + + TypeCandiateService = new TypeCandiateService(TypeCandiateProvider, IntrinsicTypePolicy, TypeCompatibilityPolicy); + } + } +} \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeSearchService.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeSearchService.cs.meta new file mode 100644 index 0000000..5645155 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeSearchService.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 119c8d609af9df044b30b7deb7cbe359 \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor.meta new file mode 100644 index 0000000..fa3e6a3 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a458ed0ab99313148aaf87cc2436cdb4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/DefaultIntrinsicTypePolicyTests.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/DefaultIntrinsicTypePolicyTests.cs new file mode 100644 index 0000000..6377b97 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/DefaultIntrinsicTypePolicyTests.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using MackySoft.SerializeReferenceExtensions.Editor; +using NUnit.Framework; + +namespace MackySoft.SerializeReferenceExtensions.Tests +{ + public sealed class DefaultIntrinsicTypePolicyTests + { + public static IEnumerable Cases () + { + yield return new TestCaseData(typeof(PublicSerializableClass), true).SetName("Intrinsic_PublicSerializableClass_OK"); + yield return new TestCaseData(typeof(SerializableStruct), true).SetName("Intrinsic_ValueTypeStruct_OK"); + yield return new TestCaseData(typeof(Outer.NestedPublicSerializableClass), true).SetName("Intrinsic_NestedPublic_OK"); + yield return new TestCaseData(Outer.NestedPrivateType, true).SetName("Intrinsic_NestedPrivate_OK"); + + yield return new TestCaseData(typeof(InternalSerializableClass), false).SetName("Intrinsic_Internal_NG"); + yield return new TestCaseData(typeof(AbstractSerializableClass), false).SetName("Intrinsic_Abstract_NG"); + yield return new TestCaseData(typeof(NonSerializableClass), false).SetName("Intrinsic_NoSerializableAttribute_NG"); + yield return new TestCaseData(typeof(HiddenSerializableClass), false).SetName("Intrinsic_HideInTypeMenu_NG"); + yield return new TestCaseData(typeof(UnityObjectDerived), false).SetName("Intrinsic_UnityObjectDerived_NG"); + + yield return new TestCaseData(typeof(GenericCandidate), false).SetName("Intrinsic_GenericClosed_NG"); + } + + [TestCaseSource(nameof(Cases))] + public void IsAllowed_MatchesExpected (Type type, bool expected) + { + bool actual = DefaultIntrinsicTypePolicy.Instance.IsAllowed(type); + Assert.That(actual, Is.EqualTo(expected), type.FullName); + } + } +} diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/DefaultIntrinsicTypePolicyTests.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/DefaultIntrinsicTypePolicyTests.cs.meta new file mode 100644 index 0000000..be1e87f --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/DefaultIntrinsicTypePolicyTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 497d6617d2c784d418845bc691d6344c \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/MackySoft.SerializeReferenceExtensions.Tests.asmdef b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef similarity index 80% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/MackySoft.SerializeReferenceExtensions.Tests.asmdef rename to Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef index 5367dc9..8205358 100644 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/MackySoft.SerializeReferenceExtensions.Tests.asmdef +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef @@ -1,6 +1,6 @@ { - "name": "Tests", - "rootNamespace": "", + "name": "MackySoft.SerializeReferenceExtensions.Tests.Editor", + "rootNamespace": "MackySoft.SerializeReferenceExtensions.Tests", "references": [ "UnityEngine.TestRunner", "UnityEditor.TestRunner", diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/MackySoft.SerializeReferenceExtensions.Tests.asmdef.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef.meta similarity index 100% rename from Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/MackySoft.SerializeReferenceExtensions.Tests.asmdef.meta rename to Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/MackySoft.SerializeReferenceExtensions.Tests.Editor.asmdef.meta diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_BaseTypeSelfTests.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_BaseTypeSelfTests.cs new file mode 100644 index 0000000..283bc5c --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_BaseTypeSelfTests.cs @@ -0,0 +1,20 @@ +using System.Linq; +using MackySoft.SerializeReferenceExtensions.Editor; +using NUnit.Framework; + +namespace MackySoft.SerializeReferenceExtensions.Tests +{ + [TestFixture] + public sealed class TypeCandidateService_BaseTypeSelfTests + { + [Test] + public void ConcreteBaseType_IsIncluded () + { + var set = TypeSearchService.TypeCandiateService.GetDisplayableTypes(typeof(ConcreteBaseType)).ToHashSet(); + + Assert.That(set, Does.Contain(typeof(ConcreteBaseType))); + Assert.That(set, Does.Contain(typeof(ConcreteDerivedType))); + Assert.That(set, !Does.Contain(typeof(ConcreteInternalDerivedType))); + } + } +} diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_BaseTypeSelfTests.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_BaseTypeSelfTests.cs.meta new file mode 100644 index 0000000..6b0421f --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_BaseTypeSelfTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3617b1f123decbc4f90ecc60be3bd12f \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_GenericVarianceTests.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_GenericVarianceTests.cs new file mode 100644 index 0000000..b5b71f5 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_GenericVarianceTests.cs @@ -0,0 +1,44 @@ +#if UNITY_2023_2_OR_NEWER +using System.Linq; +using MackySoft.SerializeReferenceExtensions.Editor; +using NUnit.Framework; + +namespace MackySoft.SerializeReferenceExtensions.Tests +{ + [TestFixture] + public sealed class TypeCandidateService_GenericVarianceTests + { + [Test] + public void Contravariance_IsSupported () + { + // baseType: IContravariance + var set = TypeSearchService.TypeCandiateService.GetDisplayableTypes(typeof(IContravariant)).ToHashSet(); + + Assert.That(set, Does.Contain(typeof(Contravariant_NetworkActor)), "Exact match should be included."); + Assert.That(set, Does.Contain(typeof(Contravariant_Actor)), "Contravariant match should be included."); + Assert.That(set, !Does.Contain(typeof(Contravariant_DerivedNetworkActor)), "Narrower type argument should be excluded."); + } + + [Test] + public void Covariance_IsSupported () + { + // baseType: ICovariant + var set = TypeSearchService.TypeCandiateService.GetDisplayableTypes(typeof(ICovariant)).ToHashSet(); + + Assert.That(set, Does.Contain(typeof(Covariant_Actor)), "Exact match should be included."); + Assert.That(set, Does.Contain(typeof(Covariant_NetworkActor)), "Covariant match should be included."); + Assert.That(set, !Does.Contain(typeof(Covariant_Object)), "Wider type argument should be excluded."); + } + + [Test] + public void Invariance_RemainsStrict () + { + // baseType: IInvariant + var set = TypeSearchService.TypeCandiateService.GetDisplayableTypes(typeof(IInvariant)).ToHashSet(); + + Assert.That(set, Does.Contain(typeof(Invariant_Actor))); + Assert.That(set, !Does.Contain(typeof(Invariant_NetworkActor))); + } + } +} +#endif \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_GenericVarianceTests.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_GenericVarianceTests.cs.meta new file mode 100644 index 0000000..2a652eb --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_GenericVarianceTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 43a0ca9780c90f147af2b5901b9b05d1 \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_NonGenericTests.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_NonGenericTests.cs new file mode 100644 index 0000000..d677fbb --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_NonGenericTests.cs @@ -0,0 +1,56 @@ +using System; +using System.CodeDom; +using System.Collections.Generic; +using System.Linq; +using MackySoft.SerializeReferenceExtensions.Editor; +using NUnit.Framework; + +namespace MackySoft.SerializeReferenceExtensions.Tests +{ + [TestFixture] + public sealed class TypeCandidateService_NonGenericTests + { + private HashSet candidates; + + [OneTimeSetUp] + public void OneTimeSetUp () + { + candidates = TypeSearchService.TypeCandiateService.GetDisplayableTypes(typeof(ITestBase)).ToHashSet(); + } + + public static IEnumerable Cases () + { + yield return new TestCaseData(typeof(PublicSerializableClass), true).SetName("Candidates_ITestBase_PublicSerializable_OK"); + yield return new TestCaseData(typeof(SerializableStruct), true).SetName("Candidates_ITestBase_ValueTypeStruct_OK"); + yield return new TestCaseData(typeof(Outer.NestedPublicSerializableClass), true).SetName("Candidates_ITestBase_NestedPublic_OK"); + yield return new TestCaseData(Outer.NestedPrivateType, true).SetName("Candidates_ITestBase_NestedPrivate_OK"); + + yield return new TestCaseData(typeof(InternalSerializableClass), false).SetName("Candidates_ITestBase_Internal_NG"); + yield return new TestCaseData(typeof(AbstractSerializableClass), false).SetName("Candidates_ITestBase_Abstract_NG"); + yield return new TestCaseData(typeof(NonSerializableClass), false).SetName("Candidates_ITestBase_NoSerializable_NG"); + yield return new TestCaseData(typeof(HiddenSerializableClass), false).SetName("Candidates_ITestBase_HideInTypeMenu_NG"); + yield return new TestCaseData(typeof(UnityObjectDerived), false).SetName("Candidates_ITestBase_UnityObjectDerived_NG"); + yield return new TestCaseData(typeof(GenericCandidate), false).SetName("Candidates_ITestBase_GenericClosed_NG"); + } + + [TestCaseSource(nameof(Cases))] + public void CandidatesContainment_IsExpected (Type type, bool expected) + { + if (expected) + { + Assert.That(candidates, Does.Contain(type), $"Expected to contain: {type.FullName}"); + } + else + { + Assert.That(candidates, !Does.Contain(type), $"Expected NOT to contain: {type.FullName}"); + } + } + + [Test] + public void Candidates_HaveNoDuplicates () + { + var list = TypeSearchService.TypeCandiateService.GetDisplayableTypes(typeof(ITestBase)).ToList(); + Assert.That(list.Count, Is.EqualTo(list.Distinct().Count())); + } + } +} diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_NonGenericTests.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_NonGenericTests.cs.meta new file mode 100644 index 0000000..0512649 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateService_NonGenericTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ea32323e9ba718a42839ac09aaf4a5b5 \ No newline at end of file diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateTestTypes.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateTestTypes.cs new file mode 100644 index 0000000..b3e1ce3 --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateTestTypes.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections; +using System.Reflection; +using UnityEngine; + +namespace MackySoft.SerializeReferenceExtensions.Tests +{ + + public interface ITestBase { } + + [Serializable] + public sealed class PublicSerializableClass : ITestBase { } + + [Serializable] + internal sealed class InternalSerializableClass : ITestBase { } + + [Serializable] + public abstract class AbstractSerializableClass : ITestBase { } + + public sealed class NonSerializableClass : ITestBase { } + + [Serializable, HideInTypeMenu] + public sealed class HiddenSerializableClass : ITestBase { } + + [Serializable] + public sealed class UnityObjectDerived : ScriptableObject, ITestBase { } + + public sealed class Outer + { + [Serializable] + public sealed class NestedPublicSerializableClass : ITestBase { } + + [Serializable] + private sealed class NestedPrivateSerializableClass : ITestBase { } + + public static Type NestedPrivateType => typeof(Outer).GetNestedType(nameof(NestedPrivateSerializableClass), BindingFlags.NonPublic); + } + + [Serializable] + public sealed class GenericCandidate : ITestBase { } + + [Serializable] + public struct SerializableStruct : ITestBase { } + + [Serializable] + public class ConcreteBaseType { } + + [Serializable] + public sealed class ConcreteDerivedType : ConcreteBaseType { } + + [Serializable] + internal sealed class ConcreteInternalDerivedType : ConcreteBaseType { } + + // ------------------------ + // Generic variance test types (2023.2+) + // ------------------------ + public interface IActor { } + public interface INetworkActor : IActor { } + public interface IDerivedNetworkActor : INetworkActor { } + + public interface IContravariant { } + public interface ICovariant { T Create (); } + public interface IInvariant { } + + [Serializable] + public sealed class Contravariant_Actor : IContravariant { } + + [Serializable] + public sealed class Contravariant_NetworkActor : IContravariant { } + + [Serializable] + public sealed class Contravariant_DerivedNetworkActor : IContravariant { } + + [Serializable] + public sealed class Covariant_Actor : ICovariant { + public IActor Create () => null; + } + + [Serializable] + public sealed class Covariant_NetworkActor : ICovariant { + public INetworkActor Create () => null; + } + + [Serializable] + public sealed class Covariant_Object : ICovariant { + public object Create () => null; + } + + [Serializable] + public sealed class Invariant_Actor : IInvariant { } + + [Serializable] + public sealed class Invariant_NetworkActor : IInvariant { } +} diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateTestTypes.cs.meta b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateTestTypes.cs.meta new file mode 100644 index 0000000..a73406d --- /dev/null +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Tests/Editor/TypeCandidateTestTypes.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: baba1bd524348144ab4131ff5ca8ed89 \ No newline at end of file diff --git a/STATUS.md b/STATUS.md index 5515079..98d8258 100644 --- a/STATUS.md +++ b/STATUS.md @@ -1,3 +1,4 @@ 2026/01/17 -- 2023.2+でなければ、Exampleを開いたときにSerielizeReferenceの共変性・反変性の警告が出るため注意 \ No newline at end of file +- 2023.2+でなければ、Exampleを開いたときにSerielizeReferenceの共変性・反変性の警告が出るため注意 +- 型検索のリファクタ完了。各型の表示を保証するためのテストを作成する \ No newline at end of file