Search code examples
c#unity-game-enginewhile-loop

Struggling with returning data from a while loop inside nested Coroutine


this is my first issue I'm posting on stackOverflow! I'm trying to work on an AssetBundle updater for a Unity game. I'm using yield return StartCoroutine() to ensure that one operation finishes before the next begins.

What I am trying to do is show the percentage of the file that is downloaded as it is being downloaded on a Textmesh object.

Here is the code. DownloadCoroutineLauncher() is itself a coroutine launched from another script.

public IEnumerator DownloadCoroutineLauncher(){
        yield return StartCoroutine(InitialSetup());
        //Ensures that we have the correct local file structure.
        yield return StartCoroutine(EnsureFileStructureAndInitialVersionDoc());
        //Just tells us which files to get from remote.
        yield return StartCoroutine(EnsureCorrectPlatform());
        yield return StartCoroutine(FinalizeInitialSetup());
        //At this point we should at the very least have an initial version file.
        //Set up the local version dictionary.
        yield return StartCoroutine(LocalDictionarySetup());
        //Download the version file and set up the remote version dictionary.
        yield return StartCoroutine(DownloadVersionFile());
        //Figure out what new files need to be downloaded or overwritten.
        yield return StartCoroutine(FigureOutWhatToUpdate());
        //Determine files needing deletion.
        yield return StartCoroutine(FigureOutWhatToDelete());
        //Download what needs downloading.
        foreach(KeyValuePair<string,bool> file in whatToUpdate){
            if(file.Value == true){
                yield return StartCoroutine(DownloadAssetBundle(localFiles[file.Key],remoteFiles[file.Key],"Downloading " + file.Key));
            }
        }
        /*Delete what needs deleting. Future Feature.
        foreach(KeyValuePair<string,string> file in whatToDelete){

        }*/
        //Finally, update the local version doc
        yield return StartCoroutine(UpdateVersionDoc());
        yield return null;

    }

The issue is inside of the DownloadAssetBundle Coroutine.

    IEnumerator DownloadAssetBundle(string localLocation, string remoteLocation, string niceText){
        yield return CurrentItemText.text = niceText; //This returns and updates the TMP text correctly.
        var cert = new ForceAcceptAll();
        UnityWebRequest request = new UnityWebRequest(remoteLocation,UnityWebRequest.kHttpVerbGET);
        request.certificateHandler = cert;
        request.downloadHandler = new DownloadHandlerFile(localLocation);
        yield return request.SendWebRequest();
        cert?.Dispose();
        while (!request.isDone){
            Debug.Log((request.downloadProgress * 100.0f).ToString() + "%"); //troubleshooting line. Also doesn't trigger.
            ProgressText.text = (request.downloadProgress * 100.0f).ToString() + "%";
            yield return ProgressText; //This does not update as expected.

        }
        yield return null;
    }

What happens is that the current item label updates correctly and at the appropriate time, but anything inside of while(!request.isDone) doesn't update as expected. What can I do so that my little percentage tracker object updates with each frame as I'd like it to? Thanks!


Solution

  • The async operation returned by SendWebRequest is doing the time consuming part, and you are waiting for that to finish by yielding to it. Instead you should start the request and wait for it using your while loop.

    From the unity docs for SendWebRequest:

    Yielding the WebRequestAsyncOperation inside a coroutine will cause the coroutine to pause until the UnityWebRequest encounters a system error or finishes communicating.

    Essentially remove the yield return from the request operation.

    request.SendWebRequest();
    
    while (!request.isDone)
    {
        Debug.Log((request.downloadProgress * 100.0f).ToString() + "%"); 
        ProgressText.text = (request.downloadProgress * 100.0f) + "%";
        yield return null;
    }
    

    Now the yielding happens inside of the while loop and not on the request operation. You can store the async operation returned by SendWebRequest if you need to, though i believe the request object is good enough.

    You were returning the progress text ui element from inside of the while loop.
    Instead use 0 or null to wait for the next frame, as shown above.