question

JJ avatar image
JJ asked

Waiting for results in Playfab API

Hi, I would like to wait for my playfab result, store it in a variable then run some code. Heres my code.
ClassA Code

public List<myEntityFile> myEntityFileList = new List<myEntityFile>();

public void LoadAllFiles()
    {
        if (GlobalFileLock != 0)
            throw new Exception("This example overly restricts file operations for safety. Careful consideration must be made when doing multiple file operations in parallel to avoid conflict.");
        GlobalFileLock += 1; // Start GetFiles
        var request = new PlayFab.DataModels.GetFilesRequest { Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType } };
        PlayFabDataAPI.GetFiles(request, OnGetFileMeta, OnSharedFailure);
    }


    void OnGetFileMeta(PlayFab.DataModels.GetFilesResponse result)
    {
        Debug.Log("Loading " + result.Metadata.Count + " files");
        _entityFileJson.Clear();
        foreach (var eachFilePair in result.Metadata)
        {
            _entityFileJson.Add(eachFilePair.Key, null);
            GetActualFile(eachFilePair.Value);
        }
        GlobalFileLock -= 1; // Finish GetFiles
    }


    void GetActualFile(PlayFab.DataModels.GetFileMetadata fileData)
    {
        GlobalFileLock += 1; // Start Each SimpleGetCall
        PlayFabHttp.SimpleGetCall(fileData.DownloadUrl,
            result => {
            _entityFileJson[fileData.FileName] = Encoding.UTF8.GetString(result);
            GlobalFileLock -= 1;
            Debug.Log("My file is " + fileData.FileName);
            Debug.Log("My file download url is " + fileData.DownloadUrl);
            myEntityFileList.Add(new MyEntityFile(fileData.FileName, fileData.DownloadUrl));
            }, // Finish Each SimpleGetCall
            error => { Debug.Log(error); }
        );
    }

As you can see, this is how we can retrieve entity files in Playfab, in my `GetActualFile` function, I have a line that adds the retrieved data to myEntityFileList.

I want to eventually use the myEntityFileList to run some operation.
ClassB Code

ClassA.classA.LoadAllFiles();	// I want to load the file (classA is a singleton here)

// There should be a pause here, wait until ALL files are loaded

List<myEntityFile> myFileList = ClassA.classA.myEntityFileList; // then my variables will now store all the info, and I can do something with the data
foreach(myEntityFile myFile in myFileList){
	// do something
}

Please provide some advice on how I can achieve this! Thank you!

apisentitiesdatatasks
6 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Citrus Yan avatar image Citrus Yan commented ·

Looks like you already achieved this, May I know which problem you're having, exactly?

0 Likes 0 ·
JJ avatar image JJ Citrus Yan commented ·

Hi Citrus. The problem is that `LoadAllFiles` will run asynchronously (which is the default behaviour of PlayFab APIs), which means that myEntityFileList will be empty when I access it.
(i.e. if I put a debug.log statement of "1" before my LoadAllFiles call, "2" inside my GetActualFile function, and "3" after my LoadAllFiles function call, the output will be 1,3,2, because 2 will take some time to run and 3 didnt wait for 2 to finish)

The ideal situation should be having the code run synchronously, meaning, only after LoadAllFiles operation is completed (which means that the GetActualFile function is actually completed), then only will I retrieve my myEntityFileList and run the foreach loop.

The problem here is I want to wait til the result is obtained before I run my operation.
I hope I am clear with my explanation.

0 Likes 0 ·
Citrus Yan avatar image Citrus Yan JJ commented ·

You can add a flag to indicate whether LoadAllFiles function call is completed or not, and only access myEntityFileList when that's done:

if ( flag == true)
List<myEntityFile> myFileList =ClassA.classA.myEntityFileList;
0 Likes 0 ·
Show more comments
JJ avatar image
JJ answered

Hi Citrus, your solution is actually not working, because isDone = true in OnGetFileMeta will run immediately before GetActualFile operation is completed, since GetActualFile is yet another asynchronous call.


However, I have gotten some inspiration from your answer and I have found a solution to this problem - I will share it in case other users require it in the future. (Its a bit lengthy but please take the time to read carefully if you have the same problem, the solution is definitely in here)

