Asynchronous coroutine handling in unity - c#

I have used a coroutine to do a backend service call to retrieve the player categories in my category.cs file:
public override void OnEnter(Page p)
{
backend = globalScriptObject.GetComponent<IBackendController>();
items.Clear ();
StartCoroutine (backend.GetPlayerProfile ( profile =>{
this.maxSelectableItems = Mathf.CeilToInt(profile.level/10+1);
if(this.maxSelectableItems == 7) maxSelectableItems = int.MaxValue;
DisableSelections();
}));
GetPlayerProfile (In a different class which has been called using instance backend of that class)
public IEnumerator GetPlayerProfile(System.Action<Profile> callback){
yield return GetPlayerProfile (callback, false);
}
Issue:
Since i am using an external service call,sometimes the player profile is uploaded with a delay.
I need to make sure that the startcoroutine is finished with result before the rest of the lines of code is executed.
I tried creating the following class after searching from the internet which can make sure the couroutine call is finished before the rest of the lines are executed:
{
StartCoroutine(FinishFirst(5.0f, DoLast));
}
IEnumerator FinishFirst(float waitTime, Action doLast) {
print("in FinishFirst");
yield return new WaitForSeconds(waitTime);
print("leave FinishFirst");
doLast();
}
void DoLast() {
print("do after everything is finished");
print("done");
}
But how can i use the above in my source code is what i would need suggestions from the community.
Also can i do something like yield return waitForSec(Float) in the GetPlayerProfile method?
Thanks !!

Try using WaitUntil.
https://docs.unity3d.com/ScriptReference/WaitUntil.html
Something like this:
IEnumerator GetProfile(){
var profile = null;
yield GetPlayerProfile((p) => {profile = p});
yield WaitUntil(p != null);
this.maxSelectableItems = Mathf.CeilToInt(profile.level/10+1);
if(this.maxSelectableItems == 7) maxSelectableItems = int.MaxValue;
DisableSelections();
}
And then...
StartCoroutine(GetProfile);

Related

Why is the method returning in yield return get.SendWebRequest()

I have a function in unity that makes a request for a server, and for each value in the result, it should call a function to add a image on a list and get an object from the same server and add on another list
public void SaveServer() {
string serverAddress = inputField.text;
GlobalStateData.getInstance().ServerAdress = serverAddress;
StartCoroutine(Utils.GetRequest(GlobalStateData.getInstance().ServerAdress + "file/ObjectController/",
(UnityWebRequest) => {
JArray jsonResponse = JArray.Parse(UnityWebRequest.downloadHandler.text);
foreach (var json in jsonResponse) {
String base64 = (string)json["trackingImage"]["file"]["base64"];
String name = (string)json["file"]["name"];
int objectId = (int)json["id"];
Texture2D texture = Utils.Base64ToTexture2D(base64);
StartCoroutine(addImageAndObject(objectId, texture, name));
}
Debug.Log(jsonResponse);
transform.parent.Find("canvas_MainMenu").gameObject.SetActive(true);
gameObject.SetActive(false);
}));
}
private IEnumerator addImageAndObject(int objectId, Texture2D texture2D, String name) {
StartCoroutine(addImage(texture2D, name));
yield return AddObjectById(objectId, name).MoveNext();
}
but in the AddObjectById the method is stopping before complete the request
private IEnumerator AddObjectById(int id, string name) {
string url = GlobalStateData.getInstance().ServerAdress + "bundle/ObjectBundleController/" + id;
UnityWebRequest get = UnityWebRequest.Get(url);
yield return get.SendWebRequest(); // it stops here
if (get.result != UnityWebRequest.Result.Success) {
Debug.Log(get.error);
yield return false;
}
else {
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(get);
GameObject obj = (GameObject)bundle.LoadAsset(name);
_place3DObjectRef.ArPrefabs.Add(obj);
yield return true;
}
}
I didn't understand how my GetRequest Method Works and the AddObjectById
Here is the GetRequest
public static IEnumerator GetRequest(string server, Action<UnityWebRequest> successCallback, Action<String> errorCallback = null) {
UnityWebRequest get = UnityWebRequest.Get(server);
yield return get.SendWebRequest();
if (get.result != UnityWebRequest.Result.Success) {
Debug.Log(get.error);
if(errorCallback != null)
errorCallback("Cannot make request to " + server +
"\nerror:" + get.error);
yield return false;
}
else {
successCallback(get);
yield return true;
}
}
and this one runs fine.
What should I do to the method to not stops after the first yield return?
EDIT:
I solved the problem!
The reason why this was happening, it was because the gameObject was setting deactivated before the coroutine finishes, so the scripts was stopping
gameObject.SetActive(false);
https://docs.unity3d.com/ScriptReference/GameObject.SetActive.html
The unity documentation says that "Deactivating a GameObject disables each component, including attached renderers, colliders, rigidbodies, and scripts. For example, Unity will no longer call the Update() method of a script attached to a deactivated GameObject. OnEnable or OnDisable are called as the GameObject received SetActive(true) or SetActive(false)." So, as the script is an component attached to the scene object, makes sense that it stops working
Well, yield works by returning results from one iteration,and what comes after will not be executed. In your method, when a call is made to AddObjectById, it will stop at the first yield it finds and return its value, in this case get.SendWebRequest();, not continuing the flow. If you move yield return get.SendWebRequest(); after of if-else, it will never enter, as it will have some return in yield return coming from "if-else".
I found out that if the gameObject is not active gameObject.SetActive(false); the Coroutines stops too. The problem was not with the coroutines but with the gameObject being deactivated

Unity StartCoroutine MoveNext and mapper?

I have just jumped to Unity and I am doing some experiments to retrive data from an API
Below you can see the test code could you please help me to understand if i am the right way or not please?, because probably i am missing the mapper isn't it ?
Thanks in advance.
void Start()
{
comments= StartCoroutine(this.GetComments("https://jsonplaceholder.typicode.com/comments"));
Debug.Log(comments);
}
private IEnumerator Comments GetComments(string url) {
List<Comments> returnComments = new List<Comments>();
UnityWebRequest comments = UnityWebRequest.Get(url)
comments.SendWebRequest();
while (comments.MoveNext())
{
var comment = comments.Current;
returnItems.Add(comment);
}
return returnComments;
}
Yield the SendWebRequest function.
yield return webRequest.SendWebRequest();
Access the data through the download handler.
var dataString = webRequest.downloadHandler.text;
Full sample code in the docs UnityWebRequest.Get

Unity Firebase SignInAnonymously sometimes works and sometimes not

I have an issue with signing in anonymously into my Firebase database for my Unity game. I have a method for signing anonymously into the database and another one that reads the database and prints a json string.
public IEnumerator anonymousSignIn()
{
var register = auth.SignInAnonymouslyAsync();
yield return new WaitUntil(predicate: ()=> register.IsCompleted);
}
public IEnumerator readDatabase()
{
var DBTask = DBreference.Child("users").GetValueAsync();
yield return new WaitUntil(predicate: () => DBTask.IsCompleted);
if (DBTask.Exception != null)
{
Debug.LogWarning(message: $"Failed to register task with {DBTask.Exception}");
}
else if (DBTask.Result.Value == null)
{
Debug.LogWarning("No data found in the database");
}
else
{
DataSnapshot snapshot = DBTask.Result;
string json = snapshot.GetRawJsonValue();
Debug.Log(json);
}
}
I then call these functions via a button in my Unity games using the method:
public void readButton()
{
StartCoroutine(anonymousSign());
StartCoroutine(readDatabase());
}
However, this sometimes works and other times It says permission denied and I don't understand why.
My database rules for reading are: ".read": "auth != null",
I got the same error.
After that, I changed the code
From:
Auth.SignInAnonymouslyAsync().ContinueWith(task => { SignInAnonymously(task, "Guest"); });
To:
Auth.SignInAnonymouslyAsync().ContinueWithOnMainThread(task => { SignInAnonymously(task, "Guest"); });
Don't use Async for Anonymous Sign In, but run on Main Thread.
It worked for me!

GET Request dont save data

I'm trying to save some data from a GET request. I use StartCoroutine to request and I use Lambda expression for save the data.
My Code is this:
Using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour {
// Use this for initialization
public void Start () {
string url1 = "http://localhost/virtualTV/query/?risorsa=";
string ciao = "http://desktop-pqb3a65:8080/marmotta/resource/ef299b79-35f2-4942-a33b-7e4d7b7cbfb5";
url1 = url1 + ciao;
WWW www1 = new WWW(url1);
var main=new JSONObject(JSONObject.Type.OBJECT);
var final= new JSONObject(JSONObject.Type.OBJECT);;
StartCoroutine(firstParsing((value)=>{main = value;
final= main.Copy();
Debug.Log(main);
}));
Debug.Log(final);
}
public IEnumerator firstParsing( System.Action<JSONObject> callback)
{
string url2 = "http://localhost/virtualTV/FirstQuery/?risorsa=";
string ciao = "http://desktop-pqb3a65:8080/marmotta/resource/ef299b79-35f2-4942-a33b-7e4d7b7cbfb5";
url2 = url2 + ciao;
WWW www2 = new WWW(url2);
yield return www2;
string json = www2.text;
//Parsing del json con creazione di un array
var firstjson = new JSONObject(json);
var tempVideo = new JSONObject(JSONObject.Type.OBJECT);
var array2 = new JSONObject(JSONObject.Type.OBJECT);
tempVideo.AddField ("id", firstjson.GetField ("id"));
tempVideo.AddField ("type", firstjson.GetField ("type"));
tempVideo.AddField ("url", firstjson.GetField ("url"));
array2.Add (tempVideo);
yield return array2;
callback (array2);
Debug.Log ("First Run" + array2);
}
When I try to use FINAL after the command,
final=main.copy()
it is empty. Can you help me to save the value in the variable final? Thanks all.
A coroutine's execution is spread across many frames. When a coroutine encounters a yield return statement, it returns to the calling method, which finishes executing, till the task finishes.
In your case, the Debug.Log(final) statement in Start executes as soon as yield return www2; in firstParsing is executed. The callback hasn't been called yet which is why final is empty.
To be able to access the value in final after it has been assigned outside the callback function, you will have to set a bool which is set to true after final is assigned in the callback. Something like this:
StartCoroutine(firstParsing((value)=>{main = value;
final= main.Copy();
Debug.Log(main);
isFinalAssigned = true;
}));
// In another method
if(isFinalAssigned)
{
// Access final
}
You will have to note that the above if statement is useful only in a method that is called periodically like Update. If you're accessing final in a method that is called only once (like OnEnable) you will have to wait for final to be assigned. You can use another coroutine for this task like
IEnumerator DoSomethingWithFinal()
{
while(!isFinalAssigned)
yield return null; // Wait for next frame
// Do something with final
}
The easiest way out is to consume (access) final in your callback.
EDIT2: From your comments, you can do something like the following. You will have to use coroutines, because blocking the main game thread is not a good idea.
private JSONObject final = null; // Make final a field
Wherever you use final, you have two options.
Use a null check if(final == null) return; This can be impractical.
Wait for final to be assigned in a coroutine and do something as a callback. This is the only way you can do what you want cleanly.
Look below for the implementation.
// Calls callback after final has been assigned
IEnumerator WaitForFinal(System.Action callback)
{
while(final == null)
yield return null; // Wait for next frame
callback();
}
// This whole method depends on final.
// This should be similar to your method set up if you have
// good coding standards (not very long methods, each method does only 1 thing)
void MethodThatUsesFinal()
{
if (final == null)
{
// Waits till final is assigned and calls this method again
StartCoroutine(WaitForFinal(MethodThatUsesFinal));
return;
}
// use final
}

I want to create a global array in order to listen for events

I have a coroutine that is invoked several times on my scene.
IEnumerator Save_assets(string file, int i, string name){
var filename = Path.GetFileName(file);
docPath = Application.streamingAssetsPath+"/files/";
var temp_name = docPath+filename;
downloaded_asset = false;
if(!Directory.Exists(docPath)){
Directory.CreateDirectory(docPath);
}
if (!System.IO.File.Exists(temp_name)){
WWW www = new WWW(file);
yield return www;
//Save the image
System.IO.File.WriteAllBytes(temp_name, www.bytes);
}
/* I really would like to have a sort of listener here doing something like:
//pseudocode
while(global.file != true){ //while not done
yield return null;
}
*/
downloaded_asset = true;
finished = false;
tries = 0;
go = GameObject.Find(name);
go.gameObject.BroadcastMessage("paint_file", temp_name);
}
Once paint_file has been invoked the Update function on that very class
is constantly looking for a certain condition to happen, let's say it's "done = true;"
void paint_file(file){
[...]//code
}
void Update () {
var done = true;//let's pretend there's no code here, just done = true
if(done){
Debug.Log("Finished: "+paint_file._filename);
}
}
I have no idea how to set this var global.file = done,
any help would be greatly appreciated
Avoid ever using globals ( statics or singletons ) use direct callbacks instead or hook stuff up with component/object references
Also avoid Find, and broadcastMessage ideally as they are slow :-)
instead have:
var SomeComponentType myReferenceToThatComponent;
on Awake(){
if(myReferenceToThatComponent==null){
myReferenceToThatComponent = GetComponent<SomeComponentType>();
}
}
Once you have a reference to a component you can access any public items on it eg:
myReferenceToThatComponent.done = true;
myReferenceToThatComponent.SomeMethod( true );
benefit is you can set that reference in the Editor, but if you don't set it leaving it null, it goes and tries to find it, and it only finds it once, any further use will uses the cached result and not have to use an expensive search again.
Your www should be surrounded with a 'using' as is the common practice to make sure it garbage collects when done
using ( WWW www = new WWW( url, form ) ) {
yield return www;
}
The myriad of approaches to finding and getting components and objects is beyond the scope of this answer I think
Just remember Unity is preferably Component based which is quite different to the typical OOP Inheritance/Interface style of C# coding.
-
Way to structure Coroutines to run one after another waiting for one to complete:
void Start() {
StartCoroutine( BaseRoutine() );
}
IEnumerator BaseRoutine() {
for ( int i = 0; i < 10; i++ ) {
yield return StartCoroutine( ChildRoutine( i ) );
}
}
IEnumerator ChildRoutine( int i ) {
Debug.Log( "ChildRoutine number: " + i );
yield return new WaitForSeconds( 1 ); // Put your yield return WWW here for example
}

Categories