实现了一个协程调度器,允许在程序中以非阻塞的方式调度协程。协程可以在满足特定条件后暂停和恢复,如等待特定的帧数、时间、或等待其他协程执行完毕。它的设计思想与Unity的协程机制类似,但它不依赖Unity的YieldInstruction,因此适用于非Unity环境。
协程可以在以下情况下暂停:
"yield null ;" 等一帧;
"yield 一个int值",等待给定的帧数;
"yield 一个float值;",等待给定的秒数;
"yield scheduler.StartCoroutine(Coroutine())",直到另一个协程完成。
支持多个调度器实例.一个调度器中的协程可以等待另一个完全不同的调度器实例中的协程。
不使用Unity的YieldInstruction类,因为无法访问其内部数据进行调度。调度语义与Unity的调度器略有不同。
例如,在Unity中,如果启动协程,它会立即运行到第一个yield,而在这个调度器中,它直到下一次调用UpdateAllCoroutines才会运行。
这个特性允许在任何时候启动协程,但确保启动的协程只在特定时间运行。
在同一个更新中运行的协程之间不要依赖更新顺序。例如,StartCoroutine(A), StartCoroutine(B), StartCoroutine(C)
如果A、B、C都为:while(true) { print(A|B|C); yield; },不要期望 "ABC" 或 "CBA" 或任何特定的顺序。
代码结构:
CoroutineScheduler 类:
这个类是协程调度器的核心,它可以管理多个协程并根据指定的调度规则执行协程。
StartCoroutine: 启动一个新的协程。协程不会立即执行,而是在下一次调用UpdateAllCoroutines时开始运行。它可以接受IEnumerator或IEnumerable作为输入。
Rewind: 允许重置并重新运行一个已完成的协程。
Stop/StopAllCoroutines: 停止单个或所有协程。StopAllCoroutines停止调度器中的所有协程.
Pause/Resume: 可以暂停和恢复协程的执行。恢复时协程会从暂停时的位置继续。
UpdateAllCoroutines: 执行调度器中的所有协程,直到它们的下一个yield。需要调用者提供当前帧数和时间来控制协程的进度。这个方法是调度器的核心,它根据不同的条件(如时间、帧数等)来判断协程是否需要继续执行。
CoroutineNode 类:
每个协程都被封装在一个CoroutineNode对象中,保存了协程的执行状态、等待的条件(如等待的帧数或时间)以及是否暂停、完成等状态。
waitForFrame/waitForTime: 协程的等待条件,当协程需要等待一定的帧数或时间时会使用这些字段。
Reset: 允许重置协程,使其可以从头开始运行。
Pause/Resume: 这些方法用来控制协程的暂停和恢复。
IYieldWrapper 接口:
这个接口允许实现一些自定义的等待条件,比如等待某个Unity对象执行完特定任务后才继续协程。
工作原理:
每次UpdateAllCoroutines被调用时,调度器会遍历所有未完成的协程,并根据协程当前的等待条件(帧数、时间或其他协程)决定是否继续执行。如果协程满足其等待条件,就继续执行它直到遇到下一个yield。
在协程执行的过程中,可以通过yield return指定暂停条件。当满足条件时,调度器会将协程从等待状态中恢复。
这个调度器的设计受Unity协程机制的启发,但它并不依赖于Unity引擎,因此可以在任何基于帧更新或时间驱动的系统中使用。
这个调度器的灵活性允许你在任意的时间调度和控制协程,并且可以在多个调度器实例之间进行协作。
using System.Collections;
public class CoroutineScheduler
{
public CoroutineNode first = null; // 链表的第一个协程节点
int currentFrame; // 当前帧数
float currentTime; // 当前时间
CoroutineNode runTimeNuext; // 执行时的下一个节点
/**
* 启动一个协程,协程不会立即运行,而是在下一次调用UpdateAllCoroutines时运行。
* 协程的执行可以在任何时候使用yield语句暂停。yield返回值指定协程何时恢复。
*/
public CoroutineNode StartCoroutine(IEnumerator fiber)
{
// 如果函数没有yield,fiber将为null,什么也不做
if (fiber == null)
{
return null;
}
// 创建协程节点并运行,直到到达第一个yield
CoroutineNode coroutine = new CoroutineNode(fiber);
AddCoroutine(coroutine);
return coroutine;
}
public CoroutineNode StartCoroutine(IEnumerable srcFiber)
{
if (srcFiber == null)
{
return null;
}
CoroutineNode coroutine = new CoroutineNode(srcFiber.GetEnumerator());
coroutine.srcFiber = srcFiber;
AddCoroutine(coroutine);
return coroutine;
}
public void Rewind(CoroutineNode cn)
{
if (cn.srcFiber == null)
throw new System.Exception("不是IEnumerable函数");
if (cn.finished)
{
AddCoroutine(cn);
}
cn.Reset(); // 重置协程节点
}
public void Stop(CoroutineNode cn)
{
RemoveCoroutine(cn);
cn.finished = true; // 标记协程已完成
}
public void Pause(CoroutineNode cn)
{
if (cn.finished) return;
if (!cn.isPaused)
{
cn.isPrePause = true;
cn.isPaused = true;
}
}
public void Resume(CoroutineNode cn)
{
if (cn.finished) return;
if (cn.isPaused)
{
cn.isPaused = false;
cn.isPrePause = true;
AddCoroutine(cn); // 恢复协程
}
}
/**
* 停止所有在该调度器上运行的协程。建议避免使用此方法,
* 应找到一种自然的方式让协程自行完成,而不是在它们完成之前被强制停止。
* 如果你需要更细粒度的控制来停止协程,你可以使用多个调度器。
*/
public void StopAllCoroutines()
{
CoroutineNode cn = first;
CoroutineNode next;
while (cn != null)
{
next = cn.listNext;
cn.listPrevious = null;
cn.listNext = null;
cn = next;
}
first = null; // 清空调度器中的所有协程
}
/**
* 如果此调度器有任何协程运行,则返回true。你可以使用它来检查所有协程是否已完成或被停止。
*/
public bool HasCoroutines()
{
return first != null;
}
/**
* 运行所有活跃的协程直到它们的下一个yield。调用者必须提供当前的帧数和时间。
* 这允许调度器在不同于Unity主游戏循环的帧数和时间制度下运行。
*/
public void UpdateAllCoroutines(int frame, float time)
{
currentFrame = frame;
currentTime = time;
CoroutineNode coroutine = this.first;
while (coroutine != null)
{
// 在协程完成并从列表中移除之前,存储listNext
runTimeNuext = coroutine.listNext;
if (coroutine.isPaused) // 如果协程暂停,直接进入下一个节点
{
if (coroutine.isPrePause)
{
coroutine.isPrePause = false;
if (coroutine.waitForFrame > 0) coroutine.waitForFrame -= currentFrame;
else if (coroutine.waitForTime > 0) coroutine.waitForTime -= currentTime;
else if (coroutine.waitForCoroutine != null) Pause(coroutine.waitForCoroutine);
else if (coroutine.waitForUnityObject != null) coroutine.waitForUnityObject.Pause();
}
RemoveCoroutine(coroutine);
coroutine = runTimeNuext;
continue;
}
else if (coroutine.isPrePause)
{
coroutine.isPrePause = false;
if (coroutine.waitForFrame > 0) coroutine.waitForFrame += currentFrame;
else if (coroutine.waitForTime > 0) coroutine.waitForTime += currentTime;
else if (coroutine.waitForCoroutine != null) Resume(coroutine.waitForCoroutine);
else if (coroutine.waitForUnityObject != null) coroutine.waitForUnityObject.Resume();
}
if (coroutine.waitForFrame > 0 && frame >= coroutine.waitForFrame)
{
coroutine.waitForFrame = -1;
InitUpdateCoroutine(coroutine);
}
else if (coroutine.waitForTime > 0.0f && time >= coroutine.waitForTime)
{
coroutine.waitForTime = -1.0f;
InitUpdateCoroutine(coroutine);
}
else if (coroutine.waitForCoroutine != null && coroutine.waitForCoroutine.finished)
{
coroutine.waitForCoroutine = null;
InitUpdateCoroutine(coroutine);
}
else if (coroutine.waitForUnityObject != null && coroutine.waitForUnityObject.finished)
{
coroutine.waitForUnityObject = null;
InitUpdateCoroutine(coroutine);
}
else if (coroutine.waitForFrame == -1 && coroutine.waitForTime == -1.0f
&& coroutine.waitForCoroutine == null &&
coroutine.waitForUnityObject == null)
{
// 初始更新
InitUpdateCoroutine(coroutine);
}
coroutine = runTimeNuext;
}
}
/**
* 执行协程直到下一个yield。如果协程已完成,标记它为完成并将其从调度器列表中移除。
*/
void InitUpdateCoroutine(CoroutineNode coroutine)
{
IEnumerator fiber = coroutine.fiber;
if (coroutine.fiber.MoveNext())
{
// System.Object yieldCommand = fiber.Current == null ? (System.Object)1 : fiber.Current;
System.Object yieldCommand = fiber.Current;
if (fiber.Current == null)
{
coroutine.waitForFrame = 1 + currentFrame;
}
else if (yieldCommand is int)
{
coroutine.waitForFrame = (int)yieldCommand + currentFrame;
}
else if (yieldCommand is float)
{
coroutine.waitForTime = (float)yieldCommand + currentTime;
}
else if (yieldCommand is CoroutineNode)
{
coroutine.waitForCoroutine = (CoroutineNode)yieldCommand;
}
else if (yieldCommand is IYieldWrapper)
{
coroutine.waitForUnityObject = yieldCommand as IYieldWrapper;
}
else
{
throw new System.ArgumentException("CoroutineScheduler: 意外的协程yield类型: " + yieldCommand.GetType());
}
}
else
{
// 协程完成
coroutine.finished = true;
RemoveCoroutine(coroutine);
}
}
void AddCoroutine(CoroutineNode coroutine)
{
if (first == null)
{
first = coroutine;
first.listPrevious = null;
first.listNext = null;
}
else
{
var c = first;
while (c.listNext != null)
c = c.listNext;
c.listNext = coroutine;
coroutine.listNext = null;
coroutine.listPrevious = c;
}
coroutine.finished = false; // 协程未完成
}
void RemoveCoroutine(CoroutineNode coroutine)
{
if (this.first == coroutine)
{
// 移除第一个协程节点
this.first = coroutine.listNext;
if (first != null) first.listPrevious = null;
}
else
{
// 不是列表头
if (coroutine.listNext != null)
{
// 移除中间的节点
coroutine.listPrevious.listNext = coroutine.listNext;
coroutine.listNext.listPrevious = coroutine.listPrevious;
}
else if (coroutine.listPrevious != null)
{
// listNext为空,移除最后一个节点
coroutine.listPrevious.listNext = null;
}
}
if (coroutine == runTimeNuext)
runTimeNuext = coroutine.listNext;
coroutine.listPrevious = null;
coroutine.listNext = null;
}
}
public interface IYieldWrapper
{
bool finished { get; }
void Pause();
void Resume();
}
public class CoroutineNode : IYieldWrapper
{
public CoroutineNode listPrevious = null;
public CoroutineNode listNext = null;
public IEnumerable srcFiber;
public IEnumerator fiber;
public bool finished = true;
public int waitForFrame = -1;
public float waitForTime = -1.0f;
public CoroutineNode waitForCoroutine;
public IYieldWrapper waitForUnityObject; //lonewolfwilliams
public bool isPaused = false;
public bool isPrePause = false;
public CoroutineNode(IEnumerator _fiber, bool _finished = false)
{
this.fiber = _fiber;
this.finished = _finished;
}
public CoroutineNode(IEnumerable _srcFiber, bool _finished = false)
{
srcFiber = _srcFiber;
this.fiber = _srcFiber.GetEnumerator();
this.finished = _finished;
}
public void Reset()
{
if (srcFiber != null)
fiber = srcFiber.GetEnumerator();
isPaused = false;
isPrePause = false;
waitForFrame = -1;
waitForTime = -1.0f;
waitForCoroutine = null;
waitForUnityObject = null;
}
bool IYieldWrapper.finished
{
get { return finished; }
}
public void Pause()
{
if (finished) return;
if (!isPaused)
{
isPrePause = true;
isPaused = true;
}
}
public void Resume()
{
if (finished) return;
if (isPaused)
{
isPaused = false;
isPrePause = true;
}
}
}