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!
Looks like you already achieved this, May I know which problem you're having, exactly?
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.
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;
Answer by JJ · Jan 19, 2021 at 03:19 PM
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!
Answer by Citrus Yan · Jan 19, 2021 at 08:48 AM
@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()); }