diff --git a/Assets/Editor Toolbox/CHANGELOG.md b/Assets/Editor Toolbox/CHANGELOG.md index 3ac8be78..1f63ca20 100644 --- a/Assets/Editor Toolbox/CHANGELOG.md +++ b/Assets/Editor Toolbox/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.12.9 [27.01.2024] + +### Changed: +- Fix rare invalid SerializedProperty iterator when editing child properties +- Extend objects creation behaviour while using the ReferencePickerAttribute (possibility to create uninitialized objects) +- Minor UX improvements in the ScriptableObjectCreationWizard + ## 0.12.8 [29.12.2023] ### Changed: diff --git a/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertySelf/ReferencePickerAttributeDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertySelf/ReferencePickerAttributeDrawer.cs index f5b9d0ea..2343c7e1 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertySelf/ReferencePickerAttributeDrawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertySelf/ReferencePickerAttributeDrawer.cs @@ -16,13 +16,12 @@ public class ReferencePickerAttributeDrawer : ToolboxSelfPropertyDrawer @@ -50,7 +49,7 @@ private void CreateTypeProperty(Rect position, SerializedProperty property, Type { if (!property.serializedObject.isEditingMultipleObjects) { - UpdateTypeProperty(property, type); + UpdateTypeProperty(property, type, attribute); } else { @@ -60,7 +59,7 @@ private void CreateTypeProperty(Rect position, SerializedProperty property, Type using (var so = new SerializedObject(target)) { SerializedProperty sp = so.FindProperty(property.propertyPath); - UpdateTypeProperty(sp, type); + UpdateTypeProperty(sp, type, attribute); } } } @@ -72,9 +71,10 @@ private void CreateTypeProperty(Rect position, SerializedProperty property, Type }, currentType, parentType); } - private void UpdateTypeProperty(SerializedProperty property, Type referenceType) + private void UpdateTypeProperty(SerializedProperty property, Type targetType, ReferencePickerAttribute attribute) { - var obj = referenceType != null ? Activator.CreateInstance(referenceType) : null; + var forceUninitializedInstance = attribute.ForceUninitializedInstance; + var obj = ReflectionUtility.CreateInstance(targetType, forceUninitializedInstance); property.serializedObject.Update(); property.managedReferenceValue = obj; property.serializedObject.ApplyModifiedProperties(); @@ -105,7 +105,6 @@ private Rect PrepareTypePropertyPosition(bool hasLabel, in Rect labelPosition, i return position; } - protected override void OnGuiSafe(SerializedProperty property, GUIContent label, ReferencePickerAttribute attribute) { //NOTE: we want to close scope manually because ExitGUIException can interrupt drawing and SerializedProperties stack @@ -121,8 +120,8 @@ protected override void OnGuiSafe(SerializedProperty property, GUIContent label, var hasLabel = !string.IsNullOrEmpty(label.text); var position = PrepareTypePropertyPosition(hasLabel, in labelRect, in inputRect, isPropertyExpanded); - var parentType = GetParentType(property, attribute); - CreateTypeProperty(position, property, parentType); + var parentType = GetParentType(attribute, property); + CreateTypeProperty(property, parentType, attribute, position); if (isPropertyExpanded) { ToolboxEditorGui.DrawPropertyChildren(property); @@ -133,7 +132,6 @@ protected override void OnGuiSafe(SerializedProperty property, GUIContent label, } } - public override bool IsPropertyValid(SerializedProperty property) { return property.propertyType == SerializedPropertyType.ManagedReference; diff --git a/Assets/Editor Toolbox/Editor/ToolboxEditorGui.cs b/Assets/Editor Toolbox/Editor/ToolboxEditorGui.cs index c8f0aa5e..345af0b0 100644 --- a/Assets/Editor Toolbox/Editor/ToolboxEditorGui.cs +++ b/Assets/Editor Toolbox/Editor/ToolboxEditorGui.cs @@ -531,7 +531,12 @@ public static void DrawPropertyChildren(SerializedProperty property, Action /// Returns of the searched method within the Editor . /// @@ -96,5 +95,31 @@ internal static bool TryInvokeMethod(string methodName, SerializedObject seriali return true; } + + internal static object CreateInstance(Type targetType, bool forceUninitializedInstance) + { + if (targetType == null) + { + return null; + } + + if (forceUninitializedInstance) + { + return FormatterServices.GetUninitializedObject(targetType); + } + + if (targetType.IsValueType) + { + return Activator.CreateInstance(targetType); + } + + var defaultConstructor = targetType.GetConstructor(Type.EmptyTypes); + if (defaultConstructor != null) + { + return Activator.CreateInstance(targetType); + } + + return FormatterServices.GetUninitializedObject(targetType); + } } } \ No newline at end of file diff --git a/Assets/Editor Toolbox/Editor/Wizards/ScriptableObjectCreationWizard.cs b/Assets/Editor Toolbox/Editor/Wizards/ScriptableObjectCreationWizard.cs index 4d288fb0..9295e08b 100644 --- a/Assets/Editor Toolbox/Editor/Wizards/ScriptableObjectCreationWizard.cs +++ b/Assets/Editor Toolbox/Editor/Wizards/ScriptableObjectCreationWizard.cs @@ -9,6 +9,7 @@ namespace Toolbox.Editor.Wizards { using Toolbox.Editor.Internal; + using Editor = UnityEditor.Editor; /// /// Utility window responsible for creation of s. @@ -34,7 +35,7 @@ private class CreationData { private bool IsDefaultObjectValid() { - return DefaultObject != null && DefaultObject.GetType() == InstanceType; + return BlueprintObject != null && BlueprintObject.GetType() == InstanceType; } public void Validate() @@ -47,7 +48,7 @@ public void Validate() InstancesCount = Mathf.Max(InstancesCount, 1); if (!IsDefaultObjectValid()) { - DefaultObject = null; + BlueprintObject = null; } } @@ -59,7 +60,7 @@ public void Validate() public int InstancesCount { get; set; } = 1; [field: SerializeField, InLineEditor] [field: Tooltip("Will be used as a blueprint for all created ScriptableObjects.")] - public Object DefaultObject { get; set; } + public Object BlueprintObject { get; set; } } private static readonly TypeConstraintContext sharedConstraint = new TypeConstraintScriptableObject(); @@ -69,7 +70,13 @@ public void Validate() private readonly CreationData data = new CreationData(); private bool inspectDefaultObject; - private bool useSearchField = true; + private Editor blueprintObjectEditor; + + protected override void OnDestroy() + { + base.OnDestroy(); + DestroyImmediate(blueprintObjectEditor); + } [MenuItem("Assets/Create/Editor Toolbox/Wizards/ScriptableObject Creation Wizard", priority = 5)] internal static void Initialize() @@ -83,10 +90,8 @@ private void DrawSettingsPanel() { EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel); - useSearchField = EditorGUILayout.ToggleLeft("Use Search Field", useSearchField); - var rect = EditorGUILayout.GetControlRect(true); - typeField.OnGui(rect, useSearchField, OnTypeSelected, data.InstanceType); + typeField.OnGui(rect, true, OnTypeSelected, data.InstanceType); if (data.InstanceType == null) { return; @@ -97,7 +102,15 @@ private void DrawSettingsPanel() EditorGUI.BeginChangeCheck(); data.InstanceName = EditorGUILayout.TextField(Style.nameContent, data.InstanceName); data.InstancesCount = EditorGUILayout.IntField(Style.countContent, data.InstancesCount); - var assignedInstance = EditorGUILayout.ObjectField(Style.objectContent, data.DefaultObject, data.InstanceType, false); + + EditorGUI.BeginChangeCheck(); + var assignedInstance = EditorGUILayout.ObjectField(Style.objectContent, data.BlueprintObject, data.InstanceType, false); + data.BlueprintObject = assignedInstance; + if (EditorGUI.EndChangeCheck()) + { + UpdateBlueprintObjectEditor(); + } + if (assignedInstance != null) { inspectDefaultObject = GUILayout.Toggle(inspectDefaultObject, @@ -112,11 +125,11 @@ private void DrawSettingsPanel() { using (new EditorGUILayout.VerticalScope(Style.backgroundStyle)) { - ToolboxEditorGui.DrawObjectProperties(assignedInstance); + blueprintObjectEditor.OnInspectorGUI(); } } - data.DefaultObject = assignedInstance; + if (EditorGUI.EndChangeCheck()) { OnWizardUpdate(); @@ -147,7 +160,7 @@ private void CreateObjects(CreationData data) var instancesCount = data.InstancesCount; for (var i = 0; i < instancesCount; i++) { - var instance = CreateObject(data.InstanceType, data.DefaultObject); + var instance = CreateObject(data.InstanceType, data.BlueprintObject); CreateAsset(instance, data.InstanceName, assetPath, i); } @@ -181,6 +194,25 @@ private void OnTypeSelected(Type type) } } + private void UpdateBlueprintObjectEditor() + { + DestroyImmediate(blueprintObjectEditor); + blueprintObjectEditor = null; + + var targetObject = data.BlueprintObject; + if (targetObject == null) + { + return; + } + + blueprintObjectEditor = Editor.CreateEditor(targetObject); + blueprintObjectEditor.hideFlags = HideFlags.HideAndDontSave; + if (blueprintObjectEditor is ToolboxEditor toolboxEditor) + { + toolboxEditor.IgnoreProperty(PropertyUtility.Defaults.scriptPropertyName); + } + } + private static string GetActiveFolderPath() { var projectWindowUtilType = typeof(ProjectWindowUtil); @@ -219,8 +251,8 @@ private static class Style internal static readonly GUIStyle foldoutStyle; internal static readonly GUIContent nameContent = new GUIContent("Instance Name"); - internal static readonly GUIContent countContent = new GUIContent("Instances To Create", "Indicates how many instances will be created."); - internal static readonly GUIContent objectContent = new GUIContent("Default Object", "Will be used as a blueprint for all created ScriptableObjects."); + internal static readonly GUIContent countContent = new GUIContent("Instances Count", "Indicates how many instances will be created."); + internal static readonly GUIContent objectContent = new GUIContent("Blueprint Object", "Will be used as a blueprint for all created ScriptableObjects."); internal static readonly GUIContent foldoutContent = new GUIContent("Inspect", "Show/Hide Properties"); internal static readonly GUILayoutOption[] foldoutOptions = new GUILayoutOption[] diff --git a/Assets/Editor Toolbox/Editor/Wizards/ToolboxWizard.cs b/Assets/Editor Toolbox/Editor/Wizards/ToolboxWizard.cs index 138a42e2..ca125666 100644 --- a/Assets/Editor Toolbox/Editor/Wizards/ToolboxWizard.cs +++ b/Assets/Editor Toolbox/Editor/Wizards/ToolboxWizard.cs @@ -11,7 +11,7 @@ public class ToolboxWizard : EditorWindow private Vector2 scrollPosition; - private void OnEnable() + protected virtual void OnEnable() { if (targetEditor != null) { @@ -19,7 +19,7 @@ private void OnEnable() } } - private void OnDestroy() + protected virtual void OnDestroy() { DestroyImmediate(targetEditor); } diff --git a/Assets/Editor Toolbox/README.md b/Assets/Editor Toolbox/README.md index f79899ec..a6648408 100644 --- a/Assets/Editor Toolbox/README.md +++ b/Assets/Editor Toolbox/README.md @@ -631,13 +631,20 @@ public int var1; #### SerializeReference (ReferencePicker) You can draw properties marked with the **[SerializeReference]** attribute with an additional type picker that allows you to manipulate what managed type will be serialized. - +Depending on the picked type we have different object creation strategies: +- `Activator.CreateInstance(targetType)` (default constructor will be called and all readonly members will be initialized) + - Target type has default constructor + - Target type is a value type +- `FormatterServices.GetUninitializedObject(targetType)` (object will be uninitialized) + - Target type has one or more constructors with arguments + - `ForceUninitializedInstance` property is set to true + To prevent issues after renaming types use `UnityEngine.Scripting.APIUpdating.MovedFromAttribute`. ```csharp [SerializeReference, ReferencePicker(TypeGrouping = TypeGrouping.ByFlatName)] public Interface1 var1; -[SerializeReference, ReferencePicker] +[SerializeReference, ReferencePicker(ForceUninitializedInstance = true)] public Interface1 var1; [SerializeReference, ReferencePicker(ParentType = typeof(ClassWithInterface2)] public ClassWithInterfaceBase var2; diff --git a/Assets/Editor Toolbox/Runtime/Attributes/Toolbox/PropertySelfAttributes/ReferencePickerAttribute.cs b/Assets/Editor Toolbox/Runtime/Attributes/Toolbox/PropertySelfAttributes/ReferencePickerAttribute.cs index c76811dc..5640cc70 100644 --- a/Assets/Editor Toolbox/Runtime/Attributes/Toolbox/PropertySelfAttributes/ReferencePickerAttribute.cs +++ b/Assets/Editor Toolbox/Runtime/Attributes/Toolbox/PropertySelfAttributes/ReferencePickerAttribute.cs @@ -31,6 +31,8 @@ public ReferencePickerAttribute(Type parentType, TypeGrouping typeGrouping) /// Defaults to unless explicitly specified. /// public TypeGrouping TypeGrouping { get; set; } = TypeGrouping.None; + + public bool ForceUninitializedInstance { get; set; } } } #endif \ No newline at end of file diff --git a/Assets/Editor Toolbox/package.json b/Assets/Editor Toolbox/package.json index 25d8c358..e40428ad 100644 --- a/Assets/Editor Toolbox/package.json +++ b/Assets/Editor Toolbox/package.json @@ -1,7 +1,7 @@ { "name": "com.browar.editor-toolbox", "displayName": "Editor Toolbox", - "version": "0.12.8", + "version": "0.12.9", "unity": "2018.1", "description": "Tools, custom attributes, drawers, hierarchy overlay, and other extensions for the Unity Editor.", "keywords": [ diff --git a/Assets/Examples/Scripts/SampleBehaviour6.cs b/Assets/Examples/Scripts/SampleBehaviour6.cs index f4b919d9..9248e273 100644 --- a/Assets/Examples/Scripts/SampleBehaviour6.cs +++ b/Assets/Examples/Scripts/SampleBehaviour6.cs @@ -9,7 +9,7 @@ public class SampleBehaviour6 : MonoBehaviour #if UNITY_2019_3_OR_NEWER [SerializeReference, ReferencePicker(TypeGrouping = TypeGrouping.ByFlatName)] public Interface1 var1; - [SerializeReference, ReferencePicker] + [SerializeReference, ReferencePicker(ForceUninitializedInstance = true)] public ClassWithInterfaceBase var2; [SerializeReference, ReferencePicker(ParentType = typeof(ClassWithInterface2))] public ClassWithInterfaceBase var3; @@ -25,6 +25,12 @@ public struct Struct : Interface1 { public bool var1; public bool var2; + + public Struct(bool var1, bool var2) + { + this.var1 = var1; + this.var2 = var2; + } } public abstract class ClassWithInterfaceBase : Interface1 @@ -54,6 +60,11 @@ public class ClassWithInterface2 : ClassWithInterfaceBase public class ClassWithInterface3 : ClassWithInterfaceBase { public int var1; + + public ClassWithInterface3(int var1) + { + this.var1 = var1; + } } [Serializable] diff --git a/README.md b/README.md index f79899ec..a6648408 100644 --- a/README.md +++ b/README.md @@ -631,13 +631,20 @@ public int var1; #### SerializeReference (ReferencePicker) You can draw properties marked with the **[SerializeReference]** attribute with an additional type picker that allows you to manipulate what managed type will be serialized. - +Depending on the picked type we have different object creation strategies: +- `Activator.CreateInstance(targetType)` (default constructor will be called and all readonly members will be initialized) + - Target type has default constructor + - Target type is a value type +- `FormatterServices.GetUninitializedObject(targetType)` (object will be uninitialized) + - Target type has one or more constructors with arguments + - `ForceUninitializedInstance` property is set to true + To prevent issues after renaming types use `UnityEngine.Scripting.APIUpdating.MovedFromAttribute`. ```csharp [SerializeReference, ReferencePicker(TypeGrouping = TypeGrouping.ByFlatName)] public Interface1 var1; -[SerializeReference, ReferencePicker] +[SerializeReference, ReferencePicker(ForceUninitializedInstance = true)] public Interface1 var1; [SerializeReference, ReferencePicker(ParentType = typeof(ClassWithInterface2)] public ClassWithInterfaceBase var2;