// You don't have to care about this, just providing some context to make it less confusing
public class MyEntityFile
    {
        public string filename;
        public string downloadURL;        
        public MyEntityFile(string filename, string downloadURL)
        {
            this.filename = filename;
            this.downloadURL = downloadURL;            
        }
    }
    List<MyEntityFile> myEntityFileList = new List<MyEntityFile>();
    int numOfFiles = -1;        // This will be used as the condition to keep the coroutine running, 
    public bool getAllFilesHasError = false;       // This will store the state of the get files operation, so we know if there is an error.

 void OnGetFileFailure(PlayFabError error)
    {
        numOfFiles = myEntityFileList.Count;        // Set the numOfFiles = myEntityFileList to prevent infinite loop
        getAllFilesHasError = true;                 // Set the error state, so we can handle it somewhere
        Debug.LogError(error.GenerateErrorReport());
        GlobalFileLock -= 1;
    }

    public IEnumerator LoadAllFilesCoroutine()
    {
        if (GlobalFileLock != 0)
            throw new Exception("This example overly restricts file operations for safety. Careful consideration must be made when doing multiple file operations in parallel to avoid conflict.");
        GlobalFileLock += 1; // Start GetFiles
        var request = new PlayFab.DataModels.GetFilesRequest { Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType } };
        PlayFabDataAPI.GetFiles(request, OnGetFileMeta, OnGetFileFailure);      // I changed the error function to a custom function to prevent potential infinite loop
        yield return new WaitUntil(() => myEntityFileList.Count == numOfFiles); // We will wait for this operation to complete ONLY if we receive all the entity files
        // You can perform your operation here, at this point, all entity files will be added to your list
    }

    void OnGetFileMeta(PlayFab.DataModels.GetFilesResponse result)
    {
        Debug.Log("Loading " + result.Metadata.Count + " files");
        numOfFiles = result.Metadata.Count;             // result.Metadata.Count contains the number of file we have in our entity database, we want to set this number as our condition, we will keep waiting until our list become as big as this number - which signify that it has added all elements
        _entityFileJson.Clear();
        foreach (var eachFilePair in result.Metadata)
        {
            _entityFileJson.Add(eachFilePair.Key, null);
            GetActualFile(eachFilePair.Value);
        }
	// You can't set the condition here as it will run before GetActualFile Operation is completed        
        GlobalFileLock -= 1; // Finish GetFiles
    }

    void GetActualFile(PlayFab.DataModels.GetFileMetadata fileData)
    {
        GlobalFileLock += 1; // Start Each SimpleGetCall
        PlayFabHttp.SimpleGetCall(fileData.DownloadUrl,
            result => {
            _entityFileJson[fileData.FileName] = Encoding.UTF8.GetString(result);
            GlobalFileLock -= 1;
            // Grabbing the file name and download url here and adding it to my list
            myEntityFileList.Add(new MyEntityFile(fileData.FileName, fileData.DownloadUrl));                     
            }, // Finish Each SimpleGetCall
            error => { Debug.Log(error); }
        );
    }

The idea here is that I am using the number of files as the condition on when I want to end the function. If the number of elements in my list != the number of files we have in the entity database (we can find the number of files in result.Metadata.count) we will yield return new WaitUntil (to keep waiting)

Bonus

If you want to run some code after all the files are loaded, but you don't want to put the code within the coroutine, you can do this

IEnumerator DoSomethingAfterAllFilesAreLoaded()
    {
	// This will load all the files
        yield return StartCoroutine(LoadAllFilesCoroutine());
	// From here onwards, all files are loaded
        // You can do something with the file list or any other data you have collected in the coroutine
	// i.e. loop through your entity file list	
	foreach(MyEntityFile myFile in myEntityFileList){
		Debug.Log(myFile.filename);	// This will print all the filename of the the files you have loaded previously!
	}
    }

I hope this is helpful for someone.

Thanks so much to Citrus for the inspiration to this answer. Cheers!

1 comment
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Citrus Yan avatar image Citrus Yan commented ·

Thanks for sharing this with the community!

0 Likes 0 ·
Citrus Yan avatar image
Citrus Yan answered

@JJConsider this: a flag is still used in this case, just wrap all the logic inside a corroutine and use yield instructions to wait for the flag to be updated: the basic flow would be:

public List<myEntityFile> myEntityFileList = new List<myEntityFile>();
public bool isDone = false
IEnumerator LoadAllFiles()
    {
        if (GlobalFileLock != 0)
            throw new Exception("This example overly restricts file operations for safety. Careful consideration must be made when doing multiple file operations in parallel to avoid conflict.");
        GlobalFileLock += 1; // Start GetFiles
        var request = new PlayFab.DataModels.GetFilesRequest { Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType } };
        PlayFabDataAPI.GetFiles(request, OnGetFileMeta, OnSharedFailure);

        yield return new WaitUntil(() => isDone == true); // do the following only when isDone is true, 

        List<myEntityFile> myFileList = ClassA.classA.myEntityFileList; // then my variables will now store all the info, and I can do something with the data
        foreach(myEntityFile myFile in myFileList){
    // do something
        }
    }
 
 
    void OnGetFileMeta(PlayFab.DataModels.GetFilesResponse result)
    {
        Debug.Log("Loading " + result.Metadata.Count + " files");
        _entityFileJson.Clear();
        foreach (var eachFilePair in result.Metadata)
        {
            _entityFileJson.Add(eachFilePair.Key, null);
            GetActualFile(eachFilePair.Value);
        }
        // when all the files are loaded, set isDone to true
        isDone == true

        GlobalFileLock -= 1; // Finish GetFiles
    }
 
 
    void GetActualFile(PlayFab.DataModels.GetFileMetadata fileData)
    {
        GlobalFileLock += 1; // Start Each SimpleGetCall
        PlayFabHttp.SimpleGetCall(fileData.DownloadUrl,
            result => {
            _entityFileJson[fileData.FileName] = Encoding.UTF8.GetString(result);
            GlobalFileLock -= 1;
            Debug.Log("My file is " + fileData.FileName);
            Debug.Log("My file download url is " + fileData.DownloadUrl);
            myEntityFileList.Add(new MyEntityFile(fileData.FileName, fileData.DownloadUrl));
            }, // Finish Each SimpleGetCall
            error => { Debug.Log(error); }
        );
    }
    void Start()
    {
        //run this in Start();
        StartCoroutine(LoadAllFiles());

    }
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.