I’m having some issues using the new EditorCoroutines (or any yield instructions, really) in Unity build scripts. Specifically, I have a pre-build script that derives from IPreprocessBuildWithReport, and I would like this script to wait for some Resources to load. I have tried the two approaches shown in the following code, but neither is working. Does anyone know of a way to wait on yield statements in a build script? Any help would be much appreciated!
public class PreprocessBuildEntrypoint : IPreprocessBuildWithReport {
public void OnPreprocessBuild(BuildReport report) {
IEnumerator longOp = longRunningOperation();
// Option 1: Start an EditorCoroutine
// Doesn't work because the operation won't be done before we reach the next statements
EditorCoroutineUtility.StartCoroutine(longOp, owner: this);
// Option 2: Loop over the operation's IEnumerator
// Doesn't work because the loop hogs Unity's main thread and prevents the actual operation from running
while (longOp.MoveNext()) ;
// Do some other stuff that depends on the long-running operation being complete
}
private IEnumerator longRunningOperation() {
ResourceRequest req = Resources.LoadAsync<MyObject>("resource-name");
while (!req.isDone)
yield return null;
// Do stuff with the result of `req`
}
}
Generally you would move the stuff on line 14 into around line 20.
You could also use events… for example:
public class PreprocessBuildEntrypoint : IPreprocessBuildWithReport {
event Action OnLongRunningOperationCompleted;
public void OnPreprocessBuild(BuildReport report) {
IEnumerator longOp = longRunningOperation();
OnLongRunningOperationCompleted += DoLater;
// Start an EditorCoroutine
EditorCoroutineUtility.StartCoroutine(longOp, owner: this);
}
void DoLater() {
// Do some other stuff that depends on the long-running operation
}
private IEnumerator longRunningOperation() {
ResourceRequest req = Resources.LoadAsync<MyObject>("resource-name");
while (!req.isDone)
yield return null;
// Do stuff with the result of `req`
OnLongRunningOperationCompleted?.Invoke();
}
}
@PraetorBlue Thanks for the reply! I should have clarified that this long-running operation, and the statements that follow, need to be completed before the build continues. If I only call EditorCoroutineUtility.StartCoroutine and let OnPreprocessBuild return, then the resources most likely won’t be done loading yet.
Because I need to be able to wait on yield statements, like the Resources.LoadAsync call in the example above, or web requests. For the particular case of Resources, I could just call the blocking Resources.Load method, but there is no such equivalent for some of the other methods that I’m calling.
As far as I know there’s no way to delay the build process without hogging the main thread inside your callback, e.g. while (!myTask.isDone) or something. So if the async task also needs to use the main thread, I’m not sure how you’d do it. Are you sure the operation needs the main thread? I thought the point of the async APIs was so they could be done on other threads. What operation specifically are you trying to do?
Yeah that was my thinking as well, but when I made test builds, they just ran in an infinite loop with ResourceRequest.progress never advancing above 0 and isDone staying false. I’m still trying some different setups to see if I can get Resources.LoadAsync to work though.
Well, it’s kinda complicated… I have a custom configuration system in place so that I can load “secrets” at build or run time without having to store them in version control (think API keys, passwords, and the like). I have an IConfigurationSource interface with a single Load method, and implementations for reading from (gitignored) Resources, from CSV or JSON files on disk, Remote Config, etc. To account for as many config sources as possible, I had to make IConfigurayionSource.Load “awaitable” by returning IEnumerator, since some APIs (particularly Remote Config) have no blocking alternatives, plus then sources can be loaded in parallel. This system works great at runtime, but I also need it to run in this pre-build script so that some of the loaded secrets can be written to files within the build, like AndroidManifest.xml and Info.plist. And so here we are, I need to call and wait on IEnumerator-returning methods within a pre-build script. So to answer ur question, I guess the API that started all this mess was Remote Config’s ConfigManager.FetchConfigs method, which runs asynchronously.
Well, in the end I just had to modify my custom configuration system. Those IConfigurationSources now have a Load and a LoadAsync method. In builds and in Play Mode, where Coroutines are supported, I can call the LoadAsync method and get some performance benefits. In build scripts, where (Editor)Coroutines are NOT supported, I can call Load, and things will work, albeit a teeny bit slower. This also means that certain config sources cannot be loaded in build scripts if they only have a LoadAsync implementation (as is the case for Remote Config), but those sources were probably never meant to be called at build time anyway.