Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  Creating rope at runtime looks unprofessional because of coroutines. How to fix?
#1
Hello, really enjoying Obi Rope so far, but have run into a couple of issues. Im in the final steps of releasing my game, and needs to work some of these out before launch.

In my game I have a save system that counts how many ropes were in the room before you left, then when you come back it loads all those ropes back into the room. My problem with this is that the only documentation I found for creating ropes uses coroutines, and this makes spawning all weird. Instead of being instantiated all at once the ropes are spawned one after another, then it takes some time for them to gain the variables that I have given them, such as their length, they also take time to attach the start and end prefabs and be affected by physics. It can be a solid couple of seconds after i start the game until the ropes are done doing their thing, and it looks super weird and un-professional the entire time. 

How can I fix this? Is there a way to create snakes without coroutines?

Here is my code
Code:
   public IEnumerator makinSnakesLoop(int totalSnake){
       for(int x =0; x<totalSnake; x++){
            GameObject gg = Instantiate(snake, Vector3.zero, Quaternion.identity);
            yield return gg.GetComponent<ObiRope>().StartCoroutine(gg.GetComponent<ObiRope>().GeneratePhysicRepresentationForMesh());

            gg.GetComponent<ObiRope>().AddToSolver(null);


               gg.GetComponent<length>().length2 = newController.ss.lengths[x];
               gg.GetComponent<length>().exp = newController.ss.exp[x];
               gg.GetComponent<length>().goal = newController.ss.goals[x];
       }
   }

Thank you.
Reply
#2
(06-05-2019, 06:35 PM)yyy-2c2p Wrote: Hello, really enjoying Obi Rope so far, but have run into a couple of issues. Im in the final steps of releasing my game, and needs to work some of these out before launch.

In my game I have a save system that counts how many ropes were in the room before you left, then when you come back it loads all those ropes back into the room. My problem with this is that the only documentation I found for creating ropes uses coroutines, and this makes spawning all weird. Instead of being instantiated all at once the ropes are spawned one after another, then it takes some time for them to gain the variables that I have given them, such as their length, they also take time to attach the start and end prefabs and be affected by physics. It can be a solid couple of seconds after i start the game until the ropes are done doing their thing, and it looks super weird and un-professional the entire time. 

How can I fix this? Is there a way to create snakes without coroutines?

Here is my code
Code:
   public IEnumerator makinSnakesLoop(int totalSnake){
    for(int x =0; x<totalSnake; x++){
    GameObject gg = Instantiate(snake, Vector3.zero, Quaternion.identity);
    yield return gg.GetComponent<ObiRope>().StartCoroutine(gg.GetComponent<ObiRope>().GeneratePhysicRepresentationForMesh());

       gg.GetComponent<ObiRope>().AddToSolver(null);


      gg.GetComponent<length>().length2 = newController.ss.lengths[x];
      gg.GetComponent<length>().exp = newController.ss.exp[x];
      gg.GetComponent<length>().goal = newController.ss.goals[x];
    }
   }

Thank you.

No need to wait for coroutines to be done over multiple frames, if you don't want/need asynchronous initialization. Simply advance the coroutine to completion before continuing execution:

Code:
IEnumerator coroutine = gg.GetComponent<ObiRope>().GeneratePhysicRepresentationForMesh();
while (coroutine.MoveNext());

Note that this is not some Obi-specific trickery. It's part of the concept of "coroutine", that you don't seem to understand well. They're not magic async code, nor do they run in a separate thread. They're generally used to distribute lengthy calculations over multiple frames, but you do not *have* to use them like this. Instead of telling Unity to advance the coroutine over several frames (which is what StartCoroutine does), you can run it synchronously by calling MoveNext() until it finishes.

Obi exposes potentially long initialization methods as coroutines, in case you want to show a loading bar, wait for multiple coroutines to be completed, etc. Think of it as "professional courtesy" Guiño. But otherwise, they're regular methods that can be run synchronously just like any other method.
Reply
#3
(06-05-2019, 07:03 PM)josemendez Wrote: No need to wait for coroutines to be done over multiple frames, if you don't want/need asynchronous initialization. Simply advance the coroutine to completion before continuing execution:

Code:
IEnumerator coroutine = gg.GetComponent<ObiRope>().GeneratePhysicRepresentationForMesh();
while (coroutine.MoveNext());

Note that this is not some Obi-specific trickery. It's part of the concept of "coroutine", that you don't seem to understand well. They're not magic async code, nor do they run in a separate thread. They're generally used to distribute lengthy calculations over multiple frames, but you do not *have* to use them like this. Instead of telling Unity to advance the coroutine over several frames (which is what StartCoroutine does), you can run it synchronously by calling MoveNext() until it finishes.

Obi exposes potentially long initialization methods as coroutines, in case you want to show a loading bar, wait for multiple coroutines to be completed, etc. Think of it as "professional courtesy" Guiño. But otherwise, they're regular methods that can be run synchronously just like any other method.

Thanks for the reply!
Ahhhhhh interesting, I tried looking into stopping the asynchronous initialization, but couldn't find much about it. I figured it would be easier to just not use a coroutine, because Im not interested in doing something over the course of multiple frames. 

