Texture2D in ScriptableObject’s Property drawer experiences serious lag

I have created a simple test scriptable object (edit: tests show same behavior with custom serializable objects, that are NOT ScriptableObject derived):

public class TestScriptableObj : ScriptableObject {
    public string title;
    public Texture2D insignia;
}

And used it in a simple MonoBehavior:

public class Monotest : MonoBehaviour {
  
    public TestScriptableObj tester;

    void Reset () {
        tester = TestScriptableObj.CreateInstance<TestScriptableObj>();
        tester.title = "testTitle2";
    }
}

I also created a property drawer for it. In order to make the insignia show the texture, like on a material inspector, I was unable to use EditorGUI.PropertyField, and needed to use EditorGUI.ObjectField, and specify the type as Texture2D.

[CustomPropertyDrawer(typeof(TestScriptableObj))]
public class TestScriptableObjectPropertyDrawer :PropertyDrawer{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        
        if (property.objectReferenceValue == null)
        {
            EditorGUI.HelpBox(position, "Null object reference ", MessageType.Error);
            return;
        }
        EditorGUI.BeginProperty(position, label, property);
        
        //since TestScriptableObj is a ScriptableObject derivation (a Unity class)- we need to do this backasswards stuff, and get the object from the property:  FindPropertyRelative will return null
        SerializedObject propObj = new SerializedObject(property.objectReferenceValue);
        SerializedProperty title = propObj.FindProperty("title");
        SerializedProperty insignia = propObj.FindProperty("insignia");

        position.height = EditorGUIUtility.singleLineHeight;        position.width -= 80;
        EditorGUI.PropertyField(position, title, label);

        position.x = position.xMax;        position.width = 80;        position.height = 80;
        insignia.objectReferenceValue = EditorGUI.ObjectField(position, GUIContent.none, insignia.objectReferenceValue, typeof(Texture2D), true);
        
        propObj.ApplyModifiedProperties();

        EditorGUI.EndProperty();
    }
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return 80f;
    }
}

This works: kind-of. The displayed texture consistently fails to update properly. When it does eventual update, the time it takes to do so, is inconsistent, and seems to depend on what I do with the mouse. Before it updates, there is NO texture shown- just the field background.

87833-blanktexture-field.png
87834-filledtexture-field.png
What is causing the delayed update? Or, does nobody else experience these delays?

Edit/upadte:

NO!! Not this solution again!!
New main question is: What the heck? Surely users of my ScriptableObject asset are not required to specify a do-nothing-custom-editor for their classes that use it, do they?
Adding the following do-nothing script resolves the issue:

 using UnityEditor;
 
 [CustomEditor(typeof(Monotest))]
 public class MonoTestEditor : Editor {}

This is a bug as a consequence of an implementation detail in Unity.

What is happening here is that Unity internally has a concept of what are called optimized GUI blocks in the Inspector. The idea is that the Editor tries to avoid redrawing stuff unnecessarily unless the user actually changes/interacts with something. This optimization helps tremendously with things like big arrays.

Whenever users define their own custom Editors, this code path is effectively ignored, because we don’t want to make assumptions about what might need to be updated in the user’s Editor.

Right now, textures drawn with GUI.DrawTexture (which is what this thumbnail uses internally) do not properly register themselves with the optimized GUI block. (The same goes for some things like Handles.) I’m looking into it, but it’s unfortunately not a trivial fix. On the plus side, 2017.3 adds a method to PropertyDrawer, CanCacheInspectorGUI(), which you can override to disable the optimized path for individual property drawers. I confirmed that it fixes the problem in this case.

This is likely due to the fact that the GUI does not repaint every “frame” like you would in a game. The editor GUI only updates and repaints as it needs to in response to various events. So what I think is happening here is that you’re data is updating immediately, then when you move your mouse over the inspector, or some other event occurs that causes the GUI to update, you then see the inspector draw the new data, with the perceived delay having occurred.

However, you can get around this. I’ve not personally needed this yet for any of my drawers so I cant give exact code solution, however there is a post that has a number of suggested examples that appear to have success for various people on the post. So I’d suggest giving one of those a try.

I’m not sure what might cause your updating problem, but it might still be related to the missing layout event. Maybe somethings else. Usually Unity will automatically repaint the inspector when an inspected object has changed. Though the object you are changing is not really “inspected”. You can try to manually repaint the inspector when the value has changed. Unfortunately only custom Inspectors can request a redraw.

The Repaint method of the Editor class just calls the static InspectorWindow.RepaintAllInspectors(); method. Unfortunately this class and method are internal. So there are two ways to invoke it manually:

  • using reflection
  • using a temp “Editor” instance

This might work:

if(propObj.ApplyModifiedProperties())
{
    var tmp = ScriptableObject.Createinstance<Editor>();
    tmp.Repaint();
    DestroyImmediate(tmp);
}

How exactly do you assign your texture? Do you use the object selector or do you use drag&drop? Is there a difference between the two ways?

You also might want to add a propObj.Update(); after you create the serialized object, just in case.