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?

why do you say obviously? I would say that this works only if www is multithread. I did not know it was multithread.

It is not. That is the problem.

weird because I did the while like you did and it works

Hey, so even though this question is a couple of years old, I made a plugin that that makes it possible to use coroutines in Editor code: http://forum.unity3d.com/threads/released-editor-coroutines.289703/

9 Answers

9

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;
}
}

Made gist with a few more features and xml documentation comments https://gist.github.com/createdbyx/2c43c0518f622a652442

Up until Unity 5.5 this was working great for us. Starting at Unity 5.5 it stopped working. Any ideas what might have changed from Unity to break this?

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

Hah, didn't see there was a 2nd page before posting my own. #4 on this link actually looks promising.

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

As great as that might be, this is supposed to work for every scene. A MonoBehavior object needs to be attached to something in the Game for it to even be instantiated - I need something that can exist in any scene and be a permanent fixture in the entire project.

How about...wait for it... a singleton with DontDestroyOnLoad()! Another day, another singleton post from me :D - Oops, this question is 2 months old x(

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

Does not work. WWW needs to be on the main 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.

Honestly I had the same problem, but since the lag is not an issue for me, for the time being I use www synchronously. I watch this question in case you find a solution.

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);
 }