How to make coroutines run in order? - c#

This is using Unity3D. I have three coroutines: GetJSONFromSelectedSubreddit(), LoadMoreMemes(), and a function in a separate script that needs to be able to access the array of memes through the GetNewMemes() function (must return type Meme[]). LoadNewMemes() produces. The thing is, LoadMoreMemes() requires the json to work, so they have to run in the mentioned order. If you need the functions, here they are:
public void GetNewMemes(string subReddit, int count)
{
SetSelectedSubreddit(subReddit);
memesAtATime = count;
subJSON = null;
StartCoroutine(GetJSONFromSelectedSubreddit());
StartCoroutine(LoadMoreMemes());
}
IEnumerator GetJSONFromSelectedSubreddit()
{
gettingJSON = true;
WWW requester = new WWW("https://www.reddit.com/r/" + selectedSub + "/new.json?sort=new&count=25&after=" + postIndex);
yield return requester;
subJSON = requester.text;
json = new JSONObject(subJSON);
gettingJSON = false;
}
IEnumerator LoadMoreMemes()
{
while (gettingJSON)
yield return new WaitForSeconds(0.1f);
for (int i = 0; i < memesAtATime; i++)
{
yield return StartCoroutine(GetUserPostKarma(json["data"]["children"][i]["data"]["author"].str));
string sourceURL = json["data"]["children"][i]["data"]["preview"]["images"][0]["source"]["url"].str;
sourceURL = sourceURL.Replace("&", "&");
yield return StartCoroutine(GrabImage(sourceURL));
Meme currentMeme = new Meme(
json["data"]["children"][i]["data"]["preview"]["images"][0]["source"]["url"].str,
authorPostKarma,
(int) json["data"]["children"][i]["data"]["score"].i,
json["data"]["children"][i]["data"]["permalink"].str,
json["data"]["children"][i]["data"]["title"].str,
currentBitmap
);
Debug.Log(currentMeme.cost);
memes[i] = currentMeme;
}
}
Here's the other script:
void Start ()
{
RedditCommunicator redditCommunicator = GetComponent<RedditCommunicator>();
redditCommunicator.GetNewMemes("me_irl", 1);
Meme[] memes = redditCommunicator.GetCurrentMemes();
Debug.Log(memes[0].currentScore);
redditCommunicator.SpawnOneMeme(memes[0]);
}
Each function works fine on its own, but they need to wait for each other to finish, as well as run in the correct order to work. I'd like the functions to stay separate so I can call them individually in the future. memes is a private variable, and the one I'd like to pass to the other script calling these functions. If you don't think I've tried my options Googling and solving this on my own, just believe me, I've done my best. Thanks for your help in advance. If you need more information, just ask me for it. The current state of this code is it returns memes to early, before the coroutines can finish, resulting in empty memes.

You can yield a Coroutine in an IEnumerator which will halt the progression of that Coroutine until that Coroutine is done. Like this:
void Start()
{
StartCoroutine(DoThings((text) => {
Debug.Log("Dothings told me: " + text);
}));
}
IEnumerator DoThings(Action<string>() callback)
{
yield return StartCoroutine(DoThisFirst());
callback("Returning a value mid-method!");
yield return StartCoroutine(ThenThis());
Debug.Log(3);
}
IEnumerator DoThisFirst()
{
yield return new WaitForSeconds(1);
Debug.Log(1);
}
IEnumerator ThenThis()
{
yield return new WaitForSeconds(1);
Debug.Log(2);
}

Problem is that GetJSONFromSelectedSubreddit and LoadNewMemes methods are called as two "parallel" coroutines in GetNewMemes method.
If you do not need to run a coroutine "asynchronously", you can just enumerate through it:
public void GetNewMemes(string subReddit, int count)
{
SetSelectedSubreddit(subReddit);
memesAtATime = count;
subJSON = null;
var enumerator = GetJSONFromSelectedSubreddit();
while (enumerator.MoveNext());
enumerator = LoadNewMemes();
while (enumerator.MoveNext());
}

Related

How to activate and deactivate GameObjects with a delay in Untiy

