Unity - Need to Return Value Only After Coroutine Finishes

Unity - need to return value only after coroutine finishes

Only a coroutine can wait for another coroutine. Since you need to wait for the coroutine you started (WaitForRequest), it means you have to convert POST to be a coroutine and it won't be able to return int.

It looks like success_fail is a member variable, so if that's exposed to whoever is starts POST (as a coroutine), you wouldn't need to return it anyway.

public int success_fail

IEnumerator POST(string username, string passw)
{
WWWForm form = new WWWForm();
form.AddField("usr", username);
form.AddField("pass", passw);

WWW www = new WWW(url, form);

yield return StartCoroutine(WaitForRequest(www));
}

private IEnumerator WaitForRequest(WWW www)
{
yield return www;
if (www.error == null)
{
if(www.text.Contains("user exists"))
{
success_fail = 2;
}
else
{
success_fail=1;
}
} else {
success_fail=0;
}
}

Basically, if you want your code to "wait", it has to be a coroutine. You can't make a call that waits without blocking the whole engine (without some type of loop hack).

This thread gives a way where you could return the int from your coroutine if you really need to, but POST still can't be a blocking call...

http://answers.unity3d.com/questions/24640/how-do-i-return-a-value-from-a-coroutine.html

Can a coroutine in C# return a string value?

The problem you are encountering is that you are trying to use the returnData before it has been returned. The coroutine is an asynchronous function that will terminate at some point in the future, so you must perform any actions on the returned value only after it has completed. This is usually done by continuing the code flow within the callback. For example:

            // START COROUTINE
StartCoroutine(rest.GetData2(URL, (value) => {
// This function is called after GetData2 has completed.
returnData = value;
Debug.Log(returnData); // <=== logs value
} ));
// This code flow continues IMMEDIATELY AFTER we START COROUTINE,
// and has not waited for GetData2 to complete, so it logs null
Debug.Log(returnData);

How to use a value from a Coroutine or tell when it's done

Well then, me, let me show me a neat way of doing this!

Here we make an IEnumerator that takes in an Action (method in our case) as parameter and call it when our WWW is done:

    public static IEnumerator GetSomething(Action<string> callback)
{
// The www-stuff isn't really important to what I wish to mediate
WWWForm wwwForm = new WWWForm();
wwwForm.AddField("select", "something");
WWW www = new WWW(URL, wwwForm);
yield return www;

if (www.error == null)
{
callback(www.text);
}
else
{
callback("Error");
}
}

And this is how we use it:

StartCoroutine(
GetSomething((text) =>
{
if (text != "Error")
{
// Do something with the text you got from the WWW
}
else
{
// Handle the error
}
})
);

The parameter we send in is (text) which is a namelessly declared method. We call it "callback" in the IEnumerator but it can be called anything, what's important is that it calls the method we have declared in the parameters of where we call the method GetSomething.