I'm having a hard time getting this to work still though, and having trouble wrapping my mind around the nested coroutines in this issue. I tried a couple different implementations, and physics would never be added, or it would only create some of the ropes and not assign the variables, or some other issue. 

Sorry, but what exactly am I supposed to do here?
Reply
#4
(06-05-2019, 10:55 PM)yyy-2c2p Wrote: Thanks for the reply!
Ahhhhhh interesting, I tried looking into stopping the asynchronous initialization, but couldn't find much about it. I figured it would be easier to just not use a coroutine, because Im not interested in doing something over the course of multiple frames. 

I'm having a hard time getting this to work still though, and having trouble wrapping my mind around the nested coroutines in this issue. I tried a couple different implementations, and physics would never be added, or it would only create some of the ropes and not assign the variables, or some other issue. 

Sorry, but what exactly am I supposed to do here?

Understanding coroutines is pretty essential for working with Unity (or C# for that matter). In your code, since "makinSnakesLoop" is a coroutine itself (which you probably don't need if running the inner coroutine synchronously since you're not yielding anything anymore), you could return the partial results of the inner coroutine:

Code:
IEnumerator coroutine = gg.GetComponent<ObiRope>().GeneratePhysicRepresentationForMesh();
while (coroutine.MoveNext())
         yield return coroutine.Current;

This would yield after each partial result of the initialization coroutine, and would not execute these lines:

Code:
gg.GetComponent<length>().length2 = newController.ss.lengths[x];
     gg.GetComponent<length>().exp = newController.ss.exp[x];
     gg.GetComponent<length>().goal = newController.ss.goals[x];

Until after the inner coroutine had finished.

You could also start the initialization of all snakes at once, then wait until all snakes have been created (asynchronously), then set their length or perform any other post-processing afterwards.
Use something like this to wait for all of them:

Code:
public static IEnumerator WaitForAll(params Coroutine[] coroutines)
   {
       for (int i = 0; i < coroutines.Length; i++)
       {
           yield return coroutines[i];
       }
   }

Then you'd do:

Code:
yield return WaitForAll(coroutine1, coroutine2, coroutine3...);

Which approach to take depends on what exactly you're looking to do, and how it fits with the rest of your game code. Again, the main purpose of coroutines is to spread execution over multiple frames, instead of freezing the whole game until their work is done. If you're creating many snakes and it takes too long, showing a loading bar is the appropriate -most professional- thing to do. All initialization coroutines in Obi return a completion percentage along with a message (both packed in a "ProgressInfo" struct) so that you can get feedback on their current status. You can get it like this:

Code:
while (coroutine.MoveNext()){
         var pctg = coroutine.Current as CoroutineJob.ProgressInfo;
         Debug.Log("Loading..." + pctg.progress*100 + "%");
         yield return pctg;
}
Reply
#5
(07-05-2019, 08:56 AM)josemendez Wrote: Understanding coroutines is pretty essential for working with Unity (or C# for that matter). In your code, since "makinSnakesLoop" is a coroutine itself (which you probably don't need if running the inner coroutine synchronously since you're not yielding anything anymore), you could return the partial results of the inner coroutine:

Code:
IEnumerator coroutine = gg.GetComponent<ObiRope>().GeneratePhysicRepresentationForMesh();
while (coroutine.MoveNext())
         yield return coroutine.Current;

This would yield after each partial result of the initialization coroutine, and would not execute these lines:

Code:
gg.GetComponent<length>().length2 = newController.ss.lengths[x];
     gg.GetComponent<length>().exp = newController.ss.exp[x];
     gg.GetComponent<length>().goal = newController.ss.goals[x];

Until after the inner coroutine had finished.

You could also start the initialization of all snakes at once, then wait until all snakes have been created (asynchronously), then set their length or perform any other post-processing afterwards.
Use something like this to wait for all of them:

Code:
public static IEnumerator WaitForAll(params Coroutine[] coroutines)
   {
       for (int i = 0; i < coroutines.Length; i++)
       {
           yield return coroutines[i];
       }
   }

Then you'd do:

Code:
yield return WaitForAll(coroutine1, coroutine2, coroutine3...);

Which approach to take depends on what exactly you're looking to do, and how it fits with the rest of your game code. Again, the main purpose of coroutines is to spread execution over multiple frames, instead of freezing the whole game until their work is done. If you're creating many snakes and it takes too long, showing a loading bar is the appropriate -most professional- thing to do. All initialization coroutines in Obi return a completion percentage along with a message (both packed in a "ProgressInfo" struct) so that you can get feedback on their current status. You can get it like this:

Code:
while (coroutine.MoveNext()){
         var pctg = coroutine.Current as CoroutineJob.ProgressInfo;
         Debug.Log("Loading..." + pctg.progress*100 + "%");
         yield return pctg;
}

I really appreciate the detailed response, Im doing some more research on it all, and I'm getting some pretty nice stuff working now. Thank you. 

I have a couple of other issues before I ship out, namely things with getting particle position and collision with other ropes. Would you like me to create separate threads for each issue or keep the rest of the forum cleaner, and just post them here?
Reply