I am working on a game, that has a keypad puzzle. A specific key combination lights up one by one, which the player must repeat to solve that puzzle. I am going to let the player know what the combination is by activating and deactivating some GameObjects systematically, one by one. As it suggests, there is some time delay between the deactivation of one GameObject and the activation of another. The problem is, in my code, all the GameObjects activate simultaneously instead of one by one, after a delay.
Here is the code:
public string Generate(int length, float delay)
{
// Variables for logic
string combination = "";
int prev = -1; int current = 0;
int rnd = 0;
for (int i = 0; i < length; i++)
{
rnd = Random.Range(0, BUTTONS);
while (rnd == prev)
{
rnd = Random.Range(0, BUTTONS);
}
prev = current;
current = rnd;
combination += current.ToString();
// Activation and Deactivation
StartCoroutine(GenerateDelay(delay, current));
}
return combination;
}
IEnumerator GenerateDelay(float delay, int index)
{
ButtonClicks[index].SetActive(true);
yield return new WaitForSeconds(delay);
ButtonClicks[index].SetActive(false);
}
The loop counter specifies the length of the combination. I believe there is something wrong with the Coroutine I made? Since all the objects activate simultaneously.
Here is the result in the game as well:
We can see, only one button should turn green at a time, but all of them do in this case. Any solutions?
You start all your Coroutines parallel so things happen at the same time.
StartCoroutine does not delay the method which calls it (unless it is yielded as well.
You would need to run the entire loop within a Coroutine in order to delay it as well.
You could simply split up the creation of the combination and while you already return it you start the visualization in the background in parallel
public string Generate(int length, float delay, Action<string> onCombination)
{
// Variables for logic
var combination = List<int>();
var prev = -1;
for (var i = 0; i < length; i++)
{
int rnd;
do
{
rnd = Random.Range(0, BUTTONS);
}
while (rnd == prev);
prev = rnd;
combination.Add(rnd);
}
StartCorouine (ShowCombination(combination, delay));
return string.Join("", combination);
}
private IEnumerator ShowCombination (IEnumerable<int> combination, float delay)
{
foreach(var i in combination)
{
ButtonClicks[i].SetActive(true);
yield return new WaitForSeconds(delay);
ButtonClicks[i].SetActive(false);
}
}
something alot easier then a coroutine is invoking a function, basically create a function to activate/deactivate the wanted object and whenever you want to call it do:
Invoke("FUNCTIONNAME", TIME);
and it will run the function specified after the TIME.

Clarification using closures in IEnumerator coroutines

I have 2 IEnumerator coroutines that are similar enough that they should be combined:
A:
IEnumerator CountDown_A() {
timeLeft_A = totalTime_A;
while (timeLeft_A > 0) {
yield return new WaitForSeconds(1);
timeLeft_A--;
}
}
B:
IEnumerator CountDown_B() {
timeLeft_B = totalTime_B;
while (timeLeft_B > 0) {
yield return new WaitForSeconds(1);
timeLeft_B--;
}
}
The only 2 differences are the variables totalTime_A vs totalTime_B and timeLeft_A vs timeLeft_B. These vars are from outside the scope of this function.
The problem I'm having modularizing this coroutine is that the incremented value of timeLeft_A and timeLeft_B needs to apply outside this function, so I need to pass a reference to them somehow.
User "Kurt-Dekker" posted a great solution in this thread but I'm having trouble applying it to my code. He says to "use a closure (functor) to allow the coroutine to modify it by callback":
IEnumerator CountDown( System.Action<int> callback){
....
}
which I think would be called like so:
StartCoroutine ( CountDown( (i) => { timeLeft_A = i; } ) );
StartCoroutine ( CountDown( (i) => { timeLeft_B = i; } ) );
What I don't understand is how to then reference/modify the actual value of the int being passed in, inside the IEnumerator, if all I have to work with is a callback function. For example to do the following:
while(callback > 0){
or:
callback--;
Any help appreciated.
I think this may answer your question, on how to use the System.Action<float> within a coroutine.
Basicaly, when you call StartCoroutine you give the maximum time for you counter as a normal parameter and a callback, this callback takes a float as argument (here it is i).
If you call callback(--timeLeft); in your coroutine, it will execute the System.Action you passed in.
Here it will set the timeLeft_A or timeLeft_B to the timeLeft variable of the corresponding coroutine.
public float timeLeft_A = 0f;
public float timeLeft_B = 0f;
public float totalTime_A = 15f;
public float totalTime_B = 20f;
// Start is called before the first frame update
void Start()
{
StartCoroutine(Cooldown(totalTime_A, (i) =>
{
timeLeft_A = i;
}));
StartCoroutine(Cooldown(totalTime_B, (i) =>
{
timeLeft_B = i;
}));
}
IEnumerator Cooldown(float totalTime, Action<float> callback)
{
var timeLeft = totalTime;
while (timeLeft > 0)
{
yield return new WaitForSeconds(1);
callback(--timeLeft);
}
}
Just putting my implementation here for posterity, based off Antoine Thiry's answer.
private static int totalTime_A = 45; // Num seconds
private int timeLeft_A = totalTime_A;
private static int totalTime_B = 30; // Num seconds
private int timeLeft_B = totalTime_B;
// Pass in totalTime separately from the callback (totalTime doesn't need to change)
IEnumerator CountDown(int totalTime, Action<int> callback){
int timeLeft = totalTime; // Create new local var here to operate on it
while (timeLeft > 0) {
yield return new WaitForSeconds(1);
callback(--timeLeft); // Then put the local var into callback after operating on it
}
}
CountDown() called like so:
// timeLeft_A and timeLeft_B change globally
IEnumerator Countdown_A = CountDown(totalTime_A, (i) => { timeLeft_A = i; });
IEnumerator Countdown_B = CountDown(totalTime_B, (i) => { timeLeft_B = i; });
StartCoroutine(Countdown_A);
StopCoroutine(Countdown_A);
StartCoroutine(Countdown_B);
StopCoroutine(Countdown_B);

Wait while file load in Unity

How I can load and return file from another method(use WWW)?
I want to do next:
Method GetSettings(). Download file text, parse json and
return result.
Call method from Start() and wait while GetSettings() return result.
How I can do this?
Looks like you want to downloaded data then wait then wait for the download to finish then download another data. If this is true the you can the code below will download data 2 times. You can increase the number of times by increasing the REQ_AMOUNT value.
It uses yield return StartCoroutine to wait for the current coroutine function to return before running again.
IEnumerator Start()
{
int REQ_AMOUNT = 2;
for (int i = 0; i < REQ_AMOUNT; i++)
{
yield return StartCoroutine(GetSettings());
}
}
IEnumerator GetSettings()
{
string url = RoomSettings.AbsoluteFilenamePath;
if (Application.isEditor)
{
url = "file:///" + url;
}
var www = new WWW(url);
yield return www;
// Do some code, when file loaded
}

Trying to understand c# yield in Unity3D

I've got a class. It has a method that does a lot of work. I would like to not have the program hang while it does the work. I understand yield will do this for me.
void Start() {
DoWork(10,10);
}
void DoWork (int x, int y) {
for (int i=0; i < x; i++) {
for (int j=0; j < y; j++) {
// Stuff
}
}
}
If I add a yield like this
void Start() {
DoWork(10, 10);
}
IEnumerable DoWork (int x, int y) {
for (int i=0; i < x; i++) {
for (int j=0; j < y; j++) {
// Stuff
}
Debug.Log (1);
yield return null;
}
}
None of the work gets done and on top of that I see no log statements at all.
How do I yield my code so the program doesn't freeze?
This is Unity3D engine so your coroutine needs to return IEnumerator to be valid:
void Start() {
StartCoroutine(DoWork(10, 10));
}
IEnumerator DoWork (int x, int y) {
for (int i=0; i < x; i++) {
for (int j=0; j < y; j++) {
// Stuff
}
Debug.Log (1);
yield return null;
}
}
This is in no way multithreading. It is run just like an update once per frame between the Update and the LateUpdate except if you use
yield return new WaitForEndOfFrame();
then it is postponed until after the rendering process. What it does is create a new object of type Coroutine and place it on the calling MonoBehaviour stack of coroutines.
This works as a method that performs some repetitive action but always return to the main program when hitting a yield. It will then catch back from there on the next frame.
You need to use the StartCoroutine method:
void Start() {
StartCoroutine(DoWork(10, 10));
}
IEnumerator DoWork (int x, int y) {
// (A)
yield return null;
// (B)
for (int i=0; i < x; i++) {
for (int j=0; j < y; j++) {
// Stuff
}
Debug.Log (1);
yield return null;
// (C)
}
}
Yur code is executed piece by piece where delimiter of steps is the yield operator, i.e. when Framework calls MoveNext() the first time - the code (A) will be executed, when it calls MoveNext() second time - the code (B) will be executed, then code (C), and so on and so forth.
When you add a yield statement, the compiler actually generates a private class that acts as a state machine that implements IEnumerable. As such none of the code wrapped up from the original method will be called unless you enumerate the result of the method - in your example, you're throwing away the return value, so nothing would happen.
Yield keyword is used for lazy loading/computation support in C#.
Try doing:
var result = DoWork().ToList();
This forces an evaluation of the DoWork() method and you will see the logging taking place.
C# yield in Unity works just like C# yield always does. Unity does not influence this in any way.
yield is a keyword that is used to allow enumeration over a set of return values.
IEnumerator<int> MyEnumerationMethod()
{
yield return 5;
yield return 1;
yield return 9;
yield return 4;
}
void UserMethod1()
{
foreach (int retVal in MyEnumerationMethod())
Console.Write(retVal + ", ");
// this does print out 5, 1, 9, 4,
}
void UserMethod2()
{
IEnumerator<int> myEnumerator = MyEnumerationMethod();
while (myEnumerator.MoveNext())
Console.Write(myEnumerator.Current + ", ");
// this does print out 5, 1, 9, 4,
}
UserMethod1() and UserMethod2() are pretty much the same. UserMethod1() is just the C# syntactic sugar version of UserMethod2().
Unity uses this language feature to implement Coroutines:
When you call StartCoroutine() and pass it an IEnumerator, Unity stores this enumerator and calls MoveNext() for the first time. This will cause MyEnumerationMethod() to be called and executed up until the first yield return. At this point, MoveNext() returns and the first result (5) can be retrieved by looking at the Current property of the enumerator.
Now, Unity regularly checks the Current property and - depending on its value - decides whether the time has come to call MoveNext() again. The value of Current might be an instance of WaitForEndOfFrame, an instance of WWW or whatever, and depending on that the time, MoveNext() is called is decided.
Once MoveNext() is called again, execution of MyEnumerationMethod() will be continued at the point where it was interrupted last time, and executes until the next yield return is executed. And so on.
That's all there is to yield, and to Coroutines in Unity.

How to "interrupt" a while loop in C# and return to the same place (without using any Threading)

I need help switching between while loops and resuming to the exact state that they were in.
An example would be this:
while(1==1)
{
x++;
x++;
x++;
x++;
}
while(1==1)
{
Console.WriteLine("X=" + x);
Console.WriteLine("X=" + x);
Console.WriteLine("X=" + x);
Console.WriteLine("X=" + x);
}
I am working for a project that lets you create an OS in C#. It is called Cosmos and a quick google search should land you with some info.
What I need to do is to pause one of the loops, and resume (or start) a different loop until the time is over, then that loop will be paused and a different one will be resumed and so on in an infinite cycle.
I am trying to make a simple task scheduler, and I plan to make a lot more changes than simple while loop switching, but I would like this as a primitive state and for testing.
So, what would need to happen is that one while loop executes, then is paused and the second one is executed. What would need to happen is that each loop would pause and switch to a different one, effectively seeming as if they are running at the same time. So, what would happen is that x would increase, then be printed, x increased, and so on.
Well, we could do coöperative (nonpreëmptive) multi-tasking by creating state machines to handle each loop:
private interface IStateMachine
{
void DoNext();
}
private class Loop0 : IStateMachine
{
private int _state;
private int x;
public void DoNext()
{
switch (_state)
{
case 0:
x++;
_state = 1;
break;
case 1:
x++; // This is of course the same as previous, but I'm matching
// the code in your question. There's no reason why it need
// not be something else.
_state = 2;
break;
case 2:
x++;
_state = 3;
break;
case 3:
x++;
_state = 0;
break;
}
}
}
private class Loop1 : IStateMachine
{
private int _state;
private int x;
public void DoNext()
{
switch (_state)
{
case 0:
Console.WriteLine("X=" + x);
_state = 1;
break;
case 1:
Console.WriteLine("X=" + x);
_state = 2;
break;
case 2:
Console.WriteLine("X=" + x);
_state = 3;
break;
case 3:
Console.WriteLine("X=" + x);
_state = 0;
break;
}
}
}
private static void Driver()
{
// We could have all manner of mechanisms for deciding which to call, e.g. keep calling one and
// then the other, and so on. I'm going to do a simple time-based one here:
var stateMachines = new IStateMachine[] { new Loop0(), new Loop1() };
for (int i = 0;; i = (i + 1) % stateMachines.Length)
{
var cur = stateMachines [i];
DateTime until = DateTime.UtcNow.AddMilliseconds (100);
do
{
cur.DoNext ();
} while (DateTime.UtcNow < until);
}
}
There are two big problems with this:
The x in each is a separate x. We need to box the int or wrap it in a reference type so that both methods can be accessing the same variable.
The relationship between your loops and these state machines isn't very clear.
Luckily there already exists a way (indeed more than one) to write a method in C# that is turned into a state-machine with a method for moving to the next state that handles both of these issues:
private static int x;
private static IEnumerator Loop0()
{
for(;;)
{
x++;
yield return null;
x++;
yield return null;
x++;
yield return null;
x++;
yield return null;
}
}
private static IEnumerator Loop1()
{
for(;;)
{
Console.WriteLine("X=" + x);
yield return null;
Console.WriteLine("X=" + x);
yield return null;
Console.WriteLine("X=" + x);
yield return null;
Console.WriteLine("X=" + x);
yield return null;
}
}
private static void Driver()
{
// Again, I'm going to do a simple time-based mechanism here:
var stateMachines = new IEnumerator[] { Loop0(), Loop1() };
for (int i = 0;; i = (i + 1) % stateMachines.Length)
{
var cur = stateMachines [i];
DateTime until = DateTime.UtcNow.AddMilliseconds (100);
do
{
cur.MoveNext ();
} while (DateTime.UtcNow < until);
}
}
Now not only is it easy to see how this relates to your loops (each of the two methods have the same loop, just with added yield return statements), but the sharing of x is handled for us too, so this example actually shows it increasing, rather than an unseen x incrementing and a different x that is always 0 being displayed.
We can also use the value yielded to provide information about what our coöperative "thread" wants to do. For example, returning true to always give up its time slice (equivalent to calling Thread.Yield() in C# multi-threaded code):
private static int x;
private static IEnumerator<bool> Loop0()
{
for(;;)
{
x++;
yield return false;
x++;
yield return false;
x++;
yield return false;
x++;
yield return true;
}
}
private static IEnumerator<bool> Loop1()
{
for(;;)
{
Console.WriteLine("X=" + x);
yield return false;
Console.WriteLine("X=" + x);
yield return false;
Console.WriteLine("X=" + x);
yield return false;
Console.WriteLine("X=" + x);
yield return true;
}
}
private static void Driver()
{
// The same simple time-based one mechanism, but this time each coroutine can
// request that the rest of its time-slot be abandoned.
var stateMachines = new IEnumerator<bool>[] { Loop0(), Loop1() };
for (int i = 0;; i = (i + 1) % stateMachines.Length)
{
var cur = stateMachines [i];
DateTime until = DateTime.UtcNow.AddMilliseconds (100);
do
{
cur.MoveNext ();
} while (!cur.Current && DateTime.UtcNow < until);
}
}
As I'm using a bool here I have only two states that affect how Driver() (my simple scheduler) acts. Obviously a richer datatype would allow for more options, but be more complex.
One possibility would be to have your compiler have a type of method that must return void (comparable to how yield and await have restrictions on the return types of methods that use them in C#) which could contain keywords like thread opportunity, thread yield and thread leave which would then be mapped to yield return false, yield return true and yield break in the C# above.
Of course, being coöperative it requires explicit code to say when other "threads" might have an opportunity to run, which in this case is done by the yield return. For the sort of preëmptive multi-threading that we enjoy just writing in C# for the operating systems it can run on, where time slices can end at any point rather than just where we explicitly allow it will require you to compile the source to produce such state machines, without their being instructions in that source. This is still coöperative, but forces that coöperation out of the code when compiling.
Truly preëmptive multi-threading would require that you have some way of storing the current state of each loop when switching to another thread (just as the stack of each thread in a .NET program does). In a virtual OS you could do this by building threads on top of the underlying OS's threads. In a non-virtual OS you're likely going to have to build your threading mechanism closer to the metal, with the scheduler changing the instruction pointer when threads change,

Categories

Resources