Unexplained GUILayout: Mismatched issue, is it a Unity bug or a miss understanding?

1084600–40629–$BugTest.unitypackage (2.29 KB)

I’ve attached a stripped down repro project that I’m trying to understand why it is an issue.

In this case I’m getting “ArgumentException: GUILayout: Mismatched LayoutGroup.mouseDown” and it is tied to having a HorizontalLayout. In my real project code not matter where I move it around I get random errors related to Mismatched LayoutGroup.

In my attached scenario, I have a game object that has a script attached that I want to load some data from the web. Here I’m just loading google.com for the sample. I then have a custom inspector that I want to use for debugging so I can force a load at any point I desire. So I keep a handle around so I know the point that I trigger it in the inspector and that I can change the inspector state once it has loaded.

Somehow all these problems are seeming related to that code that that is awaiting the game object to change state on an object the inspector holds a reference to.

TestInspector.cs

using UnityEngine;
using UnityEditor;
using System.Collections;

[CustomEditor(typeof(Test))]
public class TestInspector : Editor 
{
	RequestHandle _handle = null;
	bool _loaded = false;
	
	//Example inspector, the idea is that we can trigger loading www data for the object
	//in question via the editor a debug method.
	public override void OnInspectorGUI()
	{
		Test test = target as Test;
		if(test != null)
		{
			if (!_loaded  GUILayout.Button("Load"))
			{
				_handle = test.Load();
			} 
			
			if(_handle != null  _handle.status != RequestHandle.Status.Pending)
			{
				//Done...
				_loaded = true;
			}			
			
			if(_loaded)
			{ 
				//* Uncomment first two of this to swtich logic to working version
				EditorGUILayout.BeginHorizontal(); 
				{
					//Everything falls apart with GUI mismatch errors... Why?!
					//In this case it is ArgumentException: GUILayout: Mismatched LayoutGroup.mouseDown
					//but using this same logic in acutal code causes all sorts of random results.
					EditorGUILayout.LabelField("Loaded!");
				} 
				EditorGUILayout.EndHorizontal();
				
				/*/
				EditorGUILayout.LabelField("Loaded!");
				/**/
			}
		}
	}
}

Update from Test.cs

	void Update ()
	{
		Request [] list = _wwwRequestList.ToArray();
		foreach(Request request in list)
		{
			if(request.www != null)
			{
				if(request.www.error != null)
				{
					if(Application.isEditor) Debug.LogWarning(request.www.error);
					
					request.handle.status = RequestHandle.Status.Failed;
					request.handle.error = request.www.error;
					
					_wwwRequestList.Remove(request);
					request.www.Dispose();
					request.www = null;
				}
				else if(request.www.isDone)
				{
					request.handle.status = RequestHandle.Status.Succeeded;
					
					_wwwRequestList.Remove(request);
					request.www.Dispose();
					request.www = null;
				}		
			}
		}
	}

If you remove the Horizontal layout from the equation everything works as expected, however in my case I really want to make my GUI layed out with one.

Even more crazy, I’m finding that just setting _loaded will result in this behavior.

		if(_handle != null  _handle.status != RequestHandle.Status.Pending)
			{
				_handle = null;
				_loaded = true;
			}
			
			if(_handle == null)
			{  
				//* Uncomment first slash of this to swtich logic to working version
				EditorGUILayout.BeginHorizontal(); 
				{
					//Everything falls apart with GUI mismatch errors... Why?!
					//In this case it is ArgumentException: GUILayout: Mismatched LayoutGroup.mouseDown
					//but using this same logic in acutal code causes all sorts of random results.
					EditorGUILayout.LabelField("Loaded!");
				} 
				EditorGUILayout.EndHorizontal();
				
				/*/
				EditorGUILayout.LabelField("Loaded!");
				/**/
			}

Now if I remove the _loaded = true; line it work.What is going on behind the scenes here that makes this logic so touchy?

		if(_handle != null  _handle.status != RequestHandle.Status.Pending)
			{
				_handle = null;
			}
			
			if(_handle == null)
			{  
				EditorGUILayout.BeginHorizontal(); 
				{
					//Works now that _loading = true; is gone! But not the logic I want...
					EditorGUILayout.LabelField("Loaded!");
				} 
				EditorGUILayout.EndHorizontal();
				
			}

This is a very common conceptual misunderstanding with immediate mode gui. It derives from the fact that OnGUI and OnInspectorGUI is called multiple times per frame. You can look at Event.current to see why OnGUI is called this time.

So each frame Unity starts with a Layout, does some Events(this is where your button code is triggered) and a Repaint. However the number and type of controls in the Repaint doesn’t match the Layout and thats the problem.

What you’re doing is adding a new gui control in the middle of a GUI pass. In general you should use the EventType.Layout for adding and removing controls.

if (Event.current.type == EventType.Layout  _handle != null  _handle.status != RequestHandle.Status.Pending)
{
	//Done...
	_loaded = true;
}

So if you change _loaded(which effectively changes the gui layout) only when processing the Layout event everything should work as intended.

4 Likes

Very insightful! Thank you.

Actually the problem is that the documentation doesn’t state it clearly in manual and people need to look deep in class docs to learn about this. Note that i’m a unity user from 2.5 era and know it quite well. the time when i read manual in 2.5 era it wasn’t stated there and it’s easy to miss the detait that you should only change layout in layout event only after many months when you read class documentation however if you learn it as a concept in manual it will stay there.

I realize this is an old thread, but thought I’d share my solution for random googlers since the above (sampling EventType) did not work for me.

As it turns out, custom Property Drawers do not play well with EditorGUILayout.
I was baffled for a long time by this since I had a custom property drawer with EGL that worked fine as a field in one script, but completely broke another. testing EventType, implementing GetPropertyHeight in various ways, and removing various parts of my logic did not help.

In the end, it turns out the script that was working, was using a custom inspector, which for some unapparent reason makes property drawers with EGL work like a charm.

To test this, you can do the following (verified on 2017.3):

  • create a class (lets call it Foo) with a with a single public field (make sure its serializable)

  • create a custom property drawer, expose that field via EditorGUILayout.PropertyField

  • create a MonoBehaviour (FooHolder), which has a single “public Foo foo;”

  • place FooHolder on GameObject- see it is broken.

  • now, create a Custom Inspector for FooHolder- voila, it works. (No need to even implement OnInspectorGUI!)

  • without the Custom inspector, FooHolder will display correctly if you:

  • switch to EditorGUI.PropertyField

  • implement GetPropertyHeight

TL;DR- Custom Property Drawers do not work with EditorGUILayout, unless the MonoBehaviour showing them has a custom proerty drawer

6 Likes

LOL it’s 2019 and this is still true, my god Unity docs are insane, I mean i get that programmers hate documenting but daaaaaamn. I saw a full page doc for a method the other day, something like “GetBespokeProperty()” and the docs were literally “Gets the Bespoke Property”, so clearly there’s no passion for documenting over there, which is weird because some people love bringing order to chaos, they should just find one of those to do it.

1 Like

So this is a huge frustration because it means I cant act on a dropped object on a drag and drop event but have to cache it and wait for the next layout.

Thats just ugly.

How do I add a custom property drawer?