I'm pretty new to the concept of Coroutines. now I got this problem, when I call my Coroutine function it gives an empty object back when I DON'T use
System.Threading.Thread.Sleep();. But when I use System.Threading.Thread.Sleep(int); It waits until the sleep method is completed before continuing the code. I get that it probably has something to do with that they work on the same thread but even my debug functions before the sleep method won't print untill the sleep function is done. could really use some help here!
I tried it with synchronous calls but this just makes my layout freeze.
IEnumerator PostRequestBearerToken(string uri)
{
string authorization = APIHelpers.authenticate("user", "pass");
WWWForm form = new WWWForm();
form.AddField("grant_type", "client_credentials");
using (UnityWebRequest www = UnityWebRequest.Post(uri, form))
{
www.SetRequestHeader("AUTHORIZATION", authorization);
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
}
else
{
Debug.Log("Post request complete!" + " Response Code: " + www.responseCode);
string responseText = www.downloadHandler.text;
Debug.Log("Response Text:" + responseText);
Debug.Log("Form upload complete!");
//deserialize bearertoken
BearerObject myObject = JsonUtility.FromJson<BearerObject>(www.downloadHandler.text);
Debug.Log("json : " + myObject.ToString());
//BearerTokenSingleton.getInstance();
BearerTokenSingleton.getInstance().SetBearerToken(myObject);
Debug.Log(BearerTokenSingleton.getInstance().GetBearerToken().ToString());
}
}
}
void Update()
{
if (!loadFirstFrame)
{
loadFirstFrame = true;
}
else if (loadFirstFrame)
{
StartCoroutine(PostRequestBearerToken(APIHelpers.bearerString));
//go to next scene
System.Threading.Thread.Sleep(5000);
SceneManager.LoadScene(2);
}
}
It is because of the SceneManager.LoadScene method. Coroutines run in separate threads. Since you start the coroutine and immediately load a new scene, there is no time to run the coroutine before the current scene (and script object) is unloaded. The Thread.Sleep call introduces a delay that gives enough time for the coroutine to finish before the scene load.
To get around this, you can use a flag (actually I use two, so you don't kick off the coroutine multiple times):
private bool _postComplete = false;
private bool _startedPost = false;
IEnumerator PostRequestBearerToken(string uri)
{
string authorization = APIHelpers.authenticate("user", "pass");
WWWForm form = new WWWForm();
form.AddField("grant_type", "client_credentials");
using (UnityWebRequest www = UnityWebRequest.Post(uri, form))
{
www.SetRequestHeader("AUTHORIZATION", authorization);
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
}
else
{
Debug.Log("Post request complete!" + " Response Code: " + www.responseCode);
string responseText = www.downloadHandler.text;
Debug.Log("Response Text:" + responseText);
Debug.Log("Form upload complete!");
//deserialize bearertoken
BearerObject myObject = JsonUtility.FromJson<BearerObject>(www.downloadHandler.text);
Debug.Log("json : " + myObject.ToString());
//BearerTokenSingleton.getInstance();
BearerTokenSingleton.getInstance().SetBearerToken(myObject);
Debug.Log(BearerTokenSingleton.getInstance().GetBearerToken().ToString());
}
_postComplete = true;
}
}
And then use that in the Update method:
void Update()
{
if (!loadFirstFrame)
{
loadFirstFrame = true;
}
else if (loadFirstFrame)
{
if (!_startedPost)
{
StartCoroutine(PostRequestBearerToken(APIHelpers.bearerString));
_startedPost = true;
}
if (_postComplete)
SceneManager.LoadScene(2);
}
}
Related
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);
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();
}
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).
At present, I was facing some sort of weird problem. I turn off my internet connection to put some handling code over their. I thought it will return me some error code but its just giving me blank response rather than showing exception. I got following debug output when I print more details on screen.
Basically I want to show dialog box when there is no internet connection. But how to handle this situation!!!
Because there is no json response from server side then also there is some bytes I am receiving from server. Here is my code:
Dictionary<string,string> headerDisc = new Dictionary<string, string> ();
headerDisc.Add ("Api-Key", "You API Key");
WWW www = new WWW (GameConstants.CONTESTANT_LIST_BASE_URL, new byte[] { (byte)0 }, headerDisc);
yield return www;
if (www.error == null) {
Debug.Log ("bytes: " + www.bytes.Length);
Debug.Log ("size: " + www.size);
Debug.Log ("length: " + www.text.Length);
Debug.Log ("Data: " + www.text);
if (www.text.Length <= 0) {
AppManager.Instance.DialogMessage = "No Server Response Found!";
Camera.main.SendMessage ("ActivateDialogBoxPanel", true, SendMessageOptions.DontRequireReceiver);
} else {
JSONObject jsonObj = new JSONObject (www.text);
JSONObject messageObj = jsonObj [TAG_MESSAGE];
string successValueStr = jsonObj [TAG_SUCCESS].ToString ();
if (successValueStr.Equals (VALUE_TRUE))
// success
else
// fail
}
} else {
Debug.Log ("Error: " + www.error);
AppManager.Instance.DialogMessage = "Error:" + www.error;
Camera.main.SendMessage ("ActivateDialogBoxPanel", true, SendMessageOptions.DontRequireReceiver);
}
Please give me some suggestion in this. If you want some more information then I am available.
As i understand you want to check if internet connection is disabled show a message to user . for example you can write something like this.
IEnumerator checkInternetConnection(Action<bool> action){
WWW www = new WWW("http://google.com");
yield return www;
if (www.error != null) {
action (false);
} else {
action (true);
}
}
then in your Start() function write this.
void Start(){
StartCoroutine(checkInternetConnection((isConnected)=>{
// handle connection status here
}));
}