I just realized that we can write custom yield instructions for coroutines. I’d like to share my implementation of WaitForJobCompleted that takes a JobHandle and ends the coroutine if the handle is completed.
By default, job completion is forced after 4 frames to prevent TempJob allocators from complaining. Pass false as the second parameter if you have a long-running job with a Persistent allocator.
public class WaitForJobCompleted : CustomYieldInstruction
{
private readonly bool _isUsingTempJobAllocator;
private readonly int _forceJobCompletionFrameCount;
private JobHandle _jobHandle;
public override bool keepWaiting
{
get
{
if (_isUsingTempJobAllocator && Time.frameCount >= _forceJobCompletionFrameCount)
_jobHandle.Complete();
// optional: automatically call Complete() on the handle after it completed
// side note: yes, you have to complete what has already completed, i still think that's bonkers! :)
// if (_jobHandle.IsCompleted) _jobHandle.Complete();
return _jobHandle.IsCompleted == false;
}
}
public WaitForJobCompleted(JobHandle jobHandle, bool isUsingTempJobAllocator = true)
{
_jobHandle = jobHandle;
_isUsingTempJobAllocator = isUsingTempJobAllocator;
// force completion before running into native Allocator.TempJob's lifetime limit of 4 frames
_forceJobCompletionFrameCount = Time.frameCount + 4;
}
}
Here’s an example coroutine using WaitForJobCompleted which invokes an Action with the result (in this case: a Mesh instance):
StartCoroutine(WaitForMeshCompleted(mesh => { meshFilter.sharedMesh = mesh; }));
public IEnumerator WaitForMeshCompleted(Action<Mesh> callback)
{
// check first whether job has already been completed within the current frame!
// if not completed this frame, just wait till it does complete (at most 4 frames)
if (IsCompleted() == false)
yield return new WaitForJobCompleted(_buildJobHandle, isUsingTempJobAllocator: true);
callback.Invoke(GetMesh());
}