How do you return a value that is dependent on the result of a co-routine that is started within the method? (C#)

You might want to consider using async/await

public async Task<bool> CheckListForName(string username)
{
await DownloadPlayer(username);
return playerExists;
}

then to call the method

if(await CheckListForName("foo"))
DoSomething();

or

var playerExistsTask = CheckListForName("foo"); //doesn't wait for task to finish here
Dothings();
bool playerExists = await playerExistsTask;//waits here

How can i get return value(int) in Unity3d coroutine?

You won't simply be able to wait the result of a coroutine in a normal function, except if you want your game to freeze until your answer is there by using a while loop or such.

What I can suggest you is the following strategy: use callbacks. Callbacks are functions you will call when your current function/coroutine is done working. After you received your WWW parameter in requestNowTime, call a function which will compute the elapsed time. And in that same function, when you will be done computing, use a callback which would be a function of the original caller which will be able to use the result as expected first.

WWW www;

// Call this function to start the computing process
public void ProcessElapsedSeconds(Action functionToCallWhenDone)
{
StartCoroutine(requestNowTime(functionToCallWhenDone));
}

public IEnumerator requestNowTime(Action callback)
{
www = new WWW(url);

OnTimeReceived(www, functionToCallWhenDone);
}

void OnTimeReceived(WWW www, Action functionToCallWhenDone)
{
int totalSeconds = 0;
DateTime dt = Convert.ToDateTime(www.text);
TimeSpan compareTime = dt - Convert.ToDateTime(getTime());
totalSeconds = (int)compareTime.TotalSeconds;

functionToCallWhenDone(totalSeconds);
}

Coroutine api call only finishes after a thread sleep

It is because of the SceneManager.LoadScene method. Coroutines run in separate threads. Since you start the coroutine and immediately load a new scene, there is no time to run the coroutine before the current scene (and script object) is unloaded. The Thread.Sleep call introduces a delay that gives enough time for the coroutine to finish before the scene load.

To get around this, you can use a flag (actually I use two, so you don't kick off the coroutine multiple times):

private bool _postComplete = false;
private bool _startedPost = false;

IEnumerator PostRequestBearerToken(string uri)
{
string authorization = APIHelpers.authenticate("user", "pass");
WWWForm form = new WWWForm();
form.AddField("grant_type", "client_credentials");

using (UnityWebRequest www = UnityWebRequest.Post(uri, form))
{
www.SetRequestHeader("AUTHORIZATION", authorization);
yield return www.SendWebRequest();

if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
}
else
{
Debug.Log("Post request complete!" + " Response Code: " + www.responseCode);
string responseText = www.downloadHandler.text;
Debug.Log("Response Text:" + responseText);
Debug.Log("Form upload complete!");

//deserialize bearertoken
BearerObject myObject = JsonUtility.FromJson<BearerObject>(www.downloadHandler.text);
Debug.Log("json : " + myObject.ToString());
//BearerTokenSingleton.getInstance();
BearerTokenSingleton.getInstance().SetBearerToken(myObject);
Debug.Log(BearerTokenSingleton.getInstance().GetBearerToken().ToString());
}
_postComplete = true;
}
}

And then use that in the Update method:

   void Update()
{
if (!loadFirstFrame)
{
loadFirstFrame = true;
}
else if (loadFirstFrame)
{
if (!_startedPost)
{
StartCoroutine(PostRequestBearerToken(APIHelpers.bearerString));
_startedPost = true;
}

if (_postComplete)
SceneManager.LoadScene(2);
}
}

Is it possible to return the result of an UnityWebRequest from Coroutine method?

I can see that this function return type is IEnumerator, and notice that the 'request.SendWebRequest();' return a Task. So the first thing to fix this issue would be removing the generic type , leave it just like:

private IEnumerator<string> SendPostWebRequest(){....}

Of the other hand, you need to return a string. According to Unity's documentation the following code should work for cases that the function is not a Coroutine:

string str = (string) SendPostWebRequest(...);

This will cast all elements yielded in the IEnumerator and will return the first string element on it.

However, this is a Coroutine, and from my experience this does not work. The solutions that I figured out and I usually use is the use of a lambda function.

To approach this you need to add an extra argument to your function, an Action (basically functions references) so you can execute it from your SendPostWebRequest() function. I sounds confusing but take a look at this code. This is how I usually implement this kind of things.

public void ExampleFunction(){
// Using lambda function
StartCoroutine(SendPostWebRequest("www.google.es", (string ReturnResult)=>
Debug.Log(ReturnResult)
));

// Using functions references
StartCoroutine(SendPostWebRequest("www.google.es", OtherResultManagementFunction));
}

private void OtherResultManagementFunction(string ReturnResult){
Debug.Log(ReturnResult);
}

private IEnumerator SendPostWebRequest(string url, Action<string> FinishDelegate)
{
WWWForm form = new WWWForm();
form.AddField("Key", "Value");

using (UnityWebRequest request = UnityWebRequest.Post(url, form))
{
yield return request.SendWebRequest();

if (!request.isNetworkError && !request.isHttpError && request.isDone)
yield return request.downloadHandler.text;

FinishDelegate("WHATEVER STRING");
}
}

The generic Action specifies the return time of the function encapsulated in the Action object.

Both coroutines works exactly in the same way, but the first uses a lambda fucntion and the other a function reference. If executed, both would print the string returned from the webrequest (in this case "WHATEVER STRING").

Hope this works for you, It is the best solution for me in most cases.

How to hold on coroutine until condition is true Unity

In your enemy have a class like e.g.

public class Enemy : MonoBehaviour
{
// Keeps track of all existing (alive) instances of this script
private static readonly HashSet<Enemy> _instances = new HashSet<Enemy>();

// return the amount of currently living instances
public static int Count => _instances.Count;

private void Awake ()
{
// Add yourself to the instances
_instances.Add(this);
}

private void OnDestroy ()
{
// on death remove yourself from the instances
_instances.Remove(this);
}
}

And then you can simply wait for

private IEnumerator SpawnAllWaves()
{
for (int waveIndex = startingWave; waveIndex < waveConfigs.Count; waveIndex++)
{
var currentWave = waveConfigs[waveIndex];
yield return StartCoroutine(SpawnAllEnemiesInWave(currentWave));

// After spawning all wait until all enemies are gone again
yield return new WaitUntil(() => Enemy.Count == 0);
}
}


Related Topics



Leave a reply



Submit