From a4f4a3cebc64dc2c50b33df8c5bbc373f275d598 Mon Sep 17 00:00:00 2001 From: Logyrac Date: Mon, 13 Jul 2026 01:38:40 -0400 Subject: [PATCH 1/2] Fix pasting field on null current instance, and added checks for pasting compatibility. --- .../ManagedReferenceContextualPropertyMenu.cs | 124 ++++++++++++++++-- .../Editor/TypeSearch/TypeCandiateService.cs | 9 +- 2 files changed, 122 insertions(+), 11 deletions(-) diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenu.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenu.cs index d7547f8..d6fc603 100644 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenu.cs +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenu.cs @@ -11,10 +11,20 @@ public static class ManagedReferenceContextualPropertyMenu private const string CopiedPropertyPathKey = "SerializeReferenceExtensions.CopiedPropertyPath"; private const string ClipboardKey = "SerializeReferenceExtensions.CopyAndPasteProperty"; + private const string CopiedPropertyType = "SerializeReferenceExtensions.CopiedPropertyType"; private static readonly GUIContent PasteContent = new GUIContent("Paste Property"); private static readonly GUIContent NewInstanceContent = new GUIContent("New Instance"); private static readonly GUIContent ResetAndNewInstanceContent = new GUIContent("Reset and New Instance"); + + private enum ValuePasteState + { + Unavailable, // No copied value in clipboard + Allowed, // No issues detected + WillChangeType, // Pasting will overwrite type currently assigned to property + IncompatibleType, // The copied type isn't compatible with the target property + TypeNotFound // The copied data referenced a type that cannot be found or is invalid + } [InitializeOnLoadMethod] private static void Initialize () @@ -22,6 +32,50 @@ private static void Initialize () EditorApplication.contextualPropertyMenu += OnContextualPropertyMenu; } + private static ValuePasteState GetValuePasteState (SerializedProperty property) + { + string copiedPropertyPath = SessionState.GetString(CopiedPropertyPathKey, string.Empty); + + if (string.IsNullOrEmpty(copiedPropertyPath)) + { + return ValuePasteState.Unavailable; + } + + string copiedValueTypeName = SessionState.GetString(CopiedPropertyType, string.Empty); + Type copiedValueType = Type.GetType(copiedValueTypeName); + + if (copiedValueType == null) + { + return ValuePasteState.TypeNotFound; + } + + object currentPropertyValue = property.managedReferenceValue; + + if (currentPropertyValue == null) + { + return IsValidTypeFor(property, copiedValueType) + ? ValuePasteState.Allowed + : ValuePasteState.IncompatibleType; + } + + if (copiedValueType == currentPropertyValue.GetType()) + { + return ValuePasteState.Allowed; + } + + return IsValidTypeFor(property, copiedValueType) + ? ValuePasteState.WillChangeType + : ValuePasteState.IncompatibleType; + } + + private static bool IsValidTypeFor (SerializedProperty property, Type candidateType) + { + Type baseType = ManagedReferenceUtility.GetType(property.managedReferenceFieldTypename); + if (baseType == null || candidateType == null) return false; + + return TypeSearchService.TypeCandiateService.IsCandidateQualified(baseType, candidateType); + } + private static void OnContextualPropertyMenu (GenericMenu menu, SerializedProperty property) { if (property.propertyType == SerializedPropertyType.ManagedReference) @@ -32,14 +86,36 @@ private static void OnContextualPropertyMenu (GenericMenu menu, SerializedProper menu.AddItem(new GUIContent($"Copy \"{property.propertyPath}\" property"), false, Copy, clonedProperty); + string typeName = SessionState.GetString(CopiedPropertyType, string.Empty); + string copiedPropertyPath = SessionState.GetString(CopiedPropertyPathKey, string.Empty); - if (!string.IsNullOrEmpty(copiedPropertyPath)) - { - menu.AddItem(new GUIContent($"Paste \"{copiedPropertyPath}\" property"), false, Paste, clonedProperty); - } - else + + Type targetType = Type.GetType(typeName); + + switch (GetValuePasteState(clonedProperty)) { - menu.AddDisabledItem(PasteContent); + case ValuePasteState.Allowed: + menu.AddItem(new GUIContent($"Paste \"{copiedPropertyPath}\" property"), false, Paste, clonedProperty); + break; + case ValuePasteState.WillChangeType: + menu.AddItem( + new GUIContent( + $"Paste \"{copiedPropertyPath}\" property (⚠️ Will overwrite type with {targetType?.Name})" + ), + false, + Paste, + clonedProperty + ); + break; + case ValuePasteState.IncompatibleType: + menu.AddDisabledItem(new GUIContent($"Paste \"{copiedPropertyPath}\" property (❌ Incompatible type {targetType?.FullName})")); + break; + case ValuePasteState.TypeNotFound: + menu.AddDisabledItem(new GUIContent($"Paste \"{copiedPropertyPath}\" property (❌ Invalid type)")); + break; + default: + menu.AddDisabledItem(PasteContent); + break; } menu.AddSeparator(""); @@ -64,19 +140,49 @@ private static void Copy (object customData) string json = JsonUtility.ToJson(property.managedReferenceValue); SessionState.SetString(CopiedPropertyPathKey, property.propertyPath); SessionState.SetString(ClipboardKey, json); + if (property.managedReferenceValue == null) + { + SessionState.SetString(CopiedPropertyType, string.Empty); + } + else + { + string typeName = property.managedReferenceValue.GetType().AssemblyQualifiedName; + SessionState.SetString(CopiedPropertyType, typeName); + } } private static void Paste (object customData) { SerializedProperty property = (SerializedProperty)customData; string json = SessionState.GetString(ClipboardKey, string.Empty); - if (string.IsNullOrEmpty(json)) + string typeName = SessionState.GetString(CopiedPropertyType, string.Empty); + + if (string.IsNullOrEmpty(json) || string.IsNullOrEmpty(typeName)) { return; } - Undo.RecordObject(property.serializedObject.targetObject, "Paste Property"); - JsonUtility.FromJsonOverwrite(json, property.managedReferenceValue); + + Type targetType = Type.GetType(typeName); + + if (targetType == null) + { + Debug.LogError($"Paste Failed: Could not find type {typeName}"); + return; + } + + object currentTargetValue = property.managedReferenceValue; + + if (currentTargetValue == null || targetType != currentTargetValue.GetType()) + { + object newInstance = Activator.CreateInstance(targetType); + JsonUtility.FromJsonOverwrite(json, newInstance); + property.managedReferenceValue = newInstance; + } + else + { + JsonUtility.FromJsonOverwrite(json, property.managedReferenceValue); + } property.serializedObject.ApplyModifiedProperties(); } diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateService.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateService.cs index 31c63d6..cf7f449 100644 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateService.cs +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/TypeSearch/TypeCandiateService.cs @@ -33,13 +33,18 @@ public IReadOnlyList GetDisplayableTypes (Type baseType) var candiateTypes = typeCandiateProvider.GetTypeCandidates(baseType); var result = candiateTypes - .Where(intrinsicTypePolicy.IsAllowed) - .Where(t => typeCompatibilityPolicy.IsCompatible(baseType, t)) + .Where(t => IsCandidateQualified(baseType, t)) .Distinct() .ToArray(); typeCache.Add(baseType, result); return result; } + + public bool IsCandidateQualified (Type baseType, Type candidateType) + { + return intrinsicTypePolicy.IsAllowed(candidateType) + && typeCompatibilityPolicy.IsCompatible(baseType, candidateType); + } } } \ No newline at end of file From 96565b4f99440d9f6a21b38c492b4990bfe1eed9 Mon Sep 17 00:00:00 2001 From: Logyrac Date: Tue, 14 Jul 2026 01:09:29 -0400 Subject: [PATCH 2/2] Add support for copy-pasting null references --- .../ManagedReferenceContextualPropertyMenu.cs | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenu.cs b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenu.cs index d6fc603..2ce324a 100644 --- a/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenu.cs +++ b/Assets/MackySoft/MackySoft.SerializeReferenceExtensions/Editor/ManagedReferenceContextualPropertyMenu.cs @@ -23,7 +23,8 @@ private enum ValuePasteState Allowed, // No issues detected WillChangeType, // Pasting will overwrite type currently assigned to property IncompatibleType, // The copied type isn't compatible with the target property - TypeNotFound // The copied data referenced a type that cannot be found or is invalid + TypeNotFound, // The copied data referenced a type that cannot be found or is invalid + WillNullify // The copied value is null, will nullify/delete the property value } [InitializeOnLoadMethod] @@ -44,12 +45,16 @@ private static ValuePasteState GetValuePasteState (SerializedProperty property) string copiedValueTypeName = SessionState.GetString(CopiedPropertyType, string.Empty); Type copiedValueType = Type.GetType(copiedValueTypeName); + object currentPropertyValue = property.managedReferenceValue; + if (copiedValueType == null) { - return ValuePasteState.TypeNotFound; + if (!string.IsNullOrEmpty(copiedValueTypeName)) return ValuePasteState.TypeNotFound; + + return currentPropertyValue == null + ? ValuePasteState.Unavailable + : ValuePasteState.WillNullify; } - - object currentPropertyValue = property.managedReferenceValue; if (currentPropertyValue == null) { @@ -71,8 +76,8 @@ private static ValuePasteState GetValuePasteState (SerializedProperty property) private static bool IsValidTypeFor (SerializedProperty property, Type candidateType) { Type baseType = ManagedReferenceUtility.GetType(property.managedReferenceFieldTypename); - if (baseType == null || candidateType == null) return false; - + if (baseType == null) return false; + return TypeSearchService.TypeCandiateService.IsCandidateQualified(baseType, candidateType); } @@ -97,7 +102,7 @@ private static void OnContextualPropertyMenu (GenericMenu menu, SerializedProper case ValuePasteState.Allowed: menu.AddItem(new GUIContent($"Paste \"{copiedPropertyPath}\" property"), false, Paste, clonedProperty); break; - case ValuePasteState.WillChangeType: + case ValuePasteState.WillChangeType: menu.AddItem( new GUIContent( $"Paste \"{copiedPropertyPath}\" property (⚠️ Will overwrite type with {targetType?.Name})" @@ -107,6 +112,9 @@ private static void OnContextualPropertyMenu (GenericMenu menu, SerializedProper clonedProperty ); break; + case ValuePasteState.WillNullify: + menu.AddItem(new GUIContent($"Paste \"{copiedPropertyPath}\" property (⚠️ Will set to null)"), false, Paste, clonedProperty); + break; case ValuePasteState.IncompatibleType: menu.AddDisabledItem(new GUIContent($"Paste \"{copiedPropertyPath}\" property (❌ Incompatible type {targetType?.FullName})")); break; @@ -155,25 +163,32 @@ private static void Paste (object customData) { SerializedProperty property = (SerializedProperty)customData; string json = SessionState.GetString(ClipboardKey, string.Empty); + string typeName = SessionState.GetString(CopiedPropertyType, string.Empty); + Type targetType = Type.GetType(typeName); - if (string.IsNullOrEmpty(json) || string.IsNullOrEmpty(typeName)) + if (string.IsNullOrEmpty(json) && targetType != null) { return; } Undo.RecordObject(property.serializedObject.targetObject, "Paste Property"); - - Type targetType = Type.GetType(typeName); - - if (targetType == null) + + // targetType can be null under two conditions: + // 1) The type was actually null (empty typeName) + // 2) The type was not found (non-empty typename) + if (targetType == null && !string.IsNullOrEmpty(typeName)) { Debug.LogError($"Paste Failed: Could not find type {typeName}"); return; } object currentTargetValue = property.managedReferenceValue; - - if (currentTargetValue == null || targetType != currentTargetValue.GetType()) + + if (targetType == null) + { + property.managedReferenceValue = null; + } + else if (currentTargetValue == null || targetType != currentTargetValue.GetType()) { object newInstance = Activator.CreateInstance(targetType); JsonUtility.FromJsonOverwrite(json, newInstance);