diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 0313bea..d7a8bc3 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -5,7 +5,16 @@ on: branches: - main pull_request: {} - workflow_dispatch: {} + workflow_dispatch: + inputs: + unityVersion: + description: 'Editor version for a targeted run (empty runs the full matrix)' + type: string + required: false + testFilter: + description: 'Unity test filter for a targeted run (empty runs all tests)' + type: string + required: false permissions: contents: read @@ -47,9 +56,7 @@ jobs: strategy: fail-fast: false matrix: - unityVersion: - - 2021.3.45f2 - - 6000.6.6f1 + unityVersion: ${{ fromJSON(github.event_name == 'workflow_dispatch' && inputs.unityVersion != '' && format('["{0}"]', inputs.unityVersion) || '["2021.3.45f2","6000.6.6f1","7000.0.0a11"]') }} steps: - name: Checkout @@ -80,6 +87,7 @@ jobs: env: LIBGL_ALWAYS_SOFTWARE: '1' UNITY_EDITOR_VERSION: ${{ matrix.unityVersion }} + UNITY_TEST_FILTER: ${{ github.event_name == 'workflow_dispatch' && inputs.testFilter || '' }} run: | mkdir -p Logs xvfb-run --auto-servernum --server-args="-screen 0 1280x1024x24" \ @@ -89,11 +97,16 @@ jobs: window_manager_pid=$! trap 'kill "$window_manager_pid" 2>/dev/null || true' EXIT timeout 15 bash -c 'until xprop -root _NET_SUPPORTING_WM_CHECK | grep -q "window id"; do sleep 0.1; done' + test_arguments=() + if [[ -n "$UNITY_TEST_FILTER" ]]; then + test_arguments+=(--filter "$UNITY_TEST_FILTER") + fi unity --non-interactive test . \ --editor-version "$UNITY_EDITOR_VERSION" \ --mode EditMode \ --output Logs/TestResults/editmode.xml \ --timeout 1200 \ + "${test_arguments[@]}" \ -- -force-glcore -timestamps -logFile Logs/editmode.log DISPLAY_SESSION @@ -114,7 +127,7 @@ jobs: path: Logs/ verify: - name: Verification + name: ${{ github.event_name == 'workflow_dispatch' && (inputs.unityVersion != '' || inputs.testFilter != '') && 'Targeted verification' || 'Verification' }} needs: [changes, test] if: always() runs-on: ubuntu-22.04 diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenu.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenu.cs index ca8b811..ddc4559 100644 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenu.cs +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenu.cs @@ -215,7 +215,7 @@ private static bool CanPasteTypeAndValues (SerializedProperty property, Clipboar } // Unity uses '/' for nested types in its static field type name; reflection uses '+'. - Type fieldType = ManagedReferenceUtility.GetType(property.managedReferenceFieldTypename.Replace('/', '+')); + Type fieldType = ManagedReferenceUtility.GetType(property.managedReferenceFieldTypename); return fieldType != null && fieldType.IsAssignableFrom(copiedType); } diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceSelection.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceSelection.cs index b9ead75..27b2583 100644 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceSelection.cs +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceSelection.cs @@ -11,7 +11,7 @@ internal static bool SelectType (DeferredManagedReferenceTarget target, Type typ { for (int i = 0; i < properties.Length; i++) { - Type fieldType = ManagedReferenceUtility.GetType(properties[i].managedReferenceFieldTypename.Replace('/', '+')); + Type fieldType = ManagedReferenceUtility.GetType(properties[i].managedReferenceFieldTypename); if (!properties[i].editable || (type != null && (fieldType == null || !fieldType.IsAssignableFrom(type)))) { return false; diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceUtility.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceUtility.cs index b435e13..08a102a 100644 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceUtility.cs +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceUtility.cs @@ -80,7 +80,15 @@ public static Type GetType (string typeName) int splitIndex = typeName.IndexOf(' '); var assembly = Assembly.Load(typeName.Substring(0, splitIndex)); - return assembly.GetType(typeName.Substring(splitIndex + 1)); + string className = typeName.Substring(splitIndex + 1); + Type type = assembly.GetType(className); + // CoreCLR's serialized nested names use '/', while reflection uses '+'. + // Resolve them the same way as Unity's ScriptAttributeUtility. + if (type == null && className.IndexOf('/') >= 0) + { + type = assembly.GetType(className.Replace('/', '+')); + } + return type; } } diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorField.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorField.cs index f393838..b7c36ff 100644 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorField.cs +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorField.cs @@ -96,6 +96,7 @@ private void OnDetach (DetachFromPanelEvent evt) if (nativeHeader != null) { nativeHeader.style.display = DisplayStyle.None; + nativeHeader.PrefixSlot.style.display = DisplayStyle.None; } // A parent can be traversing its children during Detach. Release the // owned header after that traversal, unless this view is reattached. @@ -108,6 +109,7 @@ private void ReleaseHeader () if (nativeHeader != null && (panel == null || nativeHeader.Host != GetFirstAncestorOfType())) { hierarchy.Insert(0, header); + nativeHeader.PrefixSlot.RemoveFromHierarchy(); nativeHeader.RemoveFromHierarchy(); nativeHeader = null; } @@ -128,6 +130,7 @@ private void EnsureHeader () } if (nativeHeader?.Host != host) { + nativeHeader?.PrefixSlot.RemoveFromHierarchy(); nativeHeader?.RemoveFromHierarchy(); nativeHeader = new SubclassSelectorHeader(host, header, ShowTypePopup, HasCurrentMenuProperty); } @@ -144,11 +147,17 @@ private void EnsureHeader () nativeHeader.RemoveFromHierarchy(); container.hierarchy.Insert(container.hierarchy.IndexOf(this), nativeHeader); } - if (header.hierarchy.parent != nativeHeader) + if (nativeHeader.PrefixSlot.hierarchy.parent != container + || container.hierarchy.IndexOf(nativeHeader.PrefixSlot) != container.hierarchy.IndexOf(nativeHeader) - 1) + { + nativeHeader.PrefixSlot.RemoveFromHierarchy(); + container.hierarchy.Insert(container.hierarchy.IndexOf(nativeHeader), nativeHeader.PrefixSlot); + } + if (header.hierarchy.parent != nativeHeader.PrefixSlot) { // Native Add preserves Foldout as logical owner (including Simple // ListView's direct-child selectors) while declaring its prefix slot. - headerSlot = nativeHeader; + headerSlot = nativeHeader.PrefixSlot; try { Insert(0, header); @@ -405,7 +414,7 @@ private void RefreshDefaultBody () private void ShowTypePopup () { if (panel == null || serializedObject.targetObject == null || !enabledInHierarchy - || nativeHeader == null || !nativeHeader.TypeField.enabledInHierarchy) + || nativeHeader == null || !nativeHeader.enabledInHierarchy) { return; } @@ -420,7 +429,7 @@ private void ShowTypePopup () { return; } - Type baseType = ManagedReferenceUtility.GetType(fieldTypeName.Replace('/', '+')); + Type baseType = ManagedReferenceUtility.GetType(fieldTypeName); var types = TypeSearchService.TypeCandiateService.GetDisplayableTypes(baseType); if (typePopup == null) { @@ -430,7 +439,7 @@ private void ShowTypePopup () { typePopup.SetTypes(types); } - typePopup.ShowForSelection(nativeHeader.TypeField.Input.worldBound, item => SelectType(target, item.Type)); + typePopup.ShowForSelection(nativeHeader.Input.worldBound, item => SelectType(target, item.Type)); } private void SelectType (DeferredManagedReferenceTarget target, Type type) diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorHeader.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorHeader.cs index 0542915..2308bbe 100644 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorHeader.cs +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorHeader.cs @@ -9,7 +9,7 @@ namespace MackySoft.SerializeReferenceExtensions.Editor { // PopupField owns the line's flow and native input column. Foldout's actual // Toggle owns the prefix; neither control supplies measurements to the other. - internal sealed class SubclassSelectorHeader : VisualElement + internal sealed class SubclassSelectorHeader : SubclassSelectorTypeField { private static readonly Action UpdatePrefabStyle = BindPrefabStyle(); private static readonly Action UpdateInspectorStyle = BindInspectorStyle(); @@ -18,23 +18,24 @@ internal sealed class SubclassSelectorHeader : VisualElement private readonly VisualElement checkmark; internal PropertyField Host { get; } - internal SubclassSelectorTypeField TypeField { get; } + internal VisualElement PrefixSlot { get; } internal SubclassSelectorHeader (PropertyField host, Toggle toggle, Action showPopup, Func hasCurrentProperty) + : base(showPopup) { Host = host; this.toggle = toggle; + // Keep the native Foldout prefix outside PopupField's own margins. + // Its absolute Toggle consumes no flow height; the PopupField remains + // a direct child of PropertyField for Unity's list-row selectors. + PrefixSlot = new VisualElement { pickingMode = PickingMode.Ignore }; toggle.showMixedValue = false; checkmark = toggle.Q(className: Toggle.checkmarkUssClassName); - pickingMode = PickingMode.Ignore; - style.justifyContent = Justify.Center; - TypeField = new SubclassSelectorTypeField(showPopup); - TypeField.SetValueWithoutNotify(TypeMenuUtility.NullDisplayName); + SetValueWithoutNotify(TypeMenuUtility.NullDisplayName); // The popup's real label region still participates in native alignment. // The Foldout Toggle renders the visible prefix, including its arrow. - TypeField.labelElement.style.visibility = Visibility.Hidden; - TypeField.Input.AddManipulator(new FoldoutPropertyMenuManipulator(toggle, hasCurrentProperty)); - Add(TypeField); + labelElement.style.visibility = Visibility.Hidden; + Input.AddManipulator(new FoldoutPropertyMenuManipulator(toggle, hasCurrentProperty)); toggle.style.position = Position.Absolute; toggle.style.left = 0; toggle.style.right = 0; @@ -45,24 +46,26 @@ internal SubclassSelectorHeader (PropertyField host, Toggle toggle, Action showP internal void Refresh (SerializedProperty property, string label, bool mixedTypes, bool enabled, string tooltip) { - TypeField.label = label; - TypeField.tooltip = tooltip; + PrefixSlot.style.display = style.display; + PrefixSlot.visible = visible; + this.label = label; + this.tooltip = tooltip; toggle.tooltip = tooltip; - TypeField.SetEnabled(enabled); + SetEnabled(enabled); toggle.SetEnabled(enabled); // Foldout's bool is expansion state. Mixed serialized child values must // not replace its label or checkmark with Toggle's mixed-value content. toggle.showMixedValue = false; checkmark.style.visibility = mixedTypes || string.IsNullOrEmpty(property.managedReferenceFullTypename) ? Visibility.Hidden : Visibility.Visible; - bool wasMixed = TypeField.showMixedValue; - TypeField.showMixedValue = mixedTypes; + bool wasMixed = showMixedValue; + showMixedValue = mixedTypes; string displayName = TypeMenuUtility.GetTypeDisplayName(property.managedReferenceValue?.GetType()); // Clearing native mixed presentation does not restore its text. Recent // PopupFields retain the value, so the transition also needs a refresh. - if (!mixedTypes && (wasMixed || TypeField.value != displayName)) + if (!mixedTypes && (wasMixed || value != displayName)) { - TypeField.SetValueWithoutNotify(displayName); + SetValueWithoutNotify(displayName); } // Older bindings discover a Foldout header with a physical Q. // This header is still the bound Toggle, but now occupies the prefix slot. diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorTypeField.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorTypeField.cs index 1af2d69..61390b0 100644 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorTypeField.cs +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/SubclassSelectorTypeField.cs @@ -6,7 +6,7 @@ namespace MackySoft.SerializeReferenceExtensions.Editor { // Keep the native PopupField hierarchy and theme; replace only menu activation. - internal sealed class SubclassSelectorTypeField : PopupField + internal class SubclassSelectorTypeField : PopupField { private readonly Action showPopup; 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 index 4ed3c3c..136f5f4 100644 --- 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 @@ -73,7 +73,7 @@ private IEnumerable GetTypesWithGeneric (Type baseType) private static IEnumerable EnumerateAllTypesSafely () { - foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + foreach (var asm in GetLoadedAssemblies()) { Type[] types; try @@ -99,6 +99,16 @@ private static IEnumerable EnumerateAllTypesSafely () } } } + +#if UNITY_7000_0_OR_NEWER + // CoreCLR can retain assemblies from unloading code contexts. Unity's + // assembly list excludes them so candidates belong to the current code. + private static IEnumerable GetLoadedAssemblies () + => UnityEngine.Assemblies.CurrentAssemblies.GetLoadedAssemblies(); +#else + private static IEnumerable GetLoadedAssemblies () + => AppDomain.CurrentDomain.GetAssemblies(); +#endif } } #endif