I have a service which handles data saving / loading. The methods on this service might take a few seconds (if not more) to complete. What is the standard operating procedure for calling this service without freezing the UI?
I noticed that the “baked in” services (LoadLevelAsync) return an AsyncOperation. alowing them to easily incorporate the service into a coroutine :
ala
var async = Application.LoadLevelAsync("myLevel");
yield return async;
// continue
This is a elegant solution, and prefer this to passing an oncomplete callback into my service or messing around with a background worker. Is there any way I can acquire this pattern for my own services? If not, any solution would be helpful.
I personally felt that the Unity documentation on Coroutines was horrible. It took me a long time to figure out how to use them. Let me tell you one thing, do not use the .net thread classes for multi-threading, it won’t work. Instead, you can write your own method that produces an IEnumerable instance. You can start a coroutine that uses that function:
Read this page to understand how to write your own function:
Here is my C# example:
public System.Collections.IEnumerable coroutineFunction() {
while (! doneYourPersistentProcess())
// ... wait for it ...
yield return "";
// now it is done.
doPostProcessing();
}
public void doPostProcessing() {
// your code here.
}
public void Update() {
if (userStartsPersistentProcess())
StartCoroutine(coroutineFunction());
}
public bool userStartsPersistentProcess() {
// your code here.
}
public bool doneYourPersistentProcess() {
// your code here.
}
So here’s another way of describing how to make a yieldable object:
class YieldableObject
{
public Coroutine SlowFunction()
{
return StartCoroutine(WaitForSlowFunction);
}
private IEnumerator WaitForSlowFunction()
{
while(!slowFunctionIsNotDone)
{
yield return null;
}
}
}
class MainApp()
{
IEnumerator CallingFunction()
{
YieldableObject yielder = new YieldableObject();
yield return yielder.SlowFunction(); // Will wait here for the operation to finish
}
void Main()
{
StartCoroutine(CallingFunction());
}
}
Here, Main() will start a Coroutine of CallingFunction which will create and yield to your YieldableObject while it is performing the SlowFunction. SlowFunction does whatever operation it needs to, but must have another StartCoroutine in it. The first Coroutine will pause execution and wait until the “inner” Coroutine is finished. That’s the secret to getting a yield to wait for your operation to complete, similar to how Unity’s built-in WWW class works.
There is the asynchronous way of doing things in Unity without using of coroutines. Check out the eDriven library and this video: The Callback Queue - YouTube
Yeah I ran in to a similar need this week. I was cleaning up my Service Model which included several async operations (Login, GetScores, ect). Moreover these services are separate from my viewmodels which invoke them.
My solution was to expose a public IEnumerator coroutine and state variable in my service.
public class MyAccountService : MonoBehaviour
{
public bool IsAuthenticated { get; protected set; }
public IEnumerator Login()
{
yield return 1;
IsAuthenticated = true;
}
}
public class MyViewModel : MonoBehaviour
{
public MyAccountService Service;
void Awake()
{
StartCoroutine(OnReady());
}
public IEnumerator OnReady()
{
yield return 1;
yield return StartCoroutine(Service.Login());
if (Service.IsAuthenticated)
{
//blah
}
}
}