Unity Editor Coroutine

为什么需要Editor Coroutine

在一些大型项目中,因为涉及到得数据比较多,处理时间比较久,所以执行Editor Script的时候经常会出现Editor卡死的情况。 解决这类问题的思路其实和在游戏中解决类似问题的思路一样——使用Coroutine。 然而Unity的StartCoroutine函数只可以在Runtime时使用,在Editor的执行环境中可不可以拥有类似于Coroutine的机制呢?

答案是:可以

Editor Coroutine Excecuter

问题的关键在于在Editor的执行环境中是否拥有类似于Update的函数。 如果有了类似于Update的函数,我们就可以在每一“帧”对于正在运行的Coroutine执行MoveNext并且取Current,达到执行Coroutine的效果。 很幸运,Unity在最近提供了EditorApplication.update这个delegate。 于是,万事俱备,动手可以写了。

EditorCoroutine Class

首先需要实现的类是EditorCoroutine类。 这个类的主要作用是实现IEnumerator接口。

注意点

如果要实现类似Unity中yield return StartCoroutine(CoroutineFunc());的功能, 则需要在EditorCoroutine类中维护一个执行栈。 Coroutine相互之间的依赖关系是一个树结构,但是在Coroutine运行的时候只相当于深度优先遍历树结构。 所以我们不需要存储整个树结构,只需要动态的维护一个Coroutine的调用栈即可。

以下是部分函数的实现:

public bool MoveNext()
{
	// Get the top element of execution stack
	IEnumerator i = this.executionStack.Peek();

	// iterate
	if (i.MoveNext())
	{
		// Get Current
		object result = i.Current;

		// If we get a new IEnumerator, then add to execution stack
		if (result != null && result is IEnumerator)
		{
			this.executionStack.Push((IEnumerator)result);
		}

		return true;
	}
	else
	{
		// No more iteration on current node, back to parent node
		if (this.executionStack.Count > 1)
		{
			this.executionStack.Pop();
			return true;
		}
	}

	return false;
}

public object Current
{
	// Returns the Current of top element in execution stack
	get { return this.executionStack.Peek().Current; }
}

EditorCoroutineRunner Class

EditorCoroutineRunner主要负责在每次Update的时候执行各个EditorCoroutine的MoveNext函数。

注意点

如果想要实现类似yield return StartCoroutine(CoroutineFunc());的写法, 则需要注意StartCoroutine()的调用发生在Current被调用的时候。

如果在StartCoroutine函数中直接向editorCoroutineList中添加IEnumerator会导致该IEnumerator在同一帧里被执行两次。 所以在这个地方需要简单做一下去重的判断处理。

重要部分代码如下:

private static List<EditorCoroutine> editorCoroutineList;
private static List<IEnumerator> buffer;

public static IEnumerator StartEditorCoroutine(IEnumerator iterator)
{
	if (editorCoroutineList == null)
	{
		editorCoroutineList = new List<EditorCoroutine>();
	}
	if (buffer == null)
	{
		buffer = new List<IEnumerator>();
	}
	if (editorCoroutineList.Count == 0)
	{
		EditorApplication.update += Update;
	}

	// add iterator to buffer first
	buffer.Add(iterator);

	return iterator;
}

private static void Update()
{
	// EditorCoroutine execution may append new iterators to buffer
	// Therefore we should run EditorCoroutine first
	editorCoroutineList.RemoveAll
	(
		coroutine => { return coroutine.MoveNext() == false; }
	);

	// If we have iterators in buffer
	if (buffer.Count > 0)
	{
		foreach (IEnumerator iterator in buffer)
		{
			// If this iterators not exists
			if (!Find(iterator))
			{
				// Added this as new EditorCoroutine
				editorCoroutineList.Add(new EditorCoroutine(iterator));
			}
		}

		// Clear buffer
		buffer.Clear();
	}

	// If we have no running EditorCoroutine
	// Stop calling update anymore
	if (editorCoroutineList.Count == 0)
	{
		EditorApplication.update -= Update;
	}
}

完整版代码见这里

觉得好就给个星吧~