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();
}
Related
public bool Login(string LoginName,string username,string password)
{
RequestHeader contentTypeHeader = new RequestHeader
{
Key = "Content-Type",
Value = "application/json"
};
JsonData data = new JsonData();
data["username"] = username;
data["password"] = password;
StartCoroutine(NetworkManage.Instance.HttpPost(tempapiConfig[LoginName], data.ToJson(), (r) =>
{
testing = OnRequestLoginComplete(r);
Debug.Log(testing);
}, new List<RequestHeader>
{
contentTypeHeader
}));
return testing;
}
bool OnRequestLoginComplete(Response response)
{
Debug.Log($"Status Code: {response.StatusCode}");
Debug.Log($"Data: {response.Data}");
Debug.Log($"Error: {response.Error}");
return true;
}
return testing;
So the problem is like as you can see I started a coroutine where the testing= the callback which return the bool.
But the login returned the testing before the coroutine is finish. So let’s say the httpost need two second before able to give the callback to testing. But the login function return testing right away without waiting which makes the returned value is the wrong one
The testing Boolean will wait for the call back, so let say I want to return the value of the testing. The login return it right away before it get hear from the callback.
Your way is a bit unconventional, there is no need to return true/false when the response is the state of the request:
public IEnumerator Login(string LoginName,string username,string password, Action<string> jsonResponse)
{
RequestHeader contentTypeHeader = new RequestHeader
{
Key = "Content-Type",
Value = "application/json"
};
JsonData data = new JsonData();
data["username"] = username;
data["password"] = password;
yield return StartCoroutine(NetworkManage.Instance.HttpPost(tempapiConfig[LoginName],
data.ToJson(),
(r) =>
{
jsonResponse?.Invoke(r);
}, new List<RequestHeader>
{
contentTypeHeader
}));
}
And this could be used as such:
IEnumerator Start()
{
string response = null;
yield return StartCoroutine(Login(loginname,username, pwd,
(r) => response = r));
if (string.IsNullOrEmpty(response)){
Debug.LogError("No response");
}
ResponseType rt = JsonUtility.FromJson<ResponseType>(response);
}
I'm working on a blackjack game in Unity. I have a coroutine called InitialDeal() which handles the flow when a player clicks a BET button. It should fetch a random number from the backend API and display the dealing of the cards one by one.
Because fetching data from the API must use UnityWebRequest because the game is browser based, it is done in another coroutine called UnityApiCall.
So basically I start coroutine A (InitialDeal()) in which I start coroutine B (UnityWebRequest), and I need that every time I get the response from API, I stop the coroutine A for a number of seconds while the card is dealt.
This is what I tried. This is coroutine B that calls my API:
private IEnumerator UnityApiCall(String method, String path, String pathParams, String body, Action<JObject> result)
{
Debug.Log("In UnityApiCall...");
using UnityWebRequest www = new UnityWebRequest(apiUrl + path);
www.method = method;
if(www.method == "POST") {
byte[] byteArray = Encoding.UTF8.GetBytes(body);
UploadHandlerRaw uH = new UploadHandlerRaw(byteArray);
www.uploadHandler = uH;
}
www.downloadHandler = new DownloadHandlerBuffer();
www.SetRequestHeader("Session-ID", sessionId);
yield return www.SendWebRequest();
if (www.result == UnityWebRequest.Result.ConnectionError)
{
Debug.Log(www.error);
if (result != null)
result(new JObject("{error: " + www.error + "}"));
}
else
{
Debug.Log(www.method + " successful!");
String str = System.Text.Encoding.Default.GetString(www.downloadHandler.data);
JObject jsonResponse = JObject.Parse(str);
if (result != null)
result(jsonResponse);
}
}
This is the coroutine A:
private IEnumerator InitialDeal()
{
// initial deal
ChangeGameState(GameState.OnDealing);
int betAmount = (int) ChipManager._Instance.stacks[(int) StackType.Standard].GetValue();
Debug.Log("Bet value: " + betAmount);
// int betAmount = 10;
BetRequest betRequest = new BetRequest();
betRequest.amount = betAmount;
string pathParams = "";
string body = JsonConvert.SerializeObject(betRequest);
yield return UnityApiCall("POST", "/bet", pathParams, body, (JObject response) =>
{
Debug.Log("response: ");
Debug.Log(response);
for (int i = 0; i < 2; i++)
{
// CardData cardData = new CardData(CardData.Suit.Hearts, CardData.Rank.Four);
// CardData cardData2 = new CardData(CardData.Suit.Spades, CardData.Rank.Five);
var playerNum = response["data"]["playerCards"]["0"]["cards"][i]["number"];
var playerSymbol = response["data"]["playerCards"]["0"]["cards"][i]["symbol"]["symbol"];
var dealerNum = response["data"]["dealerCards"][i]["number"];
var dealerSymbol = response["data"]["dealerCards"][i]["symbol"]["symbol"];
var playerCard = MapCard(playerNum.ToObject<int>(), playerSymbol.ToObject<string>());
var dealerCard = MapCard(dealerNum.ToObject<int>(), dealerSymbol.ToObject<string>());
DealQueue.DealCard(player.DealCard(playerCard));
yield return new WaitForSeconds(DEAL_WAIT_TIME);
if (i == 0)
{
DealQueue.DealCard(dealer.DealCard(dealerCard, FlipType.FlipDown));
}
else
{
DealQueue.DealCard(dealer.DealCard(dealerCard, FlipType.FlipUp));
}
yield return new WaitForSeconds(DEAL_WAIT_TIME);
}
ChangeGameState(GameState.OnPlay);
});
}
This is how I tried to stop the execution for a few seconds (in the callback of UnityApiCall in the snippet above):
yield return new WaitForSeconds(DEAL_WAIT_TIME);
However, this doesn't work since it's in the callback function, and I cannot use yield there.
This is how I called the coroutine A:
StartCoroutine(InitialDeal());
You are right in the way you wait for the UnityApiCall. The callback does get the JObject but it is only local to the callback. Instead, assign it to another JObject that is method scope:
JObject jObject = null;
yield return UnityApiCall("POST", "/bet", pathParams, body,
(response) => jObject = response);
// You can use the jObject
Debug.Log("Response " + jObject );
// Your code here processing jObject
// You are in the coroutine so you can yield all you need
yield return new WaitForSeconds(DEAL_WAIT_TIME);
ChangeGameState(GameState.OnPlay);
I'm trying to send a POST request in Unity, but I can't do that as the function that is supposed to do that is skipped, I'm not sure why.
I have added logs to have an idea of what happens and "(post json) called" never appears even though the text saying that has gone past the function ((send game played): after postjson) appears in the logs.
private static void SendGamePlayed() {
int incrementedGamePlayed = GetGamePlayed () + 1;
EventObject anEvent = CreateEvent(EVENT_GAME_PLAYED, incrementedGamePlayed, null, null);
AbcEvent aAbcEvent = new AbcEvent (bundleID, appToken, anEvent);
string json = JsonUtility.ToJson (aAbcEvent);
// test
Debug.Log("(send game played): " + json);
PostJSON (json, EVENT_ROUTE);
Debug.Log ("(send game played): after postjson");
SetGamePlayed (incrementedGamePlayed);
}
private static IEnumerator PostJSON(string jsonString, string route) {
// test
Debug.Log("(post json) called");
string url = SERVER_URL + route;
var request = new UnityWebRequest(url, "POST");
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonString);
request.uploadHandler = (UploadHandler) new UploadHandlerRaw(bodyRaw);
request.downloadHandler = (DownloadHandler) new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
Debug.Log ("(post json) before sending");
yield return request.Send();
Debug.Log ("(post json) after sending");
if (request.error != null) {
Debug.Log("request error: " + request.error);
} else {
Debug.Log("request success: " + request.downloadHandler.text);
}
}
Thanks for your help!
You're using Unity's feature for asynchronous operations. To understand the concepts, read up on generators and Unity coroutines. To run your function, call the StartCoroutine function like this:
StartCoroutine(PostJSON (json, EVENT_ROUTE));
Edit:
More specifically: You're creating a coroutine (PostJson) that calls an asynchronous function (Send).
I am creating a HoloLens app using Unity which has to take data from a REST API and display it.
I am currently using WWW datatype to get the data and yield return statement in a coroutine that will be called from the Update() function. When I try to run the code, I get the latest data from the API but when someone pushes any new data onto the API, it does not automatically get the latest data in real time and I have to restart the app to see the latest data.
My Code:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
public class TextChange : MonoBehaviour {
// Use this for initialization
WWW get;
public static string getreq;
Text text;
bool continueRequest = false;
void Start()
{
StartCoroutine(WaitForRequest());
text = GetComponent<Text>();
}
// Update is called once per frame
void Update()
{
}
private IEnumerator WaitForRequest()
{
if (continueRequest)
yield break;
continueRequest = true;
float requestFrequencyInSec = 5f; //Update after every 5 seconds
WaitForSeconds waitTime = new WaitForSeconds(requestFrequencyInSec);
while (continueRequest)
{
string url = "API Link goes Here";
WWW get = new WWW(url);
yield return get;
getreq = get.text;
//check for errors
if (get.error == null)
{
string json = #getreq;
List<MyJSC> data = JsonConvert.DeserializeObject<List<MyJSC>>(json);
int l = data.Count;
text.text = "Data: " + data[l - 1].content;
}
else
{
Debug.Log("Error!-> " + get.error);
}
yield return waitTime; //Wait for requestFrequencyInSec time
}
}
void stopRequest()
{
continueRequest = false;
}
}
public class MyJSC
{
public string _id;
public string author;
public string content;
public string _v;
public string date;
}
This is happening because resources caching is enabled on the Server.
Three possible solutions I know about:
1.Disable resources caching on the server. Instructions are different for every web server. Usually done in .htaccess.
2.Make each request with unique timestamp. The time should in Unix format.
This method will not work on iOS. You are fine since this is for HoloLens.
For example, if your url is http://url.com/file.rar, append ?t=currentTime at the end. currentTime is the actual time in Unix Format.
Full example url: http://url.com/file.rar?t=1468475141
Code:
string getUTCTime()
{
System.Int32 unixTimestamp = (System.Int32)(System.DateTime.UtcNow.Subtract(new System.DateTime(1970, 1, 1))).TotalSeconds;
return unixTimestamp.ToString();
}
private IEnumerator WaitForRequest()
{
string url = "API Link goes Here" + "?t=" + getUTCTime();
WWW get = new WWW(url);
yield return get;
getreq = get.text;
//check for errors
if (get.error == null)
{
string json = #getreq;
List<MyJSC> data = JsonConvert.DeserializeObject<List<MyJSC>>(json);
int l = data.Count;
text.text = "Data: " + data[l - 1].content;
}
else
{
Debug.Log("Error!-> " + get.error);
}
}
3.Disable Cache on the client side by supplying and modifying the Cache-Control and Pragma headers in the request.
Set Cache-Control header to max-age=0, no-cache, no-store then set Pragma header to no-cache.
I suggest you do this with UnityWebRequest instead of the WWW class. First, Include using UnityEngine.Networking;.
Code:
IEnumerator WaitForRequest(string url)
{
UnityWebRequest www = UnityWebRequest.Get(url);
www.SetRequestHeader("Cache-Control", "max-age=0, no-cache, no-store");
www.SetRequestHeader("Pragma", "no-cache");
yield return www.Send();
if (www.isError)
{
Debug.Log(www.error);
}
else
{
Debug.Log("Received " + www.downloadHandler.text);
}
}
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 !