I am using Parse for a game that I'm developing.
Everything is ok until I try to uplad a file, no matters his extension I always get this Error : "get_version can only be called from main thread"
URL=http://www.hostingpics.net/viewer.php?id=764075parseError.png
and this is my script :
byte[] data = System.Text.Encoding.UTF8.GetBytes("Working at Parse is great!");
ParseFile file = new ParseFile("resume.txt", data);
Task saveTask = file.SaveAsync();
var player = new ParseObject ("FilesLibrary");
player ["Number"] = 155;
player ["Files"] = file;
saveTask = player.SaveAsync();
I have tried to place this script in different places but I always get the same problem.
You need to make sure the file is uploaded to Parse first, before you try to save it to the db object.
The Parse Unity documentation is not very good as it doesn't fit Unity's threading model. I would suggest using co-routines to do all your Parse calls.
I posted this on Unity Answers as well, but you get the idea..
// The file first needs to be uploaded to Parse before it can be saved to the object in the database
ParseFile _playerFile;
public void CreatePlayer()
{
StartCoroutine (UploadPlayerFile ((response) => {
if(response == 1)
StartCoroutine(CreateProjectAsync());
else
Debug.LogError("The file could not be uploaded");
}));
}
IEnumerator UploadPlayerFile(Action <int> callback)
{
var fileBytes = System.IO.File.ReadAllBytes (#"C:\myfile.jpg");
_playerFile = new ParseFile ("file.jpg", fileBytes, "image/jpeg");
var saveTask = _playerFile.SaveAsync ();
while (!saveTask.IsCompleted)
yield return null;
if (saveTask.IsFaulted) {
Debug.LogError ("An error occurred while uploading the player file : " + saveTask.Exception.Message);
callback (-1);
} else {
callback (1);
}
}
IEnumerator CreateProjectAsync()
{
ParseObject player = new ParseObject ("Player");
player ["Number"] = 111;
player ["Files"] = _playerFile;
var saveTask = player.SaveAsync ();
while (!saveTask.IsCompleted)
yield return null;
if (saveTask.IsFaulted) {
Debug.LogError ("An error occurred while creating the player object : " + saveTask.Exception.Message);
} else {
Debug.Log ("Player created successfully");
}
}
I guess you need force your Parse queries to execute on Main Thread. you can achieve this by
public readonly static Queue<Action> ExecuteOnMainThread = new Queue<Action> ();
Add your query Method in this Queue. Like
ExecuteOnMainThread.Enqueue (() => {
StartCoroutine (SaveData (ID, playerName)); });
In your SaveData() Coroutine you need to execute your Parse queries.
and finally:
void Update()
{
while (ExecuteOnMainThread != null && ExecuteOnMainThread.Count > 0)
{
Action currentAction = ExecuteOnMainThread.Dequeue();
if (currentAction != null)
{
currentAction.Invoke ();
}
}
}
Weird, the same script works fine with unity 4.6 but not 5.1 !
Related
I just got done adding Xbox support code to my project, and have run into at least two issues.
The first involves save data sync which is working just fine, however when the game reads the user's login data on Windows it behaves as if login has not been completed - no gamertag is displayed in the corner, and the login provider throws error 0x87DD0005 regardless of the number of retry attempts.
Execution of the code is just fine on Xbox - only Windows seems to be affected by this. I'm also targeting the creator's showcase initially (or at least until I can get to where I'm ready for another run at ID#Xbox) so achievements and the like aren't a concern right now.
The following is the code I'm using (and in no particular order):
public void doStartup()
{
getData(-1);
for (int i = 0; i <= 5; i++)
{
getData(i);
}
ContentViewport.Source = new Uri("ms-appx-web:///logo.html");
}
public async void getData(int savefileId)
{
var users = await Windows.System.User.FindAllAsync();
string c_saveBlobName = "Advent";
//string c_saveContainerDisplayName = "GameSave";
string c_saveContainerName = "file" + savefileId;
if (savefileId == -1) c_saveContainerName = "config";
if (savefileId == 0) c_saveContainerName = "global";
GameSaveProvider gameSaveProvider;
GameSaveProviderGetResult gameSaveTask = await GameSaveProvider.GetForUserAsync(users[0], "00000000-0000-0000-0000-00006d0be05f");
//Parameters
//Windows.System.User user
//string SCID
if (gameSaveTask.Status == GameSaveErrorStatus.Ok)
{
gameSaveProvider = gameSaveTask.Value;
}
else
{
return;
//throw new Exception("Game Save Provider Initialization failed");;
}
//Now you have a GameSaveProvider
//Next you need to call CreateContainer to get a GameSaveContainer
GameSaveContainer gameSaveContainer = gameSaveProvider.CreateContainer(c_saveContainerName);
//Parameter
//string name (name of the GameSaveContainer Created)
//form an array of strings containing the blob names you would like to read.
string[] blobsToRead = new string[] { c_saveBlobName };
// GetAsync allocates a new Dictionary to hold the retrieved data. You can also use ReadAsync
// to provide your own preallocated Dictionary.
GameSaveBlobGetResult result = await gameSaveContainer.GetAsync(blobsToRead);
string loadedData = "";
//Check status to make sure data was read from the container
if (result.Status == GameSaveErrorStatus.Ok)
{
//prepare a buffer to receive blob
IBuffer loadedBuffer;
//retrieve the named blob from the GetAsync result, place it in loaded buffer.
result.Value.TryGetValue(c_saveBlobName, out loadedBuffer);
if (loadedBuffer == null)
{
//throw new Exception(String.Format("Didn't find expected blob \"{0}\" in the loaded data.", c_saveBlobName));
}
DataReader reader = DataReader.FromBuffer(loadedBuffer);
loadedData = reader.ReadString(loadedBuffer.Length);
if (savefileId == -1)
{
try
{
System.IO.File.WriteAllText(ApplicationData.Current.TemporaryFolder.Path + "\\config.json", loadedData);
}
catch { }
}
else if (savefileId == 0)
{
try
{
System.IO.File.WriteAllText(ApplicationData.Current.TemporaryFolder.Path + "\\global.json", loadedData);
}
catch { }
}
else
{
try
{
System.IO.File.WriteAllText(ApplicationData.Current.TemporaryFolder.Path + "\\file" + savefileId + ".json", loadedData);
}
catch { }
}
}
}
public async void InitializeXboxGamer()
{
try
{
XboxLiveUser user = new XboxLiveUser();
if (user.IsSignedIn == false)
{
SignInResult result = await user.SignInSilentlyAsync(Window.Current.Dispatcher);
if (result.Status == SignInStatus.UserInteractionRequired)
{
result = await user.SignInAsync(Window.Current.Dispatcher);
}
}
System.IO.File.WriteAllText(ApplicationData.Current.TemporaryFolder.Path + "\\curUser.txt", user.Gamertag);
doStartup();
}
catch (Exception ex)
{
// TODO: log an error here
}
}
I finally managed to figure out why the Xbox was working but Windows was not: it was a platform support issue. In the game's creator's dashboard for Xbox Live there's a settings window that allows the support of the game to be determined. Because I originally had separate builds for Xbox and Windows, only the Xbox support item was checked, so I went ahead and also checked off for Desktop support. After saving the changes, I resubmitted with the new configuration and now it works properly.
So, I am using Firebase Realtime Database for fetching a user values. I first check using DataSnapShot if a certain user exists or not. And then, if it exists, I call SceneManager.LoadScene (index). All the logs are correctly shown in the console. But SceneManager.LoadScene method won't work.
Here is the code block:
void authWithFirebaseFacebook(string accessToken)
{
Firebase.Auth.Credential credential =
Firebase.Auth.FacebookAuthProvider.GetCredential(accessToken);
auth.SignInWithCredentialAsync(credential).ContinueWith(task => {
if (task.IsCanceled) {
return;
}
if (task.IsFaulted) {
return;
}
loginSuccesful();
});
}
void loginSuccesful()
{
// SOME CODE HERE THEN (ONLY ASSIGNMENTS, NO TASK OR ANYTHING)
SaveUser();
}
public async void SaveUser()
{
DataSnapshot snapShot = await playerExistsAlready();
if (snapShot.Exists)
{
Debug.Log("Continue Fetching the details for user");
Debug.Log("Values fetched for new player...:" + snapShot.GetValue(true));
Debug.Log("Convert JSON to Object");
string json = snapShot.GetValue(true).ToString();
PlayerData newPlayer = JsonUtility.FromJson<PlayerData>(json);
UserManager.instance.player = newPlayer;
Debug.Log("User Creaated!");
Debug.Log("New Values are:" + newPlayer.DisplayName + " .." + newPlayer.PhotoURL) ;
Debug.Log("Load Main Menu Scene Now...");
SceneManager.LoadScene(1);
}
else
{
saveNewUser();
}
}
async Task<DataSnapshot> playerExistsAlready()
{
DataSnapshot dataSnapShot = await _database.GetReference("users/" + UserManager.instance.currentUserID ).GetValueAsync();
return dataSnapShot;
}
The output:
Continue Fetching the details for user
Values fetched for new player...: (player data here)
Convert JSON to Object
User Created!
New Values are : (values here)
Load Main Menu Scene Now...
The issue is that your code is running on a separate background thread. Your main thread will not "know" that anything was called by a thread. In general most of the Unity API can not be called from a thread .. and it makes no sense to do.
Firebase specifically for that provides an extension method ContinueWithOnMainThread which does exactly that: Makes sure that the callback is executed in the Unity main thread.
void authWithFirebaseFacebook(string accessToken)
{
Firebase.Auth.Credential credential = Firebase.Auth.FacebookAuthProvider.GetCredential(accessToken);
auth.SignInWithCredentialAsync(credential).ContinueWithOnMainThread(task =>
{
if (task.IsCanceled || task.IsFaulted)
{
return;
}
loginSuccesful();
});
}
void loginSuccesful()
{
// SOME CODE HERE THEN (ONLY ASSIGNMENTS, NO TASK OR ANYTHING)
SaveUser();
}
public void SaveUser()
{
_database.GetReference("users/" + UserManager.instance.currentUserID ).GetValueAsync().ContinueWithOnMainThread(task =>
{
if (task.IsCanceled || task.IsFaulted)
{
return;
}
var snapShot = task.Result;
if (snapShot.Exists)
{
Debug.Log("Continue Fetching the details for user");
Debug.Log("Values fetched for new player...:" + snapShot.GetValue(true));
Debug.Log("Convert JSON to Object");
string json = snapShot.GetValue(true).ToString();
PlayerData newPlayer = JsonUtility.FromJson<PlayerData>(json);
UserManager.instance.player = newPlayer;
Debug.Log("User Creaated!");
Debug.Log("New Values are:" + newPlayer.DisplayName + " .." + newPlayer.PhotoURL) ;
Debug.Log("Load Main Menu Scene Now...");
SceneManager.LoadScene(1);
}
else
{
saveNewUser();
}
}
}
Alternatively an often used pattern is a so called "main thread dispatcher" (I know there is an asset for that but this one requires a lot of refactoring actually ;) ).
But you can easily create one yourself. Usually it looks somewhat like
public class YourClass : MonoBehaviour
{
private ConcurrentQueue<Action> mainThreadActions = new ConcurrentQueue<Action>();
private void Update()
{
while(mainThreadActions.Count > 0 && mainThreadActions.TryDequeue(out var action))
{
action?.Invoke();
}
}
}
so any thread can just add an action to be executed on the main thread via e.g.
void authWithFirebaseFacebook(string accessToken)
{
Firebase.Auth.Credential credential = Firebase.Auth.FacebookAuthProvider.GetCredential(accessToken);
auth.SignInWithCredentialAsync(credential).ContinueWith(task =>
{
if (task.IsCanceled || task.IsFaulted)
{
return;
}
// either as method call
mainThreadActions.Enqueue(loginSuccesful);
});
}
void loginSuccesful()
{
// SOME CODE HERE THEN (ONLY ASSIGNMENTS, NO TASK OR ANYTHING)
SaveUser();
}
public void SaveUser()
{
// or as lambda exopression
database.GetReference("users/" + UserManager.instance.currentUserID ).GetValueAsync().ContinueWith(task =>
{
mainThreadActions.Enqueue(() =>
{
if (task.IsCanceled || task.IsFaulted)
{
return;
}
var snapShot = task.Result;
if (snapShot.Exists)
{
Debug.Log("Continue Fetching the details for user");
Debug.Log("Values fetched for new player...:" + snapShot.GetValue(true));
Debug.Log("Convert JSON to Object");
string json = snapShot.GetValue(true).ToString();
PlayerData newPlayer = JsonUtility.FromJson<PlayerData>(json);
UserManager.instance.player = newPlayer;
Debug.Log("User Creaated!");
Debug.Log("New Values are:" + newPlayer.DisplayName + " .." + newPlayer.PhotoURL) ;
Debug.Log("Load Main Menu Scene Now...");
SceneManager.LoadScene(1);
}
else
{
saveNewUser();
}
})
});
}
So the problem was the method was not being executed on Main Thread. I guess Unity Methods like SceneManager.Load() etc are only called on a Main Thread.
So, I used a class named UnityMainThreadDispatcher
And then I wrote
IEnumerator loadMainMenuScene()
{
SceneManager.LoadScene(1);
yield return null;
}
And called
UnityMainThreadDispatcher.Instance().Enqueue(loadMainMenuScene());
instead of
SceneManager.LoadScene(1);
I cannot figure out how to make asynchronous Get requests correctly. I have a coroutine with get request. It returns me the room ID that I need when I record a player, but the problem is that all this is done in one function, when creating a room and because of this, the coroutine does not have time to write data. Then this Get request is used in real time to get the necessary IDs that change and the same problem is there.
My Get request:
IEnumerator GetRequest(string uri)
{
using (UnityWebRequest webRequest = UnityWebRequest.Get(Host + uri))
{
yield return webRequest.SendWebRequest();
response = webRequest.downloadHandler.text;
}
}
method:
public int GetID(string entity, string identify, string ID)
{
StartCoroutine(GetRequest(entity + identify));
var json = ParseJson(response);
return int.Parse(json[ID]);
}
And where i call him
public void CreateRoom()
{
TypedLobby sqlLobby = new TypedLobby("myLobby", LobbyType.SqlLobby);
string sqlLobbyFilter = "C0";
SetNickName(userName.text);
RoomOptions roomOptions = new RoomOptions
{
MaxPlayers = 5,
CustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { sqlLobbyFilter, "0" } },
CustomRoomPropertiesForLobby = new string[] { sqlLobbyFilter }
};
if (roomName.text.Length != 0)
{
client.PostRoom(roomName.text);
var roomID =client.GetID("Rooms/", roomName.text, "IDRoom");
client.PostPlayer(PhotonNetwork.NickName, roomID);
PhotonNetwork.CreateRoom(roomName.text, roomOptions, sqlLobby);
}
else notice.EmptyRoomName();
}
It looks like you're trying to linearize a coroutine. "StartCoroutine" does not function like await.
Let's first go over how your GetRequest coroutine works.
IEnumerator GetRequest(string uri)
{
using (UnityWebRequest webRequest = UnityWebRequest.Get(Host + uri))
{
yield return webRequest.SendWebRequest();
response = webRequest.downloadHandler.text;
}
}
The above code creates a new web request, and returns control back to Unity every single frame until the web request completes.
Think of yield return webRequest.SendWebRequest(); like this:
while(!webRequest.isDone)
{
//suspend the coroutine until next frame
yield return null;
}
It's simply suspending the coroutine every frame until there's a response.
Now let's step through your code and see what's happening:
var roomID =client.GetID("Rooms/", roomName.text, "IDRoom");
which calls:
public int GetID(string entity, string identify, string ID)
{
StartCoroutine(GetRequest(entity + identify));
var json = ParseJson(response);
return int.Parse(json[ID]);
}
Since webRequest.SendWebRequest() in your GetRequest method is not instant, response will not contain parseable JSON data until at least the next frame.
Since GetID is called on the frame (and is not a coroutine itself), it performs all of its logic in the frame that it's called. It attempts to ParseJson(response) on an unset response, which is why your coroutine does not have time to write.
How do we fix this?
There's not a one-size-fits-all solution, unfortunately. You'll need to delinearize your logic so that it can occur over multiple frames. A clever use of Queues could serve well here, or putting more logic inside of the CoRoutine could solve it as well.
Here's an example of using CoRoutines, since Queues would likely require more code to monitor/process them:
Move your room configuration logic a coroutine (And rather than nest Coroutines, just call the webrequest from within this one):
public IEnumerator ConfigureRoom(string roomNameText, RoomOptions roomOptions, TypedLobby sqlLobby)
{
client.PostRoom(roomNameText)
using (UnityWebRequest webRequest = UnityWebRequest.Get("Rooms/" + roomNameText))
{
yield return webRequest.SendWebRequest();
response = webRequest.downloadHandler.text;
}
var json = ParseJson(response);
var roomId = int.Parse(json["IDRoom"]);
client.PostPlayer(PhotonNetwork.NickName, roomID);
PhotonNetwork.CreateRoom(roomNameText, roomOptions, sqlLobby);
}
//Inside of your CreateRoom() method
if (roomName.text.Length != 0)
{
StartCoroutine(ConfigureRoom(roomName.text, roomOptions, sqlLobby));
}
This allows your room to be created and the data to be updated all over the course of multiple frames.
Disclaimer: I've typed all of this in browser. There may be minor syntax errors. Please let me know so I may correct them.
Change GetID into a coroutine, give it an Action to use the int in, and use yield return GetRequest(...); instead of StartCoroutine(GetRequest(...));.
IEnumerator GetRequest(string uri)
{
using (UnityWebRequest webRequest = UnityWebRequest.Get(Host + uri))
{
yield return webRequest.SendWebRequest();
response = webRequest.downloadHandler.text;
}
}
method:
public IEnumerator GetID(string entity, string identify, string ID, Action<int> onComplete)
{
yield return GetRequest(entity + identify);
var json = ParseJson(response);
onComplete(int.Parse(json[ID]));
}
And call him
public void CreateRoom()
{
TypedLobby sqlLobby = new TypedLobby("myLobby", LobbyType.SqlLobby);
string sqlLobbyFilter = "C0";
SetNickName(userName.text);
RoomOptions roomOptions = new RoomOptions
{
MaxPlayers = 5,
CustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { sqlLobbyFilter, "0" } },
CustomRoomPropertiesForLobby = new string[] { sqlLobbyFilter }
};
if (roomName.text.Length != 0)
{
client.PostRoom(roomName.text);
StartCoroutine(
client.GetID("Rooms/", roomName.text, "IDRoom",
delegate(int roomID) {
client.PostPlayer(PhotonNetwork.NickName, roomID);
PhotonNetwork.CreateRoom(roomName.text, roomOptions, sqlLobby);
}));
}
else notice.EmptyRoomName();
}
Right now I am developing a VR-application for my Oculus Go. In this Application i need to import files like images, video, audio during runtime. My Problem right now is that the import of these files do work in unity, but doesn't work on my oculus go.
In my project i use a file browser prefab from the asset store. this file browser can be opened during the runtime of my application, but when i want to open a file for example an image, it only turns grey. the image should be loaded to a RawImage-Object.
Filebrowser during runtime
RawImage-Object turns grey
Unity-does work
I dont' understand why this is happening. I have to say I'm new to unity and would appreciate any kind of help! Here is the code of the image-load-script.
public class FileManager : MonoBehaviour {
public RawImage image;
public void OpenExplorer()
{
SimpleFileBrowser.FileBrowser.SetFilters(true, ".txt", ".jpg", ".png", ".mp4");
SimpleFileBrowser.FileBrowser.ShowLoadDialog( (path) => { UpdateImage(path); }, null, false, null, "Select File", "Select");
}
void UpdateImage(string pfad)
{
if (pfad != null)
{
WWW www = new WWW(pfad);
image.texture = www.texture;
var texWidth = www.texture.width;
var texHeight = www.texture.height;
if (texWidth > texHeight)
{
GetComponent<RectTransform>().sizeDelta = new Vector2(1920/2, 1080/2);
}
if (texWidth < texHeight)
{
GetComponent<RectTransform>().sizeDelta = new Vector2(1080/2, 1920/2);
}
}
}
My Problem right now is that the import of these files do work in
unity, but doesn't work on my oculus go.
Most buggy code work on the Editor but you will discover the issue with a code when you deploy or build the project.
Your problem is in the UpdateImage function:
WWW www = new WWW(pfad);
image.texture = www.texture;
...
You are supposed to yield or wait for the WWW request to finish before loading the image. The request asynchronous. Without yielding it, you would be trying to accessing incomplete image leading to a corrupted image or gray image depending on the platform.
Make the following changes:
1.Change the UpdateImage to a coroutine function (void to IEnumerator) then yield the WWW request with yield return www; before accessing the texture from it:
2.Call the UpdateImage function as a coroutine function with StartCoroutine:
Replace:
SimpleFileBrowser.FileBrowser.ShowLoadDialog( (path) => { UpdateImage(path); }, null, false, null, "Select File", "Select");
with
SimpleFileBrowser.FileBrowser.ShowLoadDialog( (path) => { StartCoroutine(UpdateImage(path)); }, null, false, null, "Select File", "Select");
3.Check for error before accessing the Texture. When the WWW request has finished, there could be an error. Check that before using the returned texture or item.
The final code should look like this:
public RawImage image;
public void OpenExplorer()
{
SimpleFileBrowser.FileBrowser.SetFilters(true, ".txt", ".jpg", ".png", ".mp4");
SimpleFileBrowser.FileBrowser.ShowLoadDialog((path) => { StartCoroutine(UpdateImage(path)); }, null, false, null, "Select File", "Select");
}
IEnumerator UpdateImage(string pfad)
{
if (pfad != null)
{
WWW www = new WWW(pfad);
//WAIT UNTIL REQUEST IS DONE!
yield return www;
//Check for error
if (!string.IsNullOrEmpty(www.error))
{
Debug.Log(www.error);
}
else
{
image.texture = www.texture;
var texWidth = www.texture.width;
var texHeight = www.texture.height;
if (texWidth > texHeight)
{
GetComponent<RectTransform>().sizeDelta = new Vector2(1920 / 2, 1080 / 2);
}
if (texWidth < texHeight)
{
GetComponent<RectTransform>().sizeDelta = new Vector2(1080 / 2, 1920 / 2);
}
}
}
}
I have been developing a game in Unity3D in C#. So I have set the game to upload the game save file
to Parse every 2 minutes. When I run the code in Unity it works fine, it saves the game save file locally, and every 2 minutes it uploads it to Parse, it also points to the user who's playing the game. Although when I run the game on a mobile device the file does not upload, I have been refreshing my parse data object in the last half an hour and I still haven't got anything.
Here is the code:
void Start ()
{
if (ParseUser.CurrentUser != null)
{
gapTest = true;
}
}
void Update ()
{
if(gapTest)
{
StartCoroutine(UploadFile());
}
if (uploadSaveFile)
{
OnZipComplete();
uploadSaveFile = false;
}
else if (createNewSaveFile)
{
CreateNewSaveFile();
createNewSaveFile = false;
}
}
IEnumerator UploadFile()
{
gapTest = false;
yield return new WaitForSeconds (120.0F);
if(ParseUser.CurrentUser != null)
{
OnZipComplete();
}
gapTest = true;
}
void OnZipComplete()
{
var query = ParseObject.GetQuery("GameSave").WhereEqualTo("UserObjectId", ParseUser.CurrentUser);
if (existingGameSave == null)
{
query.FindAsync().ContinueWith(t =>
{
IEnumerable<ParseObject> results = t.Result;
int resultCount = 0;
foreach (var result in results)
{
if (resultCount > 0)
{
Debug.LogError("Found more than one save file for user!");
}
else
{
resultCount++;
existingGameSave = result;
uploadSaveFile = true;
}
}
if (resultCount == 0)
{
createNewSaveFile = true;
}
});
}
else
{
UpdateExistingSaveFile();
}
}
private void CreateNewSaveFile()
{
//upload the file to parse
zipPath = Application.persistentDataPath + "/SaveGame.zip";
byte[] data = System.Text.Encoding.UTF8.GetBytes(zipPath);
ParseFile GameSave = new ParseFile("GameSave.zip", data);
var gameSave = new ParseObject("GameSave");
gameSave["UserObjectId"] = ParseUser.CurrentUser;
gameSave["GameSaveFile"] = GameSave;
Task saveTask = gameSave.SaveAsync();
Debug.Log("New Game save file has been uploaded");
}
void UpdateExistingSaveFile()
{
//upload the file to parse
UserIdFile = Application.persistentDataPath + "/UserId.txt";
zipPath = Application.persistentDataPath + "/SaveGame.zip";
byte[] data = System.Text.Encoding.UTF8.GetBytes(zipPath);
ParseFile GameSave = new ParseFile("GameSave.zip", data);
existingGameSave["GameSaveFile"] = GameSave;
Task saveTask = existingGameSave.SaveAsync();
Debug.Log("Existing Game save file has been uploaded");
}
This might be a problem related to permission on android device.
Go to Build Setting > Player Setting > Other Settings, and check "Write Access", by default, it is internal Only which means that the path is for development purpose only.
Try Setting the Write Access as External(SD Card) or add WRITE_ EXTERNAL_STORAGE permission into AndroidManifest.xml. And check Android/file/com.your.appid/files of your sd card if your data is actually getting written on device.
If data is getting written, then it will sure get uploaded on parse.com.