Unity Coroutine
Unity Coroutine(协程)和 IEnumerator 基本一样,类似一个状态机的概念,稍微有过深入了解C# IEnumerator 可以大概知道这里面是一个状态机的概念 。
IEnumerable
是一个状态标识,标识这个对象是可以进行迭代尝试的(没有太大深究意义,只需要知道当你需要自己实现一个迭代器的时候需要继承它就可以了)。
IEnumerator.Current
则为当前状态,IEnumerator.MoveNext()
尝试进行下个状态的切换,失败则会返回false。
我们先看个非常简单代码:
// An highlighted block
static IEnumerable TestIEnumerator01()
{
for (int i = 0; i < 10; i++)
{
yield return i;
}
yield return null;
}
在C#中,这是一个非常简单的迭代器。(作者不喜欢理论,喜欢依赖代码来解释。)
接着,我们在Main函数里面实现一点东西,整体的代码看起来是这样的:
using System;
using System.Collections;
namespace IteratorLearn
{
class IteratorLearnClass
{
static IEnumerable TestIEnumerator01()
{
for (int i = 0; i < 10; i++)
{
yield return i;
}
yield return null;
}
static void Main(string[] args)
{
IEnumerator iterator = TestIEnumerator01().GetEnumerator();
while (iterator.MoveNext())
{
Console.WriteLine(iterator.Current);
}
Console.WriteLine("iterator over!");
}
}
}
输出的结果:
我们可以看到,输出了10个阿拉伯数字以及一个Null。
那么,冲这个简单的代码中,我们便可以发现其中的奥利奥(奥秘)了。我们每调用一次MoveNext()
那么状态便继续往下走。而 yield return xxxx
便是我们的状态返回结果。既然我们已经大概了解 IEnumerable 与 IEnumerator,那么我们进入今天的主题!
基于 IEnumerator 实现一个类似Unity的协程
上面我们已经了解了 IEnumerable 与 IEnumerator,接下来我们开始写代码!并且尽可能模拟 Unity MonoBehaviour(不代表Unity就是这么写的!)
using System;
using System.Collections;
using System.Collections.Generic;
namespace IteratorLearn
{
class IteratorLearnClass
{
static IEnumerator TestIEnumerator01()
{
for (int i = 0; i < 10; i++)
{
yield return i;
Console.WriteLine(i);
}
yield return null;
}
static MonoBehaviour behaviour;
static void Main(string[] args)
{
behaviour = new MonoBehaviour();
behaviour.StartCoroutine(TestIEnumerator01());
Console.WriteLine("iterator over!");
}
}
public class Time
{
public static int frameCount;
}
public class MonoBehaviour
{
private bool m_MonoBehaviourRuning = false;
private Stack<IEnumerator> m_CurrentTorStacks = new Stack<IEnumerator>();
public MonoBehaviour()
{
}
public void StartCoroutine(IEnumerator routine)
{
this.m_CurrentTorStacks.Push(routine);
this.m_MonoBehaviourRuning = this.m_CurrentTorStacks.Count > 0;
if (this.m_MonoBehaviourRuning)
{
IEnumerator iterator = TestIEnumerator01().GetEnumerator();
while (iterator.MoveNext())
{
this.IteratorUpdate();
this.m_MonoBehaviourRuning = this.m_CurrentTorStacks.Count > 0;
}
}
}
private void IteratorUpdate()
{
if (this.m_CurrentTorStacks.Count > 0)
{
IEnumerator iterator = this.m_CurrentTorStacks.Pop();
while (iterator.MoveNext())
{
}
}
}
IEnumerable TestIEnumerator01()
{
while (this.m_MonoBehaviourRuning)
{
Time.frameCount++;
yield return Time.frameCount;
}
}
}
}
代码不难,只是为了模拟Unity的MonoBehaviour这样更清晰一些。但是这里的代码执行结果和之前是一样的。
既然是一样的,那作者写那么多废话干嘛?不急,我们再看看:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace IteratorLearn
{
class IteratorLearnClass
{
static IEnumerator TestIEnumerator01()
{
for (int i = 0; i < 10; i++)
{
yield return new WaitForFrame(50);
Console.WriteLine($"i => {i}, frame => {Time.frameCount}");
}
yield return null;
}
static MonoBehaviour behaviour;
static void Main(string[] args)
{
behaviour = new MonoBehaviour();
behaviour.StartCoroutine(TestIEnumerator01());
Console.WriteLine("iterator over!");
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct WaitForFrame
{
public int Frame;
public WaitForFrame(int frame)
{
Frame = frame;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Time
{
public static int frameCount;
}
public class MonoBehaviour
{
private bool m_Condition = false;
private bool m_MonoBehaviourRuning = false;
private Stack<IEnumerator> m_CurrentTorStacks = new Stack<IEnumerator>();
private int m_lastFrame = 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void StartCoroutine(IEnumerator routine)
{
this.m_CurrentTorStacks.Push(routine);
this.m_MonoBehaviourRuning = this.m_CurrentTorStacks.Count > 0;
if (this.m_MonoBehaviourRuning)
{
IEnumerator iterator = TestIEnumerator01().GetEnumerator();
while (iterator.MoveNext())
{
this.IteratorUpdate();
this.m_MonoBehaviourRuning = this.m_CurrentTorStacks.Count > 0;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void IteratorUpdate()
{
if (this.m_CurrentTorStacks.Count > 0)
{
WaitForFrame waitForFrame = default(WaitForFrame);
IEnumerator iterator = this.m_CurrentTorStacks.Pop();
if(iterator.Current != null)
{
if (iterator.Current is WaitForFrame)
{
waitForFrame = (WaitForFrame)iterator.Current;
if (Time.frameCount < this.m_lastFrame + waitForFrame.Frame)
{
this.m_CurrentTorStacks.Push(iterator);
this.m_Condition = false;
}
else
{
this.m_lastFrame = Time.frameCount;
this.m_Condition = true;
iterator.MoveNext();
}
}
}
else
{
this.m_Condition = iterator.MoveNext();
}
while (this.m_Condition)
{
if (iterator.Current != null)
{
if (iterator.Current is WaitForFrame)
{
waitForFrame = (WaitForFrame)iterator.Current;
if (Time.frameCount < this.m_lastFrame + waitForFrame.Frame)
{
this.m_lastFrame = Time.frameCount;
this.m_CurrentTorStacks.Push(iterator);
break;
}
}
}
else
{
this.m_Condition = iterator.MoveNext();
}
}
}
}
IEnumerable TestIEnumerator01()
{
while (this.m_MonoBehaviourRuning)
{
Time.frameCount++;
yield return Time.frameCount;
}
}
}
}
yield return new WaitForFrame(50);
这段代码很熟悉对吧?没错!等待指定的帧数!这样我们就大概的实现以下类似Unity的协程,当然这是不考虑代码优化的学习代码。各种条件判断和while。按照如此的思路,那么实现一个秒等待也是可行的。
执行后的结果返回: