Yielding with WWW in Editor

Hello!

I’m working on an editor tool that needs some SQL connectivity. It is an internal bug tracker. I’ve written a php script that will return the data I need formatted correctly, and it works, but, I have to force the application to wait using the disgusting while(!www.isDone) approach.

In my EditorWindow, I have a function:

public void RefreshData() {
	SQL.query("SELECT * FROM users"); 		
}

Whereas, the query method in question is somewhat like this.

public static void query(string q) {
	WWW www = new WWW("...?query="+WWW.EscapeURL(q));
	while(!www.isDone);
	if(www.error != null) 
		Debug.LogError(www.error); 	
	else 
		Debug.Log(www.text);
}

This works beautifully… obviously, I’ll get it to return the necessary data when I write the parsing functionality, but my question is: Is there any way I can used the yield return www; to get it to not hang the Unity Editor when I have more queries to parse?

Since this is a static method built into a static editor class, I cannot attach a MonoBehavior to anything… thus, I cannot use the StartCoroutine method. Is there some C# interface I can implement on my EditorWindow that requires a Coroutine Scheduler?

Is there any hope for me, or will my tool forever hang?

I made a ContinuationManager to handle the cases where I want to wait for a condition and then do something with an object.

The snippet below is an example of WWW using the ContinuationManager where the condition to trigger the continuation is www.isDone. The lambda closure captures the www object so it can be used when the www is done. The code is non-blocking.

var www = new WWW("someURL");
ContinuationManager.Add(() => www.isDone, () =>
{
    if (!string.IsNullOrEmpty(www.error)) Debug.Log("WWW failed: " + www.error);
    Debug.Log("WWW result : " + www.text);
});

The ContinuationManager

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;

internal static class ContinuationManager
{
    private class Job
    {
        public Job(Func<bool> completed, Action continueWith)
        {
            Completed = completed;
            ContinueWith = continueWith;
        }
        public Func<bool> Completed { get; private set; }
        public Action ContinueWith { get; private set; }
    }

    private static readonly List<Job> jobs = new List<Job>();

    public static void Add(Func<bool> completed, Action continueWith)
    {
        if (!jobs.Any()) EditorApplication.update += Update;
        jobs.Add(new Job(completed, continueWith));
    }

    private static void Update()
    {
        for (int i = 0; i >= 0; --i)
        {
            var jobIt = jobs*;*

if (jobIt.Completed())
{
jobIt.ContinueWith();
jobs.RemoveAt(i);
}
}
if (!jobs.Any()) EditorApplication.update -= Update;
}
}

My solution: Making Unity's WWW class work in Editor scripts

Does it have to be static? I’ve had pretty good luck using MonoBehaviour and ExecuteInEditMode, use Update to check isDone.

In order to yield in an editor method you need to use a regular c# Thread.

So. Im saying that this class needs to exist independent of what’s loaded in the scene. It is a bug tracker that connects to a database to retrieve bugs. If I create a brand new scene - I don’t want to have to go ahead and create an object that for some magical reason uses game time to do something that ought to be simple.

I had just wondered if anyone had found out how to avoid the lag associated with do a WWW request in editor only code.

The continuation manager (or similar) does not work when you derive from EditorWindow, unity engine seems to lock out the window thread until it moves or needs repainting (and even then still won’t service the WWW), as Skjalg says you need to spawn a separate thread to handle it. It’s odd because the WWW seems to be serviced correctly at Start() or Initialise(), but not if you invoke it during OnGUI()

EDIT:
My answer has given me an idea, why not fire up a dummy EditorWindow (from the first) when you need to grab something with WWW, let if finish, give you the result, then destroy itself, returning focus to you first window. Saves having to fiddle about with System.Threading ?

… [ExecuteInEditMode] makes no difference, firing some random move window rect makes no difference, unity editor will not finish servicing the www request.

Skjalg’s answer is the only way (although WWW can only be invoked from the main thread, which complicates things, spawning a separate object in the current scene with www grabber script attached works, you can destroy it when done so not affect the current scene being edited)

string Get_URL = “url to use with http”;
IEnumerator getData()
{
WWW get = new WWW(Get_URL);
yield return get;
Debug.Log(get.text);//parse text
}

So, I believe my method still requires an object to be in the scene, but you don’t have to be in Play mode. I just have a dialog pop up, to refresh and check isDone each time it’s clicked until it comes out as true.

	[ContextMenu("WWW Request")]
	public void StartRequest()
	{
		StartCoroutine(RunRequest());
	}

	private IEnumerator RunRequest()
	{
		WWWForm form = new WWWForm();
		form.AddField("yourKey", yourValue);

		WWW request = new WWW("https://www.your.url", form);

		do
		{
			if(EditorUtility.DisplayDialog("Performing request", "", "Refresh"))
			{
				if(request.isDone)
				{
					Debug.LogError(request.text);
					request.Dispose();
					yield break;
				}
			}
		} while(!request.isDone);
	}

I suppose for larger downloads/uploads you could very well use EditorUtility.DisplayProgressBar and update that on each refresh/click as well. Of course, by showing a dialog like this it locks you out of using the editor until the process is complete, so might suck for larger requests.

Create a MonoBehaviour class with ExecuteInEditMode

[ExecuteInEditMode]
public class TemporalMonoBehaviour : MonoBehaviour
{
}

Then create the class in editor mode when a coroutine is needed. Editor script example:

MonoBehaviour monoTemp; 

void OnEnable()
{
   var temp = EditorUtility.CreateGameObjectWithHideFlags("_Temp", HideFlags.HideAndDontSave, typeof(TemporalMonoBehaviour));
   monoTemp = temporal.GetComponent<TemporalMonoBehaviour>();
}

public override void OnInspectorGUI()
{
    if (GUILayout.Button("Start Coroutine"))
    {
       monoTemp.StartCoroutine(MyCoroutine());
    }
}

 void OnDisable()
 {
     DestroyImmediate(monoTemp.gameObject);
 }