using System;
using System.Collections;
using System.Collections.Generic;
namespace UniRx.InternalUtil
{
///
/// Simple supports(only yield return null) lightweight, threadsafe coroutine dispatcher.
///
public class MicroCoroutine
{
const int InitialSize = 16;
readonly object runningAndQueueLock = new object();
readonly object arrayLock = new object();
readonly Action unhandledExceptionCallback;
int tail = 0;
bool running = false;
IEnumerator[] coroutines = new IEnumerator[InitialSize];
Queue waitQueue = new Queue();
public MicroCoroutine(Action unhandledExceptionCallback)
{
this.unhandledExceptionCallback = unhandledExceptionCallback;
}
public void AddCoroutine(IEnumerator enumerator)
{
lock (runningAndQueueLock)
{
if (running)
{
waitQueue.Enqueue(enumerator);
return;
}
}
// worst case at multi threading, wait lock until finish Run() but it is super rarely.
lock (arrayLock)
{
// Ensure Capacity
if (coroutines.Length == tail)
{
Array.Resize(ref coroutines, checked(tail * 2));
}
coroutines[tail++] = enumerator;
}
}
public void Run()
{
lock (runningAndQueueLock)
{
running = true;
}
lock (arrayLock)
{
var j = tail - 1;
// eliminate array-bound check for i
for (int i = 0; i < coroutines.Length; i++)
{
var coroutine = coroutines[i];
if (coroutine != null)
{
try
{
if (!coroutine.MoveNext())
{
coroutines[i] = null;
}
else
{
#if UNITY_EDITOR
// validation only on Editor.
if (coroutine.Current != null)
{
UnityEngine.Debug.LogWarning("MicroCoroutine supports only yield return null. return value = " + coroutine.Current);
}
#endif
continue; // next i
}
}
catch (Exception ex)
{
coroutines[i] = null;
try
{
unhandledExceptionCallback(ex);
}
catch { }
}
}
// find null, loop from tail
while (i < j)
{
var fromTail = coroutines[j];
if (fromTail != null)
{
try
{
if (!fromTail.MoveNext())
{
coroutines[j] = null;
j--;
continue; // next j
}
else
{
#if UNITY_EDITOR
// validation only on Editor.
if (fromTail.Current != null)
{
UnityEngine.Debug.LogWarning("MicroCoroutine supports only yield return null. return value = " + coroutine.Current);
}
#endif
// swap
coroutines[i] = fromTail;
coroutines[j] = null;
j--;
goto NEXT_LOOP; // next i
}
}
catch (Exception ex)
{
coroutines[j] = null;
j--;
try
{
unhandledExceptionCallback(ex);
}
catch { }
continue; // next j
}
}
else
{
j--;
}
}
tail = i; // loop end
break; // LOOP END
NEXT_LOOP:
continue;
}
lock (runningAndQueueLock)
{
running = false;
while (waitQueue.Count != 0)
{
if (coroutines.Length == tail)
{
Array.Resize(ref coroutines, checked(tail * 2));
}
coroutines[tail++] = waitQueue.Dequeue();
}
}
}
}
}
}