Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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" \
Expand All @@ -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

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -108,6 +109,7 @@ private void ReleaseHeader ()
if (nativeHeader != null && (panel == null || nativeHeader.Host != GetFirstAncestorOfType<PropertyField>()))
{
hierarchy.Insert(0, header);
nativeHeader.PrefixSlot.RemoveFromHierarchy();
nativeHeader.RemoveFromHierarchy();
nativeHeader = null;
}
Expand All @@ -128,6 +130,7 @@ private void EnsureHeader ()
}
if (nativeHeader?.Host != host)
{
nativeHeader?.PrefixSlot.RemoveFromHierarchy();
nativeHeader?.RemoveFromHierarchy();
nativeHeader = new SubclassSelectorHeader(host, header, ShowTypePopup, HasCurrentMenuProperty);
}
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand All @@ -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)
{
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<VisualElement, SerializedProperty> UpdatePrefabStyle = BindPrefabStyle();
private static readonly Action<VisualElement> UpdateInspectorStyle = BindInspectorStyle();
Expand All @@ -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<bool> 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;
Expand All @@ -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<Toggle>.
// This header is still the bound Toggle, but now occupies the prefix slot.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace MackySoft.SerializeReferenceExtensions.Editor
{
// Keep the native PopupField hierarchy and theme; replace only menu activation.
internal sealed class SubclassSelectorTypeField : PopupField<string>
internal class SubclassSelectorTypeField : PopupField<string>
{
private readonly Action showPopup;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private IEnumerable<Type> GetTypesWithGeneric (Type baseType)

private static IEnumerable<Type> EnumerateAllTypesSafely ()
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
foreach (var asm in GetLoadedAssemblies())
{
Type[] types;
try
Expand All @@ -99,6 +99,16 @@ private static IEnumerable<Type> 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<Assembly> GetLoadedAssemblies ()
=> UnityEngine.Assemblies.CurrentAssemblies.GetLoadedAssemblies();
#else
private static IEnumerable<Assembly> GetLoadedAssemblies ()
=> AppDomain.CurrentDomain.GetAssemblies();
#endif
}
}
